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 {
32633263 <ul>
32643264 <li>Return an error from main</li>
32653265 <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>
32673267 </ul>
32683268 {#header_open|Implementation Details#}
32693269 <p>
lib/compiler/test_runner.zig+3-3
......@@ -144,7 +144,7 @@ fn mainServer(init: std.process.Init.Minimal) !void {
144144 error.SkipZigTest => .skip,
145145 else => s: {
146146 if (@errorReturnTrace()) |trace| {
147 std.debug.dumpStackTrace(trace);
147 std.debug.dumpErrorReturnTrace(trace);
148148 }
149149 break :s .fail;
150150 },
......@@ -312,7 +312,7 @@ fn mainTerminal(init: std.process.Init.Minimal) void {
312312 std.debug.print("FAIL ({t})\n", .{err});
313313 }
314314 if (@errorReturnTrace()) |trace| {
315 std.debug.dumpStackTrace(trace);
315 std.debug.dumpErrorReturnTrace(trace);
316316 }
317317 test_node.end();
318318 },
......@@ -438,7 +438,7 @@ var fuzz_runner: if (builtin.fuzz) struct {
438438 error.SkipZigTest => return,
439439 else => {
440440 if (@errorReturnTrace()) |trace| {
441 std.debug.dumpStackTrace(trace);
441 std.debug.dumpErrorReturnTrace(trace);
442442 }
443443 std.debug.print("failed with error.{t}\n", .{err});
444444 std.process.exit(1);
lib/std/Build/Step.zig+2-2
......@@ -67,7 +67,7 @@ test_results: TestResults,
6767
6868/// The return address associated with creation of this step that can be useful
6969/// to print along with debugging messages.
70debug_stack_trace: std.builtin.StackTrace,
70debug_stack_trace: std.debug.StackTrace,
7171
7272pub const TestResults = struct {
7373 /// 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 {
328328/// For debugging purposes, prints identifying information about this Step.
329329pub fn dump(step: *Step, t: Io.Terminal) void {
330330 const w = t.writer;
331 if (step.debug_stack_trace.instruction_addresses.len > 0) {
331 if (step.debug_stack_trace.return_addresses.len > 0) {
332332 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
333333 std.debug.writeStackTrace(&step.debug_stack_trace, t) catch {};
334334 } else {
lib/std/Thread.zig+2-2
......@@ -442,7 +442,7 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
442442 @call(.auto, f, args) catch |err| {
443443 std.debug.print("error: {s}\n", .{@errorName(err)});
444444 if (@errorReturnTrace()) |trace| {
445 std.debug.dumpStackTrace(trace);
445 std.debug.dumpErrorReturnTrace(trace);
446446 }
447447 };
448448
......@@ -932,7 +932,7 @@ const WasiThreadImpl = struct {
932932 @call(.auto, f, w.args) catch |err| {
933933 std.debug.print("error: {s}\n", .{@errorName(err)});
934934 if (@errorReturnTrace()) |trace| {
935 std.debug.dumpStackTrace(trace);
935 std.debug.dumpErrorReturnTrace(trace);
936936 }
937937 };
938938 },
lib/std/debug.zig+179-65
......@@ -13,7 +13,6 @@ const windows = std.os.windows;
1313const builtin = @import("builtin");
1414const native_arch = builtin.cpu.arch;
1515const native_os = builtin.os.tag;
16const StackTrace = std.builtin.StackTrace;
1716
1817const root = @import("root");
1918
......@@ -39,8 +38,8 @@ pub const cpu_context = @import("debug/cpu_context.zig");
3938/// pub const init: SelfInfo;
4039/// pub fn deinit(si: *SelfInfo, io: Io) void;
4140///
42/// /// Returns the symbol and source location of the instruction at `address`.
43/// pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) SelfInfoError!Symbol;
41/// /// Appends the symbols for the instruction at `address` to `symbols`.
42/// pub fn getSymbols(si: *SelfInfo, io: Io, symbol_allocator: Allocator, text_arena: Allocator, address: usize, include_inline_callers: bool, symbols: *std.ArrayList(Symbol)) SelfInfoError!void;
4443/// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`.
4544/// pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) SelfInfoError![]const u8;
4645/// 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 {
563562
564563 if (@errorReturnTrace()) |t| if (t.index > 0) {
565564 writer.writeAll("error return context:\n") catch break :trace;
566 writeStackTrace(t, stderr) catch break :trace;
565 writeErrorReturnTrace(t, stderr) catch break :trace;
567566 writer.writeAll("\nstack trace:\n") catch break :trace;
568567 };
569568 writeCurrentStackTrace(.{
......@@ -602,6 +601,35 @@ fn waitForOtherThreadToFinishPanicking() void {
602601 }
603602}
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
605633pub const StackUnwindOptions = struct {
606634 /// If not `null`, we will ignore all frames up until this return address. This is typically
607635 /// used to omit intermediate handling code (for instance, a panic handler and its machinery)
......@@ -621,7 +649,10 @@ pub const StackUnwindOptions = struct {
621649///
622650/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
623651pub 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 };
625656 if (!std.options.allow_stack_tracing) return empty_trace;
626657 var it: StackIterator = .init(options.context);
627658 defer it.deinit();
......@@ -632,17 +663,17 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
632663 var total_frames: usize = 0;
633664 var index: usize = 0;
634665 var wait_for = options.first_address;
635 // Ideally, we would iterate the whole stack so that the `index` in the returned trace was
666 // Ideally, we would iterate the whole stack so that the `index - min(buf.len, index)` would be
636667 // indicative of how many frames were skipped. However, this has a significant runtime cost
637668 // in some cases, so at least for now, we don't do that.
638 while (index < addr_buf.len) switch (it.next(io)) {
639 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
640 .end => break,
669 const skipped: SkippedAddresses = while (index < addr_buf.len) switch (it.next(io)) {
670 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break .unknown,
671 .end => break .none,
641672 .frame => |ret_addr| {
642673 if (total_frames > 10_000) {
643674 // Limit the number of frames in case of (e.g.) broken debug information which is
644675 // getting unwinding stuck in a loop.
645 break;
676 break .unknown;
646677 }
647678 total_frames += 1;
648679 if (wait_for) |target| {
......@@ -652,10 +683,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
652683 addr_buf[index] = ret_addr;
653684 index += 1;
654685 },
655 };
686 } else .unknown;
656687 return .{
657 .index = index,
658 .instruction_addresses = addr_buf[0..index],
688 .return_addresses = addr_buf[0..index],
689 .skipped = skipped,
659690 };
660691}
661692/// Write the current stack trace to `writer`, annotated with source locations.
......@@ -663,6 +694,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
663694/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
664695pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {
665696 const writer = t.writer;
697
698 var text_arena: std.heap.ArenaAllocator = .init(getDebugInfoAllocator());
699 defer text_arena.deinit();
700
666701 if (!std.options.allow_stack_tracing) {
667702 t.setColor(.dim) catch {};
668703 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
740775 }
741776 // `ret_addr` is the return address, which is *after* the function call.
742777 // 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 });
744782 printed_any_frame = true;
745783 },
746784 };
......@@ -773,8 +811,29 @@ pub const FormatStackTrace = struct {
773811 }
774812};
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
776823/// Write a previously captured stack trace to `writer`, annotated with source locations.
777824pub 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
778837 const writer = t.writer;
779838 if (!std.options.allow_stack_tracing) {
780839 t.setColor(.dim) catch {};
......@@ -783,10 +842,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void
783842 return;
784843 }
785844
786 // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if
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");
845 if (addresses.len == 0) return writer.writeAll("(empty stack trace)\n");
790846 const di = getSelfDebugInfo() catch |err| switch (err) {
791847 error.UnsupportedTarget => {
792848 t.setColor(.dim) catch {};
......@@ -796,16 +852,26 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void
796852 },
797853 };
798854 const io = std.Options.debug_io;
799 const captured_frames = @min(n_frames, st.instruction_addresses.len);
800 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
801 // `ret_addr` is the return address, which is *after* the function call.
855 for (addresses) |addr| {
856 // `addr` is the return address, which is *after* the function call.
802857 // 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 });
804862 }
805 if (n_frames > captured_frames) {
806 t.setColor(.bold) catch {};
807 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});
808 t.setColor(.reset) catch {};
863 switch (skipped) {
864 .none => {},
865 .unknown => {
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 },
809875 }
810876}
811877/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
......@@ -817,6 +883,15 @@ pub fn dumpStackTrace(st: *const StackTrace) void {
817883 };
818884}
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
820895const StackIterator = union(enum) {
821896 /// We will first report the current PC of this `CpuContextPtr`, then we will switch to a
822897 /// different strategy to actually unwind.
......@@ -1106,48 +1181,77 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
11061181 return ptr;
11071182}
11081183
1109fn printSourceAtAddress(io: Io, debug_info: *SelfInfo, t: Io.Terminal, address: usize) Writer.Error!void {
1110 const symbol: Symbol = debug_info.getSymbol(io, address) catch |err| switch (err) {
1111 error.MissingDebugInfo,
1112 error.UnsupportedDebugInfo,
1113 error.InvalidDebugInfo,
1114 => .unknown,
1115 error.ReadFailed, error.Unexpected, error.Canceled => s: {
1116 t.setColor(.dim) catch {};
1117 try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1118 t.setColor(.reset) catch {};
1119 break :s .unknown;
1120 },
1121 error.OutOfMemory => s: {
1122 t.setColor(.dim) catch {};
1123 try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1124 t.setColor(.reset) catch {};
1125 break :s .unknown;
1126 },
1127 };
1128 defer if (symbol.source_location) |sl| getDebugInfoAllocator().free(sl.file_name);
1129 return printLineInfo(
1184const PrintSourceAddressOptions = struct {
1185 address: usize,
1186 resolve_inline_callers: bool,
1187};
1188
1189fn printSourceAtAddress(
1190 io: Io,
1191 text_arena: *std.heap.ArenaAllocator,
1192 debug_info: *SelfInfo,
1193 t: Io.Terminal,
1194 options: PrintSourceAddressOptions,
1195) Writer.Error!void {
1196 defer _ = text_arena.reset(.retain_capacity);
1197
1198 // Initialize the symbol array with space for at least one element, allocating this on the stack
1199 // in the common case where only one element is needed
1200 var symbol_fallback_allocator = std.heap.stackFallback(@sizeOf(Symbol) + @alignOf(Symbol) - 1, getDebugInfoAllocator());
1201 const symbol_allocator = symbol_fallback_allocator.get();
1202 var symbols = std.ArrayList(Symbol).initCapacity(symbol_allocator, 1) catch unreachable;
1203 defer symbols.deinit(symbol_allocator);
1204
1205 debug_info.getSymbols(
11301206 io,
1131 t,
1132 symbol.source_location,
1133 address,
1134 symbol.name orelse "???",
1135 symbol.compile_unit_name orelse debug_info.getModuleName(io, address) catch "???",
1136 );
1207 symbol_allocator,
1208 text_arena.allocator(),
1209 options.address,
1210 options.resolve_inline_callers,
1211 &symbols,
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 }
11371238}
11381239fn printLineInfo(
11391240 io: Io,
11401241 t: Io.Terminal,
1141 source_location: ?SourceLocation,
1242 debug_info: *SelfInfo,
11421243 address: usize,
1143 symbol_name: []const u8,
1144 compile_unit_name: []const u8,
1244 symbol: Symbol,
11451245) Writer.Error!void {
11461246 const writer = t.writer;
11471247 t.setColor(.bold) catch {};
11481248
1149 if (source_location) |*sl| {
1150 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
1249 if (symbol.source_location) |*sl| {
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 }
11511255 } else {
11521256 try writer.writeAll("???:?:?");
11531257 }
......@@ -1155,12 +1259,16 @@ fn printLineInfo(
11551259 t.setColor(.reset) catch {};
11561260 try writer.writeAll(": ");
11571261 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 });
11591267 t.setColor(.reset) catch {};
11601268 try writer.writeAll("\n");
11611269
11621270 // Show the matching source code line if possible
1163 if (source_location) |sl| {
1271 if (symbol.source_location) |sl| {
11641272 if (printLineFromFile(io, writer, sl)) {
11651273 if (sl.column > 0) {
11661274 // The caret already takes one char
......@@ -1599,7 +1707,12 @@ test "manage resources correctly" {
15991707 var di: SelfInfo = .init;
16001708 defer di.deinit(io);
16011709 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 });
16031716}
16041717
16051718/// 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
16481761 t.notes[t.index] = note;
16491762 const addrs = &t.addrs[t.index];
16501763 const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs);
1651 if (st.index < addrs.len) {
1652 @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace
1764 if (st.return_addresses.len < addrs.len) {
1765 @memset(addrs[st.return_addresses.len..], 0); // zero unused frames to indicate end of trace
16531766 }
16541767 }
16551768 // 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
16671780 stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return;
16681781 var frames_array_mutable = frames_array;
16691782 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
1783 const len = @min(t.index, frames.len);
16701784 const stack_trace: StackTrace = .{
1671 .index = frames.len,
1672 .instruction_addresses = frames,
1785 .return_addresses = frames[0..len],
1786 .skipped = if (len < frames.len) .none else .unknown,
16731787 };
16741788 writeStackTrace(&stack_trace, stderr) catch return;
16751789 }
lib/std/debug/Dwarf.zig+29-9
......@@ -22,7 +22,9 @@ const cast = std.math.cast;
2222const maxInt = std.math.maxInt;
2323const ArrayList = std.ArrayList;
2424const Endian = std.builtin.Endian;
25const Reader = std.Io.Reader;
25const Io = std.Io;
26const Reader = Io.Reader;
27const Error = std.debug.SelfInfoError;
2628
2729const Dwarf = @This();
2830
......@@ -1218,6 +1220,7 @@ pub fn populateSrcLocCache(d: *Dwarf, gpa: Allocator, endian: Endian, cu: *Compi
12181220pub fn getLineNumberInfo(
12191221 d: *Dwarf,
12201222 gpa: Allocator,
1223 text_arena: Allocator,
12211224 endian: Endian,
12221225 compile_unit: *CompileUnit,
12231226 target_address: u64,
......@@ -1230,7 +1233,7 @@ pub fn getLineNumberInfo(
12301233 const file_entry = &slc.files[file_index];
12311234 if (file_entry.dir_index >= slc.directories.len) return bad();
12321235 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 });
12341237 return .{
12351238 .line = entry.line,
12361239 .column = entry.column,
......@@ -1543,21 +1546,38 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
15431546 return str[casted_offset..last :0];
15441547}
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
15471561 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
1548 error.MissingDebugInfo, error.InvalidDebugInfo => return .unknown,
1549 else => return err,
1562 error.EndOfStream => return error.MissingDebugInfo,
1563 error.Overflow => return error.InvalidDebugInfo,
1564 error.ReadFailed, error.InvalidDebugInfo, error.MissingDebugInfo => |e| return e,
15501565 };
1551 return .{
1566 try symbols.append(symbol_allocator, .{
15521567 .name = di.getSymbolName(address),
15531568 .compile_unit_name = compile_unit.die.getAttrString(di, endian, std.dwarf.AT.name, di.section(.debug_str), compile_unit) catch |err| switch (err) {
15541569 error.MissingDebugInfo, error.InvalidDebugInfo => null,
15551570 },
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) {
15571572 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,
15591579 },
1560 };
1580 });
15611581}
15621582
15631583/// 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 @@
11const std = @import("../std.zig");
2const File = std.Io.File;
2const Io = std.Io;
3const File = Io.File;
34const Allocator = std.mem.Allocator;
45const pdb = std.pdb;
56const assert = std.debug.assert;
......@@ -10,7 +11,7 @@ file_reader: *File.Reader,
1011msf: Msf,
1112allocator: Allocator,
1213string_table: ?*MsfStream,
13dbi: ?*MsfStream,
14ipi: ?[]u8,
1415modules: []Module,
1516sect_contribs: []pdb.SectionContribEntry,
1617guid: [16]u8,
......@@ -25,6 +26,10 @@ pub const Module = struct {
2526 symbols: []u8,
2627 subsect_info: []u8,
2728 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
2934 pub fn deinit(self: *Module, allocator: Allocator) void {
3035 allocator.free(self.module_name);
......@@ -32,6 +37,7 @@ pub const Module = struct {
3237 if (self.populated) {
3338 allocator.free(self.symbols);
3439 allocator.free(self.subsect_info);
40 allocator.free(self.inlinee_source_lines);
3541 }
3642 }
3743};
......@@ -41,7 +47,7 @@ pub fn init(gpa: Allocator, file_reader: *File.Reader) !Pdb {
4147 .file_reader = file_reader,
4248 .allocator = gpa,
4349 .string_table = null,
44 .dbi = null,
50 .ipi = null,
4551 .msf = try Msf.init(gpa, file_reader),
4652 .modules = &.{},
4753 .sect_contribs = &.{},
......@@ -53,6 +59,7 @@ pub fn init(gpa: Allocator, file_reader: *File.Reader) !Pdb {
5359pub fn deinit(self: *Pdb) void {
5460 const gpa = self.allocator;
5561 self.msf.deinit(gpa);
62 if (self.ipi) |ipi| gpa.free(ipi);
5663 for (self.modules) |*module| {
5764 module.deinit(gpa);
5865 }
......@@ -67,7 +74,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
6774 const gpa = self.allocator;
6875 const reader = &stream.interface;
6976
70 const header = try reader.takeStruct(std.pdb.DbiStreamHeader, .little);
77 const header = try reader.takeStruct(pdb.DbiStreamHeader, .little);
7178 if (header.version_header != 19990903) // V70, only value observed by LLVM team
7279 return error.UnknownPDBVersion;
7380 // if (header.Age != age)
......@@ -85,14 +92,14 @@ pub fn parseDbiStream(self: *Pdb) !void {
8592 const mod_info = try reader.takeStruct(pdb.ModInfo, .little);
8693 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);
8996 defer module_name.deinit();
9097 this_record_len += try reader.streamDelimiterLimit(&module_name.writer, 0, .limited(1024));
9198 assert(reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
9299 reader.toss(1);
93100 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);
96103 defer obj_file_name.deinit();
97104 this_record_len += try reader.streamDelimiterLimit(&obj_file_name.writer, 0, .limited(1024));
98105 assert(reader.buffered()[0] == 0); // TODO change streamDelimiterLimit API
......@@ -115,6 +122,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
115122 .symbols = undefined,
116123 .subsect_info = undefined,
117124 .checksum_offset = null,
125 .inlinee_source_lines = undefined,
118126 });
119127
120128 mod_info_offset += this_record_len;
......@@ -128,7 +136,7 @@ pub fn parseDbiStream(self: *Pdb) !void {
128136
129137 var sect_cont_offset: usize = 0;
130138 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) {
132140 error.InvalidEnumTag, error.EndOfStream => return error.InvalidDebugInfo,
133141 error.ReadFailed => return error.ReadFailed,
134142 };
......@@ -148,6 +156,15 @@ pub fn parseDbiStream(self: *Pdb) !void {
148156 self.sect_contribs = try sect_contribs.toOwnedSlice();
149157}
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
151168pub fn parseInfoStream(self: *Pdb) !void {
152169 var stream = self.getStream(pdb.StreamType.pdb) orelse return error.InvalidDebugInfo;
153170 const reader = &stream.interface;
......@@ -212,38 +229,500 @@ pub fn parseInfoStream(self: *Pdb) !void {
212229 return error.MissingDebugInfo;
213230}
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 {
216233 _ = self;
217234 std.debug.assert(module.populated);
218
219 var symbol_i: usize = 0;
220 while (symbol_i != module.symbols.len) {
221 const prefix: *align(1) pdb.RecordPrefix = @ptrCast(&module.symbols[symbol_i]);
235 var reader: Io.Reader = .fixed(module.symbols);
236 while (true) {
237 const prefix = reader.takeStructPointer(pdb.RecordPrefix) catch return null;
222238 if (prefix.record_len < 2)
223239 return null;
240 reader.discardAll(prefix.record_len - @sizeOf(u16)) catch return null;
224241 switch (prefix.record_kind) {
225242 .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);
227244 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;
229246 }
230247 },
231248 else => {},
232249 }
233 symbol_i += prefix.record_len + @sizeOf(u16);
234250 }
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 }
236641 return null;
237642}
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 {
240721 std.debug.assert(module.populated);
241722 const subsect_info = module.subsect_info;
242 const gpa = self.allocator;
243723
244724 var sect_offset: usize = 0;
245725 var skip_len: usize = undefined;
246 const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
247726 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
248727 const subsect_hdr: *align(1) pdb.DebugSubsectionHeader = @ptrCast(&subsect_info[sect_offset]);
249728 skip_len = subsect_hdr.length;
......@@ -290,20 +769,8 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S
290769
291770 // line_i == 0 would mean that no matching pdb.LineNumberEntry was found.
292771 if (line_i > 0) {
293 const subsect_index = checksum_offset + block_hdr.name_index;
294 const chksum_hdr: *align(1) pdb.FileChecksumEntryHeader = @ptrCast(&module.subsect_info[subsect_index]);
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);
772 const file_name = try self.getFileName(gpa, module, block_hdr.name_index);
773 errdefer gpa.free(file_name);
307774
308775 const line_entry_idx = line_i - 1;
309776
......@@ -318,7 +785,7 @@ pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.S
318785 const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);
319786
320787 return .{
321 .file_name = source_file_name,
788 .file_name = file_name,
322789 .line = line_num_entry.flags.start,
323790 .column = column,
324791 };
......@@ -366,7 +833,43 @@ pub fn getModule(self: *Pdb, index: usize) !?*Module {
366833 const gpa = self.allocator;
367834
368835 mod.symbols = try reader.readAlloc(gpa, mod.mod_info.sym_byte_size - 4);
836 errdefer gpa.free(mod.symbols);
369837 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
371874 var sect_offset: usize = 0;
372875 var skip_len: usize = undefined;
......@@ -497,7 +1000,7 @@ const MsfStream = struct {
4971000 next_read_pos: u64,
4981001 blocks: []u32,
4991002 block_size: u32,
500 interface: std.Io.Reader,
1003 interface: Io.Reader,
5011004 err: ?Error,
5021005
5031006 const Error = File.Reader.SeekError;
......@@ -527,7 +1030,7 @@ const MsfStream = struct {
5271030 };
5281031 }
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 {
5311034 const ms: *MsfStream = @alignCast(@fieldParentPtr("interface", r));
5321035
5331036 var block_id: usize = @intCast(ms.next_read_pos / ms.block_size);
......@@ -595,7 +1098,7 @@ const MsfStream = struct {
5951098 }
5961099};
5971100
598fn readSparseBitVector(reader: *std.Io.Reader, allocator: Allocator) ![]u32 {
1101fn readSparseBitVector(reader: *Io.Reader, allocator: Allocator) ![]u32 {
5991102 const num_words = try reader.takeInt(u32, .little);
6001103 var list = std.array_list.Managed(u32).init(allocator);
6011104 errdefer list.deinit();
lib/std/debug/SelfInfo/Elf.zig+19-18
......@@ -30,7 +30,15 @@ pub fn deinit(si: *SelfInfo, io: Io) void {
3030 if (si.unwind_cache) |cache| gpa.free(cache);
3131}
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 {
3442 const gpa = std.debug.getDebugInfoAllocator();
3543 const module = try si.findModule(gpa, io, address, .exclusive);
3644 defer si.rwlock.unlock(io);
......@@ -53,28 +61,21 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {
5361 };
5462 loaded_elf.scanned_dwarf = true;
5563 }
56 if (dwarf.getSymbol(gpa, native_endian, vaddr)) |sym| {
57 return sym;
58 } else |err| switch (err) {
59 error.MissingDebugInfo => {},
60
61 error.InvalidDebugInfo,
62 error.OutOfMemory,
63 => |e| return e,
64
65 error.ReadFailed,
66 error.EndOfStream,
67 error.Overflow,
68 error.StreamTooLong,
69 => return error.InvalidDebugInfo,
70 }
64 return dwarf.getSymbols(
65 symbol_allocator,
66 text_arena,
67 native_endian,
68 vaddr,
69 resolve_inline_callers,
70 symbols,
71 );
7172 }
7273 // 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) {
7475 error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo,
7576 error.BadSymtab => return error.InvalidDebugInfo,
7677 error.OutOfMemory => |e| return e,
77 };
78 });
7879}
7980pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
8081 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 {
2222 si.modules.deinit(gpa);
2323}
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;
2635 const gpa = std.debug.getDebugInfoAllocator();
36
2737 const module = try si.findModule(gpa, io, address);
2838 defer si.mutex.unlock(io);
2939
......@@ -43,23 +53,23 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {
4353
4454 const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, io, vaddr) catch {
4555 // Return at least the symbol name if available.
46 return .{
56 return symbols.append(symbol_allocator, .{
4757 .name = try file.lookupSymbolName(vaddr),
4858 .compile_unit_name = null,
4959 .source_location = null,
50 };
60 });
5161 };
5262
5363 const compile_unit = ofile_dwarf.findCompileUnit(native_endian, ofile_vaddr) catch {
5464 // Return at least the symbol name if available.
55 return .{
65 return symbols.append(symbol_allocator, .{
5666 .name = try file.lookupSymbolName(vaddr),
5767 .compile_unit_name = null,
5868 .source_location = null,
59 };
69 });
6070 };
6171
62 return .{
72 try symbols.append(symbol_allocator, .{
6373 .name = ofile_dwarf.getSymbolName(ofile_vaddr) orelse
6474 try file.lookupSymbolName(vaddr),
6575 .compile_unit_name = compile_unit.die.getAttrString(
......@@ -73,11 +83,12 @@ pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol {
7383 },
7484 .source_location = ofile_dwarf.getLineNumberInfo(
7585 gpa,
86 text_arena,
7687 native_endian,
7788 compile_unit,
7889 ofile_vaddr,
7990 ) catch null,
80 };
91 });
8192}
8293pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
8394 _ = si;
lib/std/debug/SelfInfo/Windows.zig+137-36
......@@ -1,10 +1,10 @@
1mutex: Io.Mutex,
1lock: Io.RwLock,
22ntdll_handle: ?if (load_dll_notification_procs) *anyopaque else noreturn,
33notification_cookie: ?LDR.DLL_NOTIFICATION.COOKIE,
44modules: std.ArrayList(Module),
55
66pub const init: SelfInfo = .{
7 .mutex = .init,
7 .lock = .init,
88 .ntdll_handle = null,
99 .notification_cookie = null,
1010 .modules = .empty,
......@@ -25,18 +25,33 @@ pub fn deinit(si: *SelfInfo, io: Io) void {
2525 si.modules.deinit(gpa);
2626}
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 {
2937 const gpa = std.debug.getDebugInfoAllocator();
30 try si.mutex.lock(io);
31 defer si.mutex.unlock(io);
38 try si.lock.lockShared(io);
39 defer si.lock.unlockShared(io);
3240 const module = try si.findModule(gpa, address);
3341 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 );
3549}
50
3651pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
3752 const gpa = std.debug.getDebugInfoAllocator();
38 try si.mutex.lock(io);
39 defer si.mutex.unlock(io);
53 try si.lock.lockShared(io);
54 defer si.lock.unlockShared(io);
4055 const module = try si.findModule(gpa, address);
4156 return module.name orelse {
4257 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 {
4661}
4762pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize {
4863 const gpa = std.debug.getDebugInfoAllocator();
49 try si.mutex.lock(io);
50 defer si.mutex.unlock(io);
64 try si.lock.lockShared(io);
65 defer si.lock.unlockShared(io);
5166 const module = try si.findModule(gpa, address);
5267 return module.base_address;
5368}
......@@ -240,7 +255,14 @@ const Module = struct {
240255 arena.deinit();
241256 }
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 {
244266 pdb: {
245267 const pdb = &(di.pdb orelse break :pdb);
246268 var coff_section: *align(1) const coff.SectionHeader = undefined;
......@@ -270,32 +292,101 @@ const Module = struct {
270292 } orelse {
271293 return error.InvalidDebugInfo; // bad module index
272294 };
273 return .{
274 .name = pdb.getSymbolName(module, vaddr - coff_section.virtual_address),
275 .compile_unit_name = fs.path.basename(module.obj_file_name),
276 .source_location = pdb.getLineNumberInfo(
277 module,
278 vaddr - coff_section.virtual_address,
279 ) catch null,
280 };
295
296 const addr = vaddr - coff_section.virtual_address;
297 const maybe_proc = pdb.getProcSym(module, addr);
298 const compile_unit_name = fs.path.basename(module.obj_file_name);
299 const symbols_top = symbols.items.len;
300 if (maybe_proc) |proc| {
301 const offset_in_func = addr - proc.code_offset;
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;
281375 }
376
282377 dwarf: {
283378 const dwarf = &(di.dwarf orelse break :dwarf);
284 const dwarf_address = vaddr + di.coff_image_base;
285 return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch |err| switch (err) {
286 error.MissingDebugInfo => break :dwarf,
287
288 error.InvalidDebugInfo,
289 error.OutOfMemory,
290 => |e| return e,
291
292 error.ReadFailed,
293 error.EndOfStream,
294 error.Overflow,
295 error.StreamTooLong,
296 => return error.InvalidDebugInfo,
297 };
379 const addr = vaddr + di.coff_image_base;
380 return dwarf.getSymbols(
381 symbol_allocator,
382 text_arena,
383 native_endian,
384 addr,
385 resolve_inline_callers,
386 symbols,
387 );
298388 }
389
299390 return error.MissingDebugInfo;
300391 }
301392 };
......@@ -505,6 +596,16 @@ const Module = struct {
505596 error.ReadFailed,
506597 => |e| return e,
507598 };
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
509610 if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age)
510611 return error.InvalidDebugInfo;
......@@ -531,7 +632,7 @@ const Module = struct {
531632 }
532633};
533634
534/// Assumes we already hold `si.mutex`.
635/// Assumes we already hold `si.lock`.
535636fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module {
536637 for (si.modules.items) |*mod| {
537638 const base = @intFromPtr(mod.entry.DllBase);
......@@ -601,8 +702,8 @@ fn dllNotification(
601702 .LOADED => {},
602703 .UNLOADED => {
603704 const io = std.Options.debug_io;
604 si.mutex.lockUncancelable(io);
605 defer si.mutex.unlock(io);
705 si.lock.lockUncancelable(io);
706 defer si.lock.unlock(io);
606707 for (si.modules.items, 0..) |*mod, mod_index| {
607708 if (mod.entry.DllBase != data.Unloaded.DllBase) continue;
608709 mod.deinit(std.debug.getDebugInfoAllocator(), io);
lib/std/heap/debug_allocator.zig+7-7
......@@ -81,7 +81,7 @@
8181//! Resizing and remapping are forwarded directly to the backing allocator,
8282//! except where such operations would change the category from large to small.
8383const builtin = @import("builtin");
84const StackTrace = std.builtin.StackTrace;
84const StackTrace = std.debug.StackTrace;
8585
8686const std = @import("std");
8787const log = std.log.scoped(.DebugAllocator);
......@@ -229,7 +229,7 @@ pub fn DebugAllocator(comptime config: Config) type {
229229 std.debug.dumpStackTrace(self.getStackTrace(trace_kind));
230230 }
231231
232 fn getStackTrace(self: *LargeAlloc, trace_kind: TraceKind) std.builtin.StackTrace {
232 fn getStackTrace(self: *LargeAlloc, trace_kind: TraceKind) std.debug.StackTrace {
233233 assert(@intFromEnum(trace_kind) < trace_n);
234234 const stack_addresses = &self.stack_addresses[@intFromEnum(trace_kind)];
235235 var len: usize = 0;
......@@ -237,8 +237,8 @@ pub fn DebugAllocator(comptime config: Config) type {
237237 len += 1;
238238 }
239239 return .{
240 .instruction_addresses = stack_addresses,
241 .index = len,
240 .return_addresses = stack_addresses[0..len],
241 .skipped = if (len < stack_addresses.len) .none else .unknown,
242242 };
243243 }
244244
......@@ -339,8 +339,8 @@ pub fn DebugAllocator(comptime config: Config) type {
339339 len += 1;
340340 }
341341 return .{
342 .instruction_addresses = stack_addresses,
343 .index = len,
342 .return_addresses = stack_addresses[0..len],
343 .skipped = if (len < stack_addresses.len) .none else .unknown,
344344 };
345345 }
346346
......@@ -508,7 +508,7 @@ pub fn DebugAllocator(comptime config: Config) type {
508508
509509 fn collectStackTrace(first_trace_addr: usize, addr_buf: *[stack_n]usize) void {
510510 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);
512512 }
513513
514514 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) {
314314
315315pub 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.
321317pub const ProcSym = extern struct {
318 record_len: u16,
319 record_kind: SymbolKind,
322320 parent: u32,
323321 end: u32,
324322 next: u32,
......@@ -508,3 +506,144 @@ pub const SuperBlock = extern struct {
508506 // implement it so we're kind of safe making this assumption for now.
509507 block_map_addr: u32,
510508};
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 {
761761 std.log.err("{t}", .{err});
762762 switch (native_os) {
763763 .freestanding, .other => {},
764 else => if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace),
764 else => if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace),
765765 }
766766 return 1;
767767 };
lib/std/std.zig+2
......@@ -165,6 +165,8 @@ pub const Options = struct {
165165 /// * `debug.dumpCurrentStackTrace`
166166 /// * `debug.writeStackTrace`
167167 /// * `debug.dumpStackTrace`
168 /// * `debug.writeErrorReturnTrace`
169 /// * `debug.dumpErrorReturnTrace`
168170 ///
169171 /// Stack traces can generally be collected and printed when debug info is stripped, but are
170172 /// 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(
6565 if (self.alloc_index == self.fail_index) {
6666 if (!self.has_induced_failure) {
6767 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);
6969 self.has_induced_failure = true;
7070 }
7171 return null;
......@@ -131,15 +131,15 @@ fn free(
131131}
132132
133133/// Only valid once `has_induced_failure == true`
134pub fn getStackTrace(self: *FailingAllocator) std.builtin.StackTrace {
134pub fn getStackTrace(self: *FailingAllocator) std.debug.StackTrace {
135135 std.debug.assert(self.has_induced_failure);
136136 var len: usize = 0;
137137 while (len < self.stack_addresses.len and self.stack_addresses[len] != 0) {
138138 len += 1;
139139 }
140140 return .{
141 .instruction_addresses = &self.stack_addresses,
142 .index = len,
141 .return_addresses = self.stack_addresses[0..len],
142 .skipped = if (len == self.stack_addresses.len) .unknown else .none,
143143 };
144144}
145145
test/cases/disable_stack_tracing.zig+2-2
......@@ -9,11 +9,11 @@ pub fn main() !void {
99
1010 const captured_st = try foo(&stdout.interface, &st_buf);
1111 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
1414 try stdout.interface.flush();
1515}
16fn foo(w: *std.Io.Writer, st_buf: []usize) !std.builtin.StackTrace {
16fn foo(w: *std.Io.Writer, st_buf: []usize) !std.debug.StackTrace {
1717 try std.debug.writeCurrentStackTrace(.{}, .{ .writer = w, .mode = .no_color });
1818 return std.debug.captureCurrentStackTrace(.{}, st_buf);
1919}
test/error_traces.zig+30-17
......@@ -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 {
24 cases.addCase(.{
35 .name = "return",
46 .source =
......@@ -464,17 +466,33 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {
464466 \\}
465467 ,
466468 .expect_error = "ThisIsSoSad",
467 .expect_trace =
468 \\source.zig:8:5: [address] in bar
469 \\ return error.ThisIsSoSad;
470 \\ ^
471 \\source.zig:5:5: [address] in foo
472 \\ try bar();
473 \\ ^
474 \\source.zig:2:5: [address] in main
475 \\ try foo();
476 \\ ^
477 ,
469 .expect_trace = switch (os) {
470 // LLVM doesn't emit column info in the binary annotations for inlinee callees in PDBs,
471 // so our expected result is slightly different for Windows than on other operating
472 // systems.
473 .windows =>
474 \\source.zig:8:5: [address] in bar
475 \\ return error.ThisIsSoSad;
476 \\ ^
477 \\source.zig:5: [address] in foo
478 \\ try bar();
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 },
478496 .disable_trace_optimized = &.{
479497 .{ .x86_64, .freebsd },
480498 .{ .x86_64, .netbsd },
......@@ -493,10 +511,5 @@ pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void {
493511 .{ .x86_64, .macos },
494512 .{ .aarch64, .macos },
495513 },
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,
501514 });
502515}
test/src/ErrorTrace.zig-3
......@@ -17,8 +17,6 @@ pub const Case = struct {
1717 /// LLVM ReleaseSmall builds always have the trace disabled regardless of this field, because it
1818 /// seems that LLVM is particularly good at optimizing traces away in those.
1919 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
2321 pub const DisableConfig = struct { std.Target.Cpu.Arch, std.Target.Os.Tag };
2422 pub const Backend = enum { llvm, selfhosted };
......@@ -62,7 +60,6 @@ fn addCaseConfig(
6260 const b = self.b;
6361
6462 const error_tracing: bool = tracing: {
65 if (target.result.os.tag == .windows and case.disable_trace_pdb) break :tracing false;
6663 if (optimize == .Debug) break :tracing true;
6764 if (backend != .llvm) break :tracing true;
6865 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 {
5252 continue;
5353 }
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 {
5656 try w.writeAll(in_line);
5757 continue;
5858 };
59 const src_row_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_col_end], ':') orelse {
60 try w.writeAll(in_line);
61 continue;
62 };
63 const src_path_end = std.mem.lastIndexOfScalar(u8, in_line[0..src_row_end], ':') orelse {
64 try w.writeAll(in_line);
65 continue;
59 const src_pos_start = b: {
60 const postfix = ".zig:";
61 const postfix_index = std.mem.lastIndexOf(u8, in_line[0..src_pos_end], postfix) orelse {
62 try w.writeAll(in_line);
63 continue;
64 };
65 break :b postfix_index + postfix.len;
6666 };
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 {
6969 try w.writeAll(in_line);
7070 continue;
7171 };
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 {
7373 try w.writeAll(in_line);
7474 continue;
7575 };
......@@ -88,10 +88,10 @@ pub fn main(init: std.process.Init) !void {
8888 //
8989 // ...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];
9292 const basename_start = if (std.mem.lastIndexOfAny(u8, src_path, "/\\")) |i| i + 1 else 0;
9393 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]);
9595 try w.writeAll(": [address] in ");
9696 try w.writeAll(in_line[symbol_start..symbol_end]);
9797 try w.writeByte('\n');
test/stack_traces.zig+124-10
......@@ -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 {
24 cases.addCase(.{
35 .name = "simple panic",
46 .source =
......@@ -118,13 +120,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
118120 \\ var stack_trace_buf: [8]usize = undefined;
119121 \\ dumpIt(&captureIt(&stack_trace_buf));
120122 \\}
121 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
123 \\fn captureIt(buf: []usize) std.debug.StackTrace {
122124 \\ return captureItInner(buf);
123125 \\}
124 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
126 \\fn dumpIt(st: *const std.debug.StackTrace) void {
125127 \\ std.debug.dumpStackTrace(st);
126128 \\}
127 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
129 \\fn captureItInner(buf: []usize) std.debug.StackTrace {
128130 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
129131 \\}
130132 \\const std = @import("std");
......@@ -159,13 +161,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
159161 \\ var stack_trace_buf: [8]usize = undefined;
160162 \\ dumpIt(&captureIt(&stack_trace_buf));
161163 \\}
162 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
164 \\fn captureIt(buf: []usize) std.debug.StackTrace {
163165 \\ return captureItInner(buf);
164166 \\}
165 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
167 \\fn dumpIt(st: *const std.debug.StackTrace) void {
166168 \\ std.debug.dumpStackTrace(st);
167169 \\}
168 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
170 \\fn captureItInner(buf: []usize) std.debug.StackTrace {
169171 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
170172 \\}
171173 \\const std = @import("std");
......@@ -188,13 +190,13 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
188190 \\fn threadMain(stack_trace_buf: []usize) void {
189191 \\ dumpIt(&captureIt(stack_trace_buf));
190192 \\}
191 \\fn captureIt(buf: []usize) std.builtin.StackTrace {
193 \\fn captureIt(buf: []usize) std.debug.StackTrace {
192194 \\ return captureItInner(buf);
193195 \\}
194 \\fn dumpIt(st: *const std.builtin.StackTrace) void {
196 \\fn dumpIt(st: *const std.debug.StackTrace) void {
195197 \\ std.debug.dumpStackTrace(st);
196198 \\}
197 \\fn captureItInner(buf: []usize) std.builtin.StackTrace {
199 \\fn captureItInner(buf: []usize) std.debug.StackTrace {
198200 \\ return std.debug.captureCurrentStackTrace(.{}, buf);
199201 \\}
200202 \\const std = @import("std");
......@@ -221,4 +223,116 @@ pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void {
221223 \\
222224 ,
223225 });
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 });
224338}
test/standalone/coff_dwarf/main.zig+20-2
......@@ -12,8 +12,26 @@ pub fn main(init: std.process.Init) void {
1212 var add_addr: usize = undefined;
1313 _ = add(1, 2, &add_addr);
1414
15 const symbol = di.getSymbol(io, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err});
16 defer if (symbol.source_location) |sl| std.debug.getDebugInfoAllocator().free(sl.file_name);
15 const debug_gpa = std.debug.getDebugInfoAllocator();
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
1836 if (symbol.name == null) fatal("failed to resolve symbol name", .{});
1937 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: {
19891989 };
19901990};
19911991
1992/// For stack trace tests, we only test native, because external executors are pretty unreliable at
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 {
1992fn compatible32bitArch(b: *std.Build) ?std.Target.Cpu.Arch {
19961993 const host = b.graph.host.result;
1997 const only_native = (&b.graph.host)[0..1];
1998 if (skip_non_native) return only_native;
1999 const arch32: std.Target.Cpu.Arch = switch (host.os.tag) {
1994 return switch (host.os.tag) {
20001995 .windows => switch (host.cpu.arch) {
20011996 .x86_64 => .x86,
20021997 .aarch64 => .thumb,
20031998 .aarch64_be => .thumbeb,
2004 else => return only_native,
1999 else => null,
20052000 },
20062001 .freebsd => switch (host.cpu.arch) {
20072002 .aarch64 => .arm,
20082003 .aarch64_be => .armeb,
2009 else => return only_native,
2004 else => null,
20102005 },
20112006 .linux, .netbsd => switch (host.cpu.arch) {
20122007 .x86_64 => .x86,
20132008 .aarch64 => .arm,
20142009 .aarch64_be => .armeb,
2015 else => return only_native,
2010 else => null,
20162011 },
2017 else => return only_native,
2012 else => null,
20182013 };
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;
20192024 return b.graph.arena.dupe(std.Build.ResolvedTarget, &.{
20202025 b.graph.host,
20212026 b.resolveTargetQuery(.{ .cpu_arch = arch32, .os_tag = host.os.tag }),
20222027 }) catch @panic("OOM");
20232028}
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
20252064pub fn addStackTraceTests(
20262065 b: *std.Build,
20272066 test_filters: []const []const u8,
20282067 skip_non_native: bool,
20292068) *Step {
2069 const step = b.step("test-stack-traces", "Run the stack trace tests");
2070
20302071 const convert_exe = b.addExecutable(.{
20312072 .name = "convert-stack-trace",
20322073 .root_module = b.createModule(.{
......@@ -2036,19 +2077,41 @@ pub fn addStackTraceTests(
20362077 }),
20372078 });
20382079
2039 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
2040
2041 cases.* = .{
2080 const host_cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
2081 host_cases.* = .{
20422082 .b = b,
2043 .step = b.step("test-stack-traces", "Run the stack trace tests"),
2083 .step = step,
20442084 .test_filters = test_filters,
20452085 .targets = nativeAndCompatible32bit(b, skip_non_native),
20462086 .convert_exe = convert_exe,
20472087 };
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;
20522115}
20532116
20542117pub fn addErrorTraceTests(
......@@ -2057,6 +2120,8 @@ pub fn addErrorTraceTests(
20572120 optimize_modes: []const OptimizeMode,
20582121 skip_non_native: bool,
20592122) *Step {
2123 const step = b.step("test-error-traces", "Run the error trace tests");
2124
20602125 const convert_exe = b.addExecutable(.{
20612126 .name = "convert-stack-trace",
20622127 .root_module = b.createModule(.{
......@@ -2066,19 +2131,44 @@ pub fn addErrorTraceTests(
20662131 }),
20672132 });
20682133
2069 const cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2070 cases.* = .{
2134 const host_cases = b.allocator.create(ErrorTracesContext) catch @panic("OOM");
2135 host_cases.* = .{
20712136 .b = b,
2072 .step = b.step("test-error-traces", "Run the error trace tests"),
2137 .step = step,
20732138 .test_filters = test_filters,
20742139 .targets = nativeAndCompatible32bit(b, skip_non_native),
20752140 .optimize_modes = optimize_modes,
20762141 .convert_exe = convert_exe,
20772142 };
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;
20822172}
20832173
20842174fn compilerHasPackageManager(b: *std.Build) bool {