authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-05 19:43:08+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:51+01:00
log5709369d059ba107accaadb4b977281e2ba843ed
tree0141fb2b3085b6ec8366544a7b6781b39e50ec2a
parentd4f710791f88c29e08659241e7976c08fe05ba49
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: improve the APIs and stuff


4 files changed, 456 insertions(+), 675 deletions(-)

lib/std/debug.zig+447-659
......@@ -1,19 +1,19 @@
1const builtin = @import("builtin");
21const std = @import("std.zig");
32const math = std.math;
43const mem = std.mem;
54const posix = std.posix;
65const fs = std.fs;
76const testing = std.testing;
8const root = @import("root");
7const Allocator = mem.Allocator;
98const File = std.fs.File;
109const windows = std.os.windows;
11const native_arch = builtin.cpu.arch;
12const native_os = builtin.os.tag;
13const native_endian = native_arch.endian();
1410const Writer = std.Io.Writer;
1511const tty = std.Io.tty;
1612
13const builtin = @import("builtin");
14const native_arch = builtin.cpu.arch;
15const native_os = builtin.os.tag;
16
1717pub const Dwarf = @import("debug/Dwarf.zig");
1818pub const Pdb = @import("debug/Pdb.zig");
1919pub const SelfInfo = @import("debug/SelfInfo.zig");
......@@ -156,6 +156,11 @@ pub const Symbol = struct {
156156 name: ?[]const u8,
157157 compile_unit_name: ?[]const u8,
158158 source_location: ?SourceLocation,
159 pub const unknown: Symbol = .{
160 .name = null,
161 .compile_unit_name = null,
162 .source_location = null,
163 };
159164};
160165
161166/// Deprecated because it returns the optimization mode of the standard
......@@ -186,7 +191,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
186191 .wasm64,
187192 => native_os == .emscripten and builtin.mode == .Debug,
188193
189 // `@returnAddress()` is unsupported in LLVM 13.
194 // `@returnAddress()` is unsupported in LLVM 21.
190195 .bpfel,
191196 .bpfeb,
192197 => false,
......@@ -234,8 +239,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
234239/// TODO multithreaded awareness
235240/// Marked `inline` to propagate a comptime-known error to callers.
236241pub inline fn getSelfDebugInfo() !*SelfInfo {
237 if (builtin.strip_debug_info) return error.MissingDebugInfo;
238 if (!SelfInfo.target_supported) return error.UnsupportedOperatingSystem;
242 if (!SelfInfo.target_supported) return error.UnsupportedTarget;
239243 const S = struct {
240244 var self_info: SelfInfo = .init;
241245 };
......@@ -320,40 +324,11 @@ test dumpHexFallible {
320324 try std.testing.expectEqualStrings(expected, aw.written());
321325}
322326
323/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.
324pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
325 const stderr = lockStderrWriter(&.{});
326 defer unlockStderrWriter();
327 nosuspend dumpCurrentStackTraceToWriter(start_addr, stderr) catch return;
328}
329
330/// Prints the current stack trace to the provided writer.
331pub fn dumpCurrentStackTraceToWriter(start_addr: ?usize, writer: *Writer) !void {
332 if (builtin.target.cpu.arch.isWasm()) {
333 if (native_os == .wasi) {
334 try writer.writeAll("Unable to dump stack trace: not implemented for Wasm\n");
335 }
336 return;
337 }
338 if (builtin.strip_debug_info) {
339 try writer.writeAll("Unable to dump stack trace: debug info stripped\n");
340 return;
341 }
342 const debug_info = getSelfDebugInfo() catch |err| {
343 try writer.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
344 return;
345 };
346 writeCurrentStackTrace(writer, debug_info, tty.detectConfig(.stderr()), start_addr) catch |err| {
347 try writer.print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
348 return;
349 };
350}
351
352327pub const have_ucontext = posix.ucontext_t != void;
353328
354329/// Platform-specific thread state. This contains register state, and on some platforms
355330/// information about the stack. This is not safe to trivially copy, because some platforms
356/// use internal pointers within this structure. To make a copy, use `copyContext`.
331/// use internal pointers within this structure. After copying, call `relocateContext`.
357332pub const ThreadContext = blk: {
358333 if (native_os == .windows) {
359334 break :blk windows.CONTEXT;
......@@ -363,22 +338,12 @@ pub const ThreadContext = blk: {
363338 break :blk void;
364339 }
365340};
366
367/// Copies one context to another, updating any internal pointers
368pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void {
369 if (!have_ucontext) return {};
370 dest.* = source.*;
371 relocateContext(dest);
372}
373
374/// Updates any internal pointers in the context to reflect its current location
375pub fn relocateContext(context: *ThreadContext) void {
376 return switch (native_os) {
377 .macos => {
378 context.mcontext = &context.__mcontext_data;
379 },
341/// Updates any internal pointers of a `ThreadContext` after the caller copies it.
342pub fn relocateContext(dest: *ThreadContext) void {
343 switch (native_os) {
344 .macos => dest.mcontext = &dest.__mcontext_data,
380345 else => {},
381 };
346 }
382347}
383348
384349pub const have_getcontext = @TypeOf(posix.system.getcontext) != void;
......@@ -409,142 +374,6 @@ pub inline fn getContext(context: *ThreadContext) bool {
409374 return result;
410375}
411376
412/// Tries to print the stack trace starting from the supplied base pointer to stderr,
413/// unbuffered, and ignores any error returned.
414/// TODO multithreaded awareness
415pub fn dumpStackTraceFromBase(context: *ThreadContext, stderr: *Writer) void {
416 nosuspend {
417 if (builtin.target.cpu.arch.isWasm()) {
418 if (native_os == .wasi) {
419 stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
420 }
421 return;
422 }
423 if (builtin.strip_debug_info) {
424 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
425 return;
426 }
427 const debug_info = getSelfDebugInfo() catch |err| {
428 stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
429 return;
430 };
431 const tty_config = tty.detectConfig(.stderr());
432 if (native_os == .windows) {
433 // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context
434 // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace
435 // will be captured and frames prior to the exception will be filtered.
436 // The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame,
437 // which is where the IP in `context` points to, so it can't be used as start_addr.
438 // Instead, start_addr is recovered from the stack.
439 const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null;
440 writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return;
441 return;
442 }
443
444 var it = StackIterator.initWithContext(null, debug_info, context, @frameAddress()) catch return;
445 defer it.deinit();
446
447 // DWARF unwinding on aarch64-macos is not complete so we need to get pc address from mcontext
448 const pc_addr = it.unwind_state.?.dwarf_context.pc;
449 printSourceAtAddress(debug_info, stderr, pc_addr, tty_config) catch return;
450
451 while (it.next()) |return_address| {
452 printLastUnwindError(&it, debug_info, stderr, tty_config);
453
454 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
455 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
456 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this
457 // condition on the subsequent iteration and return `null` thus terminating the loop.
458 // same behaviour for x86-windows-msvc
459 const address = return_address -| 1;
460 printSourceAtAddress(debug_info, stderr, address, tty_config) catch return;
461 } else printLastUnwindError(&it, debug_info, stderr, tty_config);
462 }
463}
464
465/// Returns a slice with the same pointer as addresses, with a potentially smaller len.
466/// On Windows, when first_address is not null, we ask for at least 32 stack frames,
467/// and then try to find the first address. If addresses.len is more than 32, we
468/// capture that many stack frames exactly, and then look for the first address,
469/// chopping off the irrelevant frames and shifting so that the returned addresses pointer
470/// equals the passed in addresses pointer.
471pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackTrace) void {
472 if (native_os == .windows) {
473 const addrs = stack_trace.instruction_addresses;
474 const first_addr = first_address orelse {
475 stack_trace.index = walkStackWindows(addrs[0..], null);
476 return;
477 };
478 var addr_buf_stack: [32]usize = undefined;
479 const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs;
480 const n = walkStackWindows(addr_buf[0..], null);
481 const first_index = for (addr_buf[0..n], 0..) |addr, i| {
482 if (addr == first_addr) {
483 break i;
484 }
485 } else {
486 stack_trace.index = 0;
487 return;
488 };
489 const end_index = @min(first_index + addrs.len, n);
490 const slice = addr_buf[first_index..end_index];
491 // We use a for loop here because slice and addrs may alias.
492 for (slice, 0..) |addr, i| {
493 addrs[i] = addr;
494 }
495 stack_trace.index = slice.len;
496 } else {
497 if (builtin.cpu.arch == .powerpc64) {
498 // https://github.com/ziglang/zig/issues/24970
499 stack_trace.index = 0;
500 return;
501 }
502 var context: ThreadContext = undefined;
503 const has_context = getContext(&context);
504
505 var it = (if (has_context) blk: {
506 break :blk StackIterator.initWithContext(first_address, getSelfDebugInfo() catch break :blk null, &context) catch null;
507 } else null) orelse StackIterator.init(first_address, null);
508 defer it.deinit();
509 for (stack_trace.instruction_addresses, 0..) |*addr, i| {
510 addr.* = it.next() orelse {
511 stack_trace.index = i;
512 return;
513 };
514 }
515 stack_trace.index = stack_trace.instruction_addresses.len;
516 }
517}
518
519/// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned.
520/// TODO multithreaded awareness
521pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void {
522 nosuspend {
523 if (builtin.target.cpu.arch.isWasm()) {
524 if (native_os == .wasi) {
525 const stderr = lockStderrWriter(&.{});
526 defer unlockStderrWriter();
527 stderr.writeAll("Unable to dump stack trace: not implemented for Wasm\n") catch return;
528 }
529 return;
530 }
531 const stderr = lockStderrWriter(&.{});
532 defer unlockStderrWriter();
533 if (builtin.strip_debug_info) {
534 stderr.writeAll("Unable to dump stack trace: debug info stripped\n") catch return;
535 return;
536 }
537 const debug_info = getSelfDebugInfo() catch |err| {
538 stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
539 return;
540 };
541 writeStackTrace(stack_trace, stderr, debug_info, tty.detectConfig(.stderr())) catch |err| {
542 stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
543 return;
544 };
545 }
546}
547
548377/// Invokes detectable illegal behavior when `ok` is `false`.
549378///
550379/// In Debug and ReleaseSafe modes, calls to this function are always
......@@ -613,6 +442,24 @@ var panicking = std.atomic.Value(u8).init(0);
613442/// This is used to catch and handle panics triggered by the panic handler.
614443threadlocal var panic_stage: usize = 0;
615444
445/// For backends that cannot handle the language features depended on by the
446/// default panic handler, we will use a simpler implementation.
447const use_trap_panic = switch (builtin.zig_backend) {
448 .stage2_aarch64,
449 .stage2_arm,
450 .stage2_powerpc,
451 .stage2_riscv64,
452 .stage2_spirv,
453 .stage2_wasm,
454 .stage2_x86,
455 => true,
456 .stage2_x86_64 => switch (builtin.target.ofmt) {
457 .elf, .macho => false,
458 else => true,
459 },
460 else => false,
461};
462
616463/// Dumps a stack trace to standard error, then aborts.
617464pub fn defaultPanic(
618465 msg: []const u8,
......@@ -620,8 +467,8 @@ pub fn defaultPanic(
620467) noreturn {
621468 @branchHint(.cold);
622469
623 // For backends that cannot handle the language features depended on by the
624 // default panic handler, we have a simpler panic handler:
470 if (use_trap_panic) @trap();
471
625472 switch (builtin.zig_backend) {
626473 .stage2_aarch64,
627474 .stage2_arm,
......@@ -686,41 +533,48 @@ pub fn defaultPanic(
686533 resetSegfaultHandler();
687534 }
688535
689 // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra.
690 nosuspend switch (panic_stage) {
536 // There is very similar logic to the following in `handleSegfault`.
537 switch (panic_stage) {
691538 0 => {
692539 panic_stage = 1;
693
694540 _ = panicking.fetchAdd(1, .seq_cst);
695541
696 {
542 trace: {
543 const tty_config = tty.detectConfig(.stderr());
544
697545 const stderr = lockStderrWriter(&.{});
698546 defer unlockStderrWriter();
699547
700548 if (builtin.single_threaded) {
701 stderr.print("panic: ", .{}) catch posix.abort();
549 stderr.print("panic: ", .{}) catch break :trace;
702550 } else {
703551 const current_thread_id = std.Thread.getCurrentId();
704 stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort();
552 stderr.print("thread {} panic: ", .{current_thread_id}) catch break :trace;
705553 }
706 stderr.print("{s}\n", .{msg}) catch posix.abort();
554 stderr.print("{s}\n", .{msg}) catch break :trace;
707555
708 if (@errorReturnTrace()) |t| dumpStackTrace(t.*);
709 dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {};
556 if (@errorReturnTrace()) |t| if (t.index > 0) {
557 stderr.writeAll("error return context:\n") catch break :trace;
558 writeStackTrace(t, stderr, tty_config) catch break :trace;
559 stderr.writeAll("\nstack trace:\n") catch break :trace;
560 };
561 writeCurrentStackTrace(.{
562 .first_address = first_trace_addr orelse @returnAddress(),
563 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
564 }, stderr, tty_config) catch break :trace;
710565 }
711566
712567 waitForOtherThreadToFinishPanicking();
713568 },
714569 1 => {
715570 panic_stage = 2;
716
717571 // A panic happened while trying to print a previous panic message.
718572 // We're still holding the mutex but that's fine as we're going to
719573 // call abort().
720574 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
721575 },
722576 else => {}, // Panicked while printing the recursive panic message.
723 };
577 }
724578
725579 posix.abort();
726580}
......@@ -739,340 +593,307 @@ fn waitForOtherThreadToFinishPanicking() void {
739593 }
740594}
741595
742pub fn writeStackTrace(
743 stack_trace: std.builtin.StackTrace,
744 writer: *Writer,
745 debug_info: *SelfInfo,
746 tty_config: tty.Config,
747) !void {
748 if (builtin.strip_debug_info) return error.MissingDebugInfo;
749 var frame_index: usize = 0;
750 var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len);
751
752 while (frames_left != 0) : ({
753 frames_left -= 1;
754 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;
755 }) {
756 const return_address = stack_trace.instruction_addresses[frame_index];
757 try printSourceAtAddress(debug_info, writer, return_address -| 1, tty_config);
758 }
596pub const StackUnwindOptions = struct {
597 /// If not `null`, we will ignore all frames up until this return address. This is typically
598 /// used to omit intermediate handling code (for instance, a panic handler and its machinery)
599 /// from stack traces.
600 first_address: ?usize = null,
601 /// If not `null`, we will unwind from this `ThreadContext` instead of the current top of the
602 /// stack. The main use case here is printing stack traces from signal handlers, where the
603 /// kernel provides a `*ThreadContext` of the state before the signal.
604 context: ?*const ThreadContext = null,
605 /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.
606 /// If `false`, only known-safe mechanisms will be attempted.
607 allow_unsafe_unwind: bool = false,
608};
759609
760 if (stack_trace.index > stack_trace.instruction_addresses.len) {
761 const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len;
610/// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in
611/// the given buffer, so `addr_buf` must have a lifetime at least equal to the `StackTrace`.
612///
613/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
614pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {
615 var context_buf: ThreadContext = undefined;
616 var it: StackIterator = .init(options.context, &context_buf);
617 defer it.deinit();
618 if (!it.stratOk(options.allow_unsafe_unwind)) {
619 return .{ .index = 0, .instruction_addresses = &.{} };
620 }
621 var frame_idx: usize = 0;
622 var wait_for = options.first_address;
623 while (true) switch (it.next()) {
624 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
625 .end => break,
626 .frame => |return_address| {
627 if (wait_for) |target| {
628 if (return_address != target) continue;
629 wait_for = null;
630 }
631 if (frame_idx < addr_buf.len) addr_buf[frame_idx] = return_address;
632 frame_idx += 1;
633 },
634 };
635 return .{
636 .index = frame_idx,
637 .instruction_addresses = addr_buf[0..@min(frame_idx, addr_buf.len)],
638 };
639}
640/// Write the current stack trace to `writer`, annotated with source locations.
641///
642/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
643pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
644 const di_gpa = getDebugInfoAllocator();
645 const di = getSelfDebugInfo() catch |err| switch (err) {
646 error.UnsupportedTarget => {
647 tty_config.setColor(writer, .dim) catch {};
648 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
649 tty_config.setColor(writer, .reset) catch {};
650 return;
651 },
652 };
653 var context_buf: ThreadContext = undefined;
654 var it: StackIterator = .init(options.context, &context_buf);
655 defer it.deinit();
656 if (!it.stratOk(options.allow_unsafe_unwind)) {
657 tty_config.setColor(writer, .dim) catch {};
658 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
659 tty_config.setColor(writer, .reset) catch {};
660 return;
661 }
662 var wait_for = options.first_address;
663 var printed_any_frame = false;
664 while (true) switch (it.next()) {
665 .switch_to_fp => |unwind_error| {
666 const module_name = di.getModuleNameForAddress(di_gpa, unwind_error.address) catch "???";
667 const caption: []const u8 = switch (unwind_error.err) {
668 error.MissingDebugInfo => "unwind info unavailable",
669 error.InvalidDebugInfo => "unwind info invalid",
670 error.UnsupportedDebugInfo => "unwind info unsupported",
671 error.ReadFailed => "filesystem error",
672 error.OutOfMemory => "out of memory",
673 error.Unexpected => "unexpected error",
674 };
675 if (it.stratOk(options.allow_unsafe_unwind)) {
676 tty_config.setColor(writer, .dim) catch {};
677 try writer.print(
678 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
679 .{ module_name, unwind_error.address, caption },
680 );
681 tty_config.setColor(writer, .reset) catch {};
682 } else {
683 tty_config.setColor(writer, .dim) catch {};
684 try writer.print(
685 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
686 .{ module_name, unwind_error.address, caption },
687 );
688 tty_config.setColor(writer, .reset) catch {};
689 return;
690 }
691 },
692 .end => break,
693 .frame => |return_address| {
694 if (wait_for) |target| {
695 if (return_address != target) continue;
696 wait_for = null;
697 }
698 try printSourceAtAddress(di_gpa, di, writer, return_address -| 1, tty_config);
699 printed_any_frame = true;
700 },
701 };
702 if (!printed_any_frame) return writer.writeAll("(empty stack trace)\n");
703}
704/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
705pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
706 const tty_config = tty.detectConfig(.stderr());
707 const stderr = lockStderrWriter(&.{});
708 defer unlockStderrWriter();
709 writeCurrentStackTrace(options, stderr, tty_config) catch |err| switch (err) {
710 error.WriteFailed => {},
711 };
712}
762713
714/// Write a previously captured stack trace to `writer`, annotated with source locations.
715pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_config: tty.Config) Writer.Error!void {
716 const di_gpa = getDebugInfoAllocator();
717 const di = getSelfDebugInfo() catch |err| switch (err) {
718 error.UnsupportedTarget => {
719 tty_config.setColor(writer, .dim) catch {};
720 try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});
721 tty_config.setColor(writer, .reset) catch {};
722 return;
723 },
724 };
725 if (st.index == 0) return writer.writeAll("(empty stack trace)\n");
726 const captured_frames = @min(st.index, st.instruction_addresses.len);
727 for (st.instruction_addresses[0..captured_frames]) |return_address| {
728 try printSourceAtAddress(di_gpa, di, writer, return_address -| 1, tty_config);
729 }
730 if (st.index > captured_frames) {
763731 tty_config.setColor(writer, .bold) catch {};
764 try writer.print("({d} additional stack frames skipped...)\n", .{dropped_frames});
732 try writer.print("({d} additional stack frames skipped...)\n", .{st.index - captured_frames});
765733 tty_config.setColor(writer, .reset) catch {};
766734 }
767735}
736/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
737pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void {
738 const tty_config = tty.detectConfig(.stderr());
739 const stderr = lockStderrWriter(&.{});
740 defer unlockStderrWriter();
741 writeStackTrace(st, stderr, tty_config) catch |err| switch (err) {
742 error.WriteFailed => {},
743 };
744}
768745
769pub const StackIterator = struct {
770 // Skip every frame before this address is found.
771 first_address: ?usize,
772 // Last known value of the frame pointer register.
746const StackIterator = union(enum) {
747 /// Unwinding using debug info (e.g. DWARF CFI).
748 di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn,
749 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
773750 fp: usize,
774751
775 // When SelfInfo and a register context is available, this iterator can unwind
776 // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),
777 // using DWARF and MachO unwind info.
778 unwind_state: if (have_ucontext) ?struct {
779 debug_info: *SelfInfo,
780 dwarf_context: SelfInfo.UnwindContext,
781 last_error: ?SelfInfo.Error = null,
782 failed: bool = false,
783 } else void = if (have_ucontext) null else {},
784
785 pub fn init(first_address: ?usize, fp: usize) StackIterator {
786 if (native_arch.isSPARC()) {
752 /// It is important that this function is marked `inline` so that it can safely use
753 /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one
754 /// and the same.
755 inline fn init(context_opt: ?*const ThreadContext, context_buf: *ThreadContext) StackIterator {
756 if (builtin.cpu.arch.isSPARC()) {
787757 // Flush all the register windows on stack.
788 asm volatile (if (builtin.cpu.has(.sparc, .v9))
789 "flushw"
790 else
791 "ta 3" // ST_FLUSH_WINDOWS
792 ::: .{ .memory = true });
793 }
794
795 return .{
796 .first_address = first_address,
797 .fp = fp,
798 };
799 }
800
801 pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t, fp: usize) !StackIterator {
802 if (SelfInfo.supports_unwinding) {
803 var iterator = init(first_address, fp);
804 iterator.unwind_state = .{
805 .debug_info = debug_info,
806 .dwarf_context = try SelfInfo.UnwindContext.init(getDebugInfoAllocator(), context),
807 };
808 return iterator;
809 }
810
811 return init(first_address, fp);
812 }
813
814 pub fn deinit(it: *StackIterator) void {
815 if (have_ucontext and it.unwind_state != null) it.unwind_state.?.dwarf_context.deinit();
816 }
817
818 pub fn getLastError(it: *StackIterator) ?struct {
819 err: SelfInfo.Error,
820 address: usize,
821 } {
822 if (!have_ucontext) return null;
823 if (it.unwind_state) |*unwind_state| {
824 if (unwind_state.last_error) |err| {
825 unwind_state.last_error = null;
826 return .{
827 .err = err,
828 .address = unwind_state.dwarf_context.pc,
829 };
758 if (builtin.cpu.has(.sparc, .v9)) {
759 asm volatile ("flushw" ::: .{ .memory = true });
760 } else {
761 asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
830762 }
831763 }
832
833 return null;
834 }
835
836 // Offset of the saved BP wrt the frame pointer.
837 const fp_offset = if (native_arch.isRISCV())
838 // On RISC-V the frame pointer points to the top of the saved register
839 // area, on pretty much every other architecture it points to the stack
840 // slot where the previous frame pointer is saved.
841 2 * @sizeOf(usize)
842 else if (native_arch.isSPARC())
843 // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS.
844 14 * @sizeOf(usize)
845 else
846 0;
847
848 const fp_bias = if (native_arch.isSPARC())
849 // On SPARC frame pointers are biased by a constant.
850 2047
851 else
852 0;
853
854 // Positive offset of the saved PC wrt the frame pointer.
855 const pc_offset = if (native_arch == .powerpc64le)
856 2 * @sizeOf(usize)
857 else
858 @sizeOf(usize);
859
860 pub fn next(it: *StackIterator) ?usize {
861 var address = it.nextInternal() orelse return null;
862
863 if (it.first_address) |first_address| {
864 while (address != first_address) {
865 address = it.nextInternal() orelse return null;
866 }
867 it.first_address = null;
764 if (context_opt) |context| {
765 context_buf.* = context.*;
766 relocateContext(context_buf);
767 return .{ .di = .init(getDebugInfoAllocator(), context_buf) };
868768 }
869
870 return address;
871 }
872
873 fn nextInternal(it: *StackIterator) ?usize {
874 if (have_ucontext) {
875 if (it.unwind_state) |*unwind_state| {
876 if (!unwind_state.failed) {
877 if (unwind_state.dwarf_context.pc == 0) return null;
878 defer it.fp = unwind_state.dwarf_context.getFp() catch 0;
879 if (unwind_state.debug_info.unwindFrame(getDebugInfoAllocator(), &unwind_state.dwarf_context)) |return_address| {
880 return return_address;
881 } else |err| {
882 unwind_state.last_error = err;
883 unwind_state.failed = true;
884
885 // Fall back to fp-based unwinding on the first failure.
886 // We can't attempt it again for other modules higher in the
887 // stack because the full register state won't have been unwound.
888 }
889 }
890 }
769 if (getContext(context_buf)) {
770 return .{ .di = .init(getDebugInfoAllocator(), context_buf) };
891771 }
892
893 if (builtin.omit_frame_pointer) return null;
894
895 const fp = if (comptime native_arch.isSPARC())
896 // On SPARC the offset is positive. (!)
897 math.add(usize, it.fp, fp_offset) catch return null
898 else
899 math.sub(usize, it.fp, fp_offset) catch return null;
900
901 // Sanity check.
902 if (fp == 0 or !mem.isAligned(fp, @alignOf(usize))) return null;
903 const new_fp = math.add(usize, @as(*usize, @ptrFromInt(fp)).*, fp_bias) catch
904 return null;
905
906 // Sanity check: the stack grows down thus all the parent frames must be
907 // be at addresses that are greater (or equal) than the previous one.
908 // A zero frame pointer often signals this is the last frame, that case
909 // is gracefully handled by the next call to nextInternal.
910 if (new_fp != 0 and new_fp < it.fp) return null;
911 const new_pc = @as(*usize, @ptrFromInt(math.add(usize, fp, pc_offset) catch return null)).*;
912
913 it.fp = new_fp;
914
915 return new_pc;
772 return .{ .fp = @frameAddress() };
916773 }
917};
918
919pub fn writeCurrentStackTrace(
920 writer: *Writer,
921 debug_info: *SelfInfo,
922 tty_config: tty.Config,
923 start_addr: ?usize,
924) !void {
925 if (native_os == .windows) {
926 var context: ThreadContext = undefined;
927 assert(getContext(&context));
928 return writeStackTraceWindows(writer, debug_info, tty_config, &context, start_addr);
774 fn deinit(si: *StackIterator) void {
775 switch (si.*) {
776 .fp => {},
777 .di => |*unwind_context| unwind_context.deinit(),
778 }
929779 }
930 var context: ThreadContext = undefined;
931 const has_context = getContext(&context);
932
933 var it = (if (has_context) blk: {
934 break :blk StackIterator.initWithContext(start_addr, debug_info, &context, @frameAddress()) catch null;
935 } else null) orelse StackIterator.init(start_addr, @frameAddress());
936 defer it.deinit();
937780
938 while (it.next()) |return_address| {
939 printLastUnwindError(&it, debug_info, writer, tty_config);
940
941 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
942 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
943 // an overflow. We do not need to signal `StackIterator` as it will correctly detect this
944 // condition on the subsequent iteration and return `null` thus terminating the loop.
945 // same behaviour for x86-windows-msvc
946 const address = return_address -| 1;
947 try printSourceAtAddress(debug_info, writer, address, tty_config);
948 } else printLastUnwindError(&it, debug_info, writer, tty_config);
949}
781 /// On aarch64-macos, Apple mandate that the frame pointer is always used.
782 /// TODO: are there any other architectures with guarantees like this?
783 const fp_unwind_is_safe = !builtin.omit_frame_pointer and builtin.cpu.arch == .aarch64 and builtin.os.tag.isDarwin();
950784
951pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize {
952 if (builtin.cpu.arch == .x86) {
953 // RtlVirtualUnwind doesn't exist on x86
954 return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null);
785 /// Whether the current unwind strategy is allowed given `allow_unsafe`.
786 fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {
787 return switch (it.*) {
788 .di => true,
789 .fp => allow_unsafe or fp_unwind_is_safe,
790 };
955791 }
956792
957 const tib = &windows.teb().NtTib;
793 const Result = union(enum) {
794 /// A stack frame has been found; this is the corresponding return address.
795 frame: usize,
796 /// The end of the stack has been reached.
797 end,
798 /// We were using the `.di` strategy, but are now switching to `.fp` due to this error.
799 switch_to_fp: struct {
800 address: usize,
801 err: SelfInfo.Error,
802 },
803 };
804 fn next(it: *StackIterator) Result {
805 switch (it.*) {
806 .di => |*unwind_context| {
807 const di = getSelfDebugInfo() catch unreachable;
808 const di_gpa = getDebugInfoAllocator();
809 if (di.unwindFrame(di_gpa, unwind_context)) |ra| {
810 if (ra == 0) return .end;
811 return .{ .frame = ra };
812 } else |err| {
813 const bad_pc = unwind_context.pc;
814 it.* = .{ .fp = unwind_context.getFp() catch 0 };
815 return .{ .switch_to_fp = .{
816 .address = bad_pc,
817 .err = err,
818 } };
819 }
820 },
821 .fp => |fp| {
822 if (fp == 0) return .end; // we reached the "sentinel" base pointer
823
824 const bp_addr = applyOffset(fp, bp_offset) orelse return .end;
825 const ra_addr = applyOffset(fp, ra_offset) orelse return .end;
826
827 if (bp_addr == 0 or !mem.isAligned(bp_addr, @alignOf(usize)) or
828 ra_addr == 0 or !mem.isAligned(ra_addr, @alignOf(usize)))
829 {
830 // This isn't valid, but it most likely indicates end of stack.
831 return .end;
832 }
958833
959 var context: windows.CONTEXT = undefined;
960 if (existing_context) |context_ptr| {
961 context = context_ptr.*;
962 } else {
963 context = std.mem.zeroes(windows.CONTEXT);
964 windows.ntdll.RtlCaptureContext(&context);
965 }
834 const bp_ptr: *const usize = @ptrFromInt(bp_addr);
835 const ra_ptr: *const usize = @ptrFromInt(ra_addr);
836 const bp = applyOffset(bp_ptr.*, bp_bias) orelse return .end;
966837
967 var i: usize = 0;
968 var image_base: windows.DWORD64 = undefined;
969 var history_table: windows.UNWIND_HISTORY_TABLE = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE);
970
971 while (i < addresses.len) : (i += 1) {
972 const current_regs = context.getRegs();
973 if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &history_table)) |runtime_function| {
974 var handler_data: ?*anyopaque = null;
975 var establisher_frame: u64 = undefined;
976 _ = windows.ntdll.RtlVirtualUnwind(
977 windows.UNW_FLAG_NHANDLER,
978 image_base,
979 current_regs.ip,
980 runtime_function,
981 &context,
982 &handler_data,
983 &establisher_frame,
984 null,
985 );
986 } else {
987 // leaf function
988 context.setIp(@as(*usize, @ptrFromInt(current_regs.sp)).*);
989 context.setSp(current_regs.sp + @sizeOf(usize));
990 }
838 // The stack grows downards, so `bp > fp` should always hold. If it doesn't, this
839 // frame is invalid, so we'll treat it as though it we reached end of stack. The
840 // exception is address 0, which is a graceful end-of-stack signal, in which case
841 // *this* return address is valid and the *next* iteration will be the last.
842 if (bp != 0 and bp <= fp) return .end;
991843
992 const next_regs = context.getRegs();
993 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {
994 break;
844 it.fp = bp;
845 return .{ .frame = ra_ptr.* };
846 },
995847 }
996
997 if (next_regs.ip == 0) {
998 break;
999 }
1000
1001 addresses[i] = next_regs.ip;
1002848 }
1003849
1004 return i;
1005}
1006
1007pub fn writeStackTraceWindows(
1008 writer: *Writer,
1009 debug_info: *SelfInfo,
1010 tty_config: tty.Config,
1011 context: *const windows.CONTEXT,
1012 start_addr: ?usize,
1013) !void {
1014 var addr_buf: [1024]usize = undefined;
1015 const n = walkStackWindows(addr_buf[0..], context);
1016 const addrs = addr_buf[0..n];
1017 const start_i: usize = if (start_addr) |saddr| blk: {
1018 for (addrs, 0..) |addr, i| {
1019 if (addr == saddr) break :blk i;
1020 }
1021 return;
1022 } else 0;
1023 for (addrs[start_i..]) |addr| {
1024 try printSourceAtAddress(debug_info, writer, addr - 1, tty_config);
1025 }
1026}
850 /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer.
851 const bp_offset = off: {
852 // On RISC-V the frame pointer points to the top of the saved register
853 // area, on pretty much every other architecture it points to the stack
854 // slot where the previous frame pointer is saved.
855 if (native_arch.isRISCV()) break :off -2 * @sizeOf(usize);
856 // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS.
857 if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize);
858 break :off 0;
859 };
1027860
1028fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {
1029 if (!have_ucontext) return;
1030 if (it.getLastError()) |unwind_error| {
1031 printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {};
1032 }
1033}
861 /// Offset of the saved return address wrt the frame pointer.
862 const ra_offset = off: {
863 if (native_arch == .powerpc64le) break :off 2 * @sizeOf(usize);
864 break :off @sizeOf(usize);
865 };
1034866
1035fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: SelfInfo.Error, tty_config: tty.Config) !void {
1036 const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
1037 error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, error.ReadFailed => "???",
1038 error.Unexpected, error.OutOfMemory => |e| return e,
867 /// Value to add to a base pointer after loading it from the stack. Yes, SPARC really does this.
868 const bp_bias = bias: {
869 if (native_arch.isSPARC()) break :bias 2047;
870 break :bias 0;
1039871 };
1040 try tty_config.setColor(writer, .dim);
1041 switch (unwind_err) {
1042 error.Unexpected, error.OutOfMemory => |e| return e,
1043 error.MissingDebugInfo => {
1044 try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
1045 },
1046 error.InvalidDebugInfo,
1047 error.UnsupportedDebugInfo,
1048 error.ReadFailed,
1049 => {
1050 const caption: []const u8 = switch (unwind_err) {
1051 error.InvalidDebugInfo => "invalid unwind info",
1052 error.UnsupportedDebugInfo => "unsupported unwind info",
1053 error.ReadFailed => "filesystem error",
1054 else => unreachable,
1055 };
1056 try writer.print("Unwind error at address `{s}:0x{x}` ({s}), trace may be incomplete\n\n", .{ module_name, address, caption });
1057 },
872
873 fn applyOffset(addr: usize, comptime off: comptime_int) ?usize {
874 if (off >= 0) return math.add(usize, addr, off) catch return null;
875 return math.sub(usize, addr, -off) catch return null;
1058876 }
1059 try tty_config.setColor(writer, .reset);
1060}
877};
1061878
1062pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
1063 const gpa = getDebugInfoAllocator();
879fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
1064880 const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) {
1065881 error.MissingDebugInfo,
1066882 error.UnsupportedDebugInfo,
1067883 error.InvalidDebugInfo,
1068 => .{ .name = null, .compile_unit_name = null, .source_location = null },
1069 error.ReadFailed => s: {
1070 try tty_config.setColor(writer, .dim);
884 => .unknown,
885 error.ReadFailed, error.Unexpected => s: {
886 tty_config.setColor(writer, .dim) catch {};
1071887 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1072 try tty_config.setColor(writer, .reset);
1073 break :s .{ .name = null, .compile_unit_name = null, .source_location = null };
888 tty_config.setColor(writer, .reset) catch {};
889 break :s .unknown;
890 },
891 error.OutOfMemory => s: {
892 tty_config.setColor(writer, .dim) catch {};
893 try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
894 tty_config.setColor(writer, .reset) catch {};
895 break :s .unknown;
1074896 },
1075 error.OutOfMemory, error.Unexpected => |e| return e,
1076897 };
1077898 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
1078899 return printLineInfo(
......@@ -1080,10 +901,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usi
1080901 symbol.source_location,
1081902 address,
1082903 symbol.name orelse "???",
1083 symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch |err| switch (err) {
1084 error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, error.ReadFailed => "???",
1085 error.Unexpected, error.OutOfMemory => |e| return e,
1086 },
904 symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch "???",
1087905 tty_config,
1088906 );
1089907}
......@@ -1094,9 +912,9 @@ fn printLineInfo(
1094912 symbol_name: []const u8,
1095913 compile_unit_name: []const u8,
1096914 tty_config: tty.Config,
1097) !void {
915) Writer.Error!void {
1098916 nosuspend {
1099 try tty_config.setColor(writer, .bold);
917 tty_config.setColor(writer, .bold) catch {};
1100918
1101919 if (source_location) |*sl| {
1102920 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
......@@ -1104,11 +922,11 @@ fn printLineInfo(
1104922 try writer.writeAll("???:?:?");
1105923 }
1106924
1107 try tty_config.setColor(writer, .reset);
925 tty_config.setColor(writer, .reset) catch {};
1108926 try writer.writeAll(": ");
1109 try tty_config.setColor(writer, .dim);
927 tty_config.setColor(writer, .dim) catch {};
1110928 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
1111 try tty_config.setColor(writer, .reset);
929 tty_config.setColor(writer, .reset) catch {};
1112930 try writer.writeAll("\n");
1113931
1114932 // Show the matching source code line if possible
......@@ -1119,21 +937,23 @@ fn printLineInfo(
1119937 const space_needed = @as(usize, @intCast(sl.column - 1));
1120938
1121939 try writer.splatByteAll(' ', space_needed);
1122 try tty_config.setColor(writer, .green);
940 tty_config.setColor(writer, .green) catch {};
1123941 try writer.writeAll("^");
1124 try tty_config.setColor(writer, .reset);
942 tty_config.setColor(writer, .reset) catch {};
1125943 }
1126944 try writer.writeAll("\n");
1127945 } else |err| switch (err) {
1128 error.EndOfFile, error.FileNotFound => {},
1129 error.BadPathName => {},
1130 error.AccessDenied => {},
1131 else => return err,
946 error.WriteFailed => |e| return e,
947 else => {
948 // Ignore everything else. Seeing some lines in the trace without the associated
949 // source line printed is a far better user experience than interleaving the
950 // trace with a load of filesystem error crap. The user can always just open the
951 // source file themselves to see the line.
952 },
1132953 }
1133954 }
1134955 }
1135956}
1136
1137957fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
1138958 // Need this to always block even in async I/O mode, because this could potentially
1139959 // be called from e.g. the event loop code crashing.
......@@ -1392,52 +1212,9 @@ fn resetSegfaultHandler() void {
13921212}
13931213
13941214fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {
1395 // Reset to the default handler so that if a segfault happens in this handler it will crash
1396 // the process. Also when this handler returns, the original instruction will be repeated
1397 // and the resulting segfault will crash the process rather than continually dump stack traces.
1398 resetSegfaultHandler();
1399
1400 const addr = switch (native_os) {
1401 .linux => @intFromPtr(info.fields.sigfault.addr),
1402 .freebsd, .macos => @intFromPtr(info.addr),
1403 .netbsd => @intFromPtr(info.info.reason.fault.addr),
1404 .openbsd => @intFromPtr(info.data.fault.addr),
1405 .solaris, .illumos => @intFromPtr(info.reason.fault.addr),
1406 else => unreachable,
1407 };
1408
1409 const code = if (native_os == .netbsd) info.info.code else info.code;
1410 nosuspend switch (panic_stage) {
1411 0 => {
1412 panic_stage = 1;
1413 _ = panicking.fetchAdd(1, .seq_cst);
1414
1415 {
1416 lockStdErr();
1417 defer unlockStdErr();
1418
1419 dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr);
1420 }
1421
1422 waitForOtherThreadToFinishPanicking();
1423 },
1424 else => {
1425 // panic mutex already locked
1426 dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr);
1427 },
1428 };
1429
1430 // We cannot allow the signal handler to return because when it runs the original instruction
1431 // again, the memory may be mapped and undefined behavior would occur rather than repeating
1432 // the segfault. So we simply abort here.
1433 posix.abort();
1434}
1435
1436fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
1437 const stderr = lockStderrWriter(&.{});
1438 defer unlockStderrWriter();
1439 _ = switch (sig) {
1440 posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL
1215 if (use_trap_panic) @trap();
1216 const addr: ?usize, const name: []const u8 = info: {
1217 if (native_os == .linux and native_arch == .x86_64) {
14411218 // x86_64 doesn't have a full 64-bit virtual address space.
14421219 // Addresses outside of that address space are non-canonical
14431220 // and the CPU won't provide the faulting address to us.
......@@ -1445,16 +1222,31 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque)
14451222 // but can also happen when no addressable memory is involved;
14461223 // for example when reading/writing model-specific registers
14471224 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).
1448 stderr.writeAll("General protection exception (no address available)\n")
1449 else
1450 stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),
1451 posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),
1452 posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),
1453 posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}),
1454 else => unreachable,
1455 } catch posix.abort();
1456
1457 switch (native_arch) {
1225 const SI_KERNEL = 0x80;
1226 if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) {
1227 break :info .{ null, "General protection exception" };
1228 }
1229 }
1230 const addr: usize = switch (native_os) {
1231 .linux => @intFromPtr(info.fields.sigfault.addr),
1232 .freebsd, .macos => @intFromPtr(info.addr),
1233 .netbsd => @intFromPtr(info.info.reason.fault.addr),
1234 .openbsd => @intFromPtr(info.data.fault.addr),
1235 .solaris, .illumos => @intFromPtr(info.reason.fault.addr),
1236 else => comptime unreachable,
1237 };
1238 const name = switch (sig) {
1239 posix.SIG.SEGV => "Segmentation fault",
1240 posix.SIG.ILL => "Illegal instruction",
1241 posix.SIG.BUS => "Bus error",
1242 posix.SIG.FPE => "Arithmetic exception",
1243 else => unreachable,
1244 };
1245 break :info .{ addr, name };
1246 };
1247
1248 // MLUGG TODO: this doesn't make any sense at all?
1249 const use_context = switch (native_arch) {
14581250 .x86,
14591251 .x86_64,
14601252 .arm,
......@@ -1463,82 +1255,90 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque)
14631255 .thumbeb,
14641256 .aarch64,
14651257 .aarch64_be,
1466 => {
1467 // Some kernels don't align `ctx_ptr` properly. Handle this defensively.
1468 const ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);
1469 var new_ctx: posix.ucontext_t = ctx.*;
1470 if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {
1471 // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`,
1472 // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the
1473 // contents to the right place so that the `mcontext` pointer will be correct after the
1474 // `relocateContext` call below.
1475 new_ctx.__mcontext_data = @as(*align(1) extern struct {
1476 onstack: c_int,
1477 sigmask: std.c.sigset_t,
1478 stack: std.c.stack_t,
1479 link: ?*std.c.ucontext_t,
1480 mcsize: u64,
1481 mcontext: *std.c.mcontext_t,
1482 __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`.
1483 }, @ptrCast(ctx)).__mcontext_data;
1484 }
1485 relocateContext(&new_ctx);
1486 dumpStackTraceFromBase(&new_ctx, stderr);
1487 },
1488 else => {},
1258 => true,
1259 else => false,
1260 };
1261 if (!have_ucontext or !use_context) return handleSegfault(addr, name, null);
1262
1263 // Some kernels don't align `ctx_ptr` properly, so we'll copy it into a local buffer.
1264 var copied_ctx: ThreadContext = undefined;
1265 const orig_ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);
1266 copied_ctx = orig_ctx.*;
1267 if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {
1268 // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`,
1269 // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the
1270 // contents to the right place so that the `mcontext` pointer will be correct after the
1271 // `relocateContext` call below.
1272 const WrittenContext = extern struct {
1273 onstack: c_int,
1274 sigmask: std.c.sigset_t,
1275 stack: std.c.stack_t,
1276 link: ?*std.c.ucontext_t,
1277 mcsize: u64,
1278 mcontext: *std.c.mcontext_t,
1279 __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`.
1280 };
1281 const written_ctx: *align(1) WrittenContext = @ptrCast(ctx_ptr);
1282 copied_ctx.__mcontext_data = written_ctx.__mcontext_data;
14891283 }
1284 relocateContext(&copied_ctx);
1285
1286 handleSegfault(addr, name, &copied_ctx);
14901287}
14911288
14921289fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {
1493 switch (info.ExceptionRecord.ExceptionCode) {
1494 windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"),
1495 windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null),
1496 windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null),
1497 windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"),
1290 if (use_trap_panic) @trap();
1291 const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) {
1292 windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null },
1293 windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] },
1294 windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip },
1295 windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null },
14981296 else => return windows.EXCEPTION_CONTINUE_SEARCH,
1499 }
1297 };
1298 handleSegfault(addr, name, info.ContextRecord);
15001299}
15011300
1502fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn {
1503 // For backends that cannot handle the language features used by this segfault handler, we have a simpler one,
1504 switch (builtin.zig_backend) {
1505 .stage2_x86_64 => if (builtin.target.ofmt == .coff) @trap(),
1506 else => {},
1507 }
1508
1509 comptime assert(windows.CONTEXT != void);
1510 nosuspend switch (panic_stage) {
1301fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) noreturn {
1302 // There is very similar logic to the following in `defaultPanic`.
1303 switch (panic_stage) {
15111304 0 => {
15121305 panic_stage = 1;
15131306 _ = panicking.fetchAdd(1, .seq_cst);
15141307
1515 {
1308 trace: {
1309 const tty_config = tty.detectConfig(.stderr());
1310
15161311 const stderr = lockStderrWriter(&.{});
15171312 defer unlockStderrWriter();
15181313
1519 dumpSegfaultInfoWindows(info, msg, label, stderr);
1314 if (addr) |a| {
1315 stderr.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
1316 } else {
1317 stderr.print("{s} (no address available)\n", .{name}) catch break :trace;
1318 }
1319 // MLUGG TODO: for this to work neatly, `ThreadContext` needs to be `noreturn` when not supported!
1320 if (opt_ctx) |context| {
1321 writeCurrentStackTrace(.{
1322 .context = context,
1323 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
1324 }, stderr, tty_config) catch break :trace;
1325 }
15201326 }
1521
1522 waitForOtherThreadToFinishPanicking();
15231327 },
15241328 1 => {
15251329 panic_stage = 2;
1330 // A segfault happened while trying to print a previous panic message.
1331 // We're still holding the mutex but that's fine as we're going to
1332 // call abort().
15261333 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
15271334 },
1528 else => {},
1529 };
1530 posix.abort();
1531}
1532
1533fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8, stderr: *Writer) void {
1534 _ = switch (msg) {
1535 0 => stderr.print("{s}\n", .{label.?}),
1536 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
1537 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}),
1538 else => unreachable,
1539 } catch posix.abort();
1335 else => {}, // Panicked while printing the recursive panic message.
1336 }
15401337
1541 dumpStackTraceFromBase(info.ContextRecord, stderr);
1338 // We cannot allow the signal handler to return because when it runs the original instruction
1339 // again, the memory may be mapped and undefined behavior would occur rather than repeating
1340 // the segfault. So we simply abort here.
1341 posix.abort();
15421342}
15431343
15441344pub fn dumpStackPointerAddr(prefix: []const u8) void {
......@@ -1549,26 +1349,22 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void {
15491349}
15501350
15511351test "manage resources correctly" {
1552 if (builtin.strip_debug_info) return error.SkipZigTest;
1553
1554 if (native_os == .wasi) return error.SkipZigTest;
1555
1556 if (native_os == .windows) {
1557 // https://github.com/ziglang/zig/issues/13963
1558 return error.SkipZigTest;
1559 }
1560
1561 // self-hosted debug info is still too buggy
1562 if (builtin.zig_backend != .stage2_llvm) return error.SkipZigTest;
1563
1564 var discarding: Writer.Discarding = .init(&.{});
1565 var di = try SelfInfo.open(testing.allocator);
1352 if (!SelfInfo.target_supported) return error.SkipZigTest;
1353 const S = struct {
1354 noinline fn showMyTrace() usize {
1355 return @returnAddress();
1356 }
1357 };
1358 var discarding: std.io.Writer.Discarding = .init(&.{});
1359 var di: SelfInfo = try .open(testing.allocator);
15661360 defer di.deinit();
1567 try printSourceAtAddress(&di, &discarding.writer, showMyTrace(), tty.detectConfig(.stderr()));
1568}
1569
1570noinline fn showMyTrace() usize {
1571 return @returnAddress();
1361 try printSourceAtAddress(
1362 testing.allocator,
1363 &di,
1364 &discarding.writer,
1365 S.showMyTrace(),
1366 tty.detectConfig(.stderr()),
1367 );
15721368}
15731369
15741370/// This API helps you track where a value originated and where it was mutated,
......@@ -1615,12 +1411,11 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16151411
16161412 if (t.index < size) {
16171413 t.notes[t.index] = note;
1618 t.addrs[t.index] = [1]usize{0} ** stack_frame_count;
1619 var stack_trace: std.builtin.StackTrace = .{
1620 .index = 0,
1621 .instruction_addresses = &t.addrs[t.index],
1622 };
1623 captureStackTrace(addr, &stack_trace);
1414 const addrs = &t.addrs[t.index];
1415 const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs);
1416 if (st.index < addrs.len) {
1417 @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace
1418 }
16241419 }
16251420 // Keep counting even if the end is reached so that the
16261421 // user can find out how much more size they need.
......@@ -1634,13 +1429,6 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16341429 const stderr = lockStderrWriter(&.{});
16351430 defer unlockStderrWriter();
16361431 const end = @min(t.index, size);
1637 const debug_info = getSelfDebugInfo() catch |err| {
1638 stderr.print(
1639 "Unable to dump stack trace: Unable to open debug info: {s}\n",
1640 .{@errorName(err)},
1641 ) catch return;
1642 return;
1643 };
16441432 for (t.addrs[0..end], 0..) |frames_array, i| {
16451433 stderr.print("{s}:\n", .{t.notes[i]}) catch return;
16461434 var frames_array_mutable = frames_array;
......@@ -1649,7 +1437,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16491437 .index = frames.len,
16501438 .instruction_addresses = frames,
16511439 };
1652 writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue;
1440 writeStackTrace(stack_trace, stderr, tty_config) catch return;
16531441 }
16541442 if (t.index > end) {
16551443 stderr.print("{d} more traces not shown; consider increasing trace size\n", .{
lib/std/debug/Dwarf.zig+1-1
......@@ -1449,7 +1449,7 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
14491449
14501450pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64) !std.debug.Symbol {
14511451 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
1452 error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = null, .compile_unit_name = null, .source_location = null },
1452 error.MissingDebugInfo, error.InvalidDebugInfo => return .unknown,
14531453 else => return err,
14541454 };
14551455 return .{
lib/std/debug/SelfInfo.zig+7-10
......@@ -158,7 +158,7 @@ test {
158158}
159159
160160pub const UnwindContext = struct {
161 gpa: Allocator,
161 gpa: Allocator, // MLUGG TODO: make unmanaged (also maybe rename this type, DwarfUnwindContext or smth idk)
162162 cfa: ?usize,
163163 pc: usize,
164164 thread_context: *std.debug.ThreadContext,
......@@ -166,22 +166,20 @@ pub const UnwindContext = struct {
166166 vm: Dwarf.Unwind.VirtualMachine,
167167 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
168168
169 pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext {
169 pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) UnwindContext {
170170 comptime assert(supports_unwinding);
171171
172172 const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
173 const pc = stripInstructionPtrAuthCode(
174 (try regValueNative(thread_context, ip_reg_num, null)).*,
175 );
176
177 const context_copy = try gpa.create(std.debug.ThreadContext);
178 std.debug.copyContext(thread_context, context_copy);
173 const raw_pc_ptr = regValueNative(thread_context, ip_reg_num, null) catch {
174 unreachable; // error means unsupported, in which case `supports_unwinding` should have been `false`
175 };
176 const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*);
179177
180178 return .{
181179 .gpa = gpa,
182180 .cfa = null,
183181 .pc = pc,
184 .thread_context = context_copy,
182 .thread_context = thread_context,
185183 .reg_context = undefined,
186184 .vm = .{},
187185 .stack_machine = .{},
......@@ -191,7 +189,6 @@ pub const UnwindContext = struct {
191189 pub fn deinit(self: *UnwindContext) void {
192190 self.vm.deinit(self.gpa);
193191 self.stack_machine.deinit(self.gpa);
194 self.gpa.destroy(self.thread_context);
195192 self.* = undefined;
196193 }
197194
lib/std/debug/SelfInfo/DarwinModule.zig+1-5
......@@ -196,11 +196,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
196196 const full = &di.full.?;
197197
198198 const vaddr = address - module.load_offset;
199 const symbol = MachoSymbol.find(full.symbols, vaddr) orelse return .{
200 .name = null,
201 .compile_unit_name = null,
202 .source_location = null,
203 };
199 const symbol = MachoSymbol.find(full.symbols, vaddr) orelse return .unknown;
204200
205201 // offset of `address` from start of `symbol`
206202 const address_symbol_offset = vaddr - symbol.addr;