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 @@...@@ -1,19 +1,19 @@
1const builtin = @import("builtin");
2const std = @import("std.zig");1const std = @import("std.zig");
3const math = std.math;2const math = std.math;
4const mem = std.mem;3const mem = std.mem;
5const posix = std.posix;4const posix = std.posix;
6const fs = std.fs;5const fs = std.fs;
7const testing = std.testing;6const testing = std.testing;
8const root = @import("root");7const Allocator = mem.Allocator;
9const File = std.fs.File;8const File = std.fs.File;
10const windows = std.os.windows;9const windows = std.os.windows;
11const native_arch = builtin.cpu.arch;
12const native_os = builtin.os.tag;
13const native_endian = native_arch.endian();
14const Writer = std.Io.Writer;10const Writer = std.Io.Writer;
15const tty = std.Io.tty;11const tty = std.Io.tty;
1612
13const builtin = @import("builtin");
14const native_arch = builtin.cpu.arch;
15const native_os = builtin.os.tag;
16
17pub const Dwarf = @import("debug/Dwarf.zig");17pub const Dwarf = @import("debug/Dwarf.zig");
18pub const Pdb = @import("debug/Pdb.zig");18pub const Pdb = @import("debug/Pdb.zig");
19pub const SelfInfo = @import("debug/SelfInfo.zig");19pub const SelfInfo = @import("debug/SelfInfo.zig");
...@@ -156,6 +156,11 @@ pub const Symbol = struct {...@@ -156,6 +156,11 @@ pub const Symbol = struct {
156 name: ?[]const u8,156 name: ?[]const u8,
157 compile_unit_name: ?[]const u8,157 compile_unit_name: ?[]const u8,
158 source_location: ?SourceLocation,158 source_location: ?SourceLocation,
159 pub const unknown: Symbol = .{
160 .name = null,
161 .compile_unit_name = null,
162 .source_location = null,
163 };
159};164};
160165
161/// Deprecated because it returns the optimization mode of the standard166/// Deprecated because it returns the optimization mode of the standard
...@@ -186,7 +191,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {...@@ -186,7 +191,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
186 .wasm64,191 .wasm64,
187 => native_os == .emscripten and builtin.mode == .Debug,192 => native_os == .emscripten and builtin.mode == .Debug,
188193
189 // `@returnAddress()` is unsupported in LLVM 13.194 // `@returnAddress()` is unsupported in LLVM 21.
190 .bpfel,195 .bpfel,
191 .bpfeb,196 .bpfeb,
192 => false,197 => false,
...@@ -234,8 +239,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {...@@ -234,8 +239,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
234/// TODO multithreaded awareness239/// TODO multithreaded awareness
235/// Marked `inline` to propagate a comptime-known error to callers.240/// Marked `inline` to propagate a comptime-known error to callers.
236pub inline fn getSelfDebugInfo() !*SelfInfo {241pub inline fn getSelfDebugInfo() !*SelfInfo {
237 if (builtin.strip_debug_info) return error.MissingDebugInfo;242 if (!SelfInfo.target_supported) return error.UnsupportedTarget;
238 if (!SelfInfo.target_supported) return error.UnsupportedOperatingSystem;
239 const S = struct {243 const S = struct {
240 var self_info: SelfInfo = .init;244 var self_info: SelfInfo = .init;
241 };245 };
...@@ -320,40 +324,11 @@ test dumpHexFallible {...@@ -320,40 +324,11 @@ test dumpHexFallible {
320 try std.testing.expectEqualStrings(expected, aw.written());324 try std.testing.expectEqualStrings(expected, aw.written());
321}325}
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
352pub const have_ucontext = posix.ucontext_t != void;327pub const have_ucontext = posix.ucontext_t != void;
353328
354/// Platform-specific thread state. This contains register state, and on some platforms329/// Platform-specific thread state. This contains register state, and on some platforms
355/// information about the stack. This is not safe to trivially copy, because some platforms330/// 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`.
357pub const ThreadContext = blk: {332pub const ThreadContext = blk: {
358 if (native_os == .windows) {333 if (native_os == .windows) {
359 break :blk windows.CONTEXT;334 break :blk windows.CONTEXT;
...@@ -363,22 +338,12 @@ pub const ThreadContext = blk: {...@@ -363,22 +338,12 @@ pub const ThreadContext = blk: {
363 break :blk void;338 break :blk void;
364 }339 }
365};340};
366341/// Updates any internal pointers of a `ThreadContext` after the caller copies it.
367/// Copies one context to another, updating any internal pointers342pub fn relocateContext(dest: *ThreadContext) void {
368pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void {343 switch (native_os) {
369 if (!have_ucontext) return {};344 .macos => dest.mcontext = &dest.__mcontext_data,
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 },
380 else => {},345 else => {},
381 };346 }
382}347}
383348
384pub const have_getcontext = @TypeOf(posix.system.getcontext) != void;349pub const have_getcontext = @TypeOf(posix.system.getcontext) != void;
...@@ -409,142 +374,6 @@ pub inline fn getContext(context: *ThreadContext) bool {...@@ -409,142 +374,6 @@ pub inline fn getContext(context: *ThreadContext) bool {
409 return result;374 return result;
410}375}
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
548/// Invokes detectable illegal behavior when `ok` is `false`.377/// Invokes detectable illegal behavior when `ok` is `false`.
549///378///
550/// In Debug and ReleaseSafe modes, calls to this function are always379/// In Debug and ReleaseSafe modes, calls to this function are always
...@@ -613,6 +442,24 @@ var panicking = std.atomic.Value(u8).init(0);...@@ -613,6 +442,24 @@ var panicking = std.atomic.Value(u8).init(0);
613/// This is used to catch and handle panics triggered by the panic handler.442/// This is used to catch and handle panics triggered by the panic handler.
614threadlocal var panic_stage: usize = 0;443threadlocal 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
616/// Dumps a stack trace to standard error, then aborts.463/// Dumps a stack trace to standard error, then aborts.
617pub fn defaultPanic(464pub fn defaultPanic(
618 msg: []const u8,465 msg: []const u8,
...@@ -620,8 +467,8 @@ pub fn defaultPanic(...@@ -620,8 +467,8 @@ pub fn defaultPanic(
620) noreturn {467) noreturn {
621 @branchHint(.cold);468 @branchHint(.cold);
622469
623 // For backends that cannot handle the language features depended on by the470 if (use_trap_panic) @trap();
624 // default panic handler, we have a simpler panic handler:471
625 switch (builtin.zig_backend) {472 switch (builtin.zig_backend) {
626 .stage2_aarch64,473 .stage2_aarch64,
627 .stage2_arm,474 .stage2_arm,
...@@ -686,41 +533,48 @@ pub fn defaultPanic(...@@ -686,41 +533,48 @@ pub fn defaultPanic(
686 resetSegfaultHandler();533 resetSegfaultHandler();
687 }534 }
688535
689 // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra.536 // There is very similar logic to the following in `handleSegfault`.
690 nosuspend switch (panic_stage) {537 switch (panic_stage) {
691 0 => {538 0 => {
692 panic_stage = 1;539 panic_stage = 1;
693
694 _ = panicking.fetchAdd(1, .seq_cst);540 _ = panicking.fetchAdd(1, .seq_cst);
695541
696 {542 trace: {
543 const tty_config = tty.detectConfig(.stderr());
544
697 const stderr = lockStderrWriter(&.{});545 const stderr = lockStderrWriter(&.{});
698 defer unlockStderrWriter();546 defer unlockStderrWriter();
699547
700 if (builtin.single_threaded) {548 if (builtin.single_threaded) {
701 stderr.print("panic: ", .{}) catch posix.abort();549 stderr.print("panic: ", .{}) catch break :trace;
702 } else {550 } else {
703 const current_thread_id = std.Thread.getCurrentId();551 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;
705 }553 }
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.*);556 if (@errorReturnTrace()) |t| if (t.index > 0) {
709 dumpCurrentStackTraceToWriter(first_trace_addr orelse @returnAddress(), stderr) catch {};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;
710 }565 }
711566
712 waitForOtherThreadToFinishPanicking();567 waitForOtherThreadToFinishPanicking();
713 },568 },
714 1 => {569 1 => {
715 panic_stage = 2;570 panic_stage = 2;
716
717 // A panic happened while trying to print a previous panic message.571 // A panic happened while trying to print a previous panic message.
718 // We're still holding the mutex but that's fine as we're going to572 // We're still holding the mutex but that's fine as we're going to
719 // call abort().573 // call abort().
720 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};574 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
721 },575 },
722 else => {}, // Panicked while printing the recursive panic message.576 else => {}, // Panicked while printing the recursive panic message.
723 };577 }
724578
725 posix.abort();579 posix.abort();
726}580}
...@@ -739,340 +593,307 @@ fn waitForOtherThreadToFinishPanicking() void {...@@ -739,340 +593,307 @@ fn waitForOtherThreadToFinishPanicking() void {
739 }593 }
740}594}
741595
742pub fn writeStackTrace(596pub const StackUnwindOptions = struct {
743 stack_trace: std.builtin.StackTrace,597 /// If not `null`, we will ignore all frames up until this return address. This is typically
744 writer: *Writer,598 /// used to omit intermediate handling code (for instance, a panic handler and its machinery)
745 debug_info: *SelfInfo,599 /// from stack traces.
746 tty_config: tty.Config,600 first_address: ?usize = null,
747) !void {601 /// If not `null`, we will unwind from this `ThreadContext` instead of the current top of the
748 if (builtin.strip_debug_info) return error.MissingDebugInfo;602 /// stack. The main use case here is printing stack traces from signal handlers, where the
749 var frame_index: usize = 0;603 /// kernel provides a `*ThreadContext` of the state before the signal.
750 var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len);604 context: ?*const ThreadContext = null,
751605 /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.
752 while (frames_left != 0) : ({606 /// If `false`, only known-safe mechanisms will be attempted.
753 frames_left -= 1;607 allow_unsafe_unwind: bool = false,
754 frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len;608};
755 }) {
756 const return_address = stack_trace.instruction_addresses[frame_index];
757 try printSourceAtAddress(debug_info, writer, return_address -| 1, tty_config);
758 }
759609
760 if (stack_trace.index > stack_trace.instruction_addresses.len) {610/// Capture and return the current stack trace. The returned `StackTrace` stores its addresses in
761 const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len;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) {
763 tty_config.setColor(writer, .bold) catch {};731 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});
765 tty_config.setColor(writer, .reset) catch {};733 tty_config.setColor(writer, .reset) catch {};
766 }734 }
767}735}
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 {746const StackIterator = union(enum) {
770 // Skip every frame before this address is found.747 /// Unwinding using debug info (e.g. DWARF CFI).
771 first_address: ?usize,748 di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn,
772 // Last known value of the frame pointer register.749 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
773 fp: usize,750 fp: usize,
774751
775 // When SelfInfo and a register context is available, this iterator can unwind752 /// It is important that this function is marked `inline` so that it can safely use
776 // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),753 /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one
777 // using DWARF and MachO unwind info.754 /// and the same.
778 unwind_state: if (have_ucontext) ?struct {755 inline fn init(context_opt: ?*const ThreadContext, context_buf: *ThreadContext) StackIterator {
779 debug_info: *SelfInfo,756 if (builtin.cpu.arch.isSPARC()) {
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()) {
787 // Flush all the register windows on stack.757 // Flush all the register windows on stack.
788 asm volatile (if (builtin.cpu.has(.sparc, .v9))758 if (builtin.cpu.has(.sparc, .v9)) {
789 "flushw"759 asm volatile ("flushw" ::: .{ .memory = true });
790 else760 } else {
791 "ta 3" // ST_FLUSH_WINDOWS761 asm volatile ("ta 3" ::: .{ .memory = true }); // 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 };
830 }762 }
831 }763 }
832764 if (context_opt) |context| {
833 return null;765 context_buf.* = context.*;
834 }766 relocateContext(context_buf);
835767 return .{ .di = .init(getDebugInfoAllocator(), context_buf) };
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;
868 }768 }
869769 if (getContext(context_buf)) {
870 return address;770 return .{ .di = .init(getDebugInfoAllocator(), context_buf) };
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 }
891 }771 }
892772 return .{ .fp = @frameAddress() };
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;
916 }773 }
917};774 fn deinit(si: *StackIterator) void {
918775 switch (si.*) {
919pub fn writeCurrentStackTrace(776 .fp => {},
920 writer: *Writer,777 .di => |*unwind_context| unwind_context.deinit(),
921 debug_info: *SelfInfo,778 }
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);
929 }779 }
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| {781 /// On aarch64-macos, Apple mandate that the frame pointer is always used.
939 printLastUnwindError(&it, debug_info, writer, tty_config);782 /// TODO: are there any other architectures with guarantees like this?
940783 const fp_unwind_is_safe = !builtin.omit_frame_pointer and builtin.cpu.arch == .aarch64 and builtin.os.tag.isDarwin();
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}
950784
951pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize {785 /// Whether the current unwind strategy is allowed given `allow_unsafe`.
952 if (builtin.cpu.arch == .x86) {786 fn stratOk(it: *const StackIterator, allow_unsafe: bool) bool {
953 // RtlVirtualUnwind doesn't exist on x86787 return switch (it.*) {
954 return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null);788 .di => true,
789 .fp => allow_unsafe or fp_unwind_is_safe,
790 };
955 }791 }
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;834 const bp_ptr: *const usize = @ptrFromInt(bp_addr);
960 if (existing_context) |context_ptr| {835 const ra_ptr: *const usize = @ptrFromInt(ra_addr);
961 context = context_ptr.*;836 const bp = applyOffset(bp_ptr.*, bp_bias) orelse return .end;
962 } else {
963 context = std.mem.zeroes(windows.CONTEXT);
964 windows.ntdll.RtlCaptureContext(&context);
965 }
966837
967 var i: usize = 0;838 // The stack grows downards, so `bp > fp` should always hold. If it doesn't, this
968 var image_base: windows.DWORD64 = undefined;839 // frame is invalid, so we'll treat it as though it we reached end of stack. The
969 var history_table: windows.UNWIND_HISTORY_TABLE = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE);840 // exception is address 0, which is a graceful end-of-stack signal, in which case
970841 // *this* return address is valid and the *next* iteration will be the last.
971 while (i < addresses.len) : (i += 1) {842 if (bp != 0 and bp <= fp) return .end;
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 }
991843
992 const next_regs = context.getRegs();844 it.fp = bp;
993 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {845 return .{ .frame = ra_ptr.* };
994 break;846 },
995 }847 }
996
997 if (next_regs.ip == 0) {
998 break;
999 }
1000
1001 addresses[i] = next_regs.ip;
1002 }848 }
1003849
1004 return i;850 /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer.
1005}851 const bp_offset = off: {
1006852 // On RISC-V the frame pointer points to the top of the saved register
1007pub fn writeStackTraceWindows(853 // area, on pretty much every other architecture it points to the stack
1008 writer: *Writer,854 // slot where the previous frame pointer is saved.
1009 debug_info: *SelfInfo,855 if (native_arch.isRISCV()) break :off -2 * @sizeOf(usize);
1010 tty_config: tty.Config,856 // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS.
1011 context: *const windows.CONTEXT,857 if (native_arch.isSPARC()) break :off 14 * @sizeOf(usize);
1012 start_addr: ?usize,858 break :off 0;
1013) !void {859 };
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}
1027860
1028fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writer, tty_config: tty.Config) void {861 /// Offset of the saved return address wrt the frame pointer.
1029 if (!have_ucontext) return;862 const ra_offset = off: {
1030 if (it.getLastError()) |unwind_error| {863 if (native_arch == .powerpc64le) break :off 2 * @sizeOf(usize);
1031 printUnwindError(debug_info, writer, unwind_error.address, unwind_error.err, tty_config) catch {};864 break :off @sizeOf(usize);
1032 }865 };
1033}
1034866
1035fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: SelfInfo.Error, tty_config: tty.Config) !void {867 /// Value to add to a base pointer after loading it from the stack. Yes, SPARC really does this.
1036 const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {868 const bp_bias = bias: {
1037 error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, error.ReadFailed => "???",869 if (native_arch.isSPARC()) break :bias 2047;
1038 error.Unexpected, error.OutOfMemory => |e| return e,870 break :bias 0;
1039 };871 };
1040 try tty_config.setColor(writer, .dim);872
1041 switch (unwind_err) {873 fn applyOffset(addr: usize, comptime off: comptime_int) ?usize {
1042 error.Unexpected, error.OutOfMemory => |e| return e,874 if (off >= 0) return math.add(usize, addr, off) catch return null;
1043 error.MissingDebugInfo => {875 return math.sub(usize, addr, -off) catch return null;
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 },
1058 }876 }
1059 try tty_config.setColor(writer, .reset);877};
1060}
1061878
1062pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {879fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
1063 const gpa = getDebugInfoAllocator();
1064 const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) {880 const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) {
1065 error.MissingDebugInfo,881 error.MissingDebugInfo,
1066 error.UnsupportedDebugInfo,882 error.UnsupportedDebugInfo,
1067 error.InvalidDebugInfo,883 error.InvalidDebugInfo,
1068 => .{ .name = null, .compile_unit_name = null, .source_location = null },884 => .unknown,
1069 error.ReadFailed => s: {885 error.ReadFailed, error.Unexpected => s: {
1070 try tty_config.setColor(writer, .dim);886 tty_config.setColor(writer, .dim) catch {};
1071 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});887 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1072 try tty_config.setColor(writer, .reset);888 tty_config.setColor(writer, .reset) catch {};
1073 break :s .{ .name = null, .compile_unit_name = null, .source_location = null };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;
1074 },896 },
1075 error.OutOfMemory, error.Unexpected => |e| return e,
1076 };897 };
1077 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);898 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
1078 return printLineInfo(899 return printLineInfo(
...@@ -1080,10 +901,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usi...@@ -1080,10 +901,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usi
1080 symbol.source_location,901 symbol.source_location,
1081 address,902 address,
1082 symbol.name orelse "???",903 symbol.name orelse "???",
1083 symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch |err| switch (err) {904 symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch "???",
1084 error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, error.ReadFailed => "???",
1085 error.Unexpected, error.OutOfMemory => |e| return e,
1086 },
1087 tty_config,905 tty_config,
1088 );906 );
1089}907}
...@@ -1094,9 +912,9 @@ fn printLineInfo(...@@ -1094,9 +912,9 @@ fn printLineInfo(
1094 symbol_name: []const u8,912 symbol_name: []const u8,
1095 compile_unit_name: []const u8,913 compile_unit_name: []const u8,
1096 tty_config: tty.Config,914 tty_config: tty.Config,
1097) !void {915) Writer.Error!void {
1098 nosuspend {916 nosuspend {
1099 try tty_config.setColor(writer, .bold);917 tty_config.setColor(writer, .bold) catch {};
1100918
1101 if (source_location) |*sl| {919 if (source_location) |*sl| {
1102 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });920 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
...@@ -1104,11 +922,11 @@ fn printLineInfo(...@@ -1104,11 +922,11 @@ fn printLineInfo(
1104 try writer.writeAll("???:?:?");922 try writer.writeAll("???:?:?");
1105 }923 }
1106924
1107 try tty_config.setColor(writer, .reset);925 tty_config.setColor(writer, .reset) catch {};
1108 try writer.writeAll(": ");926 try writer.writeAll(": ");
1109 try tty_config.setColor(writer, .dim);927 tty_config.setColor(writer, .dim) catch {};
1110 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });928 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 {};
1112 try writer.writeAll("\n");930 try writer.writeAll("\n");
1113931
1114 // Show the matching source code line if possible932 // Show the matching source code line if possible
...@@ -1119,21 +937,23 @@ fn printLineInfo(...@@ -1119,21 +937,23 @@ fn printLineInfo(
1119 const space_needed = @as(usize, @intCast(sl.column - 1));937 const space_needed = @as(usize, @intCast(sl.column - 1));
1120938
1121 try writer.splatByteAll(' ', space_needed);939 try writer.splatByteAll(' ', space_needed);
1122 try tty_config.setColor(writer, .green);940 tty_config.setColor(writer, .green) catch {};
1123 try writer.writeAll("^");941 try writer.writeAll("^");
1124 try tty_config.setColor(writer, .reset);942 tty_config.setColor(writer, .reset) catch {};
1125 }943 }
1126 try writer.writeAll("\n");944 try writer.writeAll("\n");
1127 } else |err| switch (err) {945 } else |err| switch (err) {
1128 error.EndOfFile, error.FileNotFound => {},946 error.WriteFailed => |e| return e,
1129 error.BadPathName => {},947 else => {
1130 error.AccessDenied => {},948 // Ignore everything else. Seeing some lines in the trace without the associated
1131 else => return err,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 },
1132 }953 }
1133 }954 }
1134 }955 }
1135}956}
1136
1137fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {957fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
1138 // Need this to always block even in async I/O mode, because this could potentially958 // Need this to always block even in async I/O mode, because this could potentially
1139 // be called from e.g. the event loop code crashing.959 // be called from e.g. the event loop code crashing.
...@@ -1392,52 +1212,9 @@ fn resetSegfaultHandler() void {...@@ -1392,52 +1212,9 @@ fn resetSegfaultHandler() void {
1392}1212}
13931213
1394fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {1214fn 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 crash1215 if (use_trap_panic) @trap();
1396 // the process. Also when this handler returns, the original instruction will be repeated1216 const addr: ?usize, const name: []const u8 = info: {
1397 // and the resulting segfault will crash the process rather than continually dump stack traces.1217 if (native_os == .linux and native_arch == .x86_64) {
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
1441 // x86_64 doesn't have a full 64-bit virtual address space.1218 // x86_64 doesn't have a full 64-bit virtual address space.
1442 // Addresses outside of that address space are non-canonical1219 // Addresses outside of that address space are non-canonical
1443 // and the CPU won't provide the faulting address to us.1220 // 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)...@@ -1445,16 +1222,31 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque)
1445 // but can also happen when no addressable memory is involved;1222 // but can also happen when no addressable memory is involved;
1446 // for example when reading/writing model-specific registers1223 // for example when reading/writing model-specific registers
1447 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).1224 // by executing `rdmsr` or `wrmsr` in user-space (unprivileged mode).
1448 stderr.writeAll("General protection exception (no address available)\n")1225 const SI_KERNEL = 0x80;
1449 else1226 if (sig == posix.SIG.SEGV and info.code == SI_KERNEL) {
1450 stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),1227 break :info .{ null, "General protection exception" };
1451 posix.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),1228 }
1452 posix.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),1229 }
1453 posix.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}),1230 const addr: usize = switch (native_os) {
1454 else => unreachable,1231 .linux => @intFromPtr(info.fields.sigfault.addr),
1455 } catch posix.abort();1232 .freebsd, .macos => @intFromPtr(info.addr),
14561233 .netbsd => @intFromPtr(info.info.reason.fault.addr),
1457 switch (native_arch) {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) {
1458 .x86,1250 .x86,
1459 .x86_64,1251 .x86_64,
1460 .arm,1252 .arm,
...@@ -1463,82 +1255,90 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque)...@@ -1463,82 +1255,90 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque)
1463 .thumbeb,1255 .thumbeb,
1464 .aarch64,1256 .aarch64,
1465 .aarch64_be,1257 .aarch64_be,
1466 => {1258 => true,
1467 // Some kernels don't align `ctx_ptr` properly. Handle this defensively.1259 else => false,
1468 const ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);1260 };
1469 var new_ctx: posix.ucontext_t = ctx.*;1261 if (!have_ucontext or !use_context) return handleSegfault(addr, name, null);
1470 if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {1262
1471 // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`,1263 // Some kernels don't align `ctx_ptr` properly, so we'll copy it into a local buffer.
1472 // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the1264 var copied_ctx: ThreadContext = undefined;
1473 // contents to the right place so that the `mcontext` pointer will be correct after the1265 const orig_ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);
1474 // `relocateContext` call below.1266 copied_ctx = orig_ctx.*;
1475 new_ctx.__mcontext_data = @as(*align(1) extern struct {1267 if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {
1476 onstack: c_int,1268 // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`,
1477 sigmask: std.c.sigset_t,1269 // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the
1478 stack: std.c.stack_t,1270 // contents to the right place so that the `mcontext` pointer will be correct after the
1479 link: ?*std.c.ucontext_t,1271 // `relocateContext` call below.
1480 mcsize: u64,1272 const WrittenContext = extern struct {
1481 mcontext: *std.c.mcontext_t,1273 onstack: c_int,
1482 __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`.1274 sigmask: std.c.sigset_t,
1483 }, @ptrCast(ctx)).__mcontext_data;1275 stack: std.c.stack_t,
1484 }1276 link: ?*std.c.ucontext_t,
1485 relocateContext(&new_ctx);1277 mcsize: u64,
1486 dumpStackTraceFromBase(&new_ctx, stderr);1278 mcontext: *std.c.mcontext_t,
1487 },1279 __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`.
1488 else => {},1280 };
1281 const written_ctx: *align(1) WrittenContext = @ptrCast(ctx_ptr);
1282 copied_ctx.__mcontext_data = written_ctx.__mcontext_data;
1489 }1283 }
1284 relocateContext(&copied_ctx);
1285
1286 handleSegfault(addr, name, &copied_ctx);
1490}1287}
14911288
1492fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {1289fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {
1493 switch (info.ExceptionRecord.ExceptionCode) {1290 if (use_trap_panic) @trap();
1494 windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, 0, "Unaligned Memory Access"),1291 const name: []const u8, const addr: ?usize = switch (info.ExceptionRecord.ExceptionCode) {
1495 windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, 1, null),1292 windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ "Unaligned memory access", null },
1496 windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, 2, null),1293 windows.EXCEPTION_ACCESS_VIOLATION => .{ "Segmentation fault", info.ExceptionRecord.ExceptionInformation[1] },
1497 windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, 0, "Stack Overflow"),1294 windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ "Illegal instruction", info.ContextRecord.getRegs().ip },
1295 windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null },
1498 else => return windows.EXCEPTION_CONTINUE_SEARCH,1296 else => return windows.EXCEPTION_CONTINUE_SEARCH,
1499 }1297 };
1298 handleSegfault(addr, name, info.ContextRecord);
1500}1299}
15011300
1502fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) noreturn {1301fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?*ThreadContext) noreturn {
1503 // For backends that cannot handle the language features used by this segfault handler, we have a simpler one,1302 // There is very similar logic to the following in `defaultPanic`.
1504 switch (builtin.zig_backend) {1303 switch (panic_stage) {
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) {
1511 0 => {1304 0 => {
1512 panic_stage = 1;1305 panic_stage = 1;
1513 _ = panicking.fetchAdd(1, .seq_cst);1306 _ = panicking.fetchAdd(1, .seq_cst);
15141307
1515 {1308 trace: {
1309 const tty_config = tty.detectConfig(.stderr());
1310
1516 const stderr = lockStderrWriter(&.{});1311 const stderr = lockStderrWriter(&.{});
1517 defer unlockStderrWriter();1312 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 }
1520 }1326 }
1521
1522 waitForOtherThreadToFinishPanicking();
1523 },1327 },
1524 1 => {1328 1 => {
1525 panic_stage = 2;1329 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().
1526 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};1333 fs.File.stderr().writeAll("aborting due to recursive panic\n") catch {};
1527 },1334 },
1528 else => {},1335 else => {}, // Panicked while printing the recursive panic message.
1529 };1336 }
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();
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();
1542}1342}
15431343
1544pub fn dumpStackPointerAddr(prefix: []const u8) void {1344pub fn dumpStackPointerAddr(prefix: []const u8) void {
...@@ -1549,26 +1349,22 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void {...@@ -1549,26 +1349,22 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void {
1549}1349}
15501350
1551test "manage resources correctly" {1351test "manage resources correctly" {
1552 if (builtin.strip_debug_info) return error.SkipZigTest;1352 if (!SelfInfo.target_supported) return error.SkipZigTest;
15531353 const S = struct {
1554 if (native_os == .wasi) return error.SkipZigTest;1354 noinline fn showMyTrace() usize {
15551355 return @returnAddress();
1556 if (native_os == .windows) {1356 }
1557 // https://github.com/ziglang/zig/issues/139631357 };
1558 return error.SkipZigTest;1358 var discarding: std.io.Writer.Discarding = .init(&.{});
1559 }1359 var di: SelfInfo = try .open(testing.allocator);
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);
1566 defer di.deinit();1360 defer di.deinit();
1567 try printSourceAtAddress(&di, &discarding.writer, showMyTrace(), tty.detectConfig(.stderr()));1361 try printSourceAtAddress(
1568}1362 testing.allocator,
15691363 &di,
1570noinline fn showMyTrace() usize {1364 &discarding.writer,
1571 return @returnAddress();1365 S.showMyTrace(),
1366 tty.detectConfig(.stderr()),
1367 );
1572}1368}
15731369
1574/// This API helps you track where a value originated and where it was mutated,1370/// 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...@@ -1615,12 +1411,11 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16151411
1616 if (t.index < size) {1412 if (t.index < size) {
1617 t.notes[t.index] = note;1413 t.notes[t.index] = note;
1618 t.addrs[t.index] = [1]usize{0} ** stack_frame_count;1414 const addrs = &t.addrs[t.index];
1619 var stack_trace: std.builtin.StackTrace = .{1415 const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs);
1620 .index = 0,1416 if (st.index < addrs.len) {
1621 .instruction_addresses = &t.addrs[t.index],1417 @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace
1622 };1418 }
1623 captureStackTrace(addr, &stack_trace);
1624 }1419 }
1625 // Keep counting even if the end is reached so that the1420 // Keep counting even if the end is reached so that the
1626 // user can find out how much more size they need.1421 // 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...@@ -1634,13 +1429,6 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1634 const stderr = lockStderrWriter(&.{});1429 const stderr = lockStderrWriter(&.{});
1635 defer unlockStderrWriter();1430 defer unlockStderrWriter();
1636 const end = @min(t.index, size);1431 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 };
1644 for (t.addrs[0..end], 0..) |frames_array, i| {1432 for (t.addrs[0..end], 0..) |frames_array, i| {
1645 stderr.print("{s}:\n", .{t.notes[i]}) catch return;1433 stderr.print("{s}:\n", .{t.notes[i]}) catch return;
1646 var frames_array_mutable = frames_array;1434 var frames_array_mutable = frames_array;
...@@ -1649,7 +1437,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -1649,7 +1437,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1649 .index = frames.len,1437 .index = frames.len,
1650 .instruction_addresses = frames,1438 .instruction_addresses = frames,
1651 };1439 };
1652 writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue;1440 writeStackTrace(stack_trace, stderr, tty_config) catch return;
1653 }1441 }
1654 if (t.index > end) {1442 if (t.index > end) {
1655 stderr.print("{d} more traces not shown; consider increasing trace size\n", .{1443 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 {...@@ -1449,7 +1449,7 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
14491449
1450pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64) !std.debug.Symbol {1450pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64) !std.debug.Symbol {
1451 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {1451 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,
1453 else => return err,1453 else => return err,
1454 };1454 };
1455 return .{1455 return .{
lib/std/debug/SelfInfo.zig+7-10
...@@ -158,7 +158,7 @@ test {...@@ -158,7 +158,7 @@ test {
158}158}
159159
160pub const UnwindContext = struct {160pub const UnwindContext = struct {
161 gpa: Allocator,161 gpa: Allocator, // MLUGG TODO: make unmanaged (also maybe rename this type, DwarfUnwindContext or smth idk)
162 cfa: ?usize,162 cfa: ?usize,
163 pc: usize,163 pc: usize,
164 thread_context: *std.debug.ThreadContext,164 thread_context: *std.debug.ThreadContext,
...@@ -166,22 +166,20 @@ pub const UnwindContext = struct {...@@ -166,22 +166,20 @@ pub const UnwindContext = struct {
166 vm: Dwarf.Unwind.VirtualMachine,166 vm: Dwarf.Unwind.VirtualMachine,
167 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),167 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 {
170 comptime assert(supports_unwinding);170 comptime assert(supports_unwinding);
171171
172 const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;172 const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
173 const pc = stripInstructionPtrAuthCode(173 const raw_pc_ptr = regValueNative(thread_context, ip_reg_num, null) catch {
174 (try regValueNative(thread_context, ip_reg_num, null)).*,174 unreachable; // error means unsupported, in which case `supports_unwinding` should have been `false`
175 );175 };
176176 const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*);
177 const context_copy = try gpa.create(std.debug.ThreadContext);
178 std.debug.copyContext(thread_context, context_copy);
179177
180 return .{178 return .{
181 .gpa = gpa,179 .gpa = gpa,
182 .cfa = null,180 .cfa = null,
183 .pc = pc,181 .pc = pc,
184 .thread_context = context_copy,182 .thread_context = thread_context,
185 .reg_context = undefined,183 .reg_context = undefined,
186 .vm = .{},184 .vm = .{},
187 .stack_machine = .{},185 .stack_machine = .{},
...@@ -191,7 +189,6 @@ pub const UnwindContext = struct {...@@ -191,7 +189,6 @@ pub const UnwindContext = struct {
191 pub fn deinit(self: *UnwindContext) void {189 pub fn deinit(self: *UnwindContext) void {
192 self.vm.deinit(self.gpa);190 self.vm.deinit(self.gpa);
193 self.stack_machine.deinit(self.gpa);191 self.stack_machine.deinit(self.gpa);
194 self.gpa.destroy(self.thread_context);
195 self.* = undefined;192 self.* = undefined;
196 }193 }
197194
lib/std/debug/SelfInfo/DarwinModule.zig+1-5
...@@ -196,11 +196,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu...@@ -196,11 +196,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
196 const full = &di.full.?;196 const full = &di.full.?;
197197
198 const vaddr = address - module.load_offset;198 const vaddr = address - module.load_offset;
199 const symbol = MachoSymbol.find(full.symbols, vaddr) orelse return .{199 const symbol = MachoSymbol.find(full.symbols, vaddr) orelse return .unknown;
200 .name = null,
201 .compile_unit_name = null,
202 .source_location = null,
203 };
204200
205 // offset of `address` from start of `symbol`201 // offset of `address` from start of `symbol`
206 const address_symbol_offset = vaddr - symbol.addr;202 const address_symbol_offset = vaddr - symbol.addr;