| ... | ... | @@ -609,8 +609,33 @@ fn waitForOtherThreadToFinishPanicking() void { |
| 609 | 609 | /// This data structure is used by the Zig language code generation and |
| 610 | 610 | /// therefore must be kept in sync with the compiler implementation. |
| 611 | 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 | 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 | 641 | pub const StackUnwindOptions = struct { |
| ... | ... | @@ -633,8 +658,8 @@ pub const StackUnwindOptions = struct { |
| 633 | 658 | /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. |
| 634 | 659 | pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) StackTrace { |
| 635 | 660 | const empty_trace: StackTrace = .{ |
| 636 | | .index = 0, |
| 637 | 661 | .return_addresses = &.{}, |
| 662 | .skipped = .none, |
| 638 | 663 | }; |
| 639 | 664 | if (!std.options.allow_stack_tracing) return empty_trace; |
| 640 | 665 | var it: StackIterator = .init(options.context); |
| ... | ... | @@ -646,17 +671,17 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: |
| 646 | 671 | var total_frames: usize = 0; |
| 647 | 672 | var index: usize = 0; |
| 648 | 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 | 675 | // indicative of how many frames were skipped. However, this has a significant runtime cost |
| 651 | 676 | // in some cases, so at least for now, we don't do that. |
| 652 | | while (index < addr_buf.len) switch (it.next(io)) { |
| 653 | | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, |
| 654 | | .end => break, |
| 677 | const skipped: SkippedAddresses = while (index < addr_buf.len) switch (it.next(io)) { |
| 678 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break .unknown, |
| 679 | .end => break .none, |
| 655 | 680 | .frame => |ret_addr| { |
| 656 | 681 | if (total_frames > 10_000) { |
| 657 | 682 | // Limit the number of frames in case of (e.g.) broken debug information which is |
| 658 | 683 | // getting unwinding stuck in a loop. |
| 659 | | break; |
| 684 | break .unknown; |
| 660 | 685 | } |
| 661 | 686 | total_frames += 1; |
| 662 | 687 | if (wait_for) |target| { |
| ... | ... | @@ -666,10 +691,10 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: |
| 666 | 691 | addr_buf[index] = ret_addr; |
| 667 | 692 | index += 1; |
| 668 | 693 | }, |
| 669 | | }; |
| 694 | } else .unknown; |
| 670 | 695 | return .{ |
| 671 | | .index = index, |
| 672 | 696 | .return_addresses = addr_buf[0..index], |
| 697 | .skipped = skipped, |
| 673 | 698 | }; |
| 674 | 699 | } |
| 675 | 700 | /// Write the current stack trace to `writer`, annotated with source locations. |
| ... | ... | @@ -792,19 +817,21 @@ pub const FormatStackTrace = struct { |
| 792 | 817 | |
| 793 | 818 | /// Write a previously captured error return trace to `writer`, annotated with source locations. |
| 794 | 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 |
| 796 | | // errors are encountered while writing the stack trace. |
| 797 | | try writeTrace(et.instruction_addresses, et.index, t, false); |
| 820 | // We take the slice by value, preventing the length from being mutated if an error occurs while |
| 821 | // writing the stack trace. |
| 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 | 827 | /// Write a previously captured stack trace to `writer`, annotated with source locations. |
| 801 | 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 | 832 | fn writeTrace( |
| 806 | 833 | addresses: []const usize, |
| 807 | | n_frames: usize, |
| 834 | skipped: SkippedAddresses, |
| 808 | 835 | t: Io.Terminal, |
| 809 | 836 | resolve_inline_callers: bool, |
| 810 | 837 | ) Writer.Error!void { |
| ... | ... | @@ -816,7 +843,7 @@ fn writeTrace( |
| 816 | 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 | 847 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 821 | 848 | error.UnsupportedTarget => { |
| 822 | 849 | t.setColor(.dim) catch {}; |
| ... | ... | @@ -826,19 +853,26 @@ fn writeTrace( |
| 826 | 853 | }, |
| 827 | 854 | }; |
| 828 | 855 | const io = std.Options.debug_io; |
| 829 | | const captured_frames = @min(n_frames, addresses.len); |
| 830 | | for (addresses[0..captured_frames]) |ret_addr| { |
| 831 | | // `ret_addr` is the return address, which is *after* the function call. |
| 856 | for (addresses) |addr| { |
| 857 | // `addr` is the return address, which is *after* the function call. |
| 832 | 858 | // Subtract 1 to get an address *in* the function call for a better source location. |
| 833 | 859 | try printSourceAtAddress(io, di, t, .{ |
| 834 | | .address = ret_addr -| StackIterator.ra_call_offset, |
| 860 | .address = addr -| StackIterator.ra_call_offset, |
| 835 | 861 | .resolve_inline_callers = resolve_inline_callers, |
| 836 | 862 | }); |
| 837 | 863 | } |
| 838 | | if (n_frames > captured_frames) { |
| 839 | | t.setColor(.bold) catch {}; |
| 840 | | try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames}); |
| 841 | | t.setColor(.reset) catch {}; |
| 864 | switch (skipped) { |
| 865 | .none => {}, |
| 866 | .unknown => { |
| 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 | 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 | 1746 | t.notes[t.index] = note; |
| 1713 | 1747 | const addrs = &t.addrs[t.index]; |
| 1714 | 1748 | const st = captureCurrentStackTrace(.{ .first_address = addr }, addrs); |
| 1715 | | if (st.index < addrs.len) { |
| 1716 | | @memset(addrs[st.index..], 0); // zero unused frames to indicate end of trace |
| 1749 | if (st.return_addresses.len < addrs.len) { |
| 1750 | @memset(addrs[st.return_addresses.len..], 0); // zero unused frames to indicate end of trace |
| 1717 | 1751 | } |
| 1718 | 1752 | } |
| 1719 | 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 | 1765 | stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return; |
| 1732 | 1766 | var frames_array_mutable = frames_array; |
| 1733 | 1767 | const frames = mem.sliceTo(frames_array_mutable[0..], 0); |
| 1768 | const len = @min(t.index, frames.len); |
| 1734 | 1769 | const stack_trace: StackTrace = .{ |
| 1735 | | .index = frames.len, |
| 1736 | | .return_addresses = frames, |
| 1770 | .return_addresses = frames[0..len], |
| 1771 | .skipped = if (len < frames.len) .none else .unknown, |
| 1737 | 1772 | }; |
| 1738 | 1773 | writeStackTrace(&stack_trace, stderr) catch return; |
| 1739 | 1774 | } |