@@ -2,26 +2,161 @@ const std = @import("std");
2
2
const builtin = @import ("builtin" );
3
3
const debug = std .debug ;
4
4
const 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 ;
5
9
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
8
51
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
+ const 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 );
11
88
12
89
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
+ }
15
140
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
+ }
18
153
}
19
154
}
20
155
21
- noinline fn frame2 (expected : * [4 ]usize , unwound : * [ 4 ] usize ) void {
156
+ noinline fn frame2 (expected : * [4 ]usize ) void {
22
157
// Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored
23
158
if (builtin .target .ofmt != .c ) {
24
- switch (builtin . cpu . arch ) {
159
+ switch (native_arch ) {
25
160
.x86 = > {
26
161
if (builtin .omit_frame_pointer ) {
27
162
asm volatile (
@@ -67,33 +202,139 @@ noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
67
202
}
68
203
69
204
expected [1 ] = @returnAddress ();
70
- frame3 (expected , unwound );
205
+ frame3 (expected );
71
206
}
72
207
73
- noinline fn frame1 (expected : * [4 ]usize , unwound : * [ 4 ] usize ) void {
208
+ noinline fn frame1 (expected : * [4 ]usize ) void {
74
209
expected [2 ] = @returnAddress ();
75
210
76
211
// Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
77
212
// to exercise the stack-indirect encoding path
78
213
var pad : [std .math .maxInt (u8 ) * @sizeOf (usize ) + 1 ]u8 = undefined ;
79
214
_ = std .mem .doNotOptimizeAway (& pad );
80
215
81
- frame2 (expected , unwound );
216
+ frame2 (expected );
82
217
}
83
218
84
- noinline fn frame0 (expected : * [4 ]usize , unwound : * [ 4 ] usize ) void {
219
+ noinline fn frame0 (expected : * [4 ]usize ) void {
85
220
expected [3 ] = @returnAddress ();
86
- frame1 (expected , unwound );
221
+ frame1 (expected );
87
222
}
88
223
89
224
pub fn main () ! void {
90
225
// 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 );
92
245
93
- if ( ! std .debug .have_ucontext or ! std . debug . have_getcontext ) return ;
246
+ std .debug .print ( "Running... \n " , .{}) ;
94
247
95
248
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 " , .{});
99
340
}
0 commit comments