| author | |
| committer | |
| log | c457939f104595d675974f9e820ccf4187bf8e95 |
| tree | ea45f4d47c50966b57980e6cca5ed35eb47f781b |
| parent | 778f8d557bc2ab59c290e145e9ad87e36d7de220 |
| parent | 6707a5efeea1ab973c3274495bb0a5640e4f568b |
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, |
| 67 | 67 | ||
| 68 | /// The return address associated with creation of this step that can be useful | 68 | /// 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. |
| 70 | debug_stack_trace: std.builtin.StackTrace, | 70 | debug_stack_trace: std.debug.StackTrace, |
| 71 | 71 | ||
| 72 | pub const TestResults = struct { | 72 | pub 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. |
| 329 | pub fn dump(step: *Step, t: Io.Terminal) void { | 329 | pub 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 | }; |
| 448 | 448 | ||
| ... | @@ -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; |
| 13 | const builtin = @import("builtin"); | 13 | const builtin = @import("builtin"); |
| 14 | const native_arch = builtin.cpu.arch; | 14 | const native_arch = builtin.cpu.arch; |
| 15 | const native_os = builtin.os.tag; | 15 | const native_os = builtin.os.tag; |
| 16 | const StackTrace = std.builtin.StackTrace; | ||
| 17 | 16 | ||
| 18 | const root = @import("root"); | 17 | const root = @import("root"); |
| 19 | 18 | ||
| ... | @@ -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 { |
| 563 | 562 | ||
| 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 | } |
| 604 | 603 | ||
| 604 | pub 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. | ||
| 621 | pub 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 | |||
| 605 | pub const StackUnwindOptions = struct { | 633 | pub const StackUnwindOptions = struct { |
| 606 | /// If not `null`, we will ignore all frames up until this return address. This is typically | 634 | /// 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. |
| 623 | pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace { | 651 | pub 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 was | 666 | // 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 cost | 667 | // 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 is | 674 | // 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. |
| 664 | pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void { | 695 | pub 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 | }; |
| 775 | 813 | ||
| 814 | /// Write a previously captured error return trace to `writer`, annotated with source locations. | ||
| 815 | pub 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. |
| 777 | pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { | 824 | pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { |
| 825 | try writeTrace(st.return_addresses, st.skipped, t, true); | ||
| 826 | } | ||
| 827 | |||
| 828 | fn 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 | } |
| 785 | 844 | ||
| 786 | // Fetch `st.index` straight away. Aside from avoiding redundant loads, this prevents issues if | 845 | 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 | } |
| 819 | 885 | ||
| 886 | /// A thin wrapper around `writeErrorReturnTrace` which writes to stderr and ignores write errors. | ||
| 887 | pub 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 | |||
| 820 | const StackIterator = union(enum) { | 895 | const StackIterator = union(enum) { |
| 821 | /// We will first report the current PC of this `CpuContextPtr`, then we will switch to a | 896 | /// 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 | } |
| 1108 | 1183 | ||
| 1109 | fn printSourceAtAddress(io: Io, debug_info: *SelfInfo, t: Io.Terminal, address: usize) Writer.Error!void { | 1184 | const 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, | 1189 | fn 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 | } |
| 1138 | fn printLineInfo( | 1239 | fn 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 {}; |
| 1148 | 1248 | ||
| 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"); |
| 1161 | 1269 | ||
| 1162 | // Show the matching source code line if possible | 1270 | // 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 char | 1274 | // 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 | } |
| 1604 | 1717 | ||
| 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 trace | 1765 | @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 the | 1768 | // 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; |
| 22 | const maxInt = std.math.maxInt; | 22 | const maxInt = std.math.maxInt; |
| 23 | const ArrayList = std.ArrayList; | 23 | const ArrayList = std.ArrayList; |
| 24 | const Endian = std.builtin.Endian; | 24 | const Endian = std.builtin.Endian; |
| 25 | const Reader = std.Io.Reader; | 25 | const Io = std.Io; |
| 26 | const Reader = Io.Reader; | ||
| 27 | const Error = std.debug.SelfInfoError; | ||
| 26 | 28 | ||
| 27 | const Dwarf = @This(); | 29 | const Dwarf = @This(); |
| 28 | 30 | ||
| ... | @@ -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 |
| 1218 | pub fn getLineNumberInfo( | 1220 | pub 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 | } |
| 1545 | 1548 | ||
| 1546 | pub fn getSymbol(di: *Dwarf, gpa: Allocator, endian: Endian, address: u64) !std.debug.Symbol { | 1549 | pub 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 | } |
| 1562 | 1582 | ||
| 1563 | /// DWARF5 7.4: "In the 32-bit DWARF format, all values that represent lengths of DWARF sections and | 1583 | /// 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 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const File = std.Io.File; | 2 | const Io = std.Io; |
| 3 | const File = Io.File; | ||
| 3 | const Allocator = std.mem.Allocator; | 4 | const Allocator = std.mem.Allocator; |
| 4 | const pdb = std.pdb; | 5 | const pdb = std.pdb; |
| 5 | const assert = std.debug.assert; | 6 | const assert = std.debug.assert; |
| ... | @@ -10,7 +11,7 @@ file_reader: *File.Reader, | ... | @@ -10,7 +11,7 @@ file_reader: *File.Reader, |
| 10 | msf: Msf, | 11 | msf: Msf, |
| 11 | allocator: Allocator, | 12 | allocator: Allocator, |
| 12 | string_table: ?*MsfStream, | 13 | string_table: ?*MsfStream, |
| 13 | dbi: ?*MsfStream, | 14 | ipi: ?[]u8, |
| 14 | modules: []Module, | 15 | modules: []Module, |
| 15 | sect_contribs: []pdb.SectionContribEntry, | 16 | sect_contribs: []pdb.SectionContribEntry, |
| 16 | guid: [16]u8, | 17 | guid: [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, | ||
| 28 | 33 | ||
| 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 { |
| 53 | pub fn deinit(self: *Pdb) void { | 59 | pub 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; |
| 69 | 76 | ||
| 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 team | 78 | 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); |
| 87 | 94 | ||
| 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 API | 98 | 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; |
| 94 | 101 | ||
| 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 API | 105 | 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 | }); |
| 119 | 127 | ||
| 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 { |
| 128 | 136 | ||
| 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 | } |
| 150 | 158 | ||
| 159 | pub 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 | |||
| 151 | pub fn parseInfoStream(self: *Pdb) !void { | 168 | pub 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 | } |
| 214 | 231 | ||
| 215 | pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 { | 232 | pub 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); |
| 218 | 235 | 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 | |||
| 254 | pub 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 | |||
| 296 | pub 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; | ||
| 235 | 529 | ||
| 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 | |||
| 556 | pub 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 | |||
| 587 | pub 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 | |||
| 602 | pub 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 | |||
| 618 | pub 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 | } |
| 238 | 643 | ||
| 239 | pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation { | 644 | pub 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 | |||
| 659 | pub 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 | |||
| 664 | pub 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. | ||
| 681 | pub 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 | |||
| 720 | pub 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; | ||
| 243 | 723 | ||
| 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 |
| 290 | 769 | ||
| 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); | ||
| 307 | 774 | ||
| 308 | const line_entry_idx = line_i - 1; | 775 | const line_entry_idx = line_i - 1; |
| 309 | 776 | ||
| ... | @@ -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]); |
| 319 | 786 | ||
| 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; |
| 367 | 834 | ||
| 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); | ||
| 370 | 873 | ||
| 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, |
| 502 | 1005 | ||
| 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 | } |
| 529 | 1032 | ||
| 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)); |
| 532 | 1035 | ||
| 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 | }; |
| 597 | 1100 | ||
| 598 | fn readSparseBitVector(reader: *std.Io.Reader, allocator: Allocator) ![]u32 { | 1101 | fn 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 | } |
| 32 | 32 | ||
| 33 | pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol { | 33 | pub 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, |
| 60 | 68 | 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 | } |
| 79 | pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { | 80 | pub 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 | } |
| 24 | 24 | ||
| 25 | pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol { | 25 | pub 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); |
| 29 | 39 | ||
| ... | @@ -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 { |
| 43 | 53 | ||
| 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 | }; |
| 52 | 62 | ||
| 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 | }; |
| 61 | 71 | ||
| 62 | return .{ | 72 | try symbols.append(symbol_allocator, .{ |
| 63 | .name = ofile_dwarf.getSymbolName(ofile_vaddr) orelse | 73 | .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 | } |
| 82 | pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { | 93 | pub 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 @@ |
| 1 | mutex: Io.Mutex, | 1 | lock: Io.RwLock, |
| 2 | ntdll_handle: ?if (load_dll_notification_procs) *anyopaque else noreturn, | 2 | ntdll_handle: ?if (load_dll_notification_procs) *anyopaque else noreturn, |
| 3 | notification_cookie: ?LDR.DLL_NOTIFICATION.COOKIE, | 3 | notification_cookie: ?LDR.DLL_NOTIFICATION.COOKIE, |
| 4 | modules: std.ArrayList(Module), | 4 | modules: std.ArrayList(Module), |
| 5 | 5 | ||
| 6 | pub const init: SelfInfo = .{ | 6 | pub 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 | } |
| 27 | 27 | ||
| 28 | pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol { | 28 | pub 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 | |||
| 36 | pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { | 51 | pub 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 | } |
| 47 | pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize { | 62 | pub 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 | } |
| 242 | 257 | ||
| 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 index | 293 | 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, |
| 287 | 382 | text_arena, | |
| 288 | error.InvalidDebugInfo, | 383 | native_endian, |
| 289 | error.OutOfMemory, | 384 | addr, |
| 290 | => |e| return e, | 385 | resolve_inline_callers, |
| 291 | 386 | 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 | }; | ||
| 508 | 609 | ||
| 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 | }; |
| 533 | 634 | ||
| 534 | /// Assumes we already hold `si.mutex`. | 635 | /// Assumes we already hold `si.lock`. |
| 535 | fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module { | 636 | fn 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. |
| 83 | const builtin = @import("builtin"); | 83 | const builtin = @import("builtin"); |
| 84 | const StackTrace = std.builtin.StackTrace; | 84 | const StackTrace = std.debug.StackTrace; |
| 85 | 85 | ||
| 86 | const std = @import("std"); | 86 | const std = @import("std"); |
| 87 | const log = std.log.scoped(.DebugAllocator); | 87 | const 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 | } |
| 231 | 231 | ||
| 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 | } |
| 244 | 244 | ||
| ... | @@ -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 | } |
| 346 | 346 | ||
| ... | @@ -508,7 +508,7 @@ pub fn DebugAllocator(comptime config: Config) type { | ... | @@ -508,7 +508,7 @@ pub fn DebugAllocator(comptime config: Config) type { |
| 508 | 508 | ||
| 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 | } |
| 513 | 513 | ||
| 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) { |
| 314 | 314 | ||
| 315 | pub const TypeIndex = u32; | 315 | pub const TypeIndex = u32; |
| 316 | 316 | ||
| 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. | ||
| 321 | pub const ProcSym = extern struct { | 317 | pub 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 | |||
| 510 | pub const IpiStreamVersion = enum(u32) { | ||
| 511 | v40 = 19950410, | ||
| 512 | v41 = 19951122, | ||
| 513 | v50 = 19961031, | ||
| 514 | v70 = 19990903, | ||
| 515 | v80 = 20040203, | ||
| 516 | _, | ||
| 517 | }; | ||
| 518 | |||
| 519 | pub 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 | |||
| 537 | pub const LfRecordPrefix = extern struct { | ||
| 538 | len: u16, | ||
| 539 | kind: LfRecordKind, | ||
| 540 | }; | ||
| 541 | |||
| 542 | pub 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 | |||
| 586 | pub 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 | |||
| 594 | pub 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 | |||
| 602 | pub const InlineSiteSym = extern struct { | ||
| 603 | record_len: u16, | ||
| 604 | record_kind: SymbolKind, | ||
| 605 | parent: u32, | ||
| 606 | end: u32, | ||
| 607 | inlinee: u32, | ||
| 608 | }; | ||
| 609 | |||
| 610 | pub 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 | |||
| 619 | pub const InlineeSourceLineSignature = enum(u32) { normal = 0, ex = 1, _ }; | ||
| 620 | |||
| 621 | pub const InlineeSourceLine = extern struct { | ||
| 622 | inlinee: u32, | ||
| 623 | file_id: u32, | ||
| 624 | source_line_num: u32, | ||
| 625 | }; | ||
| 626 | |||
| 627 | pub const InlineeSourceLineEx = extern struct { | ||
| 628 | inlinee: u32, | ||
| 629 | file_id: u32, | ||
| 630 | source_line_num: u32, | ||
| 631 | count_of_extra_files: u32, | ||
| 632 | }; | ||
| 633 | |||
| 634 | pub 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 are | 171 | /// 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 bad | 172 | /// 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 | } |
| 132 | 132 | ||
| 133 | /// Only valid once `has_induced_failure == true` | 133 | /// Only valid once `has_induced_failure == true` |
| 134 | pub fn getStackTrace(self: *FailingAllocator) std.builtin.StackTrace { | 134 | pub 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 | } |
| 145 | 145 |
test/cases/disable_stack_tracing.zig+2-2| ... | @@ -9,11 +9,11 @@ pub fn main() !void { | ... | @@ -9,11 +9,11 @@ pub fn main() !void { |
| 9 | 9 | ||
| 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}); |
| 13 | 13 | ||
| 14 | try stdout.interface.flush(); | 14 | try stdout.interface.flush(); |
| 15 | } | 15 | } |
| 16 | fn foo(w: *std.Io.Writer, st_buf: []usize) !std.builtin.StackTrace { | 16 | fn 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 @@ |
| 1 | pub fn addCases(cases: *@import("tests.zig").ErrorTracesContext) void { | 1 | const std = @import("std"); |
| 2 | |||
| 3 | pub 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 bar | 470 | // 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 foo | 473 | .windows => |
| 472 | \\ try bar(); | 474 | \\source.zig:8:5: [address] in bar |
| 473 | \\ ^ | 475 | \\ return error.ThisIsSoSad; |
| 474 | \\source.zig:2:5: [address] in main | 476 | \\ ^ |
| 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 it | 17 | /// 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, | ||
| 22 | 20 | ||
| 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; |
| 63 | 61 | ||
| 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 | } |
| 54 | 54 | ||
| 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 | }; |
| 67 | 67 | ||
| 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. |
| 90 | 90 | ||
| 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 @@ |
| 1 | pub fn addCases(cases: *@import("tests.zig").StackTracesContext) void { | 1 | const std = @import("std"); |
| 2 | |||
| 3 | pub 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); |
| 14 | 14 | ||
| 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]; | ||
| 17 | 35 | ||
| 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 | }; |
| 1991 | 1991 | ||
| 1992 | /// For stack trace tests, we only test native, because external executors are pretty unreliable at | 1992 | fn 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! | ||
| 1995 | fn 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! | ||
| 2019 | fn 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 | } |
| 2024 | 2029 | ||
| 2030 | fn 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 | |||
| 2051 | fn 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 | |||
| 2025 | pub fn addStackTraceTests( | 2064 | pub 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 | }); |
| 2038 | 2079 | ||
| 2039 | const cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); | 2080 | const host_cases = b.allocator.create(StackTracesContext) catch @panic("OOM"); |
| 2040 | 2081 | 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); | ||
| 2048 | 2089 | ||
| 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 | } | ||
| 2050 | 2113 | ||
| 2051 | return cases.step; | 2114 | return step; |
| 2052 | } | 2115 | } |
| 2053 | 2116 | ||
| 2054 | pub fn addErrorTraceTests( | 2117 | pub 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 | }); |
| 2068 | 2133 | ||
| 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 | } | ||
| 2078 | 2157 | ||
| 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 | } | ||
| 2080 | 2170 | ||
| 2081 | return cases.step; | 2171 | return step; |
| 2082 | } | 2172 | } |
| 2083 | 2173 | ||
| 2084 | fn compilerHasPackageManager(b: *std.Build) bool { | 2174 | fn compilerHasPackageManager(b: *std.Build) bool { |