@@ -2,26 +2,160 @@ 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+         local_ctx  =  ctx .* ;
64+ 
65+         // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`, 
66+         // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the 
67+         // contents to the right place so that the `mcontext` pointer will be correct after the 
68+         // `relocateContext` call below. 
69+         local_ctx .__mcontext_data  =  @as (* align (1 ) extern  struct  {
70+             onstack : c_int ,
71+             sigmask : std.c.sigset_t ,
72+             stack : std.c.stack_t ,
73+             link : ? * std.c.ucontext_t ,
74+             mcsize : u64 ,
75+             mcontext : * std.c.mcontext_t ,
76+             __mcontext_data : std .c .mcontext_t  align (@sizeOf (usize )), // Disable padding after `mcontext`. 
77+         }, @ptrCast (ctx )).__mcontext_data ;
78+ 
79+         debug .relocateContext (& local_ctx );
80+         ctx  =  & local_ctx ;
81+     } else  {
82+         ctx  =  @ptrCast (@alignCast (ctx_ptr ));
83+     }
84+ 
85+     std .debug .print ("(from signal handler) dumpStackTraceFromBase({*} => {*}):\n " , .{ ctx_ptr , ctx  });
86+     debug .dumpStackTraceFromBase (ctx );
1187
1288    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 ();
89+     var  sig_it  =  debug .StackIterator .initWithContext (null , debug_info , ctx ) catch  @panic ("failed StackIterator.initWithContext" );
90+     defer  sig_it .deinit ();
91+ 
92+     // Save the backtrace from 'ctx' into the 'signal_frames' array 
93+     while  (sig_it .next ()) | return_address |  {
94+         signal_frames .append (return_address ) catch  @panic ("signal_frames.append()" );
95+         if  (signal_frames .len  ==  signal_frames .capacity ()) break ;
96+     }
97+ 
98+     handled_signal  =  true ;
99+ }
100+ 
101+ // Leaf test function.  Gather backtraces for comparison with "expected". 
102+ noinline  fn  frame3 (expected : * [4 ]usize ) void  {
103+     expected [0 ] =  @returnAddress ();
104+ 
105+     // Test the print-current-stack trace functions 
106+     std .debug .print ("dumpCurrentStackTrace(null):\n " , .{});
107+     debug .dumpCurrentStackTrace (null );
108+ 
109+     std .debug .print ("dumpCurrentStackTrace({x}):\n " , .{expected [0 ]});
110+     debug .dumpCurrentStackTrace (expected [0 ]);
111+ 
112+     // Trigger signal handler here and see that it's ctx is a viable start for unwinding 
113+     if  (do_signal  and  installed_signal_handler ) {
114+         posix .raise (posix .SIG .URG ) catch  @panic ("failed to raise posix.SIG.URG" );
115+     }
116+ 
117+     // Capture stack traces directly, two ways, if supported 
118+     if  (std .debug .ThreadContext  !=  void  and  native_os  !=  .windows ) {
119+         var  context : debug.ThreadContext  =  undefined ;
120+ 
121+         const  gotContext  =  debug .getContext (& context );
122+ 
123+         if  (! std .debug .have_getcontext ) {
124+             testing .expectEqual (false , gotContext ) catch  @panic ("getContext unexpectedly succeeded" );
125+         } else  {
126+             testing .expectEqual (true , gotContext ) catch  @panic ("failed to getContext" );
127+ 
128+             const  debug_info  =  debug .getSelfDebugInfo () catch  @panic ("failed to openSelfDebugInfo" );
129+ 
130+             // Run the "full" iterator 
131+             testing .expect (debug .getContext (& context )) catch  @panic ("failed to getContext" );
132+             var  full_it  =  debug .StackIterator .initWithContext (null , debug_info , & context ) catch  @panic ("failed StackIterator.initWithContext" );
133+             defer  full_it .deinit ();
134+ 
135+             while  (full_it .next ()) | return_address |  {
136+                 full_frames .append (return_address ) catch  @panic ("full_frames.append()" );
137+                 if  (full_frames .len  ==  full_frames .capacity ()) break ;
138+             }
15139
16-     for  (unwound ) | * addr |  {
17-         if  (it .next ()) | return_address |  addr .*  =  return_address ;
140+             // Run the iterator that skips until `expected[0]` is seen 
141+             testing .expect (debug .getContext (& context )) catch  @panic ("failed 2nd getContext" );
142+             var  skip_it  =  debug .StackIterator .initWithContext (expected [0 ], debug_info , & context ) catch  @panic ("failed StackIterator.initWithContext" );
143+             defer  skip_it .deinit ();
144+ 
145+             while  (skip_it .next ()) | return_address |  {
146+                 skip_frames .append (return_address ) catch  @panic ("skip_frames.append()" );
147+                 if  (skip_frames .len  ==  skip_frames .capacity ()) break ;
148+             }
149+ 
150+             captured_frames  =  true ;
151+         }
18152    }
19153}
20154
21- noinline  fn  frame2 (expected : * [4 ]usize ,  unwound :  * [ 4 ] usize ) void  {
155+ noinline  fn  frame2 (expected : * [4 ]usize ) void  {
22156    // Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored 
23157    if  (builtin .target .ofmt  !=  .c ) {
24-         switch  (builtin . cpu . arch ) {
158+         switch  (native_arch ) {
25159            .x86  = >  {
26160                if  (builtin .omit_frame_pointer ) {
27161                    asm  volatile  (
@@ -67,33 +201,139 @@ noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
67201    }
68202
69203    expected [1 ] =  @returnAddress ();
70-     frame3 (expected ,  unwound );
204+     frame3 (expected );
71205}
72206
73- noinline  fn  frame1 (expected : * [4 ]usize ,  unwound :  * [ 4 ] usize ) void  {
207+ noinline  fn  frame1 (expected : * [4 ]usize ) void  {
74208    expected [2 ] =  @returnAddress ();
75209
76210    // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding 
77211    // to exercise the stack-indirect encoding path 
78212    var  pad : [std .math .maxInt (u8 ) *  @sizeOf (usize ) +  1 ]u8  =  undefined ;
79213    _  =  std .mem .doNotOptimizeAway (& pad );
80214
81-     frame2 (expected ,  unwound );
215+     frame2 (expected );
82216}
83217
84- noinline  fn  frame0 (expected : * [4 ]usize ,  unwound :  * [ 4 ] usize ) void  {
218+ noinline  fn  frame0 (expected : * [4 ]usize ) void  {
85219    expected [3 ] =  @returnAddress ();
86-     frame1 (expected ,  unwound );
220+     frame1 (expected );
87221}
88222
89223pub  fn  main () ! void  {
90224    // 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 ;
225+     if  (builtin .omit_frame_pointer  and  comptime  builtin .target .os .tag .isDarwin () and  native_arch  ==  .aarch64 ) return ;
226+ 
227+     if  (do_signal ) {
228+         std .debug .print ("Installing SIGURG handler ...\n " , .{});
229+         posix .sigaction (posix .SIG .URG , &.{
230+             .handler  =  .{ .sigaction  =  testFromSigUrg  },
231+             .mask  =  posix .sigemptyset (),
232+             .flags  =  (posix .SA .SIGINFO  |  posix .SA .RESTART ),
233+         }, null );
234+         installed_signal_handler  =  true ;
235+     } else  {
236+         std .debug .print ("(No signal-based backtrace on this configuration.)\n " , .{});
237+         installed_signal_handler  =  false ;
238+     }
239+     handled_signal  =  false ;
240+ 
241+     signal_frames  =  try  AddrArray .init (0 );
242+     skip_frames  =  try  AddrArray .init (0 );
243+     full_frames  =  try  AddrArray .init (0 );
92244
93-     if  ( ! std .debug .have_ucontext   or   ! std . debug . have_getcontext )  return ;
245+     std .debug .print ( "Running... \n " , .{}) ;
94246
95247    var  expected : [4 ]usize  =  undefined ;
96-     var  unwound : [4 ]usize  =  undefined ;
97-     frame0 (& expected , & unwound );
98-     try  testing .expectEqual (expected , unwound );
248+     frame0 (& expected );
249+ 
250+     std .debug .print ("Verification: arch={s} link_libc={} have_ucontext={} have_getcontext={} ...\n " , .{
251+         @tagName (native_arch ), link_libc , std .debug .have_ucontext , std .debug .have_getcontext ,
252+     });
253+     std .debug .print ("  expected={any}\n " , .{expected });
254+     std .debug .print ("  full_frames={any}\n " , .{full_frames .slice ()});
255+     std .debug .print ("  skip_frames={any}\n " , .{skip_frames .slice ()});
256+     std .debug .print ("  signal_frames={any}\n " , .{signal_frames .slice ()});
257+ 
258+     var  fail_count : usize  =  0 ;
259+ 
260+     if  (do_signal  and  installed_signal_handler ) {
261+         try  testing .expectEqual (true , handled_signal );
262+     }
263+ 
264+     // None of the backtraces should overflow max_stack_trace_depth 
265+ 
266+     if  (skip_frames .len  ==  skip_frames .capacity ()) {
267+         std .debug .print ("skip_frames contains too many frames: {}\n " , .{skip_frames .len });
268+         fail_count  +=  1 ;
269+     }
270+ 
271+     if  (full_frames .len  ==  full_frames .capacity ()) {
272+         std .debug .print ("full_frames contains too many frames: {}\n " , .{full_frames .len });
273+         fail_count  +=  1 ;
274+     }
275+ 
276+     if  (signal_frames .len  ==  signal_frames .capacity ()) {
277+         if  (expect_signal_frame_overflow ) {
278+             // The signal_frames backtrace overflows.  Ignore this for now. 
279+             std .debug .print ("(expected) signal_frames overflow: {}\n " , .{signal_frames .len });
280+         } else  {
281+             std .debug .print ("signal_frames contains too many frames: {}\n " , .{signal_frames .len });
282+             fail_count  +=  1 ;
283+         }
284+     }
285+ 
286+     if  (supports_stack_iterator ) {
287+         if  (captured_frames ) {
288+             // Saved 'skip_frames' should start with the expected frames, exactly. 
289+             try  testing .expectEqual (skip_frames .slice ()[0.. 4].* , expected );
290+ 
291+             // The return addresses in "expected[]" should show up, in order, in the "full_frames" array 
292+             var  found  =  false ;
293+             for  (0.. full_frames .len ) | i |  {
294+                 const  addr  =  full_frames .get (i );
295+                 if  (addr  ==  expected [0 ]) {
296+                     try  testing .expectEqual (full_frames .get (i  +  1 ), expected [1 ]);
297+                     try  testing .expectEqual (full_frames .get (i  +  2 ), expected [2 ]);
298+                     try  testing .expectEqual (full_frames .get (i  +  3 ), expected [3 ]);
299+                     found  =  true ;
300+                 }
301+             }
302+             if  (! found ) {
303+                 std .debug .print ("full_frames[...] does not include expected[0..4]\n " , .{});
304+                 fail_count  +=  1 ;
305+             }
306+         }
307+ 
308+         if  (installed_signal_handler  and  handled_signal ) {
309+             // The return addresses in "expected[]" should show up, in order, in the "signal_frames" array 
310+             var  found  =  false ;
311+             for  (0.. signal_frames .len ) | i |  {
312+                 const  signal_addr  =  signal_frames .get (i );
313+                 if  (signal_addr  ==  expected [0 ]) {
314+                     try  testing .expectEqual (signal_frames .get (i  +  1 ), expected [1 ]);
315+                     try  testing .expectEqual (signal_frames .get (i  +  2 ), expected [2 ]);
316+                     try  testing .expectEqual (signal_frames .get (i  +  3 ), expected [3 ]);
317+                     found  =  true ;
318+                 }
319+             }
320+             if  (! found ) {
321+                 if  (expect_signal_frame_useless ) {
322+                     std .debug .print ("(expected) signal_frames[...] does not include expected[0..4]\n " , .{});
323+                 } else  {
324+                     std .debug .print ("signal_frames[...] does not include expected[0..4]\n " , .{});
325+                     fail_count  +=  1 ;
326+                 }
327+             }
328+         }
329+     } else  {
330+         // If these tests fail, then this platform now supports StackIterator 
331+         try  testing .expectEqual (0 , skip_frames .len );
332+         try  testing .expectEqual (0 , full_frames .len );
333+         try  testing .expectEqual (0 , signal_frames .len );
334+     }
335+ 
336+     try  testing .expectEqual (0 , fail_count );
337+ 
338+     std .debug .print ("Test complete.\n " , .{});
99339}
0 commit comments