1const std = @import("std.zig");
2const Io = std.Io;
3const Writer = std.Io.Writer;
4const math = std.math;
5const mem = std.mem;
6const posix = std.posix;
7const fs = std.fs;
8const testing = std.testing;
9const Allocator = mem.Allocator;
10const File = std.Io.File;
11const windows = std.os.windows;
12
13const builtin = @import("builtin");
14const native_arch = builtin.cpu.arch;
15const native_os = builtin.os.tag;
16
17const root = @import("root");
18
19pub const Dwarf = @import("debug/Dwarf.zig");
20pub const Pdb = @import("debug/Pdb.zig");
21pub const ElfFile = @import("debug/ElfFile.zig");
22pub const MachOFile = @import("debug/MachOFile.zig");
23pub const Info = @import("debug/Info.zig");
24pub const Coverage = @import("debug/Coverage.zig");
25pub const cpu_context = @import("debug/cpu_context.zig");
26
27/// This type abstracts the target-specific implementation of accessing this process' own debug
28/// information behind a generic interface which supports looking up source locations associated
29/// with addresses, as well as unwinding the stack where a safe mechanism to do so exists.
30///
31/// The Zig Standard Library provides default implementations of `SelfInfo` for common targets, but
32/// the implementation can be overriden by exposing `root.debug.SelfInfo`. Setting `SelfInfo` to
33/// `void` indicates that the `SelfInfo` API is not supported.
34///
35/// This type must expose the following declarations:
36///
37/// ```
38/// pub const init: SelfInfo;
39/// pub fn deinit(si: *SelfInfo, io: Io) void;
40///
41/// /// Appends the symbols for the instruction at `address` to `symbols`.
42/// pub fn getSymbols(si: *SelfInfo, io: Io, symbol_allocator: Allocator, text_arena: Allocator, address: usize, include_inline_callers: bool, symbols: *std.ArrayList(Symbol)) SelfInfoError!void;
43/// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`.
44/// pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) SelfInfoError![]const u8;
45/// pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) SelfInfoError!usize;
46///
47/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.
48/// pub const can_unwind: bool;
49/// /// Only required if `can_unwind == true`.
50/// pub const UnwindContext = struct {
51/// /// An address representing the instruction pointer in the last frame.
52/// pc: usize,
53///
54/// pub fn init(ctx: *cpu_context.Native) Allocator.Error!UnwindContext;
55/// pub fn deinit(ctx: *UnwindContext) void;
56/// /// Returns the frame pointer associated with the last unwound stack frame.
57/// /// If the frame pointer is unknown, 0 may be returned instead.
58/// pub fn getFp(uc: *UnwindContext) usize;
59/// };
60/// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's
61/// /// return address, or 0 if the end of the stack has been reached.
62/// pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) SelfInfoError!usize;
63/// ```
64pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))
65 root.debug.SelfInfo
66else
67 TargetInfo(native_os, native_arch);
68
69/// Returns the default `SelfInfo` for the given `os` and `arch`.
70pub fn TargetInfo(os: std.Target.Os.Tag, arch: std.Target.Cpu.Arch) type {
71 return switch (std.Target.ObjectFormat.default(os, arch)) {
72 .coff => if (os == .windows) @import("debug/SelfInfo/Windows.zig") else void,
73 .elf => switch (os) {
74 .freestanding, .other => void,
75 else => @import("debug/SelfInfo/Elf.zig"),
76 },
77 .macho => @import("debug/SelfInfo/MachO.zig"),
78 .plan9, .spirv, .wasm, .raw, .hex => void,
79 .c => unreachable,
80 };
81}
82
83pub const SelfInfoError = error{
84 /// The required debug info is invalid or corrupted.
85 InvalidDebugInfo,
86 /// The required debug info could not be found.
87 MissingDebugInfo,
88 /// The required debug info was found, and may be valid, but is not supported by this implementation.
89 UnsupportedDebugInfo,
90 /// The required debug info could not be read from disk due to some IO error.
91 ReadFailed,
92 OutOfMemory,
93 Canceled,
94 Unexpected,
95};
96
97pub const simple_panic = @import("debug/simple_panic.zig");
98pub const no_panic = @import("debug/no_panic.zig");
99
100/// A fully-featured panic handler namespace which lowers all panics to calls to `panicFn`.
101/// Safety panics will use formatted printing to provide a meaningful error message.
102/// The signature of `panicFn` should match that of `defaultPanic`.
103pub fn FullPanic(comptime panicFn: fn ([]const u8, ?usize) noreturn) type {
104 return struct {
105 pub const call = panicFn;
106 pub fn sentinelMismatch(expected: anytype, found: @TypeOf(expected)) noreturn {
107 @branchHint(.cold);
108 std.debug.panicExtra(@returnAddress(), "sentinel mismatch: expected {any}, found {any}", .{
109 expected, found,
110 });
111 }
112 pub fn unwrapError(err: anyerror) noreturn {
113 @branchHint(.cold);
114 std.debug.panicExtra(@returnAddress(), "attempt to unwrap error: {s}", .{@errorName(err)});
115 }
116 pub fn outOfBounds(index: usize, len: usize) noreturn {
117 @branchHint(.cold);
118 std.debug.panicExtra(@returnAddress(), "index out of bounds: index {d}, len {d}", .{ index, len });
119 }
120 pub fn startGreaterThanEnd(start: usize, end: usize) noreturn {
121 @branchHint(.cold);
122 std.debug.panicExtra(@returnAddress(), "start index {d} is larger than end index {d}", .{ start, end });
123 }
124 pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn {
125 @branchHint(.cold);
126 std.debug.panicExtra(@returnAddress(), "access of union field '{s}' while field '{s}' is active", .{
127 @tagName(accessed), @tagName(active),
128 });
129 }
130 pub fn sliceCastLenRemainder(src_len: usize) noreturn {
131 @branchHint(.cold);
132 std.debug.panicExtra(@returnAddress(), "slice length '{d}' does not divide exactly into destination elements", .{src_len});
133 }
134 pub fn reachedUnreachable() noreturn {
135 @branchHint(.cold);
136 call("reached unreachable code", @returnAddress());
137 }
138 pub fn unwrapNull() noreturn {
139 @branchHint(.cold);
140 call("attempt to use null value", @returnAddress());
141 }
142 pub fn castToNull() noreturn {
143 @branchHint(.cold);
144 call("cast causes pointer to be null", @returnAddress());
145 }
146 pub fn incorrectAlignment() noreturn {
147 @branchHint(.cold);
148 call("incorrect alignment", @returnAddress());
149 }
150 pub fn invalidErrorCode() noreturn {
151 @branchHint(.cold);
152 call("invalid error code", @returnAddress());
153 }
154 pub fn unexpectedErrorCode(err: anyerror) noreturn {
155 @branchHint(.cold);
156 std.debug.panicExtra(@returnAddress(), "unexpected error code, found error.{s}", .{@errorName(err)});
157 }
158 pub fn integerOutOfBounds() noreturn {
159 @branchHint(.cold);
160 call("integer does not fit in destination type", @returnAddress());
161 }
162 pub fn integerOverflow() noreturn {
163 @branchHint(.cold);
164 call("integer overflow", @returnAddress());
165 }
166 pub fn shlOverflow() noreturn {
167 @branchHint(.cold);
168 call("left shift overflowed bits", @returnAddress());
169 }
170 pub fn shrOverflow() noreturn {
171 @branchHint(.cold);
172 call("right shift overflowed bits", @returnAddress());
173 }
174 pub fn divideByZero() noreturn {
175 @branchHint(.cold);
176 call("division by zero", @returnAddress());
177 }
178 pub fn exactDivisionRemainder() noreturn {
179 @branchHint(.cold);
180 call("exact division produced remainder", @returnAddress());
181 }
182 pub fn integerPartOutOfBounds() noreturn {
183 @branchHint(.cold);
184 call("integer part of floating point value out of bounds", @returnAddress());
185 }
186 pub fn corruptSwitch() noreturn {
187 @branchHint(.cold);
188 call("switch on corrupt value", @returnAddress());
189 }
190 pub fn shiftRhsTooBig() noreturn {
191 @branchHint(.cold);
192 call("shift amount is greater than the type size", @returnAddress());
193 }
194 pub fn invalidEnumValue() noreturn {
195 @branchHint(.cold);
196 call("invalid enum value", @returnAddress());
197 }
198 pub fn forLenMismatch() noreturn {
199 @branchHint(.cold);
200 call("for loop over objects with non-equal lengths", @returnAddress());
201 }
202 pub fn copyLenMismatch() noreturn {
203 @branchHint(.cold);
204 call("source and destination arguments have non-equal lengths", @returnAddress());
205 }
206 pub fn memcpyAlias() noreturn {
207 @branchHint(.cold);
208 call("@memcpy arguments alias", @returnAddress());
209 }
210 pub fn noreturnReturned() noreturn {
211 @branchHint(.cold);
212 call("'noreturn' function returned", @returnAddress());
213 }
214 pub fn loadUninstantiableType() noreturn {
215 @branchHint(.cold);
216 call("attempt to load uninstantiable type", @returnAddress());
217 }
218 };
219}
220
221/// Unresolved source locations can be represented with a single `usize` that
222/// corresponds to a virtual memory address of the program counter. Combined
223/// with debug information, those values can be converted into a resolved
224/// source location, including file, line, and column.
225pub const SourceLocation = struct {
226 line: u64,
227 column: u64,
228 file_name: []const u8,
229
230 pub const invalid: SourceLocation = .{
231 .line = 0,
232 .column = 0,
233 .file_name = &.{},
234 };
235};
236
237pub const Symbol = struct {
238 name: ?[]const u8,
239 compile_unit_name: ?[]const u8,
240 source_location: ?SourceLocation,
241 pub const unknown: Symbol = .{
242 .name = null,
243 .compile_unit_name = null,
244 .source_location = null,
245 };
246};
247
248/// Deprecated in favor of `std.lang.Optimize.runtimeSafety`, to be removed after 0.18.0
249///
250/// Returns whether the standard library has safety checks enabled. Callsites
251/// likely would rather know whether their own module's optimization mode
252/// (found via `@import("builtin").optimize`) has safety checks enabled.
253pub const runtime_safety = builtin.mode.runtimeSafety();
254
255/// Whether we can unwind the stack on this target, allowing capturing and/or printing the current
256/// stack trace. It is still legal to call `captureCurrentStackTrace`, `writeCurrentStackTrace`, and
257/// `dumpCurrentStackTrace` if this is `false`; it will just print an error / capture an empty
258/// trace due to missing functionality. This value is just intended as a heuristic to avoid
259/// pointless work e.g. capturing always-empty stack traces.
260pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
261 // `@returnAddress()` in LLVM 10 gives
262 // "Non-Emscripten WebAssembly hasn't implemented __builtin_return_address".
263 // On Emscripten, Zig only supports `@returnAddress()` in debug builds
264 // because Emscripten's implementation is very slow.
265 .wasm32,
266 .wasm64,
267 => native_os == .emscripten and builtin.mode == .debug,
268
269 // `@returnAddress()` is unsupported in LLVM 21.
270 .bpfel,
271 .bpfeb,
272 => false,
273
274 // https://codeberg.org/ziglang/zig/issues/31127
275 .avr => false,
276
277 else => true,
278};
279
280/// Allows the caller to freely write to stderr until `unlockStderr` is called.
281///
282/// During the lock, any `std.Progress` information is cleared from the terminal.
283///
284/// The lock is recursive, so it is valid for the same thread to call
285/// `lockStderr` multiple times, allowing the panic handler to safely
286/// dump the stack trace and panic message even if the mutex was held at the
287/// panic site.
288///
289/// The returned `Writer` does not need to be manually flushed: flushing is
290/// performed automatically when the matching `unlockStderr` call occurs.
291///
292/// This is a low-level debugging primitive that bypasses the `Io` interface,
293/// writing directly to stderr using the most basic syscalls available. This
294/// function does not switch threads, switch stacks, or suspend.
295///
296/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
297/// application's chosen `Io` implementation.
298pub fn lockStderr(buffer: []u8) Io.LockedStderr {
299 const io = std.Options.debug_io;
300 const prev = io.swapCancelProtection(.blocked);
301 defer _ = io.swapCancelProtection(prev);
302 return io.lockStderr(buffer, null) catch |err| switch (err) {
303 error.Canceled => unreachable, // Cancel protection enabled above.
304 };
305}
306
307pub fn unlockStderr() void {
308 const io = std.Options.debug_io;
309 io.unlockStderr();
310}
311
312/// Writes to stderr, ignoring errors.
313///
314/// This is a low-level debugging primitive that bypasses the `Io` interface,
315/// writing directly to stderr using the most basic syscalls available. This
316/// function does not switch threads, switch stacks, or suspend.
317///
318/// Uses a 64-byte buffer for formatted printing which is flushed before this
319/// function returns.
320///
321/// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to
322/// integrate with the application's chosen `Io` implementation.
323pub fn print(comptime fmt: []const u8, args: anytype) void {
324 const io = std.Options.debug_io;
325 const prev = io.swapCancelProtection(.blocked);
326 defer _ = io.swapCancelProtection(prev);
327 var buffer: [64]u8 = undefined;
328 const stderr = lockStderr(&buffer);
329 defer unlockStderr();
330 stderr.file_writer.interface.print(fmt, args) catch return;
331}
332
333/// Marked `inline` to propagate a comptime-known error to callers.
334pub inline fn getSelfDebugInfo() !*SelfInfo {
335 if (SelfInfo == void) return error.UnsupportedTarget;
336 const S = struct {
337 var self_info: SelfInfo = .init;
338 };
339 return &S.self_info;
340}
341
342/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
343/// Obtains the stderr mutex while dumping.
344pub fn dumpHex(bytes: []const u8) void {
345 const io = std.Options.debug_io;
346 const prev = io.swapCancelProtection(.blocked);
347 defer _ = io.swapCancelProtection(prev);
348 const stderr = lockStderr(&.{}).terminal();
349 defer unlockStderr();
350 dumpHexFallible(stderr, bytes) catch {};
351}
352
353/// Prints a hexadecimal view of the bytes, returning any error that occurs.
354pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void {
355 const w = t.writer;
356 var chunks = mem.window(u8, bytes, 16, 16);
357 while (chunks.next()) |window| {
358 // 1. Print the address.
359 const address = (@intFromPtr(bytes.ptr) + 0x10 * @divCeil(chunks.index orelse bytes.len, 16) - 0x10);
360 try t.setColor(.dim);
361 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
362 // Also, make sure all lines are aligned by padding the address.
363 try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
364 try t.setColor(.reset);
365
366 // 2. Print the bytes.
367 for (window, 0..) |byte, index| {
368 try w.print("{X:0>2} ", .{byte});
369 if (index == 7) try w.writeByte(' ');
370 }
371 try w.writeByte(' ');
372 if (window.len < 16) {
373 var missing_columns = (16 - window.len) * 3;
374 if (window.len < 8) missing_columns += 1;
375 try w.splatByteAll(' ', missing_columns);
376 }
377
378 // 3. Print the characters.
379 for (window) |byte| {
380 if (std.ascii.isPrint(byte)) {
381 try w.writeByte(byte);
382 } else {
383 // Related: https://github.com/ziglang/zig/issues/7600
384 if (t.mode == .windows_api) {
385 try w.writeByte('.');
386 continue;
387 }
388
389 // Let's print some common control codes as graphical Unicode symbols.
390 // We don't want to do this for all control codes because most control codes apart from
391 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.
392 switch (byte) {
393 '\n' => try w.writeAll("␊"),
394 '\r' => try w.writeAll("␍"),
395 '\t' => try w.writeAll("␉"),
396 else => try w.writeByte('.'),
397 }
398 }
399 }
400 try w.writeByte('\n');
401 }
402}
403
404test dumpHexFallible {
405 const gpa = testing.allocator;
406 const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 };
407 var aw: Writer.Allocating = .init(gpa);
408 defer aw.deinit();
409
410 try dumpHexFallible(.{ .writer = &aw.writer, .mode = .no_color }, bytes);
411 const expected = try std.fmt.allocPrint(gpa,
412 \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........
413 \\{x:0>[2]} 01 12 13 ...
414 \\
415 , .{
416 @intFromPtr(bytes.ptr),
417 @intFromPtr(bytes.ptr) + 16,
418 @sizeOf(usize) * 2,
419 });
420 defer gpa.free(expected);
421 try testing.expectEqualStrings(expected, aw.written());
422}
423
424/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.
425pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *const cpu_context.Native;
426
427/// Invokes detectable illegal behavior when `ok` is `false`.
428///
429/// In debug and safe modes, calls to this function are always
430/// generated, and the `unreachable` statement triggers a panic.
431///
432/// In fast and small modes, calls to this function are optimized
433/// away, and in fact the optimizer is able to use the assertion in its
434/// heuristics.
435///
436/// Inside a test block, it is best to use the `testing` module rather than
437/// this function, because this function may not detect a test failure in
438/// fast and small mode. Outside of a test block, this assert
439/// function is the correct function to use.
440pub fn assert(ok: bool) void {
441 @disableInstrumentation();
442 if (!ok) unreachable; // assertion failure
443}
444
445/// Invokes detectable illegal behavior when the provided slice is not mapped
446/// or lacks read permissions.
447pub fn assertReadable(slice: []const volatile u8) void {
448 if (!runtime_safety) return;
449 for (slice) |*byte| _ = byte.*;
450}
451
452/// Invokes detectable illegal behavior when the provided array is not aligned
453/// to the provided amount.
454pub fn assertAligned(ptr: anytype, comptime alignment: std.mem.Alignment) void {
455 const aligned_ptr: *align(alignment.toByteUnits()) const anyopaque = @ptrCast(@alignCast(ptr));
456 _ = aligned_ptr;
457}
458
459/// Equivalent to `@panic` but with a formatted message.
460pub fn panic(comptime format: []const u8, args: anytype) noreturn {
461 @branchHint(.cold);
462 panicExtra(@returnAddress(), format, args);
463}
464
465/// Equivalent to `@panic` but with a formatted message and an explicitly provided return address
466/// which will be the first address in the stack trace.
467pub fn panicExtra(
468 ret_addr: ?usize,
469 comptime format: []const u8,
470 args: anytype,
471) noreturn {
472 @branchHint(.cold);
473
474 const size = 0x1000;
475 const trunc_msg = "(msg truncated)";
476 var buf: [size + trunc_msg.len]u8 = undefined;
477 var bw: Writer = .fixed(buf[0..size]);
478 // a minor annoyance with this is that it will result in the NoSpaceLeft
479 // error being part of the @panic stack trace (but that error should
480 // only happen rarely)
481 const msg = if (bw.print(format, args)) |_| bw.buffered() else |_| blk: {
482 @memcpy(buf[size..], trunc_msg);
483 break :blk &buf;
484 };
485 std.builtin.panic.call(msg, ret_addr);
486}
487
488/// Non-zero whenever the program triggered a panic.
489/// The counter is incremented/decremented atomically.
490var panicking = std.atomic.Value(u8).init(0);
491
492/// Counts how many times the panic handler is invoked by this thread.
493/// This is used to catch and handle panics triggered by the panic handler.
494threadlocal var panic_stage: usize = 0;
495
496/// For backends that cannot handle the language features depended on by the
497/// default panic handler, we will use a simpler implementation.
498const use_trap_panic = switch (builtin.zig_backend) {
499 .stage2_aarch64,
500 .stage2_arm,
501 .stage2_loongarch,
502 .stage2_powerpc,
503 .stage2_riscv64,
504 .stage2_spirv,
505 .stage2_x86,
506 => true,
507 else => false,
508};
509
510/// Dumps a stack trace to standard error, then aborts.
511pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
512 @branchHint(.cold);
513
514 if (use_trap_panic) @trap();
515
516 switch (builtin.os.tag) {
517 .freestanding,
518 .other,
519
520 .@"3ds",
521 .wiiu,
522 .@"switch",
523 .gba,
524
525 .psx,
526 .psp,
527 .vita,
528 => {
529 @trap();
530 },
531 .uefi => {
532 const uefi = std.os.uefi;
533
534 var utf16_buffer: [1000]u16 = undefined;
535 const len_minus_3 = std.unicode.utf8ToUtf16Le(&utf16_buffer, msg) catch 0;
536 utf16_buffer[len_minus_3..][0..3].* = .{ '\r', '\n', 0 };
537 const len = len_minus_3 + 3;
538 const exit_msg = utf16_buffer[0 .. len - 1 :0];
539
540 // Output to both std_err and con_out, as std_err is easier
541 // to read in stuff like QEMU at times, but, unlike con_out,
542 // isn't visible on actual hardware if directly booted into
543 inline for ([_]?*uefi.protocol.SimpleTextOutput{ uefi.system_table.std_err, uefi.system_table.con_out }) |o| {
544 if (o) |out| {
545 out.setAttribute(.{ .foreground = .red }) catch {};
546 _ = out.outputString(exit_msg) catch {};
547 out.setAttribute(.{ .foreground = .white }) catch {};
548 }
549 }
550
551 if (uefi.system_table.boot_services) |bs| {
552 // ExitData buffer must be allocated using boot_services.allocatePool (spec: page 220)
553 const exit_data = uefi.raw_pool_allocator.dupeSentinel(u16, exit_msg, 0) catch @trap();
554 bs.exit(uefi.handle, .aborted, exit_data) catch {};
555 }
556 @trap();
557 },
558 .cuda, .amdhsa => std.process.abort(),
559 .plan9 => {
560 var status: [std.os.plan9.ERRMAX]u8 = undefined;
561 const len = @min(msg.len, status.len - 1);
562 @memcpy(status[0..len], msg[0..len]);
563 status[len] = 0;
564 std.os.plan9.exits(status[0..len :0]);
565 },
566 else => {},
567 }
568
569 std.Options.debug_io.vtable.crashHandler(std.Options.debug_io.userdata);
570
571 if (enable_segfault_handler) {
572 // If a segfault happens while panicking, we want it to actually segfault, not trigger
573 // the handler.
574 resetSegfaultHandler();
575 }
576
577 // There is very similar logic to the following in `handleSegfault`.
578 switch (panic_stage) {
579 0 => {
580 panic_stage = 1;
581 _ = panicking.fetchAdd(1, .seq_cst);
582
583 trace: {
584 const stderr = lockStderr(&.{}).terminal();
585 defer unlockStderr();
586 const writer = stderr.writer;
587
588 if (builtin.single_threaded) {
589 writer.print("panic: ", .{}) catch break :trace;
590 } else {
591 const current_thread_id = std.Thread.getCurrentId();
592 writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
593 }
594 writer.print("{s}\n", .{msg}) catch break :trace;
595
596 if (@errorReturnTrace()) |t| if (t.index > 0) {
597 writer.writeAll("error return context:\n") catch break :trace;
598 writeErrorReturnTrace(t, stderr) catch break :trace;
599 writer.writeAll("\nstack trace:\n") catch break :trace;
600 };
601 writeCurrentStackTrace(.{
602 .first_address = first_trace_addr orelse @returnAddress(),
603 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
604 }, stderr) catch break :trace;
605 }
606
607 waitForOtherThreadToFinishPanicking();
608 },
609 1 => {
610 panic_stage = 2;
611 // A panic happened while trying to print a previous panic message.
612 // We're still holding the mutex but that's fine as we're going to
613 // call abort().
614 const stderr = lockStderr(&.{}).terminal();
615 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
616 },
617 else => {}, // Panicked while printing the recursive panic message.
618 }
619
620 std.process.abort();
621}
622
623/// Must be called only after adding 1 to `panicking`. There are three callsites.
624fn waitForOtherThreadToFinishPanicking() void {
625 if (panicking.fetchSub(1, .seq_cst) != 1) {
626 // Another thread is panicking, wait for the last one to finish
627 // and call abort()
628 if (builtin.single_threaded) unreachable;
629
630 // Sleep forever without hammering the CPU
631 var futex: u32 = 0;
632 while (true) std.Options.debug_io.futexWaitUncancelable(u32, &futex, 0);
633 unreachable;
634 }
635}
636
637pub const StackTrace = struct {
638 /// Each element is the "return address" of a function call, meaning the instruction address
639 /// which control flow will return to when the function returns.
640 ///
641 /// The first slice element corresponds to the innermost stack frame, and the last element to
642 /// the outermost.
643 ///
644 /// Inlined function calls do not have meaningful return addresses and are therefore not
645 /// included in this slice. Instead, when printing the stack trace, the source locations of
646 /// inline calls should be read from debug information and the corresponding "inline frames"
647 /// printed in the appropriate locations.
648 return_addresses: []usize,
649 /// Indicates whether any stack frames were omitted from `return_addresses`.
650 skipped: SkippedAddresses,
651};
652
653/// Indicates how many addresses were skipped in a trace.
654pub const SkippedAddresses = enum(usize) {
655 /// No addresses were omitted: `return_addresses` contains all stack frames, including the
656 /// outermost.
657 none = 0,
658 /// It is not known whether any frames were omitted.
659 unknown = std.math.maxInt(usize),
660 /// The full stack trace was available, but some frames are not included in
661 /// `return_addresses` due to buffer size limitations. The enum value is the exact number of
662 /// addresses which were omitted.
663 _,
664};
665
666pub const StackUnwindOptions = struct {
667 /// If not `null`, we will ignore all frames up until this return address. This is typically
668 /// used to omit intermediate handling code (for instance, a panic handler and its machinery)
669 /// from stack traces.
670 first_address: ?usize = null,
671 /// If not `null`, we will unwind from this `cpu_context.Native` instead of the current top of
672 /// the stack. The main use case here is printing stack traces from signal handlers, where the
673 /// kernel provides a `*const cpu_context.Native` of the state before the signal.
674 context: ?CpuContextPtr = null,
675 /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.
676 /// If `false`, only known-safe mechanisms will be attempted.
677 allow_unsafe_unwind: bool = false,
678};
679
680/// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in
681/// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`.
682///
683/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
684pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace {
685 const empty_trace: StackTrace = .{
686 .return_addresses = &.{},
687 .skipped = .none,
688 };
689 if (!std.options.allow_stack_tracing) return empty_trace;
690 var it: StackIterator = .init(options.context);
691 defer it.deinit();
692 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
693
694 const io = std.Options.debug_io;
695
696 var total_frames: usize = 0;
697 var index: usize = 0;
698 var wait_for = options.first_address;
699 // Ideally, we would iterate the whole stack so that the `index - min(buf.len, index)` would be
700 // indicative of how many frames were skipped. However, this has a significant runtime cost
701 // in some cases, so at least for now, we don't do that.
702 const skipped: SkippedAddresses = while (index < addr_buf.len) switch (it.next(io)) {
703 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break .unknown,
704 .end => break .none,
705 .frame => |ret_addr| {
706 if (total_frames > 10_000) {
707 // Limit the number of frames in case of (e.g.) broken debug information which is
708 // getting unwinding stuck in a loop.
709 break .unknown;
710 }
711 total_frames += 1;
712 if (wait_for) |target| {
713 if (ret_addr != target) continue;
714 wait_for = null;
715 }
716 addr_buf[index] = ret_addr;
717 index += 1;
718 },
719 } else .unknown;
720 return .{
721 .return_addresses = addr_buf[0..index],
722 .skipped = skipped,
723 };
724}
725/// Write the current stack trace to `writer`, annotated with source locations.
726///
727/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
728pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {
729 const writer = t.writer;
730
731 var text_arena: std.heap.ArenaAllocator = .init(getDebugInfoAllocator());
732 defer text_arena.deinit();
733
734 if (!std.options.allow_stack_tracing) {
735 t.setColor(.dim) catch {};
736 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
737 t.setColor(.reset) catch {};
738 return;
739 }
740 const di = getSelfDebugInfo() catch |err| switch (err) {
741 error.UnsupportedTarget => {
742 t.setColor(.dim) catch {};
743 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
744 t.setColor(.reset) catch {};
745 return;
746 },
747 };
748 var it: StackIterator = .init(options.context);
749 defer it.deinit();
750 if (!it.stratOk(options.allow_unsafe_unwind)) {
751 t.setColor(.dim) catch {};
752 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
753 t.setColor(.reset) catch {};
754 return;
755 }
756 var total_frames: usize = 0;
757 var wait_for = options.first_address;
758 var printed_any_frame = false;
759 const io = std.Options.debug_io;
760 while (true) switch (it.next(io)) {
761 .switch_to_fp => |unwind_error| {
762 switch (StackIterator.fp_usability) {
763 .useless, .unsafe => {},
764 .safe, .ideal => continue, // no need to even warn
765 }
766 const module_name = di.getModuleName(io, unwind_error.address) catch "???";
767 const caption: []const u8 = switch (unwind_error.err) {
768 error.MissingDebugInfo => "unwind info unavailable",
769 error.InvalidDebugInfo => "unwind info invalid",
770 error.UnsupportedDebugInfo => "unwind info unsupported",
771 error.ReadFailed => "filesystem error",
772 error.OutOfMemory => "out of memory",
773 error.Canceled => "operation canceled",
774 error.Unexpected => "unexpected error",
775 };
776 if (it.stratOk(options.allow_unsafe_unwind)) {
777 t.setColor(.dim) catch {};
778 try writer.print(
779 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
780 .{ module_name, unwind_error.address, caption },
781 );
782 t.setColor(.reset) catch {};
783 } else {
784 t.setColor(.dim) catch {};
785 try writer.print(
786 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
787 .{ module_name, unwind_error.address, caption },
788 );
789 t.setColor(.reset) catch {};
790 return;
791 }
792 },
793 .end => break,
794 .frame => |ret_addr| {
795 if (total_frames > 10_000) {
796 t.setColor(.dim) catch {};
797 try writer.print(
798 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
799 .{total_frames},
800 );
801 t.setColor(.reset) catch {};
802 return;
803 }
804 total_frames += 1;
805 if (wait_for) |target| {
806 if (ret_addr != target) continue;
807 wait_for = null;
808 }
809 // `ret_addr` is the return address, which is *after* the function call.
810 // Subtract 1 to get an address *in* the function call for a better source location.
811 try printSourceAtAddress(io, &text_arena, di, t, .{
812 .address = ret_addr -| StackIterator.ra_call_offset,
813 .resolve_inline_callers = true,
814 });
815 printed_any_frame = true;
816 },
817 };
818 if (!printed_any_frame) return writer.writeAll("(empty stack trace)\n");
819}
820/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
821pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
822 const io = std.Options.debug_io;
823 const prev = io.swapCancelProtection(.blocked);
824 defer _ = io.swapCancelProtection(prev);
825 const stderr = lockStderr(&.{}).terminal();
826 defer unlockStderr();
827 writeCurrentStackTrace(.{
828 .first_address = a: {
829 if (options.first_address) |a| break :a a;
830 if (options.context != null) break :a null;
831 break :a @returnAddress(); // don't include this frame in the trace
832 },
833 .context = options.context,
834 .allow_unsafe_unwind = options.allow_unsafe_unwind,
835 }, stderr) catch |err| switch (err) {
836 error.WriteFailed => {},
837 };
838}
839
840pub const FormatStackTrace = struct {
841 stack_trace: StackTrace,
842 terminal_mode: Io.Terminal.Mode = .no_color,
843
844 pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void {
845 try writer.writeByte('\n');
846 try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode });
847 }
848};
849
850/// Write a previously captured error return trace to `writer`, annotated with source locations.
851pub fn writeErrorReturnTrace(et: *const std.builtin.StackTrace, t: Io.Terminal) Writer.Error!void {
852 // We take the slice by value, preventing the length from being mutated if an error occurs while
853 // writing the stack trace.
854 const len = @min(et.instruction_addresses.len, et.index);
855 const skipped = et.index - len;
856 try writeTrace(et.instruction_addresses[0..len], @fromBackingInt(@intCast(skipped)), t, false);
857}
858
859/// Write a previously captured stack trace to `writer`, annotated with source locations.
860pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void {
861 try writeTrace(st.return_addresses, st.skipped, t, true);
862}
863
864fn writeTrace(
865 addresses: []const usize,
866 skipped: SkippedAddresses,
867 t: Io.Terminal,
868 resolve_inline_callers: bool,
869) Writer.Error!void {
870 var text_arena: std.heap.ArenaAllocator = .init(getDebugInfoAllocator());
871 defer text_arena.deinit();
872
873 const writer = t.writer;
874 if (!std.options.allow_stack_tracing) {
875 t.setColor(.dim) catch {};
876 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
877 t.setColor(.reset) catch {};
878 return;
879 }
880
881 if (addresses.len == 0) return writer.writeAll("(empty stack trace)\n");
882 const di = getSelfDebugInfo() catch |err| switch (err) {
883 error.UnsupportedTarget => {
884 t.setColor(.dim) catch {};
885 try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});
886 t.setColor(.reset) catch {};
887 return;
888 },
889 };
890 const io = std.Options.debug_io;
891 for (addresses) |addr| {
892 // `addr` is the return address, which is *after* the function call.
893 // Subtract 1 to get an address *in* the function call for a better source location.
894 try printSourceAtAddress(io, &text_arena, di, t, .{
895 .address = addr -| StackIterator.ra_call_offset,
896 .resolve_inline_callers = resolve_inline_callers,
897 });
898 }
899 switch (skipped) {
900 .none => {},
901 .unknown => {
902 t.setColor(.bold) catch {};
903 try writer.writeAll("(additional stack frames may have been skipped...)\n");
904 t.setColor(.reset) catch {};
905 },
906 else => |n| {
907 t.setColor(.bold) catch {};
908 try writer.print("({d} additional stack frames skipped due to buffer size limitations...)\n", .{n});
909 t.setColor(.reset) catch {};
910 },
911 }
912}
913/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
914pub fn dumpStackTrace(st: *const StackTrace) void {
915 const io = std.Options.debug_io;
916 const prev = io.swapCancelProtection(.blocked);
917 defer _ = io.swapCancelProtection(prev);
918 const stderr = lockStderr(&.{}).terminal();
919 defer unlockStderr();
920 writeStackTrace(st, stderr) catch |err| switch (err) {
921 error.WriteFailed => {},
922 };
923}
924
925/// A thin wrapper around `writeErrorReturnTrace` which writes to stderr and ignores write errors.
926pub fn dumpErrorReturnTrace(et: *const std.builtin.StackTrace) void {
927 const io = std.Options.debug_io;
928 const prev = io.swapCancelProtection(.blocked);
929 defer _ = io.swapCancelProtection(prev);
930 const stderr = lockStderr(&.{}).terminal();
931 defer unlockStderr();
932 writeErrorReturnTrace(et, stderr) catch |err| switch (err) {
933 error.WriteFailed => {},
934 };
935}
936
937const StackIterator = union(enum) {
938 /// We will first report the current PC of this `CpuContextPtr`, then we will switch to a
939 /// different strategy to actually unwind.
940 ctx_first: CpuContextPtr,
941 /// Unwinding using debug info (e.g. DWARF CFI).
942 di: if (SelfInfo != void and SelfInfo.can_unwind and fp_usability != .ideal)
943 SelfInfo.UnwindContext
944 else
945 noreturn,
946 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
947 fp: usize,
948
949 /// It is important that this function is marked `inline` so that it can safely use
950 /// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and
951 /// our own are one and the same.
952 ///
953 /// `opt_context_ptr` must remain valid while the `StackIterator` is used.
954 inline fn init(opt_context_ptr: ?CpuContextPtr) StackIterator {
955 if (opt_context_ptr) |context_ptr| {
956 // Use `ctx_first` here so we report the PC in the context before unwinding any further.
957 return .{ .ctx_first = context_ptr };
958 }
959
960 // Otherwise, we're going to capture the current context or frame address, so we don't need
961 // `ctx_first`, because the first PC is in `std.debug` and we need to unwind before reaching
962 // a frame we want to report.
963
964 // Workaround the C backend being unable to use inline assembly on MSVC by disabling the
965 // call to `current`. This effectively constrains stack trace collection and dumping to FP
966 // unwinding when building with CBE for MSVC.
967 if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and
968 SelfInfo != void and
969 SelfInfo.can_unwind and
970 cpu_context.Native != noreturn and
971 fp_usability != .ideal)
972 {
973 return .{ .di = .init(&.current()) };
974 }
975 return .{
976 // On SPARC, the frame pointer will point to the previous frame's save area,
977 // meaning we will read the previous return address and thus miss a frame.
978 // Instead, start at the stack pointer so we get the return address from the
979 // current frame's save area. The addition of the stack bias cannot fail here
980 // since we know we have a valid stack pointer.
981 .fp = if (native_arch.isSPARC()) sp: {
982 flushSparcWindows();
983 break :sp asm (""
984 : [_] "={o6}" (-> usize),
985 ) + stack_bias;
986 } else @frameAddress(),
987 };
988 }
989 fn deinit(si: *StackIterator) void {
990 switch (si.*) {
991 .ctx_first => {},
992 .fp => {},
993 .di => |*unwind_context| unwind_context.deinit(),
994 }
995 }
996
997 noinline fn flushSparcWindows() void {
998 // Flush all register windows except the current one (hence `noinline`). This ensures that
999 // we actually see meaningful data on the stack when we walk the frame chain.
1000 if (comptime builtin.target.cpu.has(.sparc, .v9))
1001 asm volatile ("flushw" ::: .{ .memory = true })
1002 else
1003 asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
1004 }
1005
1006 const FpUsability = enum {
1007 /// FP unwinding is impractical on this target. For example, due to its very silly ABI
1008 /// design decisions, it's not possible to do generic FP unwinding on MIPS without a
1009 /// complicated code scanning algorithm.
1010 useless,
1011 /// FP unwinding is unsafe on this target; we may crash when doing so. We will only perform
1012 /// FP unwinding in the case of crashes/panics, or if the user opts in.
1013 unsafe,
1014 /// FP unwinding is guaranteed to be safe on this target. We will do so if unwinding with
1015 /// debug info does not work, and if this compilation has frame pointers enabled.
1016 safe,
1017 /// FP unwinding is the best option on this target. This is usually because the ABI requires
1018 /// a backchain pointer, thus making it always available, safe, and fast.
1019 ideal,
1020 };
1021
1022 const fp_usability: FpUsability = switch (builtin.target.cpu.arch) {
1023 .alpha,
1024 .avr,
1025 .csky,
1026 .microblaze,
1027 .microblazeel,
1028 .mips,
1029 .mipsel,
1030 .mips64,
1031 .mips64el,
1032 .msp430,
1033 .sh,
1034 .sheb,
1035 .xcore,
1036 .xtensa,
1037 .xtensaeb,
1038 => .useless,
1039 .hexagon,
1040 // The PowerPC ABIs don't actually strictly require a backchain pointer; they allow omitting
1041 // it when full unwind info is present. Despite this, both GCC and Clang always enforce the
1042 // presence of the backchain pointer no matter what options they are given. This seems to be
1043 // a case of "the spec is only a polite suggestion", except it works in our favor this time!
1044 .powerpc,
1045 .powerpcle,
1046 .powerpc64,
1047 .powerpc64le,
1048 .sparc,
1049 .sparc64,
1050 => .ideal,
1051 // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Respect-the-purpose-of-specific-CPU-registers
1052 .aarch64 => if (builtin.target.os.tag.isDarwin()) .safe else .unsafe,
1053 else => .unsafe,
1054 };
1055
1056 /// Whether the current unwind strategy is allowed given `allow_unsafe`.
1057 fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {
1058 return switch (it.*) {
1059 .ctx_first, .di => true,
1060 // If we omitted frame pointers from *this* compilation, FP unwinding would crash
1061 // immediately regardless of anything. But FPs could also be omitted from a different
1062 // linked object, so it's not guaranteed to be safe, unless the target specifically
1063 // requires it.
1064 .fp => switch (fp_usability) {
1065 .useless => false,
1066 .unsafe => allow_unsafe and !builtin.omit_frame_pointer,
1067 .safe => !builtin.omit_frame_pointer,
1068 .ideal => true,
1069 },
1070 };
1071 }
1072
1073 const Result = union(enum) {
1074 /// A stack frame has been found; this is the corresponding return address.
1075 frame: usize,
1076 /// The end of the stack has been reached.
1077 end,
1078 /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error.
1079 switch_to_fp: struct {
1080 address: usize,
1081 err: SelfInfoError,
1082 },
1083 };
1084
1085 fn next(it: *StackIterator, io: Io) Result {
1086 switch (it.*) {
1087 .ctx_first => |context_ptr| {
1088 // After the first frame, start actually unwinding.
1089 if (SelfInfo != void and SelfInfo.can_unwind and fp_usability != .ideal) {
1090 it.* = .{ .di = .init(context_ptr) };
1091 } else {
1092 const fp = applyOffset(context_ptr.getFp(), stack_bias) orelse return .end;
1093 it.* = .{ .fp = fp };
1094 }
1095
1096 // The caller expects *return* addresses, where they will subtract 1 to find the address of the call.
1097 // However, we have the actual current PC, which should not be adjusted. Compensate by adding 1.
1098 return .{ .frame = context_ptr.getPc() +| 1 };
1099 },
1100 .di => |*unwind_context| {
1101 const di = getSelfDebugInfo() catch unreachable;
1102 const ret_addr = di.unwindFrame(io, unwind_context) catch |err| {
1103 const pc = unwind_context.pc;
1104 const fp = applyOffset(unwind_context.getFp(), stack_bias) orelse return .end;
1105 unwind_context.deinit();
1106 it.* = .{ .fp = fp };
1107 return .{ .switch_to_fp = .{
1108 .address = pc,
1109 .err = err,
1110 } };
1111 };
1112 if (ret_addr <= 1) return .end;
1113 return .{ .frame = ret_addr };
1114 },
1115 .fp => |fp| {
1116 if (fp == 0) return .end; // we reached the "sentinel" base pointer
1117
1118 const bp_addr = applyOffset(fp, fp_to_bp_offset) orelse return .end;
1119 const ra_addr = applyOffset(fp, fp_to_ra_offset) orelse return .end;
1120
1121 if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or
1122 ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize)))
1123 {
1124 // This isn't valid, but it most likely indicates end of stack.
1125 return .end;
1126 }
1127
1128 const bp_ptr: *const usize = @ptrFromInt(bp_addr);
1129 const ra_ptr: *const usize = @ptrFromInt(ra_addr);
1130 const bp = applyOffset(bp_ptr.*, stack_bias) orelse return .end;
1131
1132 // If the stack grows downwards, `bp > fp` should always hold; conversely, if it
1133 // grows upwards, `bp < fp` should always hold. If that is not the case, this
1134 // frame is invalid, so we'll treat it as though we reached end of stack. The
1135 // exception is address 0, which is a graceful end-of-stack signal, in which case
1136 // *this* return address is valid and the *next* iteration will be the last.
1137 if (bp != 0 and switch (comptime builtin.target.stackGrowth()) {
1138 .down => bp <= fp,
1139 .up => bp >= fp,
1140 }) return .end;
1141
1142 it.fp = bp;
1143 const ra = stripInstructionPtrAuthCode(ra_ptr.*);
1144 if (ra <= 1) return .end;
1145 return .{ .frame = ra };
1146 },
1147 }
1148 }
1149
1150 /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer.
1151 const fp_to_bp_offset = off: {
1152 // On 32-bit PA-RISC, the base pointer is the final word of the frame marker.
1153 if (native_arch == .hppa) break :off -1 * @sizeOf(usize);
1154 // On 64-bit PA-RISC, the frame marker was shrunk significantly; now there's just the return
1155 // address followed by the base pointer.
1156 if (native_arch == .hppa64) break :off -1 * @sizeOf(usize);
1157 // On LoongArch and RISC-V, the frame pointer points to the top of the saved register area,
1158 // in which the base pointer is the first word.
1159 if (native_arch.isLoongArch() or native_arch.isRISCV()) break :off -2 * @sizeOf(usize);
1160 // On OpenRISC, the frame pointer is stored below the return address.
1161 if (native_arch == .or1k) break :off -2 * @sizeOf(usize);
1162 // On SPARC, the frame pointer points to the save area which holds 16 slots for the local
1163 // and incoming registers. The base pointer (i6) is stored in its customary save slot.
1164 if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize);
1165 // Everywhere else, the frame pointer points directly to the location of the base pointer.
1166 break :off 0;
1167 };
1168
1169 /// Offset of the saved return address wrt the frame pointer.
1170 const fp_to_ra_offset = off: {
1171 // On 32-bit PA-RISC, the return address sits in the middle-ish of the frame marker.
1172 if (native_arch == .hppa) break :off -5 * @sizeOf(usize);
1173 // On 64-bit PA-RISC, the frame marker was shrunk significantly; now there's just the return
1174 // address followed by the base pointer.
1175 if (native_arch == .hppa64) break :off -2 * @sizeOf(usize);
1176 // On LoongArch and RISC-V, the frame pointer points to the top of the saved register area,
1177 // in which the return address is the second word.
1178 if (native_arch.isLoongArch() or native_arch.isRISCV()) break :off -1 * @sizeOf(usize);
1179 // On OpenRISC, the return address is stored below the stack parameter area.
1180 if (native_arch == .or1k) break :off -1 * @sizeOf(usize);
1181 if (native_arch.isPowerPC64()) break :off 2 * @sizeOf(usize);
1182 // On s390x, r14 is the link register and we need to grab it from its customary slot in the
1183 // register save area (ELF ABI s390x Supplement §1.2.2.2).
1184 if (native_arch == .s390x) break :off 14 * @sizeOf(usize);
1185 // On SPARC, the frame pointer points to the save area which holds 16 slots for the local
1186 // and incoming registers. The return address (i7) is stored in its customary save slot.
1187 if (native_arch.isSPARC()) break :off 15 * @sizeOf(usize);
1188 break :off @sizeOf(usize);
1189 };
1190
1191 /// Value to add to the stack pointer and frame/base pointers to get the real location being
1192 /// pointed to. Yes, SPARC really does this.
1193 const stack_bias = bias: {
1194 if (native_arch == .sparc64) break :bias 2047;
1195 break :bias 0;
1196 };
1197
1198 /// On some oddball architectures, a return address points to the call instruction rather than
1199 /// the instruction following it.
1200 const ra_call_offset = off: {
1201 if (native_arch.isSPARC()) break :off 0;
1202 break :off 1;
1203 };
1204
1205 fn applyOffset(addr: usize, comptime off: comptime_int) ?usize {
1206 if (off >= 0) return math.add(usize, addr, off) catch return null;
1207 return math.sub(usize, addr, -off) catch return null;
1208 }
1209};
1210
1211/// Some platforms use pointer authentication: the upper bits of instruction pointers contain a
1212/// signature. This function clears those signature bits to make the pointer directly usable.
1213pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1214 if (native_arch.isAARCH64()) {
1215 // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
1216 // The save / restore is because `xpaclri` operates on x30 (LR)
1217 return asm (
1218 \\mov x16, x30
1219 \\mov x30, x15
1220 \\hint 0x07
1221 \\mov x15, x30
1222 \\mov x30, x16
1223 : [ret] "={x15}" (-> usize),
1224 : [ptr] "{x15}" (ptr),
1225 : .{ .x16 = true });
1226 }
1227
1228 return ptr;
1229}
1230
1231const PrintSourceAddressOptions = struct {
1232 address: usize,
1233 resolve_inline_callers: bool,
1234};
1235
1236fn printSourceAtAddress(
1237 io: Io,
1238 text_arena: *std.heap.ArenaAllocator,
1239 debug_info: *SelfInfo,
1240 t: Io.Terminal,
1241 options: PrintSourceAddressOptions,
1242) Writer.Error!void {
1243 defer _ = text_arena.reset(.retain_capacity);
1244
1245 // Initialize the symbol array with space for at least one element, allocating this on the stack
1246 // in the common case where only one element is needed
1247 var buf: [1]Symbol = undefined;
1248 var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&buf), getDebugInfoAllocator());
1249 const symbol_allocator = bfa.allocator();
1250 var symbols = std.ArrayList(Symbol).initCapacity(symbol_allocator, 1) catch unreachable;
1251 defer symbols.deinit(symbol_allocator);
1252
1253 debug_info.getSymbols(
1254 io,
1255 symbol_allocator,
1256 text_arena.allocator(),
1257 options.address,
1258 options.resolve_inline_callers,
1259 &symbols,
1260 ) catch |err| {
1261 t.setColor(.dim) catch {};
1262 defer t.setColor(.reset) catch {};
1263 switch (err) {
1264 error.MissingDebugInfo,
1265 error.UnsupportedDebugInfo,
1266 error.InvalidDebugInfo,
1267 => {},
1268 error.ReadFailed, error.Unexpected, error.Canceled => {
1269 try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1270 },
1271 error.OutOfMemory => {
1272 t.setColor(.dim) catch {};
1273 try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1274 t.setColor(.reset) catch {};
1275 },
1276 }
1277 };
1278
1279 // If we failed to write any symbols, at least write the unknown symbol. Can't fail since we
1280 // initialized with a capacity of 1.
1281 if (symbols.items.len == 0) symbols.appendAssumeCapacity(.unknown);
1282
1283 for (symbols.items) |symbol| {
1284 try printLineInfo(io, t, debug_info, options.address, symbol);
1285 }
1286}
1287fn printLineInfo(
1288 io: Io,
1289 t: Io.Terminal,
1290 debug_info: *SelfInfo,
1291 address: usize,
1292 symbol: Symbol,
1293) Writer.Error!void {
1294 const writer = t.writer;
1295 t.setColor(.bold) catch {};
1296
1297 if (symbol.source_location) |*sl| {
1298 if (sl.column == 0) {
1299 try writer.print("{s}:{d}", .{ sl.file_name, sl.line });
1300 } else {
1301 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
1302 }
1303 } else {
1304 try writer.writeAll("???:?:?");
1305 }
1306
1307 t.setColor(.reset) catch {};
1308 try writer.writeAll(": ");
1309 t.setColor(.dim) catch {};
1310 try writer.print("0x{x} in {s} ({s})", .{
1311 address,
1312 symbol.name orelse "???",
1313 symbol.compile_unit_name orelse debug_info.getModuleName(io, address) catch "???",
1314 });
1315 t.setColor(.reset) catch {};
1316 try writer.writeAll("\n");
1317
1318 // Show the matching source code line if possible
1319 if (symbol.source_location) |sl| {
1320 if (printLineFromFile(io, writer, sl)) {
1321 if (sl.column > 0) {
1322 // The caret already takes one char
1323 const space_needed = @as(usize, @intCast(sl.column - 1));
1324
1325 try writer.splatByteAll(' ', space_needed);
1326 t.setColor(.green) catch {};
1327 try writer.writeAll("^");
1328 t.setColor(.reset) catch {};
1329 }
1330 try writer.writeAll("\n");
1331 } else |_| {
1332 // Ignore all errors; it's a better UX to just print the source location without the
1333 // corresponding line number. The user can always open the source file themselves.
1334 }
1335 }
1336}
1337fn printLineFromFile(io: Io, writer: *Writer, source_location: SourceLocation) !void {
1338 // Allow overriding the target-agnostic source line printing logic by exposing `root.debug.printLineFromFile`.
1339 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) {
1340 return root.debug.printLineFromFile(io, writer, source_location);
1341 }
1342
1343 // Need this to always block even in async I/O mode, because this could potentially
1344 // be called from e.g. the event loop code crashing.
1345 const cwd: Io.Dir = .cwd();
1346 var file = try cwd.openFile(io, source_location.file_name, .{});
1347 defer file.close(io);
1348
1349 var buffer: [4096]u8 = undefined;
1350 var file_reader: File.Reader = .init(file, io, &buffer);
1351 var line_index: usize = 0;
1352 const r = &file_reader.interface;
1353 while (true) {
1354 line_index += 1;
1355 if (line_index == source_location.line) {
1356 // TODO delete hard tabs from the language
1357 _ = try r.streamDelimiterEnding(writer, '\n');
1358 try writer.writeByte('\n');
1359 return;
1360 }
1361 _ = try r.discardDelimiterInclusive('\n');
1362 }
1363}
1364
1365test printLineFromFile {
1366 const io = testing.io;
1367 const gpa = testing.allocator;
1368
1369 var aw: Writer.Allocating = .init(gpa);
1370 defer aw.deinit();
1371 const output_stream = &aw.writer;
1372
1373 const join = std.fs.path.join;
1374 const expectError = testing.expectError;
1375 const expectEqualStrings = testing.expectEqualStrings;
1376
1377 var test_dir = testing.tmpDir(.{});
1378 defer test_dir.cleanup();
1379 // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths.
1380 const test_dir_path = try join(gpa, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
1381 defer gpa.free(test_dir_path);
1382
1383 // Cases
1384 {
1385 const path = try join(gpa, &.{ test_dir_path, "one_line.zig" });
1386 defer gpa.free(path);
1387 try test_dir.dir.writeFile(io, .{ .sub_path = "one_line.zig", .data = "no new lines in this file, but one is printed anyway" });
1388
1389 try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1390
1391 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1392 try expectEqualStrings("no new lines in this file, but one is printed anyway\n", aw.written());
1393 aw.clearRetainingCapacity();
1394 }
1395 {
1396 const path = try fs.path.join(gpa, &.{ test_dir_path, "three_lines.zig" });
1397 defer gpa.free(path);
1398 try test_dir.dir.writeFile(io, .{
1399 .sub_path = "three_lines.zig",
1400 .data =
1401 \\1
1402 \\2
1403 \\3
1404 ,
1405 });
1406
1407 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1408 try expectEqualStrings("1\n", aw.written());
1409 aw.clearRetainingCapacity();
1410
1411 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 3, .column = 0 });
1412 try expectEqualStrings("3\n", aw.written());
1413 aw.clearRetainingCapacity();
1414 }
1415 {
1416 const file = try test_dir.dir.createFile(io, "line_overlaps_page_boundary.zig", .{});
1417 defer file.close(io);
1418 const path = try fs.path.join(gpa, &.{ test_dir_path, "line_overlaps_page_boundary.zig" });
1419 defer gpa.free(path);
1420
1421 const overlap = 10;
1422 var buf: [16]u8 = undefined;
1423 var file_writer = file.writer(io, &buf);
1424 const writer = &file_writer.interface;
1425 try writer.splatByteAll('a', std.heap.page_size_min - overlap);
1426 try writer.writeByte('\n');
1427 try writer.splatByteAll('a', overlap);
1428 try writer.flush();
1429
1430 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1431 try expectEqualStrings(&@as([overlap]u8, @splat('a')) ++ "\n", aw.written());
1432 aw.clearRetainingCapacity();
1433 }
1434 {
1435 const file = try test_dir.dir.createFile(io, "file_ends_on_page_boundary.zig", .{});
1436 defer file.close(io);
1437 const path = try fs.path.join(gpa, &.{ test_dir_path, "file_ends_on_page_boundary.zig" });
1438 defer gpa.free(path);
1439
1440 var file_writer = file.writer(io, &.{});
1441 const writer = &file_writer.interface;
1442 try writer.splatByteAll('a', std.heap.page_size_max);
1443
1444 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1445 try expectEqualStrings(&@as([std.heap.page_size_max]u8, @splat('a')) ++ "\n", aw.written());
1446 aw.clearRetainingCapacity();
1447 }
1448 {
1449 const file = try test_dir.dir.createFile(io, "very_long_first_line_spanning_multiple_pages.zig", .{});
1450 defer file.close(io);
1451 const path = try fs.path.join(gpa, &.{ test_dir_path, "very_long_first_line_spanning_multiple_pages.zig" });
1452 defer gpa.free(path);
1453
1454 var file_writer = file.writer(io, &.{});
1455 const writer = &file_writer.interface;
1456 try writer.splatByteAll('a', 3 * std.heap.page_size_max);
1457
1458 try expectError(error.EndOfStream, printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 }));
1459
1460 const many_a: [3 * std.heap.page_size_max]u8 = @splat('a');
1461
1462 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1463 try expectEqualStrings(&many_a ++ "\n", aw.written());
1464 aw.clearRetainingCapacity();
1465
1466 try writer.writeAll("a\na");
1467
1468 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 1, .column = 0 });
1469 try expectEqualStrings(&many_a ++ "a\n", aw.written());
1470 aw.clearRetainingCapacity();
1471
1472 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = 2, .column = 0 });
1473 try expectEqualStrings("a\n", aw.written());
1474 aw.clearRetainingCapacity();
1475 }
1476 {
1477 const file = try test_dir.dir.createFile(io, "file_of_newlines.zig", .{});
1478 defer file.close(io);
1479 const path = try fs.path.join(gpa, &.{ test_dir_path, "file_of_newlines.zig" });
1480 defer gpa.free(path);
1481
1482 var file_writer = file.writer(io, &.{});
1483 const writer = &file_writer.interface;
1484 const real_file_start = 3 * std.heap.page_size_min;
1485 try writer.splatByteAll('\n', real_file_start);
1486 try writer.writeAll("abc\ndef");
1487
1488 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 1, .column = 0 });
1489 try expectEqualStrings("abc\n", aw.written());
1490 aw.clearRetainingCapacity();
1491
1492 try printLineFromFile(io, output_stream, .{ .file_name = path, .line = real_file_start + 2, .column = 0 });
1493 try expectEqualStrings("def\n", aw.written());
1494 aw.clearRetainingCapacity();
1495 }
1496}
1497
1498/// The returned allocator should be thread-safe if the compilation is multi-threaded, because
1499/// multiple threads could capture and/or print stack traces simultaneously.
1500pub fn getDebugInfoAllocator() Allocator {
1501 // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`.
1502 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) {
1503 return root.debug.getDebugInfoAllocator();
1504 }
1505 // Otherwise, use a global arena backed by the page allocator
1506 const S = struct {
1507 var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
1508 };
1509 return S.arena.allocator();
1510}
1511
1512/// Whether or not the current target can print useful debug information when a segfault occurs.
1513pub const have_segfault_handling_support = switch (native_os) {
1514 .haiku,
1515 .linux,
1516 .serenity,
1517
1518 .dragonfly,
1519 .freebsd,
1520 .netbsd,
1521 .openbsd,
1522
1523 .driverkit,
1524 .ios,
1525 .maccatalyst,
1526 .macos,
1527 .tvos,
1528 .visionos,
1529 .watchos,
1530
1531 .illumos,
1532
1533 .windows,
1534 => true,
1535
1536 else => false,
1537};
1538
1539const enable_segfault_handler = std.options.enable_segfault_handler;
1540pub const default_enable_segfault_handler = runtime_safety and have_segfault_handling_support;
1541
1542pub fn maybeEnableSegfaultHandler() void {
1543 if (enable_segfault_handler) {
1544 attachSegfaultHandler();
1545 }
1546}
1547
1548var windows_segfault_handle: ?windows.HANDLE = null;
1549
1550pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void {
1551 posix.sigaction(.SEGV, act, null);
1552 posix.sigaction(.ILL, act, null);
1553 posix.sigaction(.BUS, act, null);
1554 posix.sigaction(.FPE, act, null);
1555}
1556
1557/// Attaches a global handler for several signals which, when triggered, prints output to stderr
1558/// similar to the default panic handler, with a message containing the type of signal and a stack
1559/// trace if possible. This implementation does not just call the panic handler, because unwinding
1560/// the stack (for a stack trace) when a signal is received requires special target-specific logic.
1561///
1562/// On POSIX targets, the signal handler is configured to use the alternative signal stack. Such a
1563/// stack is configured by the Zig Standard Library if `std.options.signal_stack_size` is set.
1564///
1565/// The signals for which a handler is installed are:
1566/// * SIGSEGV (segmentation fault)
1567/// * SIGILL (illegal instruction)
1568/// * SIGBUS (bus error)
1569/// * SIGFPE (arithmetic exception)
1570pub fn attachSegfaultHandler() void {
1571 if (!have_segfault_handling_support) {
1572 @compileError("segfault handler not supported for this target");
1573 }
1574 if (native_os == .windows) {
1575 windows_segfault_handle = windows.ntdll.RtlAddVectoredExceptionHandler(0, handleSegfaultWindows);
1576 return;
1577 }
1578 const act: posix.Sigaction = .{
1579 .handler = .{ .sigaction = handleSegfaultPosix },
1580 .mask = posix.sigemptyset(),
1581 .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND | posix.SA.ONSTACK),
1582 };
1583 updateSegfaultHandler(&act);
1584}
1585
1586fn resetSegfaultHandler() void {
1587 if (native_os == .windows) {
1588 if (windows_segfault_handle) |handle| {
1589 assert(windows.ntdll.RtlRemoveVectoredExceptionHandler(handle) != 0);
1590 windows_segfault_handle = null;
1591 }
1592 return;
1593 }
1594 const act = posix.Sigaction{
1595 .handler = .{ .handler = posix.SIG.DFL },
1596 .mask = posix.sigemptyset(),
1597 .flags = 0,
1598 };
1599 updateSegfaultHandler(&act);
1600}
1601
1602fn handleSegfaultPosix(sig: posix.SIG, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {
1603 if (use_trap_panic) @trap();
1604 const addr: ?usize, const name: []const u8 = info: {
1605 if (native_os == .linux and native_arch == .x86_64) {
1606 // x86_64 doesn't have a full 64-bit virtual address space.
1607 // Addresses outside of that address space are non-canonical
1608 // and the CPU won't provide the faulting address to us.
1609 // This happens when accessing memory addresses such as 0xaaaaaaaaaaaaaaaa
1610 // but can also happen when no addressable memory is involved;
1611 // for example when reading/writing model-specific registers
1612 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).
1613 const SI_KERNEL = 0x80;
1614 if (sig == .SEGV and info.code == SI_KERNEL) {
1615 break :info .{ null, "General protection exception" };
1616 }
1617 }
1618 const addr: usize = switch (native_os) {
1619 .serenity,
1620 .dragonfly,
1621 .freebsd,
1622 .driverkit,
1623 .ios,
1624 .maccatalyst,
1625 .macos,
1626 .tvos,
1627 .visionos,
1628 .watchos,
1629 .haiku,
1630 => @intFromPtr(info.addr),
1631 .linux,
1632 => @intFromPtr(info.fields.sigfault.addr),
1633 .netbsd,
1634 => @intFromPtr(info.info.reason.fault.addr),
1635 .openbsd,
1636 => @intFromPtr(info.data.fault.addr),
1637 .illumos,
1638 => @intFromPtr(info.reason.fault.addr),
1639 else => comptime unreachable,
1640 };
1641 const name = switch (sig) {
1642 .SEGV => "Segmentation fault",
1643 .ILL => "Illegal instruction",
1644 .BUS => "Bus error",
1645 .FPE => "Arithmetic exception",
1646 else => unreachable,
1647 };
1648 break :info .{ addr, name };
1649 };
1650 const opt_cpu_context: ?cpu_context.Native = cpu_context.fromPosixSignalContext(ctx_ptr);
1651
1652 handleSegfault(addr, name, if (opt_cpu_context) |*ctx| ctx else null);
1653}
1654
1655fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {
1656 if (use_trap_panic) @trap();
1657 const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) {
1658 windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null },
1659 windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] },
1660 windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip },
1661 windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null },
1662 else => return windows.EXCEPTION_CONTINUE_SEARCH,
1663 };
1664 handleSegfault(addr, name, &cpu_context.fromWindowsContext(info.ContextRecord));
1665}
1666
1667fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
1668 // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`.
1669 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) {
1670 return root.debug.handleSegfault(addr, name, opt_ctx);
1671 }
1672 return defaultHandleSegfault(addr, name, opt_ctx);
1673}
1674
1675pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
1676 std.Options.debug_io.vtable.crashHandler(std.Options.debug_io.userdata);
1677
1678 // There is very similar logic to the following in `defaultPanic`.
1679 switch (panic_stage) {
1680 0 => {
1681 panic_stage = 1;
1682 _ = panicking.fetchAdd(1, .seq_cst);
1683
1684 trace: {
1685 const stderr = lockStderr(&.{}).terminal();
1686 defer unlockStderr();
1687
1688 if (addr) |a| {
1689 stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
1690 } else {
1691 stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace;
1692 }
1693 if (opt_ctx) |context| {
1694 writeCurrentStackTrace(.{
1695 .context = context,
1696 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
1697 }, stderr) catch break :trace;
1698 }
1699 }
1700 },
1701 1 => {
1702 panic_stage = 2;
1703 // A segfault happened while trying to print a previous panic message.
1704 // We're still holding the mutex but that's fine as we're going to
1705 // call abort().
1706 const stderr = lockStderr(&.{}).terminal();
1707 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
1708 },
1709 else => {}, // Panicked while printing the recursive panic message.
1710 }
1711
1712 // We cannot allow the signal handler to return because when it runs the original instruction
1713 // again, the memory may be mapped and undefined behavior would occur rather than repeating
1714 // the segfault. So we simply abort here.
1715 std.process.abort();
1716}
1717
1718pub fn dumpStackPointerAddr(prefix: []const u8) void {
1719 const sp = asm (""
1720 : [argc] "={rsp}" (-> usize),
1721 );
1722 print("{s} sp = 0x{x}\n", .{ prefix, sp });
1723}
1724
1725test "manage resources correctly" {
1726 if (SelfInfo == void) return error.SkipZigTest;
1727 if (builtin.zig_backend == .stage2_c) {
1728 // The C backend emits an extremely large C source file, meaning it has a huge
1729 // amount of debug information. Parsing this debug information makes this test
1730 // take too long to be worth running.
1731 return error.SkipZigTest;
1732 }
1733
1734 const S = struct {
1735 noinline fn showMyTrace() usize {
1736 return @returnAddress();
1737 }
1738 };
1739 const io = testing.io;
1740
1741 var discarding: Writer.Discarding = .init(&.{});
1742 var di: SelfInfo = .init;
1743 defer di.deinit(io);
1744 const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color };
1745 var text_arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
1746 defer text_arena.deinit();
1747 try printSourceAtAddress(io, &text_arena, &di, t, .{
1748 .address = S.showMyTrace(),
1749 .resolve_inline_callers = true,
1750 });
1751}
1752
1753/// This API helps you track where a value originated and where it was mutated,
1754/// or any other points of interest.
1755/// In debug mode, it adds a small size penalty (104 bytes on 64-bit architectures)
1756/// to the aggregate that you add it to.
1757/// In release mode, it is size 0 and all methods are no-ops.
1758/// This is a pre-made type with default settings.
1759/// For more advanced usage, see `ConfigurableTrace`.
1760pub const Trace = ConfigurableTrace(2, 4, builtin.mode == .debug);
1761
1762pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize, comptime is_enabled: bool) type {
1763 return struct {
1764 addrs: [actual_size][stack_frame_count]usize,
1765 notes: [actual_size][]const u8,
1766 index: Index,
1767
1768 const actual_size = if (enabled) size else 0;
1769 const Index = if (enabled) usize else u0;
1770
1771 pub const init: @This() = .{
1772 .addrs = undefined,
1773 .notes = undefined,
1774 .index = 0,
1775 };
1776
1777 pub const enabled = is_enabled;
1778
1779 pub const add = if (enabled) addNoInline else addNoOp;
1780
1781 pub noinline fn addNoInline(t: *@This(), note: []const u8) void {
1782 comptime assert(enabled);
1783 return addAddr(t, @returnAddress(), note);
1784 }
1785
1786 pub inline fn addNoOp(t: *@This(), note: []const u8) void {
1787 _ = t;
1788 _ = note;
1789 comptime assert(!enabled);
1790 }
1791
1792 pub fn addAddr(t: *@This(), addr: usize, note: []const u8) void {
1793 if (!enabled) return;
1794
1795 if (t.index < size) {
1796 t.notes[t.index] = note;
1797 const addrs = &t.addrs[t.index];
1798 const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs);
1799 if (st.return_addresses.len < addrs.len) {
1800 @memset(addrs[st.return_addresses.len..], 0); // zero unused frames to indicate end of trace
1801 }
1802 }
1803 // Keep counting even if the end is reached so that the
1804 // user can find out how much more size they need.
1805 t.index += 1;
1806 }
1807
1808 pub fn dump(t: @This()) void {
1809 if (!enabled) return;
1810
1811 const stderr = lockStderr(&.{}).terminal();
1812 defer unlockStderr();
1813 const end = @min(t.index, size);
1814 for (t.addrs[0..end], 0..) |frames_array, i| {
1815 stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return;
1816 var frames_array_mutable = frames_array;
1817 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
1818 const len = @min(t.index, frames.len);
1819 const stack_trace: StackTrace = .{
1820 .return_addresses = frames[0..len],
1821 .skipped = if (len < frames.len) .none else .unknown,
1822 };
1823 writeStackTrace(&stack_trace, stderr) catch return;
1824 }
1825 if (t.index > end) {
1826 stderr.writer.print("{d} more traces not shown; consider increasing trace size\n", .{
1827 t.index - end,
1828 }) catch return;
1829 }
1830 }
1831
1832 pub fn format(
1833 t: @This(),
1834 comptime fmt: []const u8,
1835 options: std.fmt.Options,
1836 writer: *Writer,
1837 ) !void {
1838 if (fmt.len != 0) std.fmt.invalidFmtError(fmt, t);
1839 _ = options;
1840 if (enabled) {
1841 try writer.writeAll("\n");
1842 t.dump();
1843 try writer.writeAll("\n");
1844 } else {
1845 return writer.writeAll("(value tracing disabled)");
1846 }
1847 }
1848 };
1849}
1850
1851pub const SafetyLock = struct {
1852 state: State = if (runtime_safety) .unlocked else .unknown,
1853
1854 pub const State = if (runtime_safety) enum { unlocked, locked } else enum { unknown };
1855
1856 pub fn lock(l: *SafetyLock) void {
1857 if (!runtime_safety) return;
1858 assert(l.state == .unlocked);
1859 l.state = .locked;
1860 }
1861
1862 pub fn unlock(l: *SafetyLock) void {
1863 if (!runtime_safety) return;
1864 assert(l.state == .locked);
1865 l.state = .unlocked;
1866 }
1867
1868 pub fn assertUnlocked(l: SafetyLock) void {
1869 if (!runtime_safety) return;
1870 assert(l.state == .unlocked);
1871 }
1872
1873 pub fn assertLocked(l: SafetyLock) void {
1874 if (!runtime_safety) return;
1875 assert(l.state == .locked);
1876 }
1877};
1878
1879test SafetyLock {
1880 var safety_lock: SafetyLock = .{};
1881 safety_lock.assertUnlocked();
1882 safety_lock.lock();
1883 safety_lock.assertLocked();
1884 safety_lock.unlock();
1885 safety_lock.assertUnlocked();
1886}
1887
1888/// Detect whether the program is being executed in the Valgrind virtual machine.
1889///
1890/// When Valgrind integrations are disabled, this returns comptime-known false.
1891/// Otherwise, the result is runtime-known.
1892pub inline fn inValgrind() bool {
1893 if (@inComptime()) return false;
1894 if (!builtin.valgrind_support) return false;
1895 return std.valgrind.runningOnValgrind() > 0;
1896}
1897
1898test {
1899 _ = &Dwarf;
1900 _ = &Pdb;
1901 _ = &SelfInfo;
1902 _ = &dumpHex;
1903}