Skip to content

Commit 049039c

Browse files
committed
Default std.posix.system.ucontext_t is void
PR #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 179a6e6 commit 049039c

File tree

3 files changed

+94
-1
lines changed

3 files changed

+94
-1
lines changed

lib/std/posix.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ pub const system = if (use_libc)
4545
else switch (native_os) {
4646
.linux => linux,
4747
.plan9 => std.os.plan9,
48-
else => struct {},
48+
else => struct {
49+
pub const ucontext_t = void;
50+
},
4951
};
5052

5153
pub const AF = system.AF;

test/standalone/stack_iterator/build.zig

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const std = @import("std");
2+
const builtin = @import("builtin");
23

34
pub fn build(b: *std.Build) void {
45
const test_step = b.step("test", "Test it");
@@ -93,4 +94,30 @@ pub fn build(b: *std.Build) void {
9394
const run_cmd = b.addRunArtifact(exe);
9495
test_step.dependOn(&run_cmd.step);
9596
}
97+
98+
// Unwinding without libc/posix
99+
//
100+
// No "getcontext" or "ucontext_t"
101+
{
102+
const exe = b.addExecutable(.{
103+
.name = "unwind_freestanding",
104+
.root_source_file = b.path("unwind_freestanding.zig"),
105+
.target = b.resolveTargetQuery(.{
106+
.cpu_arch = .x86_64,
107+
.os_tag = .freestanding,
108+
}),
109+
.optimize = optimize,
110+
.unwind_tables = null,
111+
.omit_frame_pointer = false,
112+
});
113+
114+
// This "freestanding" binary is runnable because it invokes the
115+
// Linux exit syscall directly.
116+
if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
117+
const run_cmd = b.addRunArtifact(exe);
118+
test_step.dependOn(&run_cmd.step);
119+
} else {
120+
test_step.dependOn(&exe.step);
121+
}
122+
}
96123
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/// Test StackIterator on 'freestanding' target. Based on unwind.zig.
2+
const std = @import("std");
3+
const builtin = @import("builtin");
4+
const debug = std.debug;
5+
6+
noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void {
7+
expected[0] = @returnAddress();
8+
9+
var it = debug.StackIterator.init(@returnAddress(), @frameAddress());
10+
defer it.deinit();
11+
12+
// Save StackIterator's frame addresses into `unwound`:
13+
for (unwound) |*addr| {
14+
if (it.next()) |return_address| addr.* = return_address;
15+
}
16+
}
17+
18+
noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
19+
expected[1] = @returnAddress();
20+
frame3(expected, unwound);
21+
}
22+
23+
noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {
24+
expected[2] = @returnAddress();
25+
26+
// Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
27+
// to exercise the stack-indirect encoding path
28+
var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
29+
_ = std.mem.doNotOptimizeAway(&pad);
30+
31+
frame2(expected, unwound);
32+
}
33+
34+
noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void {
35+
expected[3] = @returnAddress();
36+
frame1(expected, unwound);
37+
}
38+
39+
// Freestanding entrypoint
40+
export fn _start() callconv(.C) noreturn {
41+
var expected: [4]usize = undefined;
42+
var unwound: [4]usize = undefined;
43+
frame0(&expected, &unwound);
44+
45+
// Verify result (no std.testing in freestanding)
46+
var missed: c_int = 0;
47+
for (expected, unwound) |expectFA, actualFA| {
48+
if (expectFA != actualFA) {
49+
missed += 1;
50+
}
51+
}
52+
53+
// Need to compile as "freestanding" to exercise the StackIterator code, but when run as a
54+
// regression test need to actually exit. So assume we're running on x86_64-linux ...
55+
asm volatile (
56+
\\movl $60, %%eax
57+
\\syscall
58+
:
59+
: [missed] "{edi}" (missed),
60+
: "edi", "eax"
61+
);
62+
63+
while (true) {} // unreached
64+
}

0 commit comments

Comments
 (0)