authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-13 18:26:53+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-13 18:26:53+02:00
logc457939f104595d675974f9e820ccf4187bf8e95
treeea45f4d47c50966b57980e6cca5ed35eb47f781b
parent778f8d557bc2ab59c290e145e9ad87e36d7de220
parent6707a5efeea1ab973c3274495bb0a5640e4f568b

Merge pull request 'Parses inline callers when generating stack traces from PDBs' (#31814) from MasonRemaley/zig:pdb-backtrace-inlines into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31814 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

22 files changed, 1386 insertions(+), 263 deletions(-)

doc/langref.html.in+1-1
...@@ -3263,7 +3263,7 @@ fn createFoo(param: i32) !Foo {...@@ -3263,7 +3263,7 @@ fn createFoo(param: i32) !Foo {
3263 <ul>3263 <ul>
3264 <li>Return an error from main</li>3264 <li>Return an error from main</li>
3265 <li>An error makes its way to {#syntax#}catch unreachable{#endsyntax#} and you have not overridden the default panic handler</li>3265 <li>An error makes its way to {#syntax#}catch unreachable{#endsyntax#} and you have not overridden the default panic handler</li>
3266 <li>Use {#link|errorReturnTrace#} to access the current return trace. You can use {#syntax#}std.debug.dumpStackTrace{#endsyntax#} to print it. This function returns comptime-known {#link|null#} when building without error return tracing support.</li>3266 <li>Use {#link|errorReturnTrace#} to access the current return trace. You can use {#syntax#}std.debug.dumpErrorReturnTrace{#endsyntax#} to print it. This function returns comptime-known {#link|null#} when building without error return tracing support.</li>
3267 </ul>3267 </ul>
3268 {#header_open|Implementation Details#}3268 {#header_open|Implementation Details#}
3269 <p>3269 <p>
lib/compiler/test_runner.zig+3-3
...@@ -144,7 +144,7 @@ fn mainServer(init: std.process.Init.Minimal) !void {...@@ -144,7 +144,7 @@ fn mainServer(init: std.process.Init.Minimal) !void {
144 error.SkipZigTest => .skip,144 error.SkipZigTest => .skip,
145 else => s: {145 else => s: {
146 if (@errorReturnTrace()) |trace| {146 if (@errorReturnTrace()) |trace| {
147 std.debug.dumpStackTrace(trace);147 std.debug.dumpErrorReturnTrace(trace);
148 }148 }
149 break :s .fail;149 break :s .fail;
150 },150 },
...@@ -312,7 +312,7 @@ fn mainTerminal(init: std.process.Init.Minimal) void {...@@ -312,7 +312,7 @@ fn mainTerminal(init: std.process.Init.Minimal) void {
312 std.debug.print("FAIL ({t})\n", .{err});312 std.debug.print("FAIL ({t})\n", .{err});
313 }313 }
314 if (@errorReturnTrace()) |trace| {314 if (@errorReturnTrace()) |trace| {
315 std.debug.dumpStackTrace(trace);315 std.debug.dumpErrorReturnTrace(trace);
316 }316 }
317 test_node.end();317 test_node.end();
318 },318 },
...@@ -438,7 +438,7 @@ var fuzz_runner: if (builtin.fuzz) struct {...@@ -438,7 +438,7 @@ var fuzz_runner: if (builtin.fuzz) struct {
438 error.SkipZigTest => return,438 error.SkipZigTest => return,
439 else => {439 else => {
440 if (@errorReturnTrace()) |trace| {440 if (@errorReturnTrace()) |trace| {
441 std.debug.dumpStackTrace(trace);441 std.debug.dumpErrorReturnTrace(trace);
442 }442 }
443 std.debug.print("failed with error.{t}\n", .{err});443 std.debug.print("failed with error.{t}\n", .{err});
444 std.process.exit(1);444 std.process.exit(1);
lib/std/Build/Step.zig+2-2
...@@ -67,7 +67,7 @@ test_results: TestResults,...@@ -67,7 +67,7 @@ test_results: TestResults,
6767
68/// The return address associated with creation of this step that can be useful68/// The return address associated with creation of this step that can be useful
69/// to print along with debugging messages.69/// to print along with debugging messages.
70debug_stack_trace: std.builtin.StackTrace,70debug_stack_trace: std.debug.StackTrace,
7171
72pub const TestResults = struct {72pub const TestResults = struct {
73 /// The total number of tests in the step. Every test has a "status" from the following:73 /// The total number of tests in the step. Every test has a "status" from the following:
...@@ -328,7 +328,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {...@@ -328,7 +328,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
328/// For debugging purposes, prints identifying information about this Step.328/// For debugging purposes, prints identifying information about this Step.
329pub fn dump(step: *Step, t: Io.Terminal) void {329pub fn dump(step: *Step, t: Io.Terminal) void {
330 const w = t.writer;330 const w = t.writer;
331 if (step.debug_stack_trace.instruction_addresses.len > 0) {331 if (step.debug_stack_trace.return_addresses.len > 0) {
332 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};332 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
333 std.debug.writeStackTrace(&step.debug_stack_trace, t) catch {};333 std.debug.writeStackTrace(&step.debug_stack_trace, t) catch {};
334 } else {334 } else {
lib/std/Thread.zig+2-2
...@@ -442,7 +442,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {...@@ -442,7 +442,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
442 @call(.auto, f, args) catch |err| {442 @call(.auto, f, args) catch |err| {
443 std.debug.print("error: {s}\n", .{@errorName(err)});443 std.debug.print("error: {s}\n", .{@errorName(err)});
444 if (@errorReturnTrace()) |trace| {444 if (@errorReturnTrace()) |trace| {
445 std.debug.dumpStackTrace(trace);445 std.debug.dumpErrorReturnTrace(trace);
446 }446 }
447 };447 };
448448
...@@ -932,7 +932,7 @@ const WasiThreadImpl = struct {...@@ -932,7 +932,7 @@ const WasiThreadImpl = struct {
932 @call(.auto, f, w.args) catch |err| {932 @call(.auto, f, w.args) catch |err| {
933 std.debug.print("error: {s}\n", .{@errorName(err)});933 std.debug.print("error: {s}\n", .{@errorName(err)});
934 if (@errorReturnTrace()) |trace| {934 if (@errorReturnTrace()) |trace| {
935 std.debug.dumpStackTrace(trace);935 std.debug.dumpErrorReturnTrace(trace);
936 }936 }
937 };937 };
938 },938 },
lib/std/debug.zig+179-65
...@@ -13,7 +13,6 @@ const windows = std.os.windows;...@@ -13,7 +13,6 @@ const windows = std.os.windows;
13const builtin = @import("builtin");13const builtin = @import("builtin");
14const native_arch = builtin.cpu.arch;14const native_arch = builtin.cpu.arch;
15const native_os = builtin.os.tag;15const native_os = builtin.os.tag;
16const StackTrace = std.builtin.StackTrace;
1716
18const root = @import("root");17const root = @import("root");
1918
...@@ -39,8 +38,8 @@ pub const cpu_context = @import("debug/cpu_context.zig");...@@ -39,8 +38,8 @@ pub const cpu_context = @import("debug/cpu_context.zig");
39/// pub const init: SelfInfo;38/// pub const init: SelfInfo;
40/// pub fn deinit(si: *SelfInfo, io: Io) void;39/// pub fn deinit(si: *SelfInfo, io: Io) void;
41///40///
42/// /// Returns the symbol and source location of the instruction at `address`.41/// /// Appends the symbols for the instruction at `address` to `symbols`.
43/// pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) SelfInfoError!Symbol;42/// pub fn getSymbols(si: *SelfInfo, io: Io, symbol_allocator: Allocator, text_arena: Allocator, address: usize, include_inline_callers: bool, symbols: *std.ArrayList(Symbol)) SelfInfoError!void;
44/// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`.43/// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`.
45/// pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) SelfInfoError![]const u8;44/// pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) SelfInfoError![]const u8;
46/// pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) SelfInfoError!usize;45/// pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) SelfInfoError!usize;
...@@ -563,7 +562,7 @@ pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {...@@ -563,7 +562,7 @@ pub fn defaultPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
563562
564 if (@errorReturnTrace()) |t| if (t.index > 0) {563 if (@errorReturnTrace()) |t| if (t.index > 0) {
565 writer.writeAll("error return context:\n") catch break :trace;564 writer.writeAll("error return context:\n") catch break :trace;
566 writeStackTrace(t, stderr) catch break :trace;565 writeErrorReturnTrace(t, stderr) catch break :trace;
567 writer.writeAll("\nstack trace:\n") catch break :trace;566 writer.writeAll("\nstack trace:\n") catch break :trace;
568 };567 };
569 writeCurrentStackTrace(.{568 writeCurrentStackTrace(.{
...@@ -602,6 +601,35 @@ fn waitForOtherThreadToFinishPanicking() void {...@@ -602,6 +601,35 @@ fn waitForOtherThreadToFinishPanicking() void {
602 }601 }
603}602}
604603
604pub const StackTrace = struct {
605 /// Each element is the "return address" of a function call, meaning the instruction address
606 /// which control flow will return to when the function returns.
607 ///
608 /// The first slice element corresponds to the innermost stack frame, and the last element to
609 /// the outermost.
610 ///
611 /// Inlined function calls do not have meaningful return addresses and are therefore not
612 /// included in this slice. Instead, when printing the stack trace, the source locations of
613 /// inline calls should be read from debug information and the corresponding "inline frames"
614 /// printed in the appropriate locations.
615 return_addresses: []usize,
616 /// Indicates whether any stack frames were omitted from `return_addresses`.
617 skipped: SkippedAddresses,
618};
619
620/// Indicates how many addresses were skipped in a trace.
621pub const SkippedAddresses = enum(usize) {
622 /// No addresses were omitted: `return_addresses` contains all stack frames, including the
623 /// outermost.
624 none = 0,
625 /// It is not known whether any frames were omitted.
626 unknown = std.math.maxInt(usize),
627 /// The full stack trace was available, but some frames are not included in
628 /// `return_addresses` due to buffer size limitations. The enum value is the exact number of
629 /// addresses which were omitted.
630 _,
631};
632
605pub const StackUnwindOptions = struct {633pub const StackUnwindOptions = struct {
606 /// If not `null`, we will ignore all frames up until this return address. This is typically634 /// If not `null`, we will ignore all frames up until this return address. This is typically
607 /// used to omit intermediate handling code (for instance, a panic handler and its machinery)635 /// used to omit intermediate handling code (for instance, a panic handler and its machinery)
...@@ -621,7 +649,10 @@ pub const StackUnwindOptions = struct {...@@ -621,7 +649,10 @@ pub const StackUnwindOptions = struct {
621///649///
622/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.650/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
623pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace {651pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace {
624 const empty_trace: StackTrace = .{ .index = 0, .instruction_addresses = &.{} };652 const empty_trace: StackTrace = .{
653 .return_addresses = &.{},
654 .skipped = .none,
655 };
625 if (!std.options.allow_stack_tracing) return empty_trace;656 if (!std.options.allow_stack_tracing) return empty_trace;
626 var it: StackIterator = .init(options.context);657 var it: StackIterator = .init(options.context);
627 defer it.deinit();658 defer it.deinit();
...@@ -632,17 +663,17 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -632,17 +663,17 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
632 var total_frames: usize = 0;663 var total_frames: usize = 0;
633 var index: usize = 0;664 var index: usize = 0;
634 var wait_for = options.first_address;665 var wait_for = options.first_address;
635 // Ideally, we would iterate the whole stack so that the `index` in the returned trace was666 // Ideally, we would iterate the whole stack so that the `index - min(buf.len, index)` would be
636 // indicative of how many frames were skipped. However, this has a significant runtime cost667 // indicative of how many frames were skipped. However, this has a significant runtime cost
637 // in some cases, so at least for now, we don't do that.668 // in some cases, so at least for now, we don't do that.
638 while (index < addr_buf.len) switch (it.next(io)) {669 const skipped: SkippedAddresses = while (index < addr_buf.len) switch (it.next(io)) {
639 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,670 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break .unknown,
640 .end => break,671 .end => break .none,
641 .frame => |ret_addr| {672 .frame => |ret_addr| {
642 if (total_frames > 10_000) {673 if (total_frames > 10_000) {
643 // Limit the number of frames in case of (e.g.) broken debug information which is674 // Limit the number of frames in case of (e.g.) broken debug information which is
644 // getting unwinding stuck in a loop.675 // getting unwinding stuck in a loop.
645 break;676 break .unknown;
646 }677 }
647 total_frames += 1;678 total_frames += 1;
648 if (wait_for) |target| {679 if (wait_for) |target| {
...@@ -652,10 +683,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -652,10 +683,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
652 addr_buf[index] = ret_addr;683 addr_buf[index] = ret_addr;
653 index += 1;684 index += 1;
654 },685 },
655 };686 } else .unknown;
656 return .{687 return .{
657 .index = index,688 .return_addresses = addr_buf[0..index],
658 .instruction_addresses = addr_buf[0..index],689 .skipped = skipped,
659 };690 };
660}691}
661/// Write the current stack trace to `writer`, annotated with source locations.692/// Write the current stack trace to `writer`, annotated with source locations.
...@@ -663,6 +694,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -663,6 +694,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
663/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.694/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
664pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {695pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {
665 const writer = t.writer;696 const writer = t.writer;
697
698 var text_arena: std.heap.ArenaAllocator = .init(getDebugInfoAllocator());
699 defer text_arena.deinit();
700
666 if (!std.options.allow_stack_tracing) {701 if (!std.options.allow_stack_tracing) {
667 t.setColor(.dim) catch {};702 t.setColor(.dim) catch {};
668 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});703 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
...@@ -740,7 +775,10 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin...@@ -740,7 +775,10 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin
740 }775 }
741 // `ret_addr` is the return address, which is *after* the function call.776 // `ret_addr` is the return address, which is *after* the function call.
742 // Subtract 1 to get an address *in* the function call for a better source location.777 // Subtract 1 to get an address *in* the function call for a better source location.
743 try printSourceAtAddress(io, di, t, ret_addr -| StackIterator.ra_call_offset);778 try printSourceAtAddress(io, &text_arena, di, t, .{
779 .address = ret_addr -| StackIterator.ra_call_offset,
780 .resolve_inline_callers = true,
781 });
744 printed_any_frame = true;782 printed_any_frame = true;
745 },783 },
746 };784 };
...@@ -773,8 +811,29 @@ pub const FormatStackTrace = struct {...@@ -773,8 +811,29 @@ pub const FormatStackTrace = struct {
773 }811 }
774};812};
775813
814/// Write a previously captured error return trace to `writer`, annotated with source locations.
815pub fn writeErrorReturnTrace(et: *const std.builtin.StackTrace, t: Io.Terminal) Writer.Error!void {
816 // We take the slice by value, preventing the length from being mutated if an error occurs while
817 // writing the stack trace.
818 const len = @min(et.instruction_addresses.len, et.index);
819 const skipped = et.index - len;
820 try writeTrace(et.instruction_addresses[0..len], @enumFromInt(skipped), t, false);
821}
822
776/// Write a previously captured stack trace to `writer`, annotated with source locations.823/// Write a previously captured stack trace to `writer`, annotated with source locations.
777pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void {824pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void {
825 try writeTrace(st.return_addresses, st.skipped, t, true);
826}
827
828fn writeTrace(
829 addresses: []const usize,
830 skipped: SkippedAddresses,
831 t: Io.Terminal,
832 resolve_inline_callers: bool,
833) Writer.Error!void {
834 var text_arena: std.heap.ArenaAllocator = .init(getDebugInfoAllocator());
835 defer text_arena.deinit();
836
778 const writer = t.writer;837 const writer = t.writer;
779 if (!std.options.allow_stack_tracing) {838 if (!std.options.allow_stack_tracing) {
780 t.setColor(.dim) catch {};839 t.setColor(.dim) catch {};
...@@ -783,10 +842,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void...@@ -783,10 +842,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void
783 return;842 return;
784 }843 }
785844
786 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if845 if (addresses.len == 0) return writer.writeAll("(empty stack trace)\n");
787 // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace.
788 const n_frames = st.index;
789 if (n_frames == 0) return writer.writeAll("(empty stack trace)\n");
790 const di = getSelfDebugInfo() catch |err| switch (err) {846 const di = getSelfDebugInfo() catch |err| switch (err) {
791 error.UnsupportedTarget => {847 error.UnsupportedTarget => {
792 t.setColor(.dim) catch {};848 t.setColor(.dim) catch {};
...@@ -796,16 +852,26 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void...@@ -796,16 +852,26 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void
796 },852 },
797 };853 };
798 const io = std.Options.debug_io;854 const io = std.Options.debug_io;
799 const captured_frames = @min(n_frames, st.instruction_addresses.len);855 for (addresses) |addr| {
800 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {856 // `addr` is the return address, which is *after* the function call.
801 // `ret_addr` is the return address, which is *after* the function call.
802 // Subtract 1 to get an address *in* the function call for a better source location.857 // Subtract 1 to get an address *in* the function call for a better source location.
803 try printSourceAtAddress(io, di, t, ret_addr -| StackIterator.ra_call_offset);858 try printSourceAtAddress(io, &text_arena, di, t, .{
859 .address = addr -| StackIterator.ra_call_offset,
860 .resolve_inline_callers = resolve_inline_callers,
861 });
804 }862 }
805 if (n_frames > captured_frames) {863 switch (skipped) {
806 t.setColor(.bold) catch {};864 .none => {},
807 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});865 .unknown => {
808 t.setColor(.reset) catch {};866 t.setColor(.bold) catch {};
867 try writer.writeAll("(additional stack frames may have been skipped...)\n");
868 t.setColor(.reset) catch {};
869 },
870 else => |n| {
871 t.setColor(.bold) catch {};
872 try writer.print("({d} additional stack frames skipped due to buffer size limitations...)\n", .{n});
873 t.setColor(.reset) catch {};
874 },
809 }875 }
810}876}
811/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.877/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
...@@ -817,6 +883,15 @@ pub fn dumpStackTrace(st: *const StackTrace) void {...@@ -817,6 +883,15 @@ pub fn dumpStackTrace(st: *const StackTrace) void {
817 };883 };
818}884}
819885
886/// A thin wrapper around `writeErrorReturnTrace` which writes to stderr and ignores write errors.
887pub fn dumpErrorReturnTrace(et: *const std.builtin.StackTrace) void {
888 const stderr = lockStderr(&.{}).terminal();
889 defer unlockStderr();
890 writeErrorReturnTrace(et, stderr) catch |err| switch (err) {
891 error.WriteFailed => {},
892 };
893}
894
820const StackIterator = union(enum) {895const StackIterator = union(enum) {
821 /// We will first report the current PC of this `CpuContextPtr`, then we will switch to a896 /// We will first report the current PC of this `CpuContextPtr`, then we will switch to a
822 /// different strategy to actually unwind.897 /// different strategy to actually unwind.
...@@ -1106,48 +1181,77 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {...@@ -1106,48 +1181,77 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
1106 return ptr;1181 return ptr;
1107}1182}
11081183
1109fn printSourceAtAddress(io: Io, debug_info: *SelfInfo, t: Io.Terminal, address: usize) Writer.Error!void {1184const PrintSourceAddressOptions = struct {
1110 const symbol: Symbol = debug_info.getSymbol(io, address) catch |err| switch (err) {1185 address: usize,
1111 error.MissingDebugInfo,1186 resolve_inline_callers: bool,
1112 error.UnsupportedDebugInfo,1187};
1113 error.InvalidDebugInfo,1188
1114 => .unknown,1189fn printSourceAtAddress(
1115 error.ReadFailed, error.Unexpected, error.Canceled => s: {1190 io: Io,
1116 t.setColor(.dim) catch {};1191 text_arena: *std.heap.ArenaAllocator,
1117 try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});1192 debug_info: *SelfInfo,
1118 t.setColor(.reset) catch {};1193 t: Io.Terminal,
1119 break :s .unknown;1194 options: PrintSourceAddressOptions,
1120 },1195) Writer.Error!void {
1121 error.OutOfMemory => s: {1196 defer _ = text_arena.reset(.retain_capacity);
1122 t.setColor(.dim) catch {};1197
1123 try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});1198 // Initialize the symbol array with space for at least one element, allocating this on the stack
1124 t.setColor(.reset) catch {};1199 // in the common case where only one element is needed
1125 break :s .unknown;1200 var symbol_fallback_allocator = std.heap.stackFallback(@sizeOf(Symbol) + @alignOf(Symbol) - 1, getDebugInfoAllocator());
1126 },1201 const symbol_allocator = symbol_fallback_allocator.get();
1127 };1202 var symbols = std.ArrayList(Symbol).initCapacity(symbol_allocator, 1) catch unreachable;
1128 defer if (symbol.source_location) |sl| getDebugInfoAllocator().free(sl.file_name);1203 defer symbols.deinit(symbol_allocator);
1129 return printLineInfo(1204
1205 debug_info.getSymbols(
1130 io,1206 io,
1131 t,1207 symbol_allocator,
1132 symbol.source_location,1208 text_arena.allocator(),
1133 address,1209 options.address,
1134 symbol.name orelse "???",1210 options.resolve_inline_callers,
1135 symbol.compile_unit_name orelse debug_info.getModuleName(io, address) catch "???",1211 &symbols,
1136 );1212 ) catch |err| {
1213 t.setColor(.dim) catch {};
1214 defer t.setColor(.reset) catch {};
1215 switch (err) {
1216 error.MissingDebugInfo,
1217 error.UnsupportedDebugInfo,
1218 error.InvalidDebugInfo,
1219 => {},
1220 error.ReadFailed, error.Unexpected, error.Canceled => {
1221 try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1222 },
1223 error.OutOfMemory => {
1224 t.setColor(.dim) catch {};
1225 try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1226 t.setColor(.reset) catch {};
1227 },
1228 }
1229 };
1230
1231 // If we failed to write any symbols, at least write the unknown symbol. Can't fail since we
1232 // initialized with a capacity of 1.
1233 if (symbols.items.len == 0) symbols.appendAssumeCapacity(.unknown);
1234
1235 for (symbols.items) |symbol| {
1236 try printLineInfo(io, t, debug_info, options.address, symbol);
1237 }
1137}1238}
1138fn printLineInfo(1239fn printLineInfo(
1139 io: Io,1240 io: Io,
1140 t: Io.Terminal,1241 t: Io.Terminal,
1141 source_location: ?SourceLocation,1242 debug_info: *SelfInfo,
1142 address: usize,1243 address: usize,
1143 symbol_name: []const u8,1244 symbol: Symbol,
1144 compile_unit_name: []const u8,
1145) Writer.Error!void {1245) Writer.Error!void {
1146 const writer = t.writer;1246 const writer = t.writer;
1147 t.setColor(.bold) catch {};1247 t.setColor(.bold) catch {};
11481248
1149 if (source_location) |*sl| {1249 if (symbol.source_location) |*sl| {
1150 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });1250 if (sl.column == 0) {
1251 try writer.print("{s}:{d}", .{ sl.file_name, sl.line });
1252 } else {
1253 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
1254 }
1151 } else {1255 } else {
1152 try writer.writeAll("???:?:?");1256 try writer.writeAll("???:?:?");
1153 }1257 }
...@@ -1155,12 +1259,16 @@ fn printLineInfo(...@@ -1155,12 +1259,16 @@ fn printLineInfo(
1155 t.setColor(.reset) catch {};1259 t.setColor(.reset) catch {};
1156 try writer.writeAll(": ");1260 try writer.writeAll(": ");
1157 t.setColor(.dim) catch {};1261 t.setColor(.dim) catch {};
1158 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });1262 try writer.print("0x{x} in {s} ({s})", .{
1263 address,
1264 symbol.name orelse "???",
1265 symbol.compile_unit_name orelse debug_info.getModuleName(io, address) catch "???",
1266 });
1159 t.setColor(.reset) catch {};1267 t.setColor(.reset) catch {};
1160 try writer.writeAll("\n");1268 try writer.writeAll("\n");
11611269
1162 // Show the matching source code line if possible1270 // Show the matching source code line if possible
1163 if (source_location) |sl| {1271 if (symbol.source_location) |sl| {
1164 if (printLineFromFile(io, writer, sl)) {1272 if (printLineFromFile(io, writer, sl)) {
1165 if (sl.column > 0) {1273 if (sl.column > 0) {
1166 // The caret already takes one char1274 // The caret already takes one char
...@@ -1599,7 +1707,12 @@ test "manage resources correctly" {...@@ -1599,7 +1707,12 @@ test "manage resources correctly" {
1599 var di: SelfInfo = .init;1707 var di: SelfInfo = .init;
1600 defer di.deinit(io);1708 defer di.deinit(io);
1601 const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color };1709 const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color };
1602 try printSourceAtAddress(io, &di, t, S.showMyTrace());1710 var text_arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
1711 defer text_arena.deinit();
1712 try printSourceAtAddress(io, &text_arena, &di, t, .{
1713 .address = S.showMyTrace(),
1714 .resolve_inline_callers = true,
1715 });
1603}1716}
16041717
1605/// This API helps you track where a value originated and where it was mutated,1718/// This API helps you track where a value originated and where it was mutated,
...@@ -1648,8 +1761,8 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -1648,8 +1761,8 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1648 t.notes[t.index] = note;1761 t.notes[t.index] = note;
1649 const addrs = &t.addrs[t.index];1762 const addrs = &t.addrs[t.index];
1650 const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs);1763 const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs);
1651 if (st.index < addrs.len) {1764 if (st.return_addresses.len < addrs.len) {
1652 @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace1765 @memset(addrs[st.return_addresses.len..], 0); // zero unused frames to indicate end of trace
1653 }1766 }
1654 }1767 }
1655 // Keep counting even if the end is reached so that the1768 // Keep counting even if the end is reached so that the
...@@ -1667,9 +1780,10 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -1667,9 +1780,10 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1667 stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return;1780 stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return;
1668 var frames_array_mutable = frames_array;1781 var frames_array_mutable = frames_array;
1669 const frames = mem.sliceTo(frames_array_mutable[0..], 0);1782 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
1783 const len = @min(t.index, frames.len);
1670 const stack_trace: StackTrace = .{1784 const stack_trace: StackTrace = .{
1671 .index = frames.len,1785 .return_addresses = frames[0..len],
1672 .instruction_addresses = frames,1786 .skipped = if (len < frames.len) .none else .unknown,
1673 };1787 };
1674 writeStackTrace(&stack_trace, stderr) catch return;1788 writeStackTrace(&stack_trace, stderr) catch return;
1675 }1789 }
lib/std/debug/Dwarf.zig+29-9
...@@ -22,7 +22,9 @@ const cast = std.math.cast;...@@ -22,7 +22,9 @@ const cast = std.math.cast;
22const maxInt = std.math.maxInt;22const maxInt = std.math.maxInt;
23const ArrayList = std.ArrayList;23const ArrayList = std.ArrayList;
24const Endian = std.builtin.Endian;24const Endian = std.builtin.Endian;
25const Reader = std.Io.Reader;25const Io = std.Io;
26const Reader = Io.Reader;
27const Error = std.debug.SelfInfoError;
2628
27const Dwarf = @This();29const Dwarf = @This();
2830
...@@ -1218,6 +1220,7 @@ pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, endian: Endian, cu: *Compi...@@ -1218,6 +1220,7 @@ pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, endian: Endian, cu: *Compi
1218pub fn getLineNumberInfo(1220pub fn getLineNumberInfo(
1219 d: *Dwarf,1221 d: *Dwarf,
1220 gpa: Allocator,1222 gpa: Allocator,
1223 text_arena: Allocator,
1221 endian: Endian,1224 endian: Endian,
1222 compile_unit: *CompileUnit,1225 compile_unit: *CompileUnit,
1223 target_address: u64,1226 target_address: u64,
...@@ -1230,7 +1233,7 @@ pub fn getLineNumberInfo(...@@ -1230,7 +1233,7 @@ pub fn getLineNumberInfo(
1230 const file_entry = &slc.files[file_index];1233 const file_entry = &slc.files[file_index];
1231 if (file_entry.dir_index >= slc.directories.len) return bad();1234 if (file_entry.dir_index >= slc.directories.len) return bad();
1232 const dir_name = slc.directories[file_entry.dir_index].path;1235 const dir_name = slc.directories[file_entry.dir_index].path;
1233 const file_name = try std.fs.path.join(gpa, &.{ dir_name, file_entry.path });1236 const file_name = try std.fs.path.join(text_arena, &.{ dir_name, file_entry.path });
1234 return .{1237 return .{
1235 .line = entry.line,1238 .line = entry.line,
1236 .column = entry.column,1239 .column = entry.column,
...@@ -1543,21 +1546,38 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {...@@ -1543,21 +1546,38 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
1543 return str[casted_offset..last :0];1546 return str[casted_offset..last :0];
1544}1547}
15451548
1546pub fn getSymbol(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) !std.debug.Symbol {1549pub fn getSymbols(
1550 di: *Dwarf,
1551 symbol_allocator: Allocator,
1552 text_arena: Allocator,
1553 endian: Endian,
1554 address: u64,
1555 resolve_inline_callers: bool,
1556 symbols: *std.ArrayList(std.debug.Symbol),
1557) std.debug.SelfInfoError!void {
1558 _ = resolve_inline_callers;
1559 const gpa = std.debug.getDebugInfoAllocator();
1560
1547 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {1561 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
1548 error.MissingDebugInfo, error.InvalidDebugInfo => return .unknown,1562 error.EndOfStream => return error.MissingDebugInfo,
1549 else => return err,1563 error.Overflow => return error.InvalidDebugInfo,
1564 error.ReadFailed, error.InvalidDebugInfo, error.MissingDebugInfo => |e| return e,
1550 };1565 };
1551 return .{1566 try symbols.append(symbol_allocator, .{
1552 .name = di.getSymbolName(address),1567 .name = di.getSymbolName(address),
1553 .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) {1568 .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) {
1554 error.MissingDebugInfo, error.InvalidDebugInfo => null,1569 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1555 },1570 },
1556 .source_location = di.getLineNumberInfo(gpa, endian, compile_unit, address) catch |err| switch (err) {1571 .source_location = di.getLineNumberInfo(gpa, text_arena, endian, compile_unit, address) catch |err| switch (err) {
1557 error.MissingDebugInfo, error.InvalidDebugInfo => null,1572 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1558 else => return err,1573 error.ReadFailed,
1574 error.EndOfStream,
1575 error.Overflow,
1576 error.StreamTooLong,
1577 => return error.InvalidDebugInfo,
1578 else => |e| return e,
1559 },1579 },
1560 };1580 });
1561}1581}
15621582
1563/// DWARF5 7.4: "In the 32-bit DWARF format, all values that represent lengths of DWARF sections and1583/// DWARF5 7.4: "In the 32-bit DWARF format, all values that represent lengths of DWARF sections and
lib/std/debug/Pdb.zig+539-36
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const File = std.Io.File;2const Io = std.Io;
3const File = Io.File;
3const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
4const pdb = std.pdb;5const pdb = std.pdb;
5const assert = std.debug.assert;6const assert = std.debug.assert;
...@@ -10,7 +11,7 @@ file_reader: *File.Reader,...@@ -10,7 +11,7 @@ file_reader: *File.Reader,
10msf: Msf,11msf: Msf,
11allocator: Allocator,12allocator: Allocator,
12string_table: ?*MsfStream,13string_table: ?*MsfStream,
13dbi: ?*MsfStream,14ipi: ?[]u8,
14modules: []Module,15modules: []Module,
15sect_contribs: []pdb.SectionContribEntry,16sect_contribs: []pdb.SectionContribEntry,
16guid: [16]u8,17guid: [16]u8,
...@@ -25,6 +26,10 @@ pub const Module = struct {...@@ -25,6 +26,10 @@ pub const Module = struct {
25 symbols: []u8,26 symbols: []u8,
26 subsect_info: []u8,27 subsect_info: []u8,
27 checksum_offset: ?usize,28 checksum_offset: ?usize,
29 /// The inlinee source lines, sorted by inlinee. This saves us from repeatedly doing linear
30 /// searches over all inlinees. We prefer binary search over a hashmap as LLVM somtimes outputs
31 /// multiple entries for a single inlinee ID, see `getInlineeSourceLines` for more info.
32 inlinee_source_lines: []InlineeSourceLine,
2833
29 pub fn deinit(self: *Module, allocator: Allocator) void {34 pub fn deinit(self: *Module, allocator: Allocator) void {
30 allocator.free(self.module_name);35 allocator.free(self.module_name);
...@@ -32,6 +37,7 @@ pub const Module = struct {...@@ -32,6 +37,7 @@ pub const Module = struct {
32 if (self.populated) {37 if (self.populated) {
33 allocator.free(self.symbols);38 allocator.free(self.symbols);
34 allocator.free(self.subsect_info);39 allocator.free(self.subsect_info);
40 allocator.free(self.inlinee_source_lines);
35 }41 }
36 }42 }
37};43};
...@@ -41,7 +47,7 @@ pub fn init(gpa: Allocator, file_reader: *File.Reader) !Pdb {...@@ -41,7 +47,7 @@ pub fn init(gpa: Allocator, file_reader: *File.Reader) !Pdb {
41 .file_reader = file_reader,47 .file_reader = file_reader,
42 .allocator = gpa,48 .allocator = gpa,
43 .string_table = null,49 .string_table = null,
44 .dbi = null,50 .ipi = null,
45 .msf = try Msf.init(gpa, file_reader),51 .msf = try Msf.init(gpa, file_reader),
46 .modules = &.{},52 .modules = &.{},
47 .sect_contribs = &.{},53 .sect_contribs = &.{},
...@@ -53,6 +59,7 @@ pub fn init(gpa: Allocator, file_reader: *File.Reader) !Pdb {...@@ -53,6 +59,7 @@ pub fn init(gpa: Allocator, file_reader: *File.Reader) !Pdb {
53pub fn deinit(self: *Pdb) void {59pub fn deinit(self: *Pdb) void {
54 const gpa = self.allocator;60 const gpa = self.allocator;
55 self.msf.deinit(gpa);61 self.msf.deinit(gpa);
62 if (self.ipi) |ipi| gpa.free(ipi);
56 for (self.modules) |*module| {63 for (self.modules) |*module| {
57 module.deinit(gpa);64 module.deinit(gpa);
58 }65 }
...@@ -67,7 +74,7 @@ pub fn parseDbiStream(self: *Pdb) !void {...@@ -67,7 +74,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
67 const gpa = self.allocator;74 const gpa = self.allocator;
68 const reader = &stream.interface;75 const reader = &stream.interface;
6976
70 const header = try reader.takeStruct(std.pdb.DbiStreamHeader, .little);77 const header = try reader.takeStruct(pdb.DbiStreamHeader, .little);
71 if (header.version_header != 19990903) // V70, only value observed by LLVM team78 if (header.version_header != 19990903) // V70, only value observed by LLVM team
72 return error.UnknownPDBVersion;79 return error.UnknownPDBVersion;
73 // if (header.Age != age)80 // if (header.Age != age)
...@@ -85,14 +92,14 @@ pub fn parseDbiStream(self: *Pdb) !void {...@@ -85,14 +92,14 @@ pub fn parseDbiStream(self: *Pdb) !void {
85 const mod_info = try reader.takeStruct(pdb.ModInfo, .little);92 const mod_info = try reader.takeStruct(pdb.ModInfo, .little);
86 var this_record_len: usize = @sizeOf(pdb.ModInfo);93 var this_record_len: usize = @sizeOf(pdb.ModInfo);
8794
88 var module_name: std.Io.Writer.Allocating = .init(gpa);95 var module_name: Io.Writer.Allocating = .init(gpa);
89 defer module_name.deinit();96 defer module_name.deinit();
90 this_record_len += try reader.streamDelimiterLimit(&module_name.writer, 0, .limited(1024));97 this_record_len += try reader.streamDelimiterLimit(&module_name.writer, 0, .limited(1024));
91 assert(reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API98 assert(reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
92 reader.toss(1);99 reader.toss(1);
93 this_record_len += 1;100 this_record_len += 1;
94101
95 var obj_file_name: std.Io.Writer.Allocating = .init(gpa);102 var obj_file_name: Io.Writer.Allocating = .init(gpa);
96 defer obj_file_name.deinit();103 defer obj_file_name.deinit();
97 this_record_len += try reader.streamDelimiterLimit(&obj_file_name.writer, 0, .limited(1024));104 this_record_len += try reader.streamDelimiterLimit(&obj_file_name.writer, 0, .limited(1024));
98 assert(reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API105 assert(reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
...@@ -115,6 +122,7 @@ pub fn parseDbiStream(self: *Pdb) !void {...@@ -115,6 +122,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
115 .symbols = undefined,122 .symbols = undefined,
116 .subsect_info = undefined,123 .subsect_info = undefined,
117 .checksum_offset = null,124 .checksum_offset = null,
125 .inlinee_source_lines = undefined,
118 });126 });
119127
120 mod_info_offset += this_record_len;128 mod_info_offset += this_record_len;
...@@ -128,7 +136,7 @@ pub fn parseDbiStream(self: *Pdb) !void {...@@ -128,7 +136,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
128136
129 var sect_cont_offset: usize = 0;137 var sect_cont_offset: usize = 0;
130 if (section_contrib_size != 0) {138 if (section_contrib_size != 0) {
131 const version = reader.takeEnum(std.pdb.SectionContrSubstreamVersion, .little) catch |err| switch (err) {139 const version = reader.takeEnum(pdb.SectionContrSubstreamVersion, .little) catch |err| switch (err) {
132 error.InvalidEnumTag, error.EndOfStream => return error.InvalidDebugInfo,140 error.InvalidEnumTag, error.EndOfStream => return error.InvalidDebugInfo,
133 error.ReadFailed => return error.ReadFailed,141 error.ReadFailed => return error.ReadFailed,
134 };142 };
...@@ -148,6 +156,15 @@ pub fn parseDbiStream(self: *Pdb) !void {...@@ -148,6 +156,15 @@ pub fn parseDbiStream(self: *Pdb) !void {
148 self.sect_contribs = try sect_contribs.toOwnedSlice();156 self.sect_contribs = try sect_contribs.toOwnedSlice();
149}157}
150158
159pub fn parseIpiStream(self: *Pdb) !void {
160 const gpa = self.allocator;
161 const stream = self.getStream(.ipi) orelse return;
162 const header = try stream.interface.peekStruct(pdb.IpiStreamHeader, .little);
163 if (header.version != .v80) // only value observed by LLVM team
164 return error.UnknownPDBVersion;
165 self.ipi = try stream.interface.readAlloc(gpa, @sizeOf(pdb.IpiStreamHeader) + header.type_record_bytes);
166}
167
151pub fn parseInfoStream(self: *Pdb) !void {168pub fn parseInfoStream(self: *Pdb) !void {
152 var stream = self.getStream(pdb.StreamType.pdb) orelse return error.InvalidDebugInfo;169 var stream = self.getStream(pdb.StreamType.pdb) orelse return error.InvalidDebugInfo;
153 const reader = &stream.interface;170 const reader = &stream.interface;
...@@ -212,38 +229,500 @@ pub fn parseInfoStream(self: *Pdb) !void {...@@ -212,38 +229,500 @@ pub fn parseInfoStream(self: *Pdb) !void {
212 return error.MissingDebugInfo;229 return error.MissingDebugInfo;
213}230}
214231
215pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 {232pub fn getProcSym(self: *Pdb, module: *Module, address: u64) ?*align(1) pdb.ProcSym {
216 _ = self;233 _ = self;
217 std.debug.assert(module.populated);234 std.debug.assert(module.populated);
218235 var reader: Io.Reader = .fixed(module.symbols);
219 var symbol_i: usize = 0;236 while (true) {
220 while (symbol_i != module.symbols.len) {237 const prefix = reader.takeStructPointer(pdb.RecordPrefix) catch return null;
221 const prefix: *align(1) pdb.RecordPrefix = @ptrCast(&module.symbols[symbol_i]);
222 if (prefix.record_len < 2)238 if (prefix.record_len < 2)
223 return null;239 return null;
240 reader.discardAll(prefix.record_len - @sizeOf(u16)) catch return null;
224 switch (prefix.record_kind) {241 switch (prefix.record_kind) {
225 .lproc32, .gproc32 => {242 .lproc32, .gproc32 => {
226 const proc_sym: *align(1) pdb.ProcSym = @ptrCast(&module.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]);243 const proc_sym: *align(1) pdb.ProcSym = @ptrCast(prefix);
227 if (address >= proc_sym.code_offset and address < proc_sym.code_offset + proc_sym.code_size) {244 if (address >= proc_sym.code_offset and address < proc_sym.code_offset + proc_sym.code_size) {
228 return std.mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.name[0])), 0);245 return proc_sym;
229 }246 }
230 },247 },
231 else => {},248 else => {},
232 }249 }
233 symbol_i += prefix.record_len + @sizeOf(u16);
234 }250 }
251 return null;
252}
253
254pub const InlineSiteSymIterator = struct {
255 module_index: usize,
256 offset: usize,
257 end: usize,
258
259 const empty: InlineSiteSymIterator = .{
260 .module_index = 0,
261 .offset = 0,
262 .end = 0,
263 };
264
265 pub fn next(iter: *InlineSiteSymIterator, module: *Module) ?*align(1) pdb.InlineSiteSym {
266 while (iter.offset < iter.end) {
267 const inline_prefix: *align(1) pdb.RecordPrefix = @ptrCast(&module.symbols[iter.offset]);
268 const end = iter.offset + inline_prefix.record_len + @sizeOf(u16);
269 if (end > iter.end) return null;
270 defer iter.offset = end;
271 switch (inline_prefix.record_kind) {
272 // Skip nested procedures
273 .lproc32,
274 .lproc32_st,
275 .gproc32,
276 .gproc32_st,
277 .lproc32_id,
278 .gproc32_id,
279 .lproc32_dpc,
280 .lproc32_dpc_id,
281 => {
282 const skip: *align(1) pdb.ProcSym = @ptrCast(inline_prefix);
283 iter.offset = skip.end;
284 },
285 .inlinesite,
286 .inlinesite2,
287 => return @ptrCast(inline_prefix),
288 else => {},
289 }
290 }
291
292 return null;
293 }
294};
295
296pub const BinaryAnnotation = union(enum) {
297 code_offset: u32,
298 change_code_offset_base: u32,
299 change_code_offset: u32,
300 change_code_length: u32,
301 change_file: u32,
302 change_line_offset: i32,
303 change_line_end_delta: u32,
304 change_range_kind: RangeKind,
305 change_column_start: u32,
306 change_column_end_delta: i32,
307 change_code_offset_and_line_offset: struct { code_delta: u32, line_delta: i32 },
308 change_code_length_and_code_offset: struct { length: u32, delta: u32 },
309 change_column_end: u32,
310
311 pub const RangeKind = enum(u32) { expression = 0, statement = 1 };
312
313 /// A virtual machine that processed binary annotations.
314 pub const RangeIterator = struct {
315 annotations: Iterator,
316 curr: PartialRange,
317 /// The previous range is tracked as the code length is sometimes implied by the subsequent
318 /// range.
319 prev: ?PartialRange,
320
321 const PartialRange = struct {
322 line_offset: i32,
323 file_id: ?u32,
324 code_offset: u32,
325 code_length: ?u32,
326
327 /// Resolves a partial range to a range with a definite length, or returns null if this
328 /// is not possible.
329 fn resolve(self: PartialRange, next_code_offset: ?u32) ?Range {
330 return .{
331 .line_offset = self.line_offset,
332 .file_id = self.file_id,
333 .code_offset = self.code_offset,
334 .code_length = b: {
335 if (self.code_length) |l| break :b l;
336 const end = next_code_offset orelse return null;
337 break :b end - self.code_offset;
338 },
339 };
340 }
341 };
342
343 pub fn init(annotations: Iterator) RangeIterator {
344 return .{
345 .annotations = annotations,
346 .curr = .{
347 .line_offset = 0,
348 .file_id = null,
349 .code_offset = 0,
350 .code_length = null,
351 },
352 .prev = null,
353 };
354 }
355
356 pub const Range = struct {
357 line_offset: i32,
358 file_id: ?u32,
359 code_offset: u32,
360 code_length: u32,
361
362 pub fn contains(self: Range, offset_in_func: usize) bool {
363 return self.code_offset <= offset_in_func and
364 offset_in_func < self.code_offset + self.code_length;
365 }
366 };
367
368 pub fn next(self: *RangeIterator) error{InvalidDebugInfo}!?Range {
369 while (try self.annotations.next()) |annotation| {
370 switch (annotation) {
371 .change_code_offset => |delta| {
372 self.curr.code_offset += delta;
373 },
374 .change_code_length => |length| {
375 if (self.prev) |*prev| prev.code_length = prev.code_length orelse length;
376 self.curr.code_offset += length;
377 },
378 // LLVM has code to emit these, but I wasn't able to figure out how trigger it
379 // so this logic is untested.
380 .change_file => |file_id| {
381 self.curr.file_id = file_id;
382 },
383 // LLVM never emits this opcode, but it's clear enough how to interpret it so we
384 // may as well handle it in case they emit it in the future
385 .change_code_length_and_code_offset => |info| {
386 self.curr.code_length = info.length;
387 self.curr.code_offset += info.delta;
388 },
389 .change_line_offset => |delta| {
390 self.curr.line_offset += delta;
391 },
392 .change_code_offset_and_line_offset => |info| {
393 self.curr.code_offset += info.code_delta;
394 self.curr.line_offset += info.line_delta;
395 },
396
397 // Not emitted by LLVM at the time of writing, and we don't want to add support
398 // without a test case. Safe to ignore since we don't use this info right now.
399 .change_line_end_delta,
400 .change_column_start,
401 .change_column_end_delta,
402 .change_column_end,
403 => {},
404
405 // Not emitted by LLVM at the time of writing. Various sources conflict on how
406 // these opcodes should be interpreted, so we make no attempt to handle them.
407 .code_offset,
408 .change_code_offset_base,
409 .change_range_kind,
410 => {
411 self.annotations = .empty;
412 self.prev = null;
413 return null;
414 },
415 }
416
417 // If we have a new code offset, return the previous range if it exists, resolving
418 // its length if necessary.
419 switch (annotation) {
420 .change_code_offset,
421 .change_code_offset_and_line_offset,
422 .change_code_length_and_code_offset,
423 => {},
424 else => continue,
425 }
426 defer self.prev = self.curr;
427 const prev = self.prev orelse continue;
428 return prev.resolve(self.curr.code_offset);
429 }
430
431 // If we've processed all the binary operations but still have a previous range leftover
432 // with a known length, return it.
433 const prev = self.prev orelse return null;
434 defer self.prev = null;
435 return prev.resolve(null);
436 }
437 };
438
439 pub const Iterator = struct {
440 reader: Io.Reader,
441
442 pub const empty: Iterator = .{ .reader = .ending_instance };
443
444 pub fn next(self: *Iterator) error{InvalidDebugInfo}!?BinaryAnnotation {
445 return take(&self.reader) catch |err| switch (err) {
446 error.ReadFailed => return error.InvalidDebugInfo,
447 error.EndOfStream => return null,
448 };
449 }
450 };
451
452 pub fn take(reader: *Io.Reader) Io.Reader.Error!BinaryAnnotation {
453 const op = std.enums.fromInt(
454 pdb.BinaryAnnotationOpcode,
455 try takePackedU32(reader),
456 ) orelse return error.ReadFailed;
457 switch (op) {
458 // Microsoft's docs say that invalid is used as padding, though it is left ambiguous
459 // whether padding is allowed internally or only after all instructions are complete.
460 // Empirically, the latter appears to be the case, at least with the output from LLVM
461 // that I've tested.
462 .invalid => return error.EndOfStream,
463 .code_offset => return .{
464 .code_offset = try expect(takePackedU32(reader)),
465 },
466 .change_code_offset_base => return .{
467 .change_code_offset_base = try expect(takePackedU32(reader)),
468 },
469 .change_code_offset => return .{
470 .change_code_offset = try expect(takePackedU32(reader)),
471 },
472 .change_code_length => return .{
473 .change_code_length = try expect(takePackedU32(reader)),
474 },
475 .change_file => return .{
476 .change_file = try expect(takePackedU32(reader)),
477 },
478 .change_line_offset => return .{
479 .change_line_offset = try expect(takePackedI32(reader)),
480 },
481 .change_line_end_delta => return .{
482 .change_line_end_delta = try expect(takePackedU32(reader)),
483 },
484 .change_range_kind => return .{
485 .change_range_kind = std.enums.fromInt(
486 RangeKind,
487 try expect(takePackedU32(reader)),
488 ) orelse return error.ReadFailed,
489 },
490 .change_column_start => return .{
491 .change_column_start = try expect(takePackedU32(reader)),
492 },
493 .change_column_end_delta => return .{
494 .change_column_end_delta = try expect(takePackedI32(reader)),
495 },
496 .change_code_offset_and_line_offset => {
497 const EncodedArgs = packed struct(u32) {
498 code_delta: u4,
499 encoded_line_delta: u28,
500 };
501 const args: EncodedArgs = @bitCast(try expect(takePackedU32(reader)));
502 return .{
503 .change_code_offset_and_line_offset = .{
504 .code_delta = args.code_delta,
505 .line_delta = decodeI32(args.encoded_line_delta),
506 },
507 };
508 },
509 .change_code_length_and_code_offset => return .{
510 .change_code_length_and_code_offset = .{
511 .length = try expect(takePackedU32(reader)),
512 .delta = try expect(takePackedU32(reader)),
513 },
514 },
515 .change_column_end => return .{
516 .change_column_end = try expect(takePackedU32(reader)),
517 },
518 }
519 }
520
521 // Adapted from:
522 // https://github.com/microsoft/microsoft-pdb/blob/805655a28bd8198004be2ac27e6e0290121a5e89/include/cvinfo.h#L4942
523 pub fn takePackedU32(reader: *Io.Reader) Io.Reader.Error!u32 {
524 const b0: u32 = try reader.takeByte();
525 if (b0 & 0x80 == 0x00) return b0;
526
527 const b1: u32 = try reader.takeByte();
528 if (b0 & 0xC0 == 0x80) return ((b0 & 0x3F) << 8) | b1;
235529
530 const b2: u32 = try reader.takeByte();
531 const b3: u32 = try reader.takeByte();
532 if (b0 & 0xE0 == 0xC0) return ((b0 & 0x1f) << 24) | (b1 << 16) | (b2 << 8) | b3;
533
534 return error.ReadFailed;
535 }
536
537 pub fn takePackedI32(reader: *Io.Reader) Io.Reader.Error!i32 {
538 return decodeI32(try takePackedU32(reader));
539 }
540
541 pub fn decodeI32(u: u32) i32 {
542 const i: i32 = @bitCast(u);
543 if (i & 1 != 0) {
544 return -(i >> 1);
545 } else {
546 return i >> 1;
547 }
548 }
549
550 fn expect(value: anytype) error{ReadFailed}!@typeInfo(@TypeOf(value)).error_union.payload {
551 comptime assert(@typeInfo(@TypeOf(value)).error_union.error_set == Io.Reader.Error);
552 return value catch error.ReadFailed;
553 }
554};
555
556pub fn findInlineeName(self: *const Pdb, inlinee: u32) ?[]const u8 {
557 // According to LLVM, the high bit *can* be used to indicate that a type index comes from the
558 // ipi stream in which case that bit needs to be cleared. LLVM doesn't generate data in this
559 // manner, but we may as well handle it since it just involves a single bitwise and.
560 // https://llvm.org/docs/PDB/TpiStream.html#type-indices
561 const type_index = inlinee & 0x7FFFFFFF;
562
563 var reader: Io.Reader = .fixed(self.ipi orelse return null);
564 const header = reader.takeStructPointer(pdb.IpiStreamHeader) catch return null;
565 for (header.type_index_begin..header.type_index_end) |curr_type_index| {
566 const prefix = reader.takeStructPointer(pdb.LfRecordPrefix) catch return null;
567 if (prefix.len < 2) return null;
568 reader.discardAll(prefix.len - @sizeOf(u16)) catch return null;
569
570 if (curr_type_index == type_index) {
571 switch (prefix.kind) {
572 .func_id => {
573 const func: *align(1) pdb.LfFuncId = @ptrCast(prefix);
574 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&func.name[0])), 0);
575 },
576 .mfunc_id => {
577 const func: *align(1) pdb.LfMFuncId = @ptrCast(prefix);
578 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&func.name[0])), 0);
579 },
580 else => return null,
581 }
582 }
583 }
584 return null;
585}
586
587pub fn getInlinees(self: *Pdb, module: *Module, proc_sym: *align(1) const pdb.ProcSym) InlineSiteSymIterator {
588 const module_index = module - self.modules.ptr;
589 const offset = @intFromPtr(proc_sym) -
590 @intFromPtr(module.symbols.ptr) +
591 proc_sym.record_len +
592 @sizeOf(u16);
593 const symbols_end = @intFromPtr(module.symbols.ptr) + module.symbols.len;
594 if (offset > symbols_end or proc_sym.end > symbols_end) return .empty;
595 return .{
596 .module_index = module_index,
597 .offset = offset,
598 .end = proc_sym.end,
599 };
600}
601
602pub fn getBinaryAnnotations(self: *Pdb, module: *Module, site: *align(1) const pdb.InlineSiteSym) BinaryAnnotation.Iterator {
603 _ = self;
604 var start: usize = @intFromPtr(site) + @sizeOf(pdb.InlineSiteSym);
605 var end = start + site.record_len + @sizeOf(u16) - @sizeOf(pdb.InlineSiteSym);
606 switch (site.record_kind) {
607 .inlinesite => {},
608 .inlinesite2 => start += @sizeOf(pdb.InlineSiteSym2) - @sizeOf(pdb.InlineSiteSym),
609 else => end = start,
610 }
611 if (start < @intFromPtr(module.symbols.ptr) or end > @intFromPtr(module.symbols.ptr) + module.symbols.len) return .empty;
612 const len = end - start;
613 const ptr: [*]const u8 = @ptrFromInt(start);
614 const slice = ptr[0..len];
615 return .{ .reader = Io.Reader.fixed(slice) };
616}
617
618pub fn getInlineSiteSourceLocation(
619 self: *Pdb,
620 gpa: Allocator,
621 mod: *Module,
622 site: *align(1) const pdb.InlineSiteSym,
623 inlinee_src_line: *align(1) const pdb.InlineeSourceLine,
624 offset_in_func: usize,
625) !?std.debug.SourceLocation {
626 var ranges: BinaryAnnotation.RangeIterator = .init(self.getBinaryAnnotations(mod, site));
627 while (try ranges.next()) |range| {
628 if (!range.contains(offset_in_func)) continue;
629
630 const file_id = range.file_id orelse inlinee_src_line.file_id;
631 const file_name = try self.getFileName(gpa, mod, file_id);
632 errdefer self.allocator.free(file_name);
633
634 return .{
635 .line = inlinee_src_line.source_line_num +% @as(u32, @bitCast(range.line_offset)),
636 // LLVM doesn't currently emit column information for inlined calls in PDBs.
637 .column = 0,
638 .file_name = file_name,
639 };
640 }
236 return null;641 return null;
237}642}
238643
239pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation {644pub fn getFileName(self: *Pdb, gpa: Allocator, mod: *Module, file_id: u32) ![]const u8 {
645 const checksum_offset = mod.checksum_offset orelse return error.MissingDebugInfo;
646 const subsect_index = checksum_offset + file_id;
647 const chksum_hdr: *align(1) pdb.FileChecksumEntryHeader = @ptrCast(&mod.subsect_info[subsect_index]);
648 const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.file_name_offset;
649 self.string_table.?.seekTo(strtab_offset) catch return error.InvalidDebugInfo;
650 const string_reader = &self.string_table.?.interface;
651 var source_file_name: Io.Writer.Allocating = .init(gpa);
652 defer source_file_name.deinit();
653 _ = try string_reader.streamDelimiterLimit(&source_file_name.writer, 0, .limited(1024));
654 assert(string_reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
655 string_reader.toss(1);
656 return try source_file_name.toOwnedSlice();
657}
658
659pub fn getSymbolName(self: *Pdb, proc_sym: *align(1) const pdb.ProcSym) []const u8 {
660 _ = self;
661 return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(&proc_sym.name[0])), 0);
662}
663
664pub const InlineeSourceLine = struct {
665 signature: pdb.InlineeSourceLineSignature,
666 info: *align(1) const pdb.InlineeSourceLine,
667
668 fn lessThan(_: void, lhs: InlineeSourceLine, rhs: InlineeSourceLine) bool {
669 return lhs.info.inlinee < rhs.info.inlinee;
670 }
671
672 fn compare(inlinee: u32, self: InlineeSourceLine) std.math.Order {
673 return std.math.order(inlinee, self.info.inlinee);
674 }
675};
676
677/// Returns all `InlineeSourceLine`s for a given module with the given inlinee. Ideally there would
678/// only be one entry per inlinee, but LLVM appears to assign all functions that share a name the
679/// same inlinee ID. This appears to be a bug, so the best the caller can do right now is print all
680/// the results.
681pub fn getInlineeSourceLines(
682 self: *Pdb,
683 mod: *Module,
684 inlinee: u32,
685) []const InlineeSourceLine {
686 _ = self;
687
688 // Binary search to an arbitrary match, if there are other matches they will be adjacent
689 const any = std.sort.binarySearch(
690 InlineeSourceLine,
691 mod.inlinee_source_lines,
692 inlinee,
693 InlineeSourceLine.compare,
694 ) orelse return &.{};
695
696 // Linearly scan to the first match
697 const begin = b: {
698 var begin = any;
699 while (begin > 0) {
700 const prev = begin - 1;
701 if (mod.inlinee_source_lines[prev].info.inlinee != inlinee) break;
702 begin = prev;
703 }
704 break :b begin;
705 };
706
707 // Linearly scan to the last match
708 const end = b: {
709 var end = any + 1;
710 while (end < mod.inlinee_source_lines.len and
711 mod.inlinee_source_lines[end].info.inlinee == inlinee) : (end += 1)
712 {}
713 break :b end;
714 };
715
716 // Return a slice of all the matches
717 return mod.inlinee_source_lines[begin..end];
718}
719
720pub fn getLineNumberInfo(self: *Pdb, gpa: Allocator, module: *Module, address: u64) !std.debug.SourceLocation {
240 std.debug.assert(module.populated);721 std.debug.assert(module.populated);
241 const subsect_info = module.subsect_info;722 const subsect_info = module.subsect_info;
242 const gpa = self.allocator;
243723
244 var sect_offset: usize = 0;724 var sect_offset: usize = 0;
245 var skip_len: usize = undefined;725 var skip_len: usize = undefined;
246 const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
247 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {726 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
248 const subsect_hdr: *align(1) pdb.DebugSubsectionHeader = @ptrCast(&subsect_info[sect_offset]);727 const subsect_hdr: *align(1) pdb.DebugSubsectionHeader = @ptrCast(&subsect_info[sect_offset]);
249 skip_len = subsect_hdr.length;728 skip_len = subsect_hdr.length;
...@@ -290,20 +769,8 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S...@@ -290,20 +769,8 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S
290769
291 // line_i == 0 would mean that no matching pdb.LineNumberEntry was found.770 // line_i == 0 would mean that no matching pdb.LineNumberEntry was found.
292 if (line_i > 0) {771 if (line_i > 0) {
293 const subsect_index = checksum_offset + block_hdr.name_index;772 const file_name = try self.getFileName(gpa, module, block_hdr.name_index);
294 const chksum_hdr: *align(1) pdb.FileChecksumEntryHeader = @ptrCast(&module.subsect_info[subsect_index]);773 errdefer gpa.free(file_name);
295 const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.file_name_offset;
296 try self.string_table.?.seekTo(strtab_offset);
297 const source_file_name = s: {
298 const string_reader = &self.string_table.?.interface;
299 var source_file_name: std.Io.Writer.Allocating = .init(gpa);
300 defer source_file_name.deinit();
301 _ = try string_reader.streamDelimiterLimit(&source_file_name.writer, 0, .limited(1024));
302 assert(string_reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
303 string_reader.toss(1);
304 break :s try source_file_name.toOwnedSlice();
305 };
306 errdefer gpa.free(source_file_name);
307774
308 const line_entry_idx = line_i - 1;775 const line_entry_idx = line_i - 1;
309776
...@@ -318,7 +785,7 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S...@@ -318,7 +785,7 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S
318 const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);785 const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);
319786
320 return .{787 return .{
321 .file_name = source_file_name,788 .file_name = file_name,
322 .line = line_num_entry.flags.start,789 .line = line_num_entry.flags.start,
323 .column = column,790 .column = column,
324 };791 };
...@@ -366,7 +833,43 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {...@@ -366,7 +833,43 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {
366 const gpa = self.allocator;833 const gpa = self.allocator;
367834
368 mod.symbols = try reader.readAlloc(gpa, mod.mod_info.sym_byte_size - 4);835 mod.symbols = try reader.readAlloc(gpa, mod.mod_info.sym_byte_size - 4);
836 errdefer gpa.free(mod.symbols);
369 mod.subsect_info = try reader.readAlloc(gpa, mod.mod_info.c13_byte_size);837 mod.subsect_info = try reader.readAlloc(gpa, mod.mod_info.c13_byte_size);
838 errdefer gpa.free(mod.subsect_info);
839 mod.inlinee_source_lines = b: {
840 var inlinee_source_lines: std.ArrayList(InlineeSourceLine) = .empty;
841 defer inlinee_source_lines.deinit(gpa);
842 var subsects: Io.Reader = .fixed(mod.subsect_info);
843 while (subsects.takeStructPointer(pdb.DebugSubsectionHeader) catch null) |subsect_hdr| {
844 var subsect: Io.Reader = .fixed(subsects.take(subsect_hdr.length) catch return null);
845 if (subsect_hdr.kind == .inlinee_lines) {
846 const inlinee_source_line_signature = subsect.takeEnum(pdb.InlineeSourceLineSignature, .little) catch return error.InvalidDebugInfo;
847 const has_extra_files = switch (inlinee_source_line_signature) {
848 .normal => false,
849 .ex => true,
850 else => continue,
851 };
852 while (subsect.takeStructPointer(pdb.InlineeSourceLine) catch null) |info| {
853 if (has_extra_files) {
854 const file_count = subsect.takeInt(u32, .little) catch
855 return error.InvalidDebugInfo;
856 const file_bytes = std.math.mul(usize, file_count, @sizeOf(u32)) catch return error.InvalidDebugInfo;
857 subsect.discardAll(file_bytes) catch
858 return error.InvalidDebugInfo;
859 }
860
861 try inlinee_source_lines.append(gpa, .{
862 .signature = inlinee_source_line_signature,
863 .info = info,
864 });
865 }
866 }
867 }
868
869 std.mem.sortUnstable(InlineeSourceLine, inlinee_source_lines.items, {}, InlineeSourceLine.lessThan);
870 break :b try inlinee_source_lines.toOwnedSlice(gpa);
871 };
872 errdefer gpa.free(mod.inlinee_source_lines);
370873
371 var sect_offset: usize = 0;874 var sect_offset: usize = 0;
372 var skip_len: usize = undefined;875 var skip_len: usize = undefined;
...@@ -497,7 +1000,7 @@ const MsfStream = struct {...@@ -497,7 +1000,7 @@ const MsfStream = struct {
497 next_read_pos: u64,1000 next_read_pos: u64,
498 blocks: []u32,1001 blocks: []u32,
499 block_size: u32,1002 block_size: u32,
500 interface: std.Io.Reader,1003 interface: Io.Reader,
501 err: ?Error,1004 err: ?Error,
5021005
503 const Error = File.Reader.SeekError;1006 const Error = File.Reader.SeekError;
...@@ -527,7 +1030,7 @@ const MsfStream = struct {...@@ -527,7 +1030,7 @@ const MsfStream = struct {
527 };1030 };
528 }1031 }
5291032
530 fn stream(r: *std.Io.Reader, w: *std.Io.Writer, limit: std.Io.Limit) std.Io.Reader.StreamError!usize {1033 fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
531 const ms: *MsfStream = @alignCast(@fieldParentPtr("interface", r));1034 const ms: *MsfStream = @alignCast(@fieldParentPtr("interface", r));
5321035
533 var block_id: usize = @intCast(ms.next_read_pos / ms.block_size);1036 var block_id: usize = @intCast(ms.next_read_pos / ms.block_size);
...@@ -595,7 +1098,7 @@ const MsfStream = struct {...@@ -595,7 +1098,7 @@ const MsfStream = struct {
595 }1098 }
596};1099};
5971100
598fn readSparseBitVector(reader: *std.Io.Reader, allocator: Allocator) ![]u32 {1101fn readSparseBitVector(reader: *Io.Reader, allocator: Allocator) ![]u32 {
599 const num_words = try reader.takeInt(u32, .little);1102 const num_words = try reader.takeInt(u32, .little);
600 var list = std.array_list.Managed(u32).init(allocator);1103 var list = std.array_list.Managed(u32).init(allocator);
601 errdefer list.deinit();1104 errdefer list.deinit();
lib/std/debug/SelfInfo/Elf.zig+19-18
...@@ -30,7 +30,15 @@ pub fn deinit(si: *SelfInfo, io: Io) void {...@@ -30,7 +30,15 @@ pub fn deinit(si: *SelfInfo, io: Io) void {
30 if (si.unwind_cache) |cache| gpa.free(cache);30 if (si.unwind_cache) |cache| gpa.free(cache);
31}31}
3232
33pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {33pub fn getSymbols(
34 si: *SelfInfo,
35 io: Io,
36 symbol_allocator: Allocator,
37 text_arena: Allocator,
38 address: usize,
39 resolve_inline_callers: bool,
40 symbols: *std.ArrayList(std.debug.Symbol),
41) Error!void {
34 const gpa = std.debug.getDebugInfoAllocator();42 const gpa = std.debug.getDebugInfoAllocator();
35 const module = try si.findModule(gpa, io, address, .exclusive);43 const module = try si.findModule(gpa, io, address, .exclusive);
36 defer si.rwlock.unlock(io);44 defer si.rwlock.unlock(io);
...@@ -53,28 +61,21 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {...@@ -53,28 +61,21 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {
53 };61 };
54 loaded_elf.scanned_dwarf = true;62 loaded_elf.scanned_dwarf = true;
55 }63 }
56 if (dwarf.getSymbol(gpa, native_endian, vaddr)) |sym| {64 return dwarf.getSymbols(
57 return sym;65 symbol_allocator,
58 } else |err| switch (err) {66 text_arena,
59 error.MissingDebugInfo => {},67 native_endian,
6068 vaddr,
61 error.InvalidDebugInfo,69 resolve_inline_callers,
62 error.OutOfMemory,70 symbols,
63 => |e| return e,71 );
64
65 error.ReadFailed,
66 error.EndOfStream,
67 error.Overflow,
68 error.StreamTooLong,
69 => return error.InvalidDebugInfo,
70 }
71 }72 }
72 // When DWARF is unavailable, fall back to searching the symtab.73 // When DWARF is unavailable, fall back to searching the symtab.
73 return loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) {74 try symbols.append(symbol_allocator, loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) {
74 error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo,75 error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo,
75 error.BadSymtab => return error.InvalidDebugInfo,76 error.BadSymtab => return error.InvalidDebugInfo,
76 error.OutOfMemory => |e| return e,77 error.OutOfMemory => |e| return e,
77 };78 });
78}79}
79pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {80pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
80 const gpa = std.debug.getDebugInfoAllocator();81 const gpa = std.debug.getDebugInfoAllocator();
lib/std/debug/SelfInfo/MachO.zig+18-7
...@@ -22,8 +22,18 @@ pub fn deinit(si: *SelfInfo, io: Io) void {...@@ -22,8 +22,18 @@ pub fn deinit(si: *SelfInfo, io: Io) void {
22 si.modules.deinit(gpa);22 si.modules.deinit(gpa);
23}23}
2424
25pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {25pub fn getSymbols(
26 si: *SelfInfo,
27 io: Io,
28 symbol_allocator: Allocator,
29 text_arena: Allocator,
30 address: usize,
31 resolve_inline_callers: bool,
32 symbols: *std.ArrayList(std.debug.Symbol),
33) Error!void {
34 _ = resolve_inline_callers;
26 const gpa = std.debug.getDebugInfoAllocator();35 const gpa = std.debug.getDebugInfoAllocator();
36
27 const module = try si.findModule(gpa, io, address);37 const module = try si.findModule(gpa, io, address);
28 defer si.mutex.unlock(io);38 defer si.mutex.unlock(io);
2939
...@@ -43,23 +53,23 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {...@@ -43,23 +53,23 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {
4353
44 const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, io, vaddr) catch {54 const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, io, vaddr) catch {
45 // Return at least the symbol name if available.55 // Return at least the symbol name if available.
46 return .{56 return symbols.append(symbol_allocator, .{
47 .name = try file.lookupSymbolName(vaddr),57 .name = try file.lookupSymbolName(vaddr),
48 .compile_unit_name = null,58 .compile_unit_name = null,
49 .source_location = null,59 .source_location = null,
50 };60 });
51 };61 };
5262
53 const compile_unit = ofile_dwarf.findCompileUnit(native_endian, ofile_vaddr) catch {63 const compile_unit = ofile_dwarf.findCompileUnit(native_endian, ofile_vaddr) catch {
54 // Return at least the symbol name if available.64 // Return at least the symbol name if available.
55 return .{65 return symbols.append(symbol_allocator, .{
56 .name = try file.lookupSymbolName(vaddr),66 .name = try file.lookupSymbolName(vaddr),
57 .compile_unit_name = null,67 .compile_unit_name = null,
58 .source_location = null,68 .source_location = null,
59 };69 });
60 };70 };
6171
62 return .{72 try symbols.append(symbol_allocator, .{
63 .name = ofile_dwarf.getSymbolName(ofile_vaddr) orelse73 .name = ofile_dwarf.getSymbolName(ofile_vaddr) orelse
64 try file.lookupSymbolName(vaddr),74 try file.lookupSymbolName(vaddr),
65 .compile_unit_name = compile_unit.die.getAttrString(75 .compile_unit_name = compile_unit.die.getAttrString(
...@@ -73,11 +83,12 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {...@@ -73,11 +83,12 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {
73 },83 },
74 .source_location = ofile_dwarf.getLineNumberInfo(84 .source_location = ofile_dwarf.getLineNumberInfo(
75 gpa,85 gpa,
86 text_arena,
76 native_endian,87 native_endian,
77 compile_unit,88 compile_unit,
78 ofile_vaddr,89 ofile_vaddr,
79 ) catch null,90 ) catch null,
80 };91 });
81}92}
82pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {93pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
83 _ = si;94 _ = si;
lib/std/debug/SelfInfo/Windows.zig+137-36
...@@ -1,10 +1,10 @@...@@ -1,10 +1,10 @@
1mutex: Io.Mutex,1lock: Io.RwLock,
2ntdll_handle: ?if (load_dll_notification_procs) *anyopaque else noreturn,2ntdll_handle: ?if (load_dll_notification_procs) *anyopaque else noreturn,
3notification_cookie: ?LDR.DLL_NOTIFICATION.COOKIE,3notification_cookie: ?LDR.DLL_NOTIFICATION.COOKIE,
4modules: std.ArrayList(Module),4modules: std.ArrayList(Module),
55
6pub const init: SelfInfo = .{6pub const init: SelfInfo = .{
7 .mutex = .init,7 .lock = .init,
8 .ntdll_handle = null,8 .ntdll_handle = null,
9 .notification_cookie = null,9 .notification_cookie = null,
10 .modules = .empty,10 .modules = .empty,
...@@ -25,18 +25,33 @@ pub fn deinit(si: *SelfInfo, io: Io) void {...@@ -25,18 +25,33 @@ pub fn deinit(si: *SelfInfo, io: Io) void {
25 si.modules.deinit(gpa);25 si.modules.deinit(gpa);
26}26}
2727
28pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {28pub fn getSymbols(
29 si: *SelfInfo,
30 io: Io,
31 symbol_allocator: Allocator,
32 text_arena: Allocator,
33 address: usize,
34 resolve_inline_callers: bool,
35 symbols: *std.ArrayList(std.debug.Symbol),
36) Error!void {
29 const gpa = std.debug.getDebugInfoAllocator();37 const gpa = std.debug.getDebugInfoAllocator();
30 try si.mutex.lock(io);38 try si.lock.lockShared(io);
31 defer si.mutex.unlock(io);39 defer si.lock.unlockShared(io);
32 const module = try si.findModule(gpa, address);40 const module = try si.findModule(gpa, address);
33 const di = try module.getDebugInfo(gpa, io);41 const di = try module.getDebugInfo(gpa, io);
34 return di.getSymbol(gpa, address - @intFromPtr(module.entry.DllBase));42 return di.getSymbols(
43 symbol_allocator,
44 text_arena,
45 address - @intFromPtr(module.entry.DllBase),
46 resolve_inline_callers,
47 symbols,
48 );
35}49}
50
36pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {51pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
37 const gpa = std.debug.getDebugInfoAllocator();52 const gpa = std.debug.getDebugInfoAllocator();
38 try si.mutex.lock(io);53 try si.lock.lockShared(io);
39 defer si.mutex.unlock(io);54 defer si.lock.unlockShared(io);
40 const module = try si.findModule(gpa, address);55 const module = try si.findModule(gpa, address);
41 return module.name orelse {56 return module.name orelse {
42 const name = try std.unicode.wtf16LeToWtf8Alloc(gpa, module.entry.BaseDllName.slice());57 const name = try std.unicode.wtf16LeToWtf8Alloc(gpa, module.entry.BaseDllName.slice());
...@@ -46,8 +61,8 @@ pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {...@@ -46,8 +61,8 @@ pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
46}61}
47pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize {62pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize {
48 const gpa = std.debug.getDebugInfoAllocator();63 const gpa = std.debug.getDebugInfoAllocator();
49 try si.mutex.lock(io);64 try si.lock.lockShared(io);
50 defer si.mutex.unlock(io);65 defer si.lock.unlockShared(io);
51 const module = try si.findModule(gpa, address);66 const module = try si.findModule(gpa, address);
52 return module.base_address;67 return module.base_address;
53}68}
...@@ -240,7 +255,14 @@ const Module = struct {...@@ -240,7 +255,14 @@ const Module = struct {
240 arena.deinit();255 arena.deinit();
241 }256 }
242257
243 fn getSymbol(di: *DebugInfo, gpa: Allocator, vaddr: usize) Error!std.debug.Symbol {258 fn getSymbols(
259 di: *DebugInfo,
260 symbol_allocator: Allocator,
261 text_arena: Allocator,
262 vaddr: usize,
263 resolve_inline_callers: bool,
264 symbols: *std.ArrayList(std.debug.Symbol),
265 ) Error!void {
244 pdb: {266 pdb: {
245 const pdb = &(di.pdb orelse break :pdb);267 const pdb = &(di.pdb orelse break :pdb);
246 var coff_section: *align(1) const coff.SectionHeader = undefined;268 var coff_section: *align(1) const coff.SectionHeader = undefined;
...@@ -270,32 +292,101 @@ const Module = struct {...@@ -270,32 +292,101 @@ const Module = struct {
270 } orelse {292 } orelse {
271 return error.InvalidDebugInfo; // bad module index293 return error.InvalidDebugInfo; // bad module index
272 };294 };
273 return .{295
274 .name = pdb.getSymbolName(module, vaddr - coff_section.virtual_address),296 const addr = vaddr - coff_section.virtual_address;
275 .compile_unit_name = fs.path.basename(module.obj_file_name),297 const maybe_proc = pdb.getProcSym(module, addr);
276 .source_location = pdb.getLineNumberInfo(298 const compile_unit_name = fs.path.basename(module.obj_file_name);
277 module,299 const symbols_top = symbols.items.len;
278 vaddr - coff_section.virtual_address,300 if (maybe_proc) |proc| {
279 ) catch null,301 const offset_in_func = addr - proc.code_offset;
280 };302 var last_inlinee: ?u32 = null;
303 var iter = pdb.getInlinees(module, proc);
304 while (iter.next(module)) |inline_site| {
305 // Filter out duplicate inline sites. Tools like llvm-addr2line output
306 // duplicate sites in the same cases as us if we elide this check,
307 // implying that they exist in the underlying data and are not indicative
308 // of a parser bug. No useful information is lost here since an inline site
309 // can't actually reference itself.
310 if (inline_site.inlinee == last_inlinee) continue;
311
312 // If our address points into this site, get the source location(s) it
313 // points at
314 for (pdb.getInlineeSourceLines(
315 module,
316 inline_site.inlinee,
317 )) |inlinee_src_line| {
318 const maybe_loc = pdb.getInlineSiteSourceLocation(
319 text_arena,
320 module,
321 inline_site,
322 inlinee_src_line.info,
323 offset_in_func,
324 ) catch continue;
325 const loc = maybe_loc orelse continue;
326
327 // If we aren't trying to resolve inline callers, and we've matched a
328 // new inline site, we want to overwrite the previously appended
329 // results.
330 if (!resolve_inline_callers and inline_site.inlinee != last_inlinee) {
331 symbols.items.len = symbols_top;
332 }
333
334 // Only resolve the name if we're resolving inline callers, otherwise
335 // wait until we're done to avoid duplicated work.
336 const name = if (resolve_inline_callers)
337 pdb.findInlineeName(inline_site.inlinee)
338 else
339 null;
340
341 try symbols.append(symbol_allocator, .{
342 .name = name,
343 .compile_unit_name = compile_unit_name,
344 .source_location = loc,
345 });
346
347 last_inlinee = inline_site.inlinee;
348 }
349 }
350
351 if (resolve_inline_callers) {
352 // Inline sites are stored in the pdb in reverse order, so we reverse the
353 // matching sites here. We could alternatively use the parent fields to
354 // determine the order, but this would introduce seemingly unecessary
355 // complexity.
356 std.mem.reverse(std.debug.Symbol, symbols.items);
357 } else if (last_inlinee) |inlinee| {
358 // If we aren't resolving inline callers, then all results will have the
359 // same inline site, and we resolve its name once at the end.
360 const name = pdb.findInlineeName(inlinee);
361 for (symbols.items) |*symbol| symbol.name = name;
362 }
363 }
364
365 // If there's room for another symbol, add the actual proc
366 if (resolve_inline_callers or symbols.items.len == 0) {
367 try symbols.append(symbol_allocator, .{
368 .name = if (maybe_proc) |proc| pdb.getSymbolName(proc) else null,
369 .compile_unit_name = compile_unit_name,
370 .source_location = pdb.getLineNumberInfo(text_arena, module, addr) catch null,
371 });
372 }
373
374 return;
281 }375 }
376
282 dwarf: {377 dwarf: {
283 const dwarf = &(di.dwarf orelse break :dwarf);378 const dwarf = &(di.dwarf orelse break :dwarf);
284 const dwarf_address = vaddr + di.coff_image_base;379 const addr = vaddr + di.coff_image_base;
285 return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch |err| switch (err) {380 return dwarf.getSymbols(
286 error.MissingDebugInfo => break :dwarf,381 symbol_allocator,
287382 text_arena,
288 error.InvalidDebugInfo,383 native_endian,
289 error.OutOfMemory,384 addr,
290 => |e| return e,385 resolve_inline_callers,
291386 symbols,
292 error.ReadFailed,387 );
293 error.EndOfStream,
294 error.Overflow,
295 error.StreamTooLong,
296 => return error.InvalidDebugInfo,
297 };
298 }388 }
389
299 return error.MissingDebugInfo;390 return error.MissingDebugInfo;
300 }391 }
301 };392 };
...@@ -505,6 +596,16 @@ const Module = struct {...@@ -505,6 +596,16 @@ const Module = struct {
505 error.ReadFailed,596 error.ReadFailed,
506 => |e| return e,597 => |e| return e,
507 };598 };
599 pdb.parseIpiStream() catch |err| switch (err) {
600 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
601
602 error.EndOfStream,
603 => return error.InvalidDebugInfo,
604
605 error.OutOfMemory,
606 error.ReadFailed,
607 => |e| return e,
608 };
508609
509 if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age)610 if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age)
510 return error.InvalidDebugInfo;611 return error.InvalidDebugInfo;
...@@ -531,7 +632,7 @@ const Module = struct {...@@ -531,7 +632,7 @@ const Module = struct {
531 }632 }
532};633};
533634
534/// Assumes we already hold `si.mutex`.635/// Assumes we already hold `si.lock`.
535fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module {636fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module {
536 for (si.modules.items) |*mod| {637 for (si.modules.items) |*mod| {
537 const base = @intFromPtr(mod.entry.DllBase);638 const base = @intFromPtr(mod.entry.DllBase);
...@@ -601,8 +702,8 @@ fn dllNotification(...@@ -601,8 +702,8 @@ fn dllNotification(
601 .LOADED => {},702 .LOADED => {},
602 .UNLOADED => {703 .UNLOADED => {
603 const io = std.Options.debug_io;704 const io = std.Options.debug_io;
604 si.mutex.lockUncancelable(io);705 si.lock.lockUncancelable(io);
605 defer si.mutex.unlock(io);706 defer si.lock.unlock(io);
606 for (si.modules.items, 0..) |*mod, mod_index| {707 for (si.modules.items, 0..) |*mod, mod_index| {
607 if (mod.entry.DllBase != data.Unloaded.DllBase) continue;708 if (mod.entry.DllBase != data.Unloaded.DllBase) continue;
608 mod.deinit(std.debug.getDebugInfoAllocator(), io);709 mod.deinit(std.debug.getDebugInfoAllocator(), io);
lib/std/heap/debug_allocator.zig+7-7
...@@ -81,7 +81,7 @@...@@ -81,7 +81,7 @@
81//! Resizing and remapping are forwarded directly to the backing allocator,81//! Resizing and remapping are forwarded directly to the backing allocator,
82//! except where such operations would change the category from large to small.82//! except where such operations would change the category from large to small.
83const builtin = @import("builtin");83const builtin = @import("builtin");
84const StackTrace = std.builtin.StackTrace;84const StackTrace = std.debug.StackTrace;
8585
86const std = @import("std");86const std = @import("std");
87const log = std.log.scoped(.DebugAllocator);87const log = std.log.scoped(.DebugAllocator);
...@@ -229,7 +229,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -229,7 +229,7 @@ pub fn DebugAllocator(comptime config: Config) type {
229 std.debug.dumpStackTrace(self.getStackTrace(trace_kind));229 std.debug.dumpStackTrace(self.getStackTrace(trace_kind));
230 }230 }
231231
232 fn getStackTrace(self: *LargeAlloc, trace_kind: TraceKind) std.builtin.StackTrace {232 fn getStackTrace(self: *LargeAlloc, trace_kind: TraceKind) std.debug.StackTrace {
233 assert(@intFromEnum(trace_kind) < trace_n);233 assert(@intFromEnum(trace_kind) < trace_n);
234 const stack_addresses = &self.stack_addresses[@intFromEnum(trace_kind)];234 const stack_addresses = &self.stack_addresses[@intFromEnum(trace_kind)];
235 var len: usize = 0;235 var len: usize = 0;
...@@ -237,8 +237,8 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -237,8 +237,8 @@ pub fn DebugAllocator(comptime config: Config) type {
237 len += 1;237 len += 1;
238 }238 }
239 return .{239 return .{
240 .instruction_addresses = stack_addresses,240 .return_addresses = stack_addresses[0..len],
241 .index = len,241 .skipped = if (len < stack_addresses.len) .none else .unknown,
242 };242 };
243 }243 }
244244
...@@ -339,8 +339,8 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -339,8 +339,8 @@ pub fn DebugAllocator(comptime config: Config) type {
339 len += 1;339 len += 1;
340 }340 }
341 return .{341 return .{
342 .instruction_addresses = stack_addresses,342 .return_addresses = stack_addresses[0..len],
343 .index = len,343 .skipped = if (len < stack_addresses.len) .none else .unknown,
344 };344 };
345 }345 }
346346
...@@ -508,7 +508,7 @@ pub fn DebugAllocator(comptime config: Config) type {...@@ -508,7 +508,7 @@ pub fn DebugAllocator(comptime config: Config) type {
508508
509 fn collectStackTrace(first_trace_addr: usize, addr_buf: *[stack_n]usize) void {509 fn collectStackTrace(first_trace_addr: usize, addr_buf: *[stack_n]usize) void {
510 const st = std.debug.captureCurrentStackTrace(.{ .first_address = first_trace_addr }, addr_buf);510 const st = std.debug.captureCurrentStackTrace(.{ .first_address = first_trace_addr }, addr_buf);
511 @memset(addr_buf[@min(st.index, addr_buf.len)..], 0);511 @memset(addr_buf[@min(st.return_addresses.len, addr_buf.len)..], 0);
512 }512 }
513513
514 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {514 fn reportDoubleFree(ret_addr: usize, alloc_stack_trace: StackTrace, free_stack_trace: StackTrace) void {
lib/std/pdb.zig+143-4
...@@ -314,11 +314,9 @@ pub const SymbolKind = enum(u16) {...@@ -314,11 +314,9 @@ pub const SymbolKind = enum(u16) {
314314
315pub const TypeIndex = u32;315pub const TypeIndex = u32;
316316
317// TODO According to this header:
318// https://github.com/microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/include/cvinfo.h#L3722
319// we should define RecordPrefix as part of the ProcSym structure.
320// This might be important when we start generating PDB in self-hosted with our own PE linker.
321pub const ProcSym = extern struct {317pub const ProcSym = extern struct {
318 record_len: u16,
319 record_kind: SymbolKind,
322 parent: u32,320 parent: u32,
323 end: u32,321 end: u32,
324 next: u32,322 next: u32,
...@@ -508,3 +506,144 @@ pub const SuperBlock = extern struct {...@@ -508,3 +506,144 @@ pub const SuperBlock = extern struct {
508 // implement it so we're kind of safe making this assumption for now.506 // implement it so we're kind of safe making this assumption for now.
509 block_map_addr: u32,507 block_map_addr: u32,
510};508};
509
510pub const IpiStreamVersion = enum(u32) {
511 v40 = 19950410,
512 v41 = 19951122,
513 v50 = 19961031,
514 v70 = 19990903,
515 v80 = 20040203,
516 _,
517};
518
519pub const IpiStreamHeader = extern struct {
520 version: IpiStreamVersion,
521 header_size: u32,
522 type_index_begin: u32,
523 type_index_end: u32,
524 type_record_bytes: u32,
525 hash_stream_index: u16,
526 hash_aux_stream_index: u16,
527 hash_key_size: u32,
528 num_hash_buckets: u32,
529 hash_value_buffer_offset: i32,
530 hash_value_buffer_length: u32,
531 index_offset_buffer_offset: i32,
532 index_offset_buffer_length: u32,
533 hash_adj_buffer_offset: i32,
534 hash_adj_buffer_length: u32,
535};
536
537pub const LfRecordPrefix = extern struct {
538 len: u16,
539 kind: LfRecordKind,
540};
541
542pub const LfRecordKind = enum(u16) {
543 pointer = 0x1002,
544 modifier = 0x1001,
545 procedure = 0x1008,
546 mfunction = 0x1009,
547 label = 0x000e,
548 arglist = 0x1201,
549 fieldlist = 0x1203,
550 array = 0x1503,
551 class = 0x1504,
552 structure = 0x1505,
553 interface = 0x1519,
554 @"union" = 0x1506,
555 @"enum" = 0x1507,
556 typeserver2 = 0x1515,
557 vftable = 0x151d,
558 vtshape = 0x000a,
559 bitfield = 0x1205,
560 func_id = 0x1601,
561 mfunc_id = 0x1602,
562 buildinfo = 0x1603,
563 substr_list = 0x1604,
564 string_id = 0x1605,
565 udt_src_line = 0x1606,
566 udt_mod_src_line = 0x1607,
567 methodlist = 0x1206,
568 precomp = 0x1509,
569 endprecomp = 0x0014,
570 bclass = 0x1400,
571 binterface = 0x151a,
572 vbclass = 0x1401,
573 ivbclass = 0x1402,
574 vfunctab = 0x1409,
575 stmember = 0x150e,
576 method = 0x150f,
577 member = 0x150d,
578 nesttype = 0x1510,
579 onemethod = 0x1511,
580 enumerate = 0x1502,
581 index = 0x1404,
582 pad0 = 0xf0,
583 _,
584};
585
586pub const LfFuncId = extern struct {
587 len: u16,
588 kind: LfRecordKind,
589 scope_id: u32,
590 type: u32,
591 name: [1]u8, // null-terminated
592};
593
594pub const LfMFuncId = extern struct {
595 len: u16,
596 kind: LfRecordKind,
597 parent_type: u32,
598 type: u32,
599 name: [1]u8, // null-terminated
600};
601
602pub const InlineSiteSym = extern struct {
603 record_len: u16,
604 record_kind: SymbolKind,
605 parent: u32,
606 end: u32,
607 inlinee: u32,
608};
609
610pub const InlineSiteSym2 = extern struct {
611 record_len: u16,
612 record_kind: SymbolKind,
613 parent: u32,
614 end: u32,
615 inlinee: u32,
616 invocations: u32,
617};
618
619pub const InlineeSourceLineSignature = enum(u32) { normal = 0, ex = 1, _ };
620
621pub const InlineeSourceLine = extern struct {
622 inlinee: u32,
623 file_id: u32,
624 source_line_num: u32,
625};
626
627pub const InlineeSourceLineEx = extern struct {
628 inlinee: u32,
629 file_id: u32,
630 source_line_num: u32,
631 count_of_extra_files: u32,
632};
633
634pub const BinaryAnnotationOpcode = enum(u8) {
635 invalid = 0,
636 code_offset = 1,
637 change_code_offset_base = 2,
638 change_code_offset = 3,
639 change_code_length = 4,
640 change_file = 5,
641 change_line_offset = 6,
642 change_line_end_delta = 7,
643 change_range_kind = 8,
644 change_column_start = 9,
645 change_column_end_delta = 10,
646 change_code_offset_and_line_offset = 11,
647 change_code_length_and_code_offset = 12,
648 change_column_end = 13,
649};
lib/std/start.zig+1-1
...@@ -761,7 +761,7 @@ inline fn wrapMain(result: anytype) u8 {...@@ -761,7 +761,7 @@ inline fn wrapMain(result: anytype) u8 {
761 std.log.err("{t}", .{err});761 std.log.err("{t}", .{err});
762 switch (native_os) {762 switch (native_os) {
763 .freestanding, .other => {},763 .freestanding, .other => {},
764 else => if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace),764 else => if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace),
765 }765 }
766 return 1;766 return 1;
767 };767 };
lib/std/std.zig+2
...@@ -165,6 +165,8 @@ pub const Options = struct {...@@ -165,6 +165,8 @@ pub const Options = struct {
165 /// * `debug.dumpCurrentStackTrace`165 /// * `debug.dumpCurrentStackTrace`
166 /// * `debug.writeStackTrace`166 /// * `debug.writeStackTrace`
167 /// * `debug.dumpStackTrace`167 /// * `debug.dumpStackTrace`
168 /// * `debug.writeErrorReturnTrace`
169 /// * `debug.dumpErrorReturnTrace`
168 ///170 ///
169 /// Stack traces can generally be collected and printed when debug info is stripped, but are171 /// Stack traces can generally be collected and printed when debug info is stripped, but are
170 /// often less useful since they usually cannot be mapped to source locations and/or have bad172 /// often less useful since they usually cannot be mapped to source locations and/or have bad
lib/std/testing/FailingAllocator.zig+4-4
...@@ -65,7 +65,7 @@ fn alloc(...@@ -65,7 +65,7 @@ fn alloc(
65 if (self.alloc_index == self.fail_index) {65 if (self.alloc_index == self.fail_index) {
66 if (!self.has_induced_failure) {66 if (!self.has_induced_failure) {
67 const st = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &self.stack_addresses);67 const st = std.debug.captureCurrentStackTrace(.{ .first_address = return_address }, &self.stack_addresses);
68 @memset(self.stack_addresses[@min(st.index, self.stack_addresses.len)..], 0);68 @memset(self.stack_addresses[@min(st.return_addresses.len, self.stack_addresses.len)..], 0);
69 self.has_induced_failure = true;69 self.has_induced_failure = true;
70 }70 }
71 return null;71 return null;
...@@ -131,15 +131,15 @@ fn free(...@@ -131,15 +131,15 @@ fn free(
131}131}
132132
133/// Only valid once `has_induced_failure == true`133/// Only valid once `has_induced_failure == true`
134pub fn getStackTrace(self: *FailingAllocator) std.builtin.StackTrace {134pub fn getStackTrace(self: *FailingAllocator) std.debug.StackTrace {
135 std.debug.assert(self.has_induced_failure);135 std.debug.assert(self.has_induced_failure);
136 var len: usize = 0;136 var len: usize = 0;
137 while (len < self.stack_addresses.len and self.stack_addresses[len] != 0) {137 while (len < self.stack_addresses.len and self.stack_addresses[len] != 0) {
138 len += 1;138 len += 1;
139 }139 }
140 return .{140 return .{
141 .instruction_addresses = &self.stack_addresses,141 .return_addresses = self.stack_addresses[0..len],
142 .index = len,142 .skipped = if (len == self.stack_addresses.len) .unknown else .none,
143 };143 };
144}144}
145145
test/cases/disable_stack_tracing.zig+2-2
...@@ -9,11 +9,11 @@ pub fn main() !void {...@@ -9,11 +9,11 @@ pub fn main() !void {
99
10 const captured_st = try foo(&stdout.interface, &st_buf);10 const captured_st = try foo(&stdout.interface, &st_buf);
11 try std.debug.writeStackTrace(&captured_st, .{ .writer = &stdout.interface, .mode = .no_color });11 try std.debug.writeStackTrace(&captured_st, .{ .writer = &stdout.interface, .mode = .no_color });
12 try stdout.interface.print("stack trace index: {d}\n", .{captured_st.index});12 try stdout.interface.print("stack trace index: {d}\n", .{captured_st.return_addresses.len});
1313
14 try stdout.interface.flush();14 try stdout.interface.flush();
15}15}
16fn foo(w: *std.Io.Writer, st_buf: []usize) !std.builtin.StackTrace {16fn foo(w: *std.Io.Writer, st_buf: []usize) !std.debug.StackTrace {
17 try std.debug.writeCurrentStackTrace(.{}, .{ .writer = w, .mode = .no_color });17 try std.debug.writeCurrentStackTrace(.{}, .{ .writer = w, .mode = .no_color });
18 return std.debug.captureCurrentStackTrace(.{}, st_buf);18 return std.debug.captureCurrentStackTrace(.{}, st_buf);
19}19}
test/error_traces.zig+30-17
...@@ -1,4 +1,6 @@...@@ -1,4 +1,6 @@
1pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {1const std = @import("std");
2
3pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext, os: std.Target.Os.Tag) void {
2 cases.addCase(.{4 cases.addCase(.{
3 .name = "return",5 .name = "return",
4 .source =6 .source =
...@@ -464,17 +466,33 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {...@@ -464,17 +466,33 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {
464 \\}466 \\}
465 ,467 ,
466 .expect_error = "ThisIsSoSad",468 .expect_error = "ThisIsSoSad",
467 .expect_trace =469 .expect_trace = switch (os) {
468 \\source.zig:8:5: [address] in bar470 // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs,
469 \\ return error.ThisIsSoSad;471 // so our expected result is slightly different for Windows than on other operating
470 \\ ^472 // systems.
471 \\source.zig:5:5: [address] in foo473 .windows =>
472 \\ try bar();474 \\source.zig:8:5: [address] in bar
473 \\ ^475 \\ return error.ThisIsSoSad;
474 \\source.zig:2:5: [address] in main476 \\ ^
475 \\ try foo();477 \\source.zig:5: [address] in foo
476 \\ ^478 \\ try bar();
477 ,479 \\
480 \\source.zig:2:5: [address] in main
481 \\ try foo();
482 \\ ^
483 ,
484 else =>
485 \\source.zig:8:5: [address] in bar
486 \\ return error.ThisIsSoSad;
487 \\ ^
488 \\source.zig:5:5: [address] in foo
489 \\ try bar();
490 \\ ^
491 \\source.zig:2:5: [address] in main
492 \\ try foo();
493 \\ ^
494 ,
495 },
478 .disable_trace_optimized = &.{496 .disable_trace_optimized = &.{
479 .{ .x86_64, .freebsd },497 .{ .x86_64, .freebsd },
480 .{ .x86_64, .netbsd },498 .{ .x86_64, .netbsd },
...@@ -493,10 +511,5 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {...@@ -493,10 +511,5 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {
493 .{ .x86_64, .macos },511 .{ .x86_64, .macos },
494 .{ .aarch64, .macos },512 .{ .aarch64, .macos },
495 },513 },
496 // TODO: the standard library has a bug in PDB parsing where given an address corresponding
497 // to an inline call, the frame we see will be for the *caller*, not the *callee*. As a
498 // result this test gives bogus results on Windows right now.
499 // This is a part of https://codeberg.org/ziglang/zig/issues/30847.
500 .disable_trace_pdb = true,
501 });514 });
502}515}
test/src/ErrorTrace.zig-3
...@@ -17,8 +17,6 @@ pub const Case = struct {...@@ -17,8 +17,6 @@ pub const Case = struct {
17 /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it17 /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it
18 /// seems that LLVM is particularly good at optimizing traces away in those.18 /// seems that LLVM is particularly good at optimizing traces away in those.
19 disable_trace_optimized: []const DisableConfig = &.{},19 disable_trace_optimized: []const DisableConfig = &.{},
20 /// If `true` then we will not test the error trace on Windows due to bugs in PDB handling.
21 disable_trace_pdb: bool = false,
2220
23 pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag };21 pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag };
24 pub const Backend = enum { llvm, selfhosted };22 pub const Backend = enum { llvm, selfhosted };
...@@ -62,7 +60,6 @@ fn addCaseConfig(...@@ -62,7 +60,6 @@ fn addCaseConfig(
62 const b = self.b;60 const b = self.b;
6361
64 const error_tracing: bool = tracing: {62 const error_tracing: bool = tracing: {
65 if (target.result.os.tag == .windows and case.disable_trace_pdb) break :tracing false;
66 if (optimize == .Debug) break :tracing true;63 if (optimize == .Debug) break :tracing true;
67 if (backend != .llvm) break :tracing true;64 if (backend != .llvm) break :tracing true;
68 if (optimize == .ReleaseSmall) break :tracing false;65 if (optimize == .ReleaseSmall) break :tracing false;
test/src/convert-stack-trace.zig+12-12
...@@ -52,24 +52,24 @@ pub fn main(init: std.process.Init) !void {...@@ -52,24 +52,24 @@ pub fn main(init: std.process.Init) !void {
52 continue;52 continue;
53 }53 }
5454
55 const src_col_end = std.mem.indexOf(u8, in_line, ": 0x") orelse {55 const src_pos_end = std.mem.indexOf(u8, in_line, ": 0x") orelse {
56 try w.writeAll(in_line);56 try w.writeAll(in_line);
57 continue;57 continue;
58 };58 };
59 const src_row_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_col_end], ':') orelse {59 const src_pos_start = b: {
60 try w.writeAll(in_line);60 const postfix = ".zig:";
61 continue;61 const postfix_index = std.mem.lastIndexOf(u8, in_line[0..src_pos_end], postfix) orelse {
62 };62 try w.writeAll(in_line);
63 const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_end], ':') orelse {63 continue;
64 try w.writeAll(in_line);64 };
65 continue;65 break :b postfix_index + postfix.len;
66 };66 };
6767
68 const addr_end = std.mem.indexOfPos(u8, in_line, src_col_end, " in ") orelse {68 const addr_end = std.mem.findPos(u8, in_line, src_pos_end, " in ") orelse {
69 try w.writeAll(in_line);69 try w.writeAll(in_line);
70 continue;70 continue;
71 };71 };
72 const symbol_end = std.mem.indexOfPos(u8, in_line, addr_end, " (") orelse {72 const symbol_end = std.mem.findPos(u8, in_line, addr_end, " (") orelse {
73 try w.writeAll(in_line);73 try w.writeAll(in_line);
74 continue;74 continue;
75 };75 };
...@@ -88,10 +88,10 @@ pub fn main(init: std.process.Init) !void {...@@ -88,10 +88,10 @@ pub fn main(init: std.process.Init) !void {
88 //88 //
89 // ...with that first '_' being replaced by its basename.89 // ...with that first '_' being replaced by its basename.
9090
91 const src_path = in_line[0..src_path_end];91 const src_path = in_line[0..src_pos_start];
92 const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0;92 const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0;
93 const symbol_start = addr_end + " in ".len;93 const symbol_start = addr_end + " in ".len;
94 try w.writeAll(in_line[basename_start..src_col_end]);94 try w.writeAll(in_line[basename_start..src_pos_end]);
95 try w.writeAll(": [address] in ");95 try w.writeAll(": [address] in ");
96 try w.writeAll(in_line[symbol_start..symbol_end]);96 try w.writeAll(in_line[symbol_start..symbol_end]);
97 try w.writeByte('\n');97 try w.writeByte('\n');
test/stack_traces.zig+124-10
...@@ -1,4 +1,6 @@...@@ -1,4 +1,6 @@
1pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {1const std = @import("std");
2
3pub fn addCases(cases: *@import("tests.zig").StackTracesContext, os: std.Target.Os.Tag) void {
2 cases.addCase(.{4 cases.addCase(.{
3 .name = "simple panic",5 .name = "simple panic",
4 .source =6 .source =
...@@ -118,13 +120,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {...@@ -118,13 +120,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
118 \\ var stack_trace_buf: [8]usize = undefined;120 \\ var stack_trace_buf: [8]usize = undefined;
119 \\ dumpIt(&captureIt(&stack_trace_buf));121 \\ dumpIt(&captureIt(&stack_trace_buf));
120 \\}122 \\}
121 \\fn captureIt(buf: []usize) std.builtin.StackTrace {123 \\fn captureIt(buf: []usize) std.debug.StackTrace {
122 \\ return captureItInner(buf);124 \\ return captureItInner(buf);
123 \\}125 \\}
124 \\fn dumpIt(st: *const std.builtin.StackTrace) void {126 \\fn dumpIt(st: *const std.debug.StackTrace) void {
125 \\ std.debug.dumpStackTrace(st);127 \\ std.debug.dumpStackTrace(st);
126 \\}128 \\}
127 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {129 \\fn captureItInner(buf: []usize) std.debug.StackTrace {
128 \\ return std.debug.captureCurrentStackTrace(.{}, buf);130 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
129 \\}131 \\}
130 \\const std = @import("std");132 \\const std = @import("std");
...@@ -159,13 +161,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {...@@ -159,13 +161,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
159 \\ var stack_trace_buf: [8]usize = undefined;161 \\ var stack_trace_buf: [8]usize = undefined;
160 \\ dumpIt(&captureIt(&stack_trace_buf));162 \\ dumpIt(&captureIt(&stack_trace_buf));
161 \\}163 \\}
162 \\fn captureIt(buf: []usize) std.builtin.StackTrace {164 \\fn captureIt(buf: []usize) std.debug.StackTrace {
163 \\ return captureItInner(buf);165 \\ return captureItInner(buf);
164 \\}166 \\}
165 \\fn dumpIt(st: *const std.builtin.StackTrace) void {167 \\fn dumpIt(st: *const std.debug.StackTrace) void {
166 \\ std.debug.dumpStackTrace(st);168 \\ std.debug.dumpStackTrace(st);
167 \\}169 \\}
168 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {170 \\fn captureItInner(buf: []usize) std.debug.StackTrace {
169 \\ return std.debug.captureCurrentStackTrace(.{}, buf);171 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
170 \\}172 \\}
171 \\const std = @import("std");173 \\const std = @import("std");
...@@ -188,13 +190,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {...@@ -188,13 +190,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
188 \\fn threadMain(stack_trace_buf: []usize) void {190 \\fn threadMain(stack_trace_buf: []usize) void {
189 \\ dumpIt(&captureIt(stack_trace_buf));191 \\ dumpIt(&captureIt(stack_trace_buf));
190 \\}192 \\}
191 \\fn captureIt(buf: []usize) std.builtin.StackTrace {193 \\fn captureIt(buf: []usize) std.debug.StackTrace {
192 \\ return captureItInner(buf);194 \\ return captureItInner(buf);
193 \\}195 \\}
194 \\fn dumpIt(st: *const std.builtin.StackTrace) void {196 \\fn dumpIt(st: *const std.debug.StackTrace) void {
195 \\ std.debug.dumpStackTrace(st);197 \\ std.debug.dumpStackTrace(st);
196 \\}198 \\}
197 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {199 \\fn captureItInner(buf: []usize) std.debug.StackTrace {
198 \\ return std.debug.captureCurrentStackTrace(.{}, buf);200 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
199 \\}201 \\}
200 \\const std = @import("std");202 \\const std = @import("std");
...@@ -221,4 +223,116 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {...@@ -221,4 +223,116 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
221 \\223 \\
222 ,224 ,
223 });225 });
226
227 cases.addCase(.{
228 .name = "simple inline panic",
229 .source =
230 \\pub fn main() void {
231 \\ foo();
232 \\}
233 \\inline fn foo() void {
234 \\ @panic("oh no");
235 \\}
236 \\
237 ,
238 .unwind = .any,
239 .expect_panic = true,
240 .expect = switch (os) {
241 // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs,
242 // so the first location has only a row.
243 .windows =>
244 \\panic: oh no
245 \\source.zig:5: [address] in foo
246 \\ @panic("oh no");
247 \\
248 \\source.zig:2:8: [address] in main
249 \\ foo();
250 \\ ^
251 \\
252 ,
253 // On all other platforms, we resolve the innermost inline callee but we don't yet
254 // resolve the inline callers.
255 else =>
256 \\panic: oh no
257 \\source.zig:5:5: [address] in foo
258 \\ @panic("oh no");
259 \\ ^
260 ,
261 },
262 .expect_strip = switch (os) {
263 .windows =>
264 \\panic: oh no
265 \\???:?:?: [address] in source.foo
266 \\???:?:?: [address] in source.main
267 \\
268 ,
269 else =>
270 \\panic: oh no
271 \\???:?:?: [address] in source.foo
272 \\
273 ,
274 },
275 });
276
277 // Make sure all inline calls are resolved and in the right order!
278 cases.addCase(.{
279 .name = "nested inline panic",
280 .source =
281 \\pub fn main() void {
282 \\ foo();
283 \\}
284 \\inline fn foo() void {
285 \\ bar();
286 \\}
287 \\inline fn bar() void {
288 \\ baz();
289 \\}
290 \\inline fn baz() void {
291 \\ @panic("oh no");
292 \\}
293 \\
294 ,
295 .unwind = .any,
296 .expect_panic = true,
297 // This switch serves a similar purpose as in "inline panic".
298 .expect = switch (os) {
299 .windows =>
300 \\panic: oh no
301 \\source.zig:11: [address] in baz
302 \\ @panic("oh no");
303 \\
304 \\source.zig:8: [address] in bar
305 \\ baz();
306 \\
307 \\source.zig:5: [address] in foo
308 \\ bar();
309 \\
310 \\source.zig:2:8: [address] in main
311 \\ foo();
312 \\ ^
313 \\
314 ,
315 else =>
316 \\panic: oh no
317 \\source.zig:11:5: [address] in baz
318 \\ @panic("oh no");
319 \\ ^
320 ,
321 },
322 .expect_strip = switch (os) {
323 .windows =>
324 \\panic: oh no
325 \\???:?:?: [address] in baz
326 \\???:?:?: [address] in bar
327 \\???:?:?: [address] in foo
328 \\???:?:?: [address] in main
329 \\
330 ,
331 else =>
332 \\panic: oh no
333 \\???:?:?: [address] in baz
334 \\
335 ,
336 },
337 });
224}338}
test/standalone/coff_dwarf/main.zig+20-2
...@@ -12,8 +12,26 @@ pub fn main(init: std.process.Init) void {...@@ -12,8 +12,26 @@ pub fn main(init: std.process.Init) void {
12 var add_addr: usize = undefined;12 var add_addr: usize = undefined;
13 _ = add(1, 2, &add_addr);13 _ = add(1, 2, &add_addr);
1414
15 const symbol = di.getSymbol(io, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err});15 const debug_gpa = std.debug.getDebugInfoAllocator();
16 defer if (symbol.source_location) |sl| std.debug.getDebugInfoAllocator().free(sl.file_name);16 const symbol_allocator = debug_gpa;
17
18 var symbols: std.ArrayList(std.debug.Symbol) = .empty;
19 defer symbols.deinit(symbol_allocator);
20
21 var text_arena: std.heap.ArenaAllocator = .init(debug_gpa);
22 defer text_arena.deinit();
23
24 di.getSymbols(
25 io,
26 symbol_allocator,
27 text_arena.allocator(),
28 add_addr,
29 false,
30 &symbols,
31 ) catch |err| fatal("failed to get symbol: {t}", .{err});
32
33 if (symbols.items.len != 1) fatal("expected 1 symbol, found {}", .{symbols.items.len});
34 const symbol = symbols.items[0];
1735
18 if (symbol.name == null) fatal("failed to resolve symbol name", .{});36 if (symbol.name == null) fatal("failed to resolve symbol name", .{});
19 if (symbol.compile_unit_name == null) fatal("failed to resolve compile unit", .{});37 if (symbol.compile_unit_name == null) fatal("failed to resolve compile unit", .{});
test/tests.zig+112-22
...@@ -1989,44 +1989,85 @@ const c_abi_targets = blk: {...@@ -1989,44 +1989,85 @@ const c_abi_targets = blk: {
1989 };1989 };
1990};1990};
19911991
1992/// For stack trace tests, we only test native, because external executors are pretty unreliable at1992fn compatible32bitArch(b: *std.Build) ?std.Target.Cpu.Arch {
1993/// stack tracing. However, if there's a 32-bit equivalent target which the host can trivially run,
1994/// we may as well at least test that!
1995fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
1996 const host = b.graph.host.result;1993 const host = b.graph.host.result;
1997 const only_native = (&b.graph.host)[0..1];1994 return switch (host.os.tag) {
1998 if (skip_non_native) return only_native;
1999 const arch32: std.Target.Cpu.Arch = switch (host.os.tag) {
2000 .windows => switch (host.cpu.arch) {1995 .windows => switch (host.cpu.arch) {
2001 .x86_64 => .x86,1996 .x86_64 => .x86,
2002 .aarch64 => .thumb,1997 .aarch64 => .thumb,
2003 .aarch64_be => .thumbeb,1998 .aarch64_be => .thumbeb,
2004 else => return only_native,1999 else => null,
2005 },2000 },
2006 .freebsd => switch (host.cpu.arch) {2001 .freebsd => switch (host.cpu.arch) {
2007 .aarch64 => .arm,2002 .aarch64 => .arm,
2008 .aarch64_be => .armeb,2003 .aarch64_be => .armeb,
2009 else => return only_native,2004 else => null,
2010 },2005 },
2011 .linux, .netbsd => switch (host.cpu.arch) {2006 .linux, .netbsd => switch (host.cpu.arch) {
2012 .x86_64 => .x86,2007 .x86_64 => .x86,
2013 .aarch64 => .arm,2008 .aarch64 => .arm,
2014 .aarch64_be => .armeb,2009 .aarch64_be => .armeb,
2015 else => return only_native,2010 else => null,
2016 },2011 },
2017 else => return only_native,2012 else => null,
2018 };2013 };
2014}
2015
2016/// For stack trace tests, we only test native by default, because external executors are pretty
2017/// unreliable at stack tracing. However, if there's a 32-bit equivalent target which the host can
2018/// trivially run, we may as well at least test that!
2019fn nativeAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
2020 const host = b.graph.host.result;
2021 const only_native = (&b.graph.host)[0..1];
2022 if (skip_non_native) return only_native;
2023 const arch32 = compatible32bitArch(b) orelse return only_native;
2019 return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{2024 return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{
2020 b.graph.host,2025 b.graph.host,
2021 b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }),2026 b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }),
2022 }) catch @panic("OOM");2027 }) catch @panic("OOM");
2023}2028}
20242029
2030fn wineAndCompatible32bit(b: *std.Build, skip_non_native: bool) []const std.Build.ResolvedTarget {
2031 var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty;
2032
2033 const host = b.graph.host.result;
2034
2035 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2036 .cpu_arch = host.cpu.arch,
2037 .os_tag = .windows,
2038 })) catch @panic("OOM");
2039 if (!skip_non_native) {
2040 if (compatible32bitArch(b)) |arch| {
2041 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2042 .cpu_arch = arch,
2043 .os_tag = .windows,
2044 })) catch @panic("OOM");
2045 }
2046 }
2047
2048 return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM");
2049}
2050
2051fn darlingTargets(b: *std.Build) []const std.Build.ResolvedTarget {
2052 var targets: std.ArrayList(std.Build.ResolvedTarget) = .empty;
2053
2054 const host = b.graph.host.result;
2055
2056 targets.append(b.graph.arena, b.resolveTargetQuery(.{
2057 .cpu_arch = host.cpu.arch,
2058 .os_tag = .macos,
2059 })) catch @panic("OOM");
2060
2061 return targets.toOwnedSlice(b.graph.arena) catch @panic("OOM");
2062}
2063
2025pub fn addStackTraceTests(2064pub fn addStackTraceTests(
2026 b: *std.Build,2065 b: *std.Build,
2027 test_filters: []const []const u8,2066 test_filters: []const []const u8,
2028 skip_non_native: bool,2067 skip_non_native: bool,
2029) *Step {2068) *Step {
2069 const step = b.step("test-stack-traces", "Run the stack trace tests");
2070
2030 const convert_exe = b.addExecutable(.{2071 const convert_exe = b.addExecutable(.{
2031 .name = "convert-stack-trace",2072 .name = "convert-stack-trace",
2032 .root_module = b.createModule(.{2073 .root_module = b.createModule(.{
...@@ -2036,19 +2077,41 @@ pub fn addStackTraceTests(...@@ -2036,19 +2077,41 @@ pub fn addStackTraceTests(
2036 }),2077 }),
2037 });2078 });
20382079
2039 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");2080 const host_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
20402081 host_cases.* = .{
2041 cases.* = .{
2042 .b = b,2082 .b = b,
2043 .step = b.step("test-stack-traces", "Run the stack trace tests"),2083 .step = step,
2044 .test_filters = test_filters,2084 .test_filters = test_filters,
2045 .targets = nativeAndCompatible32bit(b, skip_non_native),2085 .targets = nativeAndCompatible32bit(b, skip_non_native),
2046 .convert_exe = convert_exe,2086 .convert_exe = convert_exe,
2047 };2087 };
2088 stack_traces.addCases(host_cases, b.graph.host.result.os.tag);
20482089
2049 stack_traces.addCases(cases);2090 if (b.enable_wine) {
2091 const wine_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
2092 wine_cases.* = .{
2093 .b = b,
2094 .step = step,
2095 .test_filters = test_filters,
2096 .targets = wineAndCompatible32bit(b, skip_non_native),
2097 .convert_exe = convert_exe,
2098 };
2099 stack_traces.addCases(wine_cases, .windows);
2100 }
2101
2102 if (b.enable_darling) {
2103 const darling_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
2104 darling_cases.* = .{
2105 .b = b,
2106 .step = step,
2107 .test_filters = test_filters,
2108 .targets = darlingTargets(b),
2109 .convert_exe = convert_exe,
2110 };
2111 stack_traces.addCases(darling_cases, .macos);
2112 }
20502113
2051 return cases.step;2114 return step;
2052}2115}
20532116
2054pub fn addErrorTraceTests(2117pub fn addErrorTraceTests(
...@@ -2057,6 +2120,8 @@ pub fn addErrorTraceTests(...@@ -2057,6 +2120,8 @@ pub fn addErrorTraceTests(
2057 optimize_modes: []const OptimizeMode,2120 optimize_modes: []const OptimizeMode,
2058 skip_non_native: bool,2121 skip_non_native: bool,
2059) *Step {2122) *Step {
2123 const step = b.step("test-error-traces", "Run the error trace tests");
2124
2060 const convert_exe = b.addExecutable(.{2125 const convert_exe = b.addExecutable(.{
2061 .name = "convert-stack-trace",2126 .name = "convert-stack-trace",
2062 .root_module = b.createModule(.{2127 .root_module = b.createModule(.{
...@@ -2066,19 +2131,44 @@ pub fn addErrorTraceTests(...@@ -2066,19 +2131,44 @@ pub fn addErrorTraceTests(
2066 }),2131 }),
2067 });2132 });
20682133
2069 const cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");2134 const host_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2070 cases.* = .{2135 host_cases.* = .{
2071 .b = b,2136 .b = b,
2072 .step = b.step("test-error-traces", "Run the error trace tests"),2137 .step = step,
2073 .test_filters = test_filters,2138 .test_filters = test_filters,
2074 .targets = nativeAndCompatible32bit(b, skip_non_native),2139 .targets = nativeAndCompatible32bit(b, skip_non_native),
2075 .optimize_modes = optimize_modes,2140 .optimize_modes = optimize_modes,
2076 .convert_exe = convert_exe,2141 .convert_exe = convert_exe,
2077 };2142 };
2143 error_traces.addCases(host_cases, b.graph.host.result.os.tag);
2144
2145 if (b.enable_wine) {
2146 const wine_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2147 wine_cases.* = .{
2148 .b = b,
2149 .step = step,
2150 .test_filters = test_filters,
2151 .targets = wineAndCompatible32bit(b, skip_non_native),
2152 .optimize_modes = optimize_modes,
2153 .convert_exe = convert_exe,
2154 };
2155 error_traces.addCases(wine_cases, .windows);
2156 }
20782157
2079 error_traces.addCases(cases);2158 if (b.enable_darling) {
2159 const darling_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2160 darling_cases.* = .{
2161 .b = b,
2162 .step = step,
2163 .test_filters = test_filters,
2164 .targets = darlingTargets(b),
2165 .optimize_modes = optimize_modes,
2166 .convert_exe = convert_exe,
2167 };
2168 error_traces.addCases(darling_cases, .macos);
2169 }
20802170
2081 return cases.step;2171 return step;
2082}2172}
20832173
2084fn compilerHasPackageManager(b: *std.Build) bool {2174fn compilerHasPackageManager(b: *std.Build) bool {