Commit e8503ecb65

Pat Tullmann <pat.github@tullmann.org>
2024-07-22 01:03:58
Default std.posix.system.ucontext_t is void
PR https://github.com/ziglang/zig/pull/20679 ("std.c reorganization") switched feature-detection code to use "T != void" checks in place of "@hasDecl". However, the std.posix.system struct is empty, so compile-time feature detection against symbols in there (specifically `std.posix.system.ucontext_t` in this case), fail at compile time on freestanding targets. This PR adds a void ucontext_t into the std.posix.system default. This PR also adds pseudo-"freestanding" variation of the StackIterator "unwind" test. It is sort of hacky (its freestanding, but assumes it can invoke a Linux exit syscall), but it does detect this problem. Fixes #20710
1 parent 33c7984
Changed files (3)
lib
test
standalone
lib/std/posix.zig
@@ -45,7 +45,9 @@ pub const system = if (use_libc)
 else switch (native_os) {
     .linux => linux,
     .plan9 => std.os.plan9,
-    else => struct {},
+    else => struct {
+        pub const ucontext_t = void;
+    },
 };
 
 pub const AF = system.AF;
test/standalone/stack_iterator/build.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const builtin = @import("builtin");
 
 pub fn build(b: *std.Build) void {
     const test_step = b.step("test", "Test it");
@@ -93,4 +94,30 @@ pub fn build(b: *std.Build) void {
         const run_cmd = b.addRunArtifact(exe);
         test_step.dependOn(&run_cmd.step);
     }
+
+    // Unwinding without libc/posix
+    //
+    // No "getcontext" or "ucontext_t"
+    {
+        const exe = b.addExecutable(.{
+            .name = "unwind_freestanding",
+            .root_source_file = b.path("unwind_freestanding.zig"),
+            .target = b.resolveTargetQuery(.{
+                .cpu_arch = .x86_64,
+                .os_tag = .freestanding,
+            }),
+            .optimize = optimize,
+            .unwind_tables = null,
+            .omit_frame_pointer = false,
+        });
+
+        // This "freestanding" binary is runnable because it invokes the
+        // Linux exit syscall directly.
+        if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
+            const run_cmd = b.addRunArtifact(exe);
+            test_step.dependOn(&run_cmd.step);
+        } else {
+            test_step.dependOn(&exe.step);
+        }
+    }
 }
test/standalone/stack_iterator/unwind_freestanding.zig
@@ -0,0 +1,64 @@
+/// Test StackIterator on 'freestanding' target.  Based on unwind.zig.
+const std = @import("std");
+const builtin = @import("builtin");
+const debug = std.debug;
+
+noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void {
+    expected[0] = @returnAddress();
+
+    var it = debug.StackIterator.init(@returnAddress(), @frameAddress());
+    defer it.deinit();
+
+    // Save StackIterator's frame addresses into `unwound`:
+    for (unwound) |*addr| {
+        if (it.next()) |return_address| addr.* = return_address;
+    }
+}
+
+noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
+    expected[1] = @returnAddress();
+    frame3(expected, unwound);
+}
+
+noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {
+    expected[2] = @returnAddress();
+
+    // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
+    // to exercise the stack-indirect encoding path
+    var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
+    _ = std.mem.doNotOptimizeAway(&pad);
+
+    frame2(expected, unwound);
+}
+
+noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void {
+    expected[3] = @returnAddress();
+    frame1(expected, unwound);
+}
+
+// Freestanding entrypoint
+export fn _start() callconv(.C) noreturn {
+    var expected: [4]usize = undefined;
+    var unwound: [4]usize = undefined;
+    frame0(&expected, &unwound);
+
+    // Verify result (no std.testing in freestanding)
+    var missed: c_int = 0;
+    for (expected, unwound) |expectFA, actualFA| {
+        if (expectFA != actualFA) {
+            missed += 1;
+        }
+    }
+
+    // Need to compile as "freestanding" to exercise the StackIterator code, but when run as a
+    // regression test need to actually exit.  So assume we're running on x86_64-linux ...
+    asm volatile (
+        \\movl $60, %%eax
+        \\syscall
+        :
+        : [missed] "{edi}" (missed),
+        : "edi", "eax"
+    );
+
+    while (true) {} // unreached
+}