From 049039c4d49d0ea91d06ddc94bc707e2bb1fcc11 Mon Sep 17 00:00:00 2001 From: Pat Tullmann Date: Sun, 21 Jul 2024 16:03:58 -0700 Subject: [PATCH] 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 --- lib/std/posix.zig | 4 +- test/standalone/stack_iterator/build.zig | 27 ++++++++ .../stack_iterator/unwind_freestanding.zig | 64 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 test/standalone/stack_iterator/unwind_freestanding.zig diff --git a/lib/std/posix.zig b/lib/std/posix.zig index facb4b8cd664..cc15b6d9a448 100644 --- a/lib/std/posix.zig +++ b/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; diff --git a/test/standalone/stack_iterator/build.zig b/test/standalone/stack_iterator/build.zig index 7041aaa0b89d..a989828a8c0f 100644 --- a/test/standalone/stack_iterator/build.zig +++ b/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); + } + } } diff --git a/test/standalone/stack_iterator/unwind_freestanding.zig b/test/standalone/stack_iterator/unwind_freestanding.zig new file mode 100644 index 000000000000..b81a8a66e89d --- /dev/null +++ b/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 +}