| ... | @@ -609,8 +609,33 @@ fn waitForOtherThreadToFinishPanicking() void { | ... | @@ -609,8 +609,33 @@ fn waitForOtherThreadToFinishPanicking() void { |
| 609 | /// This data structure is used by the Zig language code generation and | 609 | /// This data structure is used by the Zig language code generation and |
| 610 | /// therefore must be kept in sync with the compiler implementation. | 610 | /// therefore must be kept in sync with the compiler implementation. |
| 611 | pub const StackTrace = struct { | 611 | pub const StackTrace = struct { |
| 612 | index: usize, | 612 | /// Each element is the "return address" of a function call, meaning the instruction address |
| | 613 | /// which control flow will return to when the function returns. |
| | 614 | /// |
| | 615 | /// The first slice element corresponds to the innermost stack frame, and the last element to |
| | 616 | /// the outermost. |
| | 617 | /// |
| | 618 | /// Inlined function calls do not have meaningful return addresses and are therefore not |
| | 619 | /// included in this slice. Instead, when printing the stack trace, the source locations of |
| | 620 | /// inline calls should be read from debug information and the corresponding "inline frames" |
| | 621 | /// printed in the appropriate locations. |
| 613 | return_addresses: []usize, | 622 | return_addresses: []usize, |
| | 623 | /// Indicates whether any stack frames were omitted from `return_addresses`. |
| | 624 | skipped: SkippedAddresses, |
| | 625 | |
| | 626 | }; |
| | 627 | |
| | 628 | /// Indicates how many addresses were skipped in a trace. |
| | 629 | pub const SkippedAddresses = enum(usize) { |
| | 630 | /// No addresses were omitted: `return_addresses` contains all stack frames, including the |
| | 631 | /// outermost. |
| | 632 | none = 0, |
| | 633 | /// It is not known whether any frames were omitted. |
| | 634 | unknown = std.math.maxInt(usize), |
| | 635 | /// The full stack trace was available, but some frames are not included in |
| | 636 | /// `return_addresses` due to buffer size limitations. The enum value is the exact number of |
| | 637 | /// addresses which were omitted. |
| | 638 | _, |
| 614 | }; | 639 | }; |
| 615 | | 640 | |
| 616 | pub const StackUnwindOptions = struct { | 641 | pub const StackUnwindOptions = struct { |
| ... | @@ -633,8 +658,8 @@ pub const StackUnwindOptions = struct { | ... | @@ -633,8 +658,8 @@ pub const StackUnwindOptions = struct { |
| 633 | /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. | 658 | /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. |
| 634 | pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace { | 659 | pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace { |
| 635 | const empty_trace: StackTrace = .{ | 660 | const empty_trace: StackTrace = .{ |
| 636 | .index = 0, | | |
| 637 | .return_addresses = &.{}, | 661 | .return_addresses = &.{}, |
| | 662 | .skipped = .none, |
| 638 | }; | 663 | }; |
| 639 | if (!std.options.allow_stack_tracing) return empty_trace; | 664 | if (!std.options.allow_stack_tracing) return empty_trace; |
| 640 | var it: StackIterator = .init(options.context); | 665 | var it: StackIterator = .init(options.context); |
| ... | @@ -646,17 +671,17 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: | ... | @@ -646,17 +671,17 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: |
| 646 | var total_frames: usize = 0; | 671 | var total_frames: usize = 0; |
| 647 | var index: usize = 0; | 672 | var index: usize = 0; |
| 648 | var wait_for = options.first_address; | 673 | var wait_for = options.first_address; |
| 649 | // Ideally, we would iterate the whole stack so that the `index` in the returned trace was | 674 | // Ideally, we would iterate the whole stack so that the `index - min(buf.len, index)` would be |
| 650 | // indicative of how many frames were skipped. However, this has a significant runtime cost | 675 | // indicative of how many frames were skipped. However, this has a significant runtime cost |
| 651 | // in some cases, so at least for now, we don't do that. | 676 | // in some cases, so at least for now, we don't do that. |
| 652 | while (index < addr_buf.len) switch (it.next(io)) { | 677 | const skipped: SkippedAddresses = while (index < addr_buf.len) switch (it.next(io)) { |
| 653 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, | 678 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break .unknown, |
| 654 | .end => break, | 679 | .end => break .none, |
| 655 | .frame => |ret_addr| { | 680 | .frame => |ret_addr| { |
| 656 | if (total_frames > 10_000) { | 681 | if (total_frames > 10_000) { |
| 657 | // Limit the number of frames in case of (e.g.) broken debug information which is | 682 | // Limit the number of frames in case of (e.g.) broken debug information which is |
| 658 | // getting unwinding stuck in a loop. | 683 | // getting unwinding stuck in a loop. |
| 659 | break; | 684 | break .unknown; |
| 660 | } | 685 | } |
| 661 | total_frames += 1; | 686 | total_frames += 1; |
| 662 | if (wait_for) |target| { | 687 | if (wait_for) |target| { |
| ... | @@ -666,10 +691,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: | ... | @@ -666,10 +691,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: |
| 666 | addr_buf[index] = ret_addr; | 691 | addr_buf[index] = ret_addr; |
| 667 | index += 1; | 692 | index += 1; |
| 668 | }, | 693 | }, |
| 669 | }; | 694 | } else .unknown; |
| 670 | return .{ | 695 | return .{ |
| 671 | .index = index, | | |
| 672 | .return_addresses = addr_buf[0..index], | 696 | .return_addresses = addr_buf[0..index], |
| | 697 | .skipped = skipped, |
| 673 | }; | 698 | }; |
| 674 | } | 699 | } |
| 675 | /// Write the current stack trace to `writer`, annotated with source locations. | 700 | /// Write the current stack trace to `writer`, annotated with source locations. |
| ... | @@ -792,19 +817,21 @@ pub const FormatStackTrace = struct { | ... | @@ -792,19 +817,21 @@ pub const FormatStackTrace = struct { |
| 792 | | 817 | |
| 793 | /// Write a previously captured error return trace to `writer`, annotated with source locations. | 818 | /// Write a previously captured error return trace to `writer`, annotated with source locations. |
| 794 | pub fn writeErrorReturnTrace(et: *const std.builtin.ErrorReturnTrace, t: Io.Terminal) Writer.Error!void { | 819 | pub fn writeErrorReturnTrace(et: *const std.builtin.ErrorReturnTrace, t: Io.Terminal) Writer.Error!void { |
| 795 | // Fetch `et.index` straight away. Aside from avoiding redundant loads, this prevents issues if | 820 | // We take the slice by value, preventing the length from being mutated if an error occurs while |
| 796 | // errors are encountered while writing the stack trace. | 821 | // writing the stack trace. |
| 797 | try writeTrace(et.instruction_addresses, et.index, t, false); | 822 | const len = @min(et.instruction_addresses.len, et.index); |
| | 823 | const skipped = et.index - len; |
| | 824 | try writeTrace(et.instruction_addresses[0..len], @enumFromInt(skipped), t, false); |
| 798 | } | 825 | } |
| 799 | | 826 | |
| 800 | /// Write a previously captured stack trace to `writer`, annotated with source locations. | 827 | /// Write a previously captured stack trace to `writer`, annotated with source locations. |
| 801 | pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { | 828 | pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { |
| 802 | try writeTrace(st.return_addresses, st.index, t, true); | 829 | try writeTrace(st.return_addresses, st.skipped, t, true); |
| 803 | } | 830 | } |
| 804 | | 831 | |
| 805 | fn writeTrace( | 832 | fn writeTrace( |
| 806 | addresses: []const usize, | 833 | addresses: []const usize, |
| 807 | n_frames: usize, | 834 | skipped: SkippedAddresses, |
| 808 | t: Io.Terminal, | 835 | t: Io.Terminal, |
| 809 | resolve_inline_callers: bool, | 836 | resolve_inline_callers: bool, |
| 810 | ) Writer.Error!void { | 837 | ) Writer.Error!void { |
| ... | @@ -816,7 +843,7 @@ fn writeTrace( | ... | @@ -816,7 +843,7 @@ fn writeTrace( |
| 816 | return; | 843 | return; |
| 817 | } | 844 | } |
| 818 | | 845 | |
| 819 | if (n_frames == 0) return writer.writeAll("(empty stack trace)\n"); | 846 | if (addresses.len == 0) return writer.writeAll("(empty stack trace)\n"); |
| 820 | const di = getSelfDebugInfo() catch |err| switch (err) { | 847 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 821 | error.UnsupportedTarget => { | 848 | error.UnsupportedTarget => { |
| 822 | t.setColor(.dim) catch {}; | 849 | t.setColor(.dim) catch {}; |
| ... | @@ -826,19 +853,26 @@ fn writeTrace( | ... | @@ -826,19 +853,26 @@ fn writeTrace( |
| 826 | }, | 853 | }, |
| 827 | }; | 854 | }; |
| 828 | const io = std.Options.debug_io; | 855 | const io = std.Options.debug_io; |
| 829 | const captured_frames = @min(n_frames, addresses.len); | 856 | for (addresses) |addr| { |
| 830 | for (addresses[0..captured_frames]) |ret_addr| { | 857 | // `addr` is the return address, which is *after* the function call. |
| 831 | // `ret_addr` is the return address, which is *after* the function call. | | |
| 832 | // Subtract 1 to get an address *in* the function call for a better source location. | 858 | // Subtract 1 to get an address *in* the function call for a better source location. |
| 833 | try printSourceAtAddress(io, di, t, .{ | 859 | try printSourceAtAddress(io, di, t, .{ |
| 834 | .address = ret_addr -| StackIterator.ra_call_offset, | 860 | .address = addr -| StackIterator.ra_call_offset, |
| 835 | .resolve_inline_callers = resolve_inline_callers, | 861 | .resolve_inline_callers = resolve_inline_callers, |
| 836 | }); | 862 | }); |
| 837 | } | 863 | } |
| 838 | if (n_frames > captured_frames) { | 864 | switch (skipped) { |
| 839 | t.setColor(.bold) catch {}; | 865 | .none => {}, |
| 840 | try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames}); | 866 | .unknown => { |
| 841 | t.setColor(.reset) catch {}; | 867 | t.setColor(.bold) catch {}; |
| | 868 | try writer.writeAll("(additional stack frames may have been skipped...)\n"); |
| | 869 | t.setColor(.reset) catch {}; |
| | 870 | }, |
| | 871 | else => |n| { |
| | 872 | t.setColor(.bold) catch {}; |
| | 873 | try writer.print("({d} additional stack frames skipped due to buffer size limitations...)\n", .{n}); |
| | 874 | t.setColor(.reset) catch {}; |
| | 875 | }, |
| 842 | } | 876 | } |
| 843 | } | 877 | } |
| 844 | /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. | 878 | /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. |
| ... | @@ -1712,8 +1746,8 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize | ... | @@ -1712,8 +1746,8 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1712 | t.notes[t.index] = note; | 1746 | t.notes[t.index] = note; |
| 1713 | const addrs = &t.addrs[t.index]; | 1747 | const addrs = &t.addrs[t.index]; |
| 1714 | const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs); | 1748 | const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs); |
| 1715 | if (st.index < addrs.len) { | 1749 | if (st.return_addresses.len < addrs.len) { |
| 1716 | @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace | 1750 | @memset(addrs[st.return_addresses.len..], 0); // zero unused frames to indicate end of trace |
| 1717 | } | 1751 | } |
| 1718 | } | 1752 | } |
| 1719 | // Keep counting even if the end is reached so that the | 1753 | // Keep counting even if the end is reached so that the |
| ... | @@ -1731,9 +1765,10 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize | ... | @@ -1731,9 +1765,10 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1731 | stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return; | 1765 | stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return; |
| 1732 | var frames_array_mutable = frames_array; | 1766 | var frames_array_mutable = frames_array; |
| 1733 | const frames = mem.sliceTo(frames_array_mutable[0..], 0); | 1767 | const frames = mem.sliceTo(frames_array_mutable[0..], 0); |
| | 1768 | const len = @min(t.index, frames.len); |
| 1734 | const stack_trace: StackTrace = .{ | 1769 | const stack_trace: StackTrace = .{ |
| 1735 | .index = frames.len, | 1770 | .return_addresses = frames[0..len], |
| 1736 | .return_addresses = frames, | 1771 | .skipped = if (len < frames.len) .none else .unknown, |
| 1737 | }; | 1772 | }; |
| 1738 | writeStackTrace(&stack_trace, stderr) catch return; | 1773 | writeStackTrace(&stack_trace, stderr) catch return; |
| 1739 | } | 1774 | } |