@@ -2,26 +2,157 @@ 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 != .wasm32 ) and
33
+ (native_arch != .wasm64 ); // wasm has no introspection
34
+
35
+ // Getting the backtrace inside the signal handler (with the ucontext_t)
36
+ // gets stuck in a loop on some systems:
37
+ const expect_signal_frame_overflow =
38
+ (native_arch == .arm and link_libc ) or // loops above main()
39
+ (native_arch == .aarch64 ); // non-deterministic, sometimes overflows, sometimes not
40
+
41
+ // Getting the backtrace inside the signal handler (with the ucontext_t)
42
+ // does not contain the expected content on some systems:
43
+ const expect_signal_frame_useless =
44
+ (native_arch == .x86_64 and link_libc and builtin .abi .isGnu ()) or // stuck on pthread_kill?
45
+ (native_arch == .x86_64 and link_libc and builtin .abi .isMusl () and builtin .omit_frame_pointer ) or // immediately confused backtrace
46
+ (native_arch == .x86_64 and builtin .os .tag .isDarwin ()) or // immediately confused backtrace
47
+ (native_arch == .aarch64 or native_arch == .aarch64_be ) or // non-deterministic, sometimes overflows, sometimes confused
48
+ (native_arch == .riscv64 and link_libc ) or // `ucontext_t` not defined yet
49
+ native_arch == .mips or // Missing ucontext_t. Most stack traces are empty ... (with or without libc)
50
+ native_arch == .mipsel or // same as .mips
51
+ native_arch == .mips64 or // same as .mips
52
+ native_arch == .mips64el or // same as .mips
53
+ native_arch == .powerpc64 or // dumpCurrent* useless, StackIterator empty, ctx-based trace empty (with or without libc)
54
+ native_arch == .powerpc64le ; // same as .powerpc64
8
55
9
- var context : debug.ThreadContext = undefined ;
10
- testing .expect (debug .getContext (& context )) catch @panic ("failed to getContext" );
56
+ // Signal handler to gather stack traces from the given signal context.
57
+ fn testFromSigUrg (sig : i32 , info : * const posix.siginfo_t , ctx_ptr : ? * anyopaque ) callconv (.c ) void {
58
+ // std.debug.print("sig={} info={*} ctx_ptr={*}\n", .{ sig, info, ctx_ptr });
59
+ _ = info ;
60
+ _ = sig ;
61
+
62
+ // Some kernels don't align `ctx_ptr` properly. Handle this defensively.
63
+ const ctx : * align (1 ) posix.ucontext_t = @ptrCast (ctx_ptr );
64
+ var new_ctx : posix.ucontext_t = ctx .* ;
65
+ if (builtin .os .tag .isDarwin () and builtin .cpu .arch == .aarch64 ) {
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
+ new_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 (ctx )).__mcontext_data ;
79
+ }
80
+ debug .relocateContext (& new_ctx );
81
+
82
+ std .debug .print ("(from signal handler) dumpStackTraceFromBase({*} => {*}):\n " , .{ ctx_ptr , & new_ctx });
83
+ debug .dumpStackTraceFromBase (& new_ctx );
11
84
12
85
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 ();
86
+ var sig_it = debug .StackIterator .initWithContext (null , debug_info , & new_ctx ) catch @panic ("failed StackIterator. initWithContext" );
87
+ defer sig_it .deinit ();
15
88
16
- for (unwound ) | * addr | {
17
- if (it .next ()) | return_address | addr .* = return_address ;
89
+ // Save the backtrace from 'ctx' into the 'signal_frames' array
90
+ while (sig_it .next ()) | return_address | {
91
+ signal_frames .append (return_address ) catch @panic ("signal_frames.append()" );
92
+ if (signal_frames .len == signal_frames .capacity ()) break ;
18
93
}
94
+
95
+ handled_signal = true ;
19
96
}
20
97
21
- noinline fn frame2 (expected : * [4 ]usize , unwound : * [4 ]usize ) void {
98
+ // Leaf test function. Gather backtraces for comparison with "expected".
99
+ noinline fn frame3 (expected : * [4 ]usize ) void {
100
+ expected [0 ] = @returnAddress ();
101
+
102
+ // Test the print-current-stack trace functions
103
+ std .debug .print ("dumpCurrentStackTrace(null):\n " , .{});
104
+ debug .dumpCurrentStackTrace (null );
105
+
106
+ std .debug .print ("dumpCurrentStackTrace({x}):\n " , .{expected [0 ]});
107
+ debug .dumpCurrentStackTrace (expected [0 ]);
108
+
109
+ // Trigger signal handler here and see that it's ctx is a viable start for unwinding
110
+ if (do_signal and installed_signal_handler ) {
111
+ posix .raise (posix .SIG .URG ) catch @panic ("failed to raise posix.SIG.URG" );
112
+ }
113
+
114
+ // Capture stack traces directly, two ways, if supported
115
+ if (std .debug .ThreadContext != void and native_os != .windows ) {
116
+ var context : debug.ThreadContext = undefined ;
117
+
118
+ const gotContext = debug .getContext (& context );
119
+
120
+ if (! std .debug .have_getcontext ) {
121
+ testing .expectEqual (false , gotContext ) catch @panic ("getContext unexpectedly succeeded" );
122
+ } else {
123
+ testing .expectEqual (true , gotContext ) catch @panic ("failed to getContext" );
124
+
125
+ const debug_info = debug .getSelfDebugInfo () catch @panic ("failed to openSelfDebugInfo" );
126
+
127
+ // Run the "full" iterator
128
+ testing .expect (debug .getContext (& context )) catch @panic ("failed to getContext" );
129
+ var full_it = debug .StackIterator .initWithContext (null , debug_info , & context ) catch @panic ("failed StackIterator.initWithContext" );
130
+ defer full_it .deinit ();
131
+
132
+ while (full_it .next ()) | return_address | {
133
+ full_frames .append (return_address ) catch @panic ("full_frames.append()" );
134
+ if (full_frames .len == full_frames .capacity ()) break ;
135
+ }
136
+
137
+ // Run the iterator that skips until `expected[0]` is seen
138
+ testing .expect (debug .getContext (& context )) catch @panic ("failed 2nd getContext" );
139
+ var skip_it = debug .StackIterator .initWithContext (expected [0 ], debug_info , & context ) catch @panic ("failed StackIterator.initWithContext" );
140
+ defer skip_it .deinit ();
141
+
142
+ while (skip_it .next ()) | return_address | {
143
+ skip_frames .append (return_address ) catch @panic ("skip_frames.append()" );
144
+ if (skip_frames .len == skip_frames .capacity ()) break ;
145
+ }
146
+
147
+ captured_frames = true ;
148
+ }
149
+ }
150
+ }
151
+
152
+ noinline fn frame2 (expected : * [4 ]usize ) void {
22
153
// Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored
23
154
if (builtin .target .ofmt != .c ) {
24
- switch (builtin . cpu . arch ) {
155
+ switch (native_arch ) {
25
156
.x86 = > {
26
157
if (builtin .omit_frame_pointer ) {
27
158
asm volatile (
@@ -67,33 +198,139 @@ noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
67
198
}
68
199
69
200
expected [1 ] = @returnAddress ();
70
- frame3 (expected , unwound );
201
+ frame3 (expected );
71
202
}
72
203
73
- noinline fn frame1 (expected : * [4 ]usize , unwound : * [ 4 ] usize ) void {
204
+ noinline fn frame1 (expected : * [4 ]usize ) void {
74
205
expected [2 ] = @returnAddress ();
75
206
76
207
// Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
77
208
// to exercise the stack-indirect encoding path
78
209
var pad : [std .math .maxInt (u8 ) * @sizeOf (usize ) + 1 ]u8 = undefined ;
79
210
_ = std .mem .doNotOptimizeAway (& pad );
80
211
81
- frame2 (expected , unwound );
212
+ frame2 (expected );
82
213
}
83
214
84
- noinline fn frame0 (expected : * [4 ]usize , unwound : * [ 4 ] usize ) void {
215
+ noinline fn frame0 (expected : * [4 ]usize ) void {
85
216
expected [3 ] = @returnAddress ();
86
- frame1 (expected , unwound );
217
+ frame1 (expected );
87
218
}
88
219
89
220
pub fn main () ! void {
90
221
// 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 ;
222
+ if (builtin .omit_frame_pointer and comptime builtin .target .os .tag .isDarwin () and native_arch == .aarch64 ) return ;
223
+
224
+ if (do_signal ) {
225
+ std .debug .print ("Installing SIGURG handler ...\n " , .{});
226
+ posix .sigaction (posix .SIG .URG , &.{
227
+ .handler = .{ .sigaction = testFromSigUrg },
228
+ .mask = posix .sigemptyset (),
229
+ .flags = (posix .SA .SIGINFO | posix .SA .RESTART ),
230
+ }, null );
231
+ installed_signal_handler = true ;
232
+ } else {
233
+ std .debug .print ("(No signal-based backtrace on this configuration.)\n " , .{});
234
+ installed_signal_handler = false ;
235
+ }
236
+ handled_signal = false ;
237
+
238
+ signal_frames = try AddrArray .init (0 );
239
+ skip_frames = try AddrArray .init (0 );
240
+ full_frames = try AddrArray .init (0 );
92
241
93
- if ( ! std .debug .have_ucontext or ! std . debug . have_getcontext ) return ;
242
+ std .debug .print ( "Running... \n " , .{}) ;
94
243
95
244
var expected : [4 ]usize = undefined ;
96
- var unwound : [4 ]usize = undefined ;
97
- frame0 (& expected , & unwound );
98
- try testing .expectEqual (expected , unwound );
245
+ frame0 (& expected );
246
+
247
+ std .debug .print ("Verification: arch={s} link_libc={} have_ucontext={} have_getcontext={} ...\n " , .{
248
+ @tagName (native_arch ), link_libc , std .debug .have_ucontext , std .debug .have_getcontext ,
249
+ });
250
+ std .debug .print (" expected={any}\n " , .{expected });
251
+ std .debug .print (" full_frames={any}\n " , .{full_frames .slice ()});
252
+ std .debug .print (" skip_frames={any}\n " , .{skip_frames .slice ()});
253
+ std .debug .print (" signal_frames={any}\n " , .{signal_frames .slice ()});
254
+
255
+ var fail_count : usize = 0 ;
256
+
257
+ if (do_signal and installed_signal_handler ) {
258
+ try testing .expectEqual (true , handled_signal );
259
+ }
260
+
261
+ // None of the backtraces should overflow max_stack_trace_depth
262
+
263
+ if (skip_frames .len == skip_frames .capacity ()) {
264
+ std .debug .print ("skip_frames contains too many frames: {}\n " , .{skip_frames .len });
265
+ fail_count += 1 ;
266
+ }
267
+
268
+ if (full_frames .len == full_frames .capacity ()) {
269
+ std .debug .print ("full_frames contains too many frames: {}\n " , .{full_frames .len });
270
+ fail_count += 1 ;
271
+ }
272
+
273
+ if (signal_frames .len == signal_frames .capacity ()) {
274
+ if (expect_signal_frame_overflow ) {
275
+ // The signal_frames backtrace overflows. Ignore this for now.
276
+ std .debug .print ("(expected) signal_frames overflow: {}\n " , .{signal_frames .len });
277
+ } else {
278
+ std .debug .print ("signal_frames contains too many frames: {}\n " , .{signal_frames .len });
279
+ fail_count += 1 ;
280
+ }
281
+ }
282
+
283
+ if (supports_stack_iterator ) {
284
+ if (captured_frames ) {
285
+ // Saved 'skip_frames' should start with the expected frames, exactly.
286
+ try testing .expectEqual (skip_frames .slice ()[0.. 4].* , expected );
287
+
288
+ // The return addresses in "expected[]" should show up, in order, in the "full_frames" array
289
+ var found = false ;
290
+ for (0.. full_frames .len ) | i | {
291
+ const addr = full_frames .get (i );
292
+ if (addr == expected [0 ]) {
293
+ try testing .expectEqual (full_frames .get (i + 1 ), expected [1 ]);
294
+ try testing .expectEqual (full_frames .get (i + 2 ), expected [2 ]);
295
+ try testing .expectEqual (full_frames .get (i + 3 ), expected [3 ]);
296
+ found = true ;
297
+ }
298
+ }
299
+ if (! found ) {
300
+ std .debug .print ("full_frames[...] does not include expected[0..4]\n " , .{});
301
+ fail_count += 1 ;
302
+ }
303
+ }
304
+
305
+ if (installed_signal_handler and handled_signal ) {
306
+ // The return addresses in "expected[]" should show up, in order, in the "signal_frames" array
307
+ var found = false ;
308
+ for (0.. signal_frames .len ) | i | {
309
+ const signal_addr = signal_frames .get (i );
310
+ if (signal_addr == expected [0 ]) {
311
+ try testing .expectEqual (signal_frames .get (i + 1 ), expected [1 ]);
312
+ try testing .expectEqual (signal_frames .get (i + 2 ), expected [2 ]);
313
+ try testing .expectEqual (signal_frames .get (i + 3 ), expected [3 ]);
314
+ found = true ;
315
+ }
316
+ }
317
+ if (! found ) {
318
+ if (expect_signal_frame_useless ) {
319
+ std .debug .print ("(expected) signal_frames[...] does not include expected[0..4]\n " , .{});
320
+ } else {
321
+ std .debug .print ("signal_frames[...] does not include expected[0..4]\n " , .{});
322
+ fail_count += 1 ;
323
+ }
324
+ }
325
+ }
326
+ } else {
327
+ // If these tests fail, then this platform now supports StackIterator
328
+ try testing .expectEqual (0 , skip_frames .len );
329
+ try testing .expectEqual (0 , full_frames .len );
330
+ try testing .expectEqual (0 , signal_frames .len );
331
+ }
332
+
333
+ std .debug .print ("Test complete.\n " , .{});
334
+
335
+ try testing .expectEqual (0 , fail_count );
99
336
}
0 commit comments