@@ -2,26 +2,161 @@ const std = @import("std");
22const builtin = @import ("builtin" );
33const debug = std .debug ;
44const testing = std .testing ;
5+ const posix = std .posix ;
6+ const native_arch = builtin .cpu .arch ;
7+ const native_os = builtin .os .tag ;
8+ const link_libc = builtin .link_libc ;
59
6- noinline fn frame3 (expected : * [4 ]usize , unwound : * [4 ]usize ) void {
7- expected [0 ] = @returnAddress ();
10+ const max_stack_trace_depth = 32 ;
11+
12+ const do_signal = switch (native_os ) {
13+ .wasi , .windows = > false ,
14+ else = > true ,
15+ };
16+
17+ var installed_signal_handler = false ;
18+ var handled_signal = false ;
19+ var captured_frames = false ;
20+
21+ const AddrArray = std .BoundedArray (usize , max_stack_trace_depth );
22+
23+ // Global variables to capture different stack traces in. Compared at the end of main() against "expected" array
24+ var signal_frames : AddrArray = undefined ;
25+ var full_frames : AddrArray = undefined ;
26+ var skip_frames : AddrArray = undefined ;
27+
28+ // StackIterator is the core of this test, but still worth executing the dumpCurrentStackTrace* functions
29+ // (These platforms don't fail on StackIterator, they just return empty traces.)
30+ const supports_stack_iterator =
31+ (native_os != .windows ) and // StackIterator is (currently?) POSIX/DWARF centered.
32+ ! native_arch .isWasm (); // wasm has no introspection
33+
34+ // Getting the backtrace inside the signal handler (with the ucontext_t)
35+ // gets stuck in a loop on some systems:
36+ const expect_signal_frame_overflow =
37+ (native_arch .isArm () and link_libc ) or // loops above main()
38+ native_arch .isAARCH64 (); // non-deterministic, sometimes overflows, sometimes not
39+
40+ // Getting the backtrace inside the signal handler (with the ucontext_t)
41+ // does not contain the expected content on some systems:
42+ const expect_signal_frame_useless =
43+ (native_arch == .x86_64 and link_libc and builtin .abi .isGnu ()) or // stuck on pthread_kill?
44+ (native_arch == .x86_64 and link_libc and builtin .abi .isMusl () and builtin .omit_frame_pointer ) or // immediately confused backtrace
45+ (native_arch == .x86_64 and builtin .os .tag .isDarwin ()) or // immediately confused backtrace
46+ native_arch .isAARCH64 () or // non-deterministic, sometimes overflows, sometimes confused
47+ native_arch .isRISCV () or // `ucontext_t` not defined yet
48+ native_arch .isMIPS () or // Missing ucontext_t. Most stack traces are empty ... (with or without libc)
49+ native_arch .isPowerPC () or // dumpCurrent* useless, StackIterator empty, ctx-based trace empty (with or without libc)
50+ (native_arch .isThumb () and ! link_libc ); // stops on first element of trace
851
9- var context : debug.ThreadContext = undefined ;
10- testing .expect (debug .getContext (& context )) catch @panic ("failed to getContext" );
52+ // Signal handler to gather stack traces from the given signal context.
53+ fn testFromSigUrg (sig : i32 , info : * const posix.siginfo_t , ctx_ptr : ? * anyopaque ) callconv (.c ) void {
54+ // std.debug.print("sig={} info={*} ctx_ptr={*}\n", .{ sig, info, ctx_ptr });
55+ _ = info ;
56+ _ = sig ;
57+
58+ var ctx : * posix.ucontext_t = undefined ;
59+ var local_ctx : posix.ucontext_t = undefined ;
60+
61+ // Darwin kernels don't align `ctx_ptr` properly. Handle this defensively.
62+ if (builtin .os .tag .isDarwin () and builtin .cpu .arch == .aarch64 ) {
63+ var align_ctx : * align (1 ) posix.ucontext_t = @ptrCast (ctx_ptr );
64+ local_ctx = align_ctx .* ;
65+
66+ // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`,
67+ // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the
68+ // contents to the right place so that the `mcontext` pointer will be correct after the
69+ // `relocateContext` call below.
70+ local_ctx .__mcontext_data = @as (* align (1 ) extern struct {
71+ onstack : c_int ,
72+ sigmask : std.c.sigset_t ,
73+ stack : std.c.stack_t ,
74+ link : ? * std.c.ucontext_t ,
75+ mcsize : u64 ,
76+ mcontext : * std.c.mcontext_t ,
77+ __mcontext_data : std .c .mcontext_t align (@sizeOf (usize )), // Disable padding after `mcontext`.
78+ }, @ptrCast (align_ctx )).__mcontext_data ;
79+
80+ debug .relocateContext (& local_ctx );
81+ ctx = & local_ctx ;
82+ } else {
83+ ctx = @ptrCast (@alignCast (ctx_ptr ));
84+ }
85+
86+ std .debug .print ("(from signal handler) dumpStackTraceFromBase({*} => {*}):\n " , .{ ctx_ptr , ctx });
87+ debug .dumpStackTraceFromBase (ctx );
1188
1289 const debug_info = debug .getSelfDebugInfo () catch @panic ("failed to openSelfDebugInfo" );
13- var it = debug .StackIterator .initWithContext (expected [0 ], debug_info , & context ) catch @panic ("failed to initWithContext" );
14- defer it .deinit ();
90+ var sig_it = debug .StackIterator .initWithContext (null , debug_info , ctx ) catch @panic ("failed StackIterator.initWithContext" );
91+ defer sig_it .deinit ();
92+
93+ // Save the backtrace from 'ctx' into the 'signal_frames' array
94+ while (sig_it .next ()) | return_address | {
95+ signal_frames .append (return_address ) catch @panic ("signal_frames.append()" );
96+ if (signal_frames .len == signal_frames .capacity ()) break ;
97+ }
98+
99+ handled_signal = true ;
100+ }
101+
102+ // Leaf test function. Gather backtraces for comparison with "expected".
103+ noinline fn frame3 (expected : * [4 ]usize ) void {
104+ expected [0 ] = @returnAddress ();
105+
106+ // Test the print-current-stack trace functions
107+ std .debug .print ("dumpCurrentStackTrace(null):\n " , .{});
108+ debug .dumpCurrentStackTrace (null );
109+
110+ std .debug .print ("dumpCurrentStackTrace({x}):\n " , .{expected [0 ]});
111+ debug .dumpCurrentStackTrace (expected [0 ]);
112+
113+ // Trigger signal handler here and see that it's ctx is a viable start for unwinding
114+ if (do_signal and installed_signal_handler ) {
115+ posix .raise (posix .SIG .URG ) catch @panic ("failed to raise posix.SIG.URG" );
116+ }
117+
118+ // Capture stack traces directly, two ways, if supported
119+ if (std .debug .ThreadContext != void and native_os != .windows ) {
120+ var context : debug.ThreadContext = undefined ;
121+
122+ const gotContext = debug .getContext (& context );
123+
124+ if (! std .debug .have_getcontext ) {
125+ testing .expectEqual (false , gotContext ) catch @panic ("getContext unexpectedly succeeded" );
126+ } else {
127+ testing .expectEqual (true , gotContext ) catch @panic ("failed to getContext" );
128+
129+ const debug_info = debug .getSelfDebugInfo () catch @panic ("failed to openSelfDebugInfo" );
130+
131+ // Run the "full" iterator
132+ testing .expect (debug .getContext (& context )) catch @panic ("failed to getContext" );
133+ var full_it = debug .StackIterator .initWithContext (null , debug_info , & context ) catch @panic ("failed StackIterator.initWithContext" );
134+ defer full_it .deinit ();
135+
136+ while (full_it .next ()) | return_address | {
137+ full_frames .append (return_address ) catch @panic ("full_frames.append()" );
138+ if (full_frames .len == full_frames .capacity ()) break ;
139+ }
15140
16- for (unwound ) | * addr | {
17- if (it .next ()) | return_address | addr .* = return_address ;
141+ // Run the iterator that skips until `expected[0]` is seen
142+ testing .expect (debug .getContext (& context )) catch @panic ("failed 2nd getContext" );
143+ var skip_it = debug .StackIterator .initWithContext (expected [0 ], debug_info , & context ) catch @panic ("failed StackIterator.initWithContext" );
144+ defer skip_it .deinit ();
145+
146+ while (skip_it .next ()) | return_address | {
147+ skip_frames .append (return_address ) catch @panic ("skip_frames.append()" );
148+ if (skip_frames .len == skip_frames .capacity ()) break ;
149+ }
150+
151+ captured_frames = true ;
152+ }
18153 }
19154}
20155
21- noinline fn frame2 (expected : * [4 ]usize , unwound : * [ 4 ] usize ) void {
156+ noinline fn frame2 (expected : * [4 ]usize ) void {
22157 // Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored
23158 if (builtin .target .ofmt != .c ) {
24- switch (builtin . cpu . arch ) {
159+ switch (native_arch ) {
25160 .x86 = > {
26161 if (builtin .omit_frame_pointer ) {
27162 asm volatile (
@@ -67,33 +202,139 @@ noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
67202 }
68203
69204 expected [1 ] = @returnAddress ();
70- frame3 (expected , unwound );
205+ frame3 (expected );
71206}
72207
73- noinline fn frame1 (expected : * [4 ]usize , unwound : * [ 4 ] usize ) void {
208+ noinline fn frame1 (expected : * [4 ]usize ) void {
74209 expected [2 ] = @returnAddress ();
75210
76211 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
77212 // to exercise the stack-indirect encoding path
78213 var pad : [std .math .maxInt (u8 ) * @sizeOf (usize ) + 1 ]u8 = undefined ;
79214 _ = std .mem .doNotOptimizeAway (& pad );
80215
81- frame2 (expected , unwound );
216+ frame2 (expected );
82217}
83218
84- noinline fn frame0 (expected : * [4 ]usize , unwound : * [ 4 ] usize ) void {
219+ noinline fn frame0 (expected : * [4 ]usize ) void {
85220 expected [3 ] = @returnAddress ();
86- frame1 (expected , unwound );
221+ frame1 (expected );
87222}
88223
89224pub fn main () ! void {
90225 // Disabled until the DWARF unwinder bugs on .aarch64 are solved
91- if (builtin .omit_frame_pointer and comptime builtin .target .os .tag .isDarwin () and builtin .cpu .arch == .aarch64 ) return ;
226+ if (builtin .omit_frame_pointer and comptime builtin .target .os .tag .isDarwin () and native_arch == .aarch64 ) return ;
227+
228+ if (do_signal ) {
229+ std .debug .print ("Installing SIGURG handler ...\n " , .{});
230+ posix .sigaction (posix .SIG .URG , &.{
231+ .handler = .{ .sigaction = testFromSigUrg },
232+ .mask = posix .sigemptyset (),
233+ .flags = (posix .SA .SIGINFO | posix .SA .RESTART ),
234+ }, null );
235+ installed_signal_handler = true ;
236+ } else {
237+ std .debug .print ("(No signal-based backtrace on this configuration.)\n " , .{});
238+ installed_signal_handler = false ;
239+ }
240+ handled_signal = false ;
241+
242+ signal_frames = try AddrArray .init (0 );
243+ skip_frames = try AddrArray .init (0 );
244+ full_frames = try AddrArray .init (0 );
92245
93- if ( ! std .debug .have_ucontext or ! std . debug . have_getcontext ) return ;
246+ std .debug .print ( "Running... \n " , .{}) ;
94247
95248 var expected : [4 ]usize = undefined ;
96- var unwound : [4 ]usize = undefined ;
97- frame0 (& expected , & unwound );
98- try testing .expectEqual (expected , unwound );
249+ frame0 (& expected );
250+
251+ std .debug .print ("Verification: arch={s} link_libc={} have_ucontext={} have_getcontext={} ...\n " , .{
252+ @tagName (native_arch ), link_libc , std .debug .have_ucontext , std .debug .have_getcontext ,
253+ });
254+ std .debug .print (" expected={any}\n " , .{expected });
255+ std .debug .print (" full_frames={any}\n " , .{full_frames .slice ()});
256+ std .debug .print (" skip_frames={any}\n " , .{skip_frames .slice ()});
257+ std .debug .print (" signal_frames={any}\n " , .{signal_frames .slice ()});
258+
259+ var fail_count : usize = 0 ;
260+
261+ if (do_signal and installed_signal_handler ) {
262+ try testing .expectEqual (true , handled_signal );
263+ }
264+
265+ // None of the backtraces should overflow max_stack_trace_depth
266+
267+ if (skip_frames .len == skip_frames .capacity ()) {
268+ std .debug .print ("skip_frames contains too many frames: {}\n " , .{skip_frames .len });
269+ fail_count += 1 ;
270+ }
271+
272+ if (full_frames .len == full_frames .capacity ()) {
273+ std .debug .print ("full_frames contains too many frames: {}\n " , .{full_frames .len });
274+ fail_count += 1 ;
275+ }
276+
277+ if (signal_frames .len == signal_frames .capacity ()) {
278+ if (expect_signal_frame_overflow ) {
279+ // The signal_frames backtrace overflows. Ignore this for now.
280+ std .debug .print ("(expected) signal_frames overflow: {}\n " , .{signal_frames .len });
281+ } else {
282+ std .debug .print ("signal_frames contains too many frames: {}\n " , .{signal_frames .len });
283+ fail_count += 1 ;
284+ }
285+ }
286+
287+ if (supports_stack_iterator ) {
288+ if (captured_frames ) {
289+ // Saved 'skip_frames' should start with the expected frames, exactly.
290+ try testing .expectEqual (skip_frames .slice ()[0.. 4].* , expected );
291+
292+ // The return addresses in "expected[]" should show up, in order, in the "full_frames" array
293+ var found = false ;
294+ for (0.. full_frames .len ) | i | {
295+ const addr = full_frames .get (i );
296+ if (addr == expected [0 ]) {
297+ try testing .expectEqual (full_frames .get (i + 1 ), expected [1 ]);
298+ try testing .expectEqual (full_frames .get (i + 2 ), expected [2 ]);
299+ try testing .expectEqual (full_frames .get (i + 3 ), expected [3 ]);
300+ found = true ;
301+ }
302+ }
303+ if (! found ) {
304+ std .debug .print ("full_frames[...] does not include expected[0..4]\n " , .{});
305+ fail_count += 1 ;
306+ }
307+ }
308+
309+ if (installed_signal_handler and handled_signal ) {
310+ // The return addresses in "expected[]" should show up, in order, in the "signal_frames" array
311+ var found = false ;
312+ for (0.. signal_frames .len ) | i | {
313+ const signal_addr = signal_frames .get (i );
314+ if (signal_addr == expected [0 ]) {
315+ try testing .expectEqual (signal_frames .get (i + 1 ), expected [1 ]);
316+ try testing .expectEqual (signal_frames .get (i + 2 ), expected [2 ]);
317+ try testing .expectEqual (signal_frames .get (i + 3 ), expected [3 ]);
318+ found = true ;
319+ }
320+ }
321+ if (! found ) {
322+ if (expect_signal_frame_useless ) {
323+ std .debug .print ("(expected) signal_frames[...] does not include expected[0..4]\n " , .{});
324+ } else {
325+ std .debug .print ("signal_frames[...] does not include expected[0..4]\n " , .{});
326+ fail_count += 1 ;
327+ }
328+ }
329+ }
330+ } else {
331+ // If these tests fail, then this platform now supports StackIterator
332+ try testing .expectEqual (0 , skip_frames .len );
333+ try testing .expectEqual (0 , full_frames .len );
334+ try testing .expectEqual (0 , signal_frames .len );
335+ }
336+
337+ try testing .expectEqual (0 , fail_count );
338+
339+ std .debug .print ("Test complete.\n " , .{});
99340}
0 commit comments