| author | |
| committer | |
| log | 2ab650b4817cbb22244c17de828e82cbb0ccf15e |
| tree | deebb1090f939f52a363de30179f2136f8819588 |
| parent | 9434bab3134edadae7ae7e575f6b025cafc6a59a |
| signature |
...and just deal with signal handlers by adding 1 to create a fake
"return address". The system I tried out where the addresses returned by
`StackIterator` were pre-subtracted didn't play nicely with error
traces, which in hindsight, makes perfect sense. This definition also
removes some ugly off-by-one issues in matching `first_address`, so I do
think this is a better approach.7 files changed, 65 insertions(+), 48 deletions(-)
lib/std/debug.zig+21-18| ... | ... | @@ -577,14 +577,12 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) |
| 577 | 577 | while (true) switch (it.next()) { |
| 578 | 578 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, |
| 579 | 579 | .end => break, |
| 580 | .frame => |pc_addr| { | |
| 580 | .frame => |ret_addr| { | |
| 581 | 581 | if (wait_for) |target| { |
| 582 | // Possible off-by-one error: `pc_addr` might be one less than the return address (so | |
| 583 | // that it falls *inside* the function call), while `target` *is* a return address. | |
| 584 | if (pc_addr != target and pc_addr + 1 != target) continue; | |
| 582 | if (ret_addr != target) continue; | |
| 585 | 583 | wait_for = null; |
| 586 | 584 | } |
| 587 | if (frame_idx < addr_buf.len) addr_buf[frame_idx] = pc_addr; | |
| 585 | if (frame_idx < addr_buf.len) addr_buf[frame_idx] = ret_addr; | |
| 588 | 586 | frame_idx += 1; |
| 589 | 587 | }, |
| 590 | 588 | }; |
| ... | ... | @@ -659,14 +657,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_ |
| 659 | 657 | } |
| 660 | 658 | }, |
| 661 | 659 | .end => break, |
| 662 | .frame => |pc_addr| { | |
| 660 | .frame => |ret_addr| { | |
| 663 | 661 | if (wait_for) |target| { |
| 664 | // Possible off-by-one error: `pc_addr` might be one less than the return address (so | |
| 665 | // that it falls *inside* the function call), while `target` *is* a return address. | |
| 666 | if (pc_addr != target and pc_addr + 1 != target) continue; | |
| 662 | if (ret_addr != target) continue; | |
| 667 | 663 | wait_for = null; |
| 668 | 664 | } |
| 669 | try printSourceAtAddress(di_gpa, di, writer, pc_addr, tty_config); | |
| 665 | // `ret_addr` is the return address, which is *after* the function call. | |
| 666 | // Subtract 1 to get an address *in* the function call for a better source location. | |
| 667 | try printSourceAtAddress(di_gpa, di, writer, ret_addr -| 1, tty_config); | |
| 670 | 668 | printed_any_frame = true; |
| 671 | 669 | }, |
| 672 | 670 | }; |
| ... | ... | @@ -712,8 +710,10 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c |
| 712 | 710 | }, |
| 713 | 711 | }; |
| 714 | 712 | const captured_frames = @min(n_frames, st.instruction_addresses.len); |
| 715 | for (st.instruction_addresses[0..captured_frames]) |pc_addr| { | |
| 716 | try printSourceAtAddress(di_gpa, di, writer, pc_addr, tty_config); | |
| 713 | for (st.instruction_addresses[0..captured_frames]) |ret_addr| { | |
| 714 | // `ret_addr` is the return address, which is *after* the function call. | |
| 715 | // Subtract 1 to get an address *in* the function call for a better source location. | |
| 716 | try printSourceAtAddress(di_gpa, di, writer, ret_addr -| 1, tty_config); | |
| 717 | 717 | } |
| 718 | 718 | if (n_frames > captured_frames) { |
| 719 | 719 | tty_config.setColor(writer, .bold) catch {}; |
| ... | ... | @@ -787,7 +787,7 @@ const StackIterator = union(enum) { |
| 787 | 787 | } |
| 788 | 788 | |
| 789 | 789 | const Result = union(enum) { |
| 790 | /// A stack frame has been found; this is the corresponding program counter address. | |
| 790 | /// A stack frame has been found; this is the corresponding return address. | |
| 791 | 791 | frame: usize, |
| 792 | 792 | /// The end of the stack has been reached. |
| 793 | 793 | end, |
| ... | ... | @@ -797,18 +797,21 @@ const StackIterator = union(enum) { |
| 797 | 797 | err: SelfInfo.Error, |
| 798 | 798 | }, |
| 799 | 799 | }; |
| 800 | ||
| 800 | 801 | fn next(it: *StackIterator) Result { |
| 801 | 802 | switch (it.*) { |
| 802 | 803 | .di_first => |unwind_context| { |
| 803 | 804 | const first_pc = unwind_context.pc; |
| 804 | 805 | if (first_pc == 0) return .end; |
| 805 | 806 | it.* = .{ .di = unwind_context }; |
| 806 | return .{ .frame = first_pc }; | |
| 807 | // The caller expects *return* addresses, where they will subtract 1 to find the address of the call. | |
| 808 | // However, we have the actual current PC, which should not be adjusted. Compensate by adding 1. | |
| 809 | return .{ .frame = first_pc +| 1 }; | |
| 807 | 810 | }, |
| 808 | 811 | .di => |*unwind_context| { |
| 809 | 812 | const di = getSelfDebugInfo() catch unreachable; |
| 810 | 813 | const di_gpa = getDebugInfoAllocator(); |
| 811 | di.unwindFrame(di_gpa, unwind_context) catch |err| { | |
| 814 | const ret_addr = di.unwindFrame(di_gpa, unwind_context) catch |err| { | |
| 812 | 815 | const pc = unwind_context.pc; |
| 813 | 816 | it.* = .{ .fp = unwind_context.getFp() }; |
| 814 | 817 | return .{ .switch_to_fp = .{ |
| ... | ... | @@ -816,8 +819,8 @@ const StackIterator = union(enum) { |
| 816 | 819 | .err = err, |
| 817 | 820 | } }; |
| 818 | 821 | }; |
| 819 | const pc = unwind_context.pc; | |
| 820 | return if (pc == 0) .end else .{ .frame = pc }; | |
| 822 | if (ret_addr <= 1) return .end; | |
| 823 | return .{ .frame = ret_addr }; | |
| 821 | 824 | }, |
| 822 | 825 | .fp => |fp| { |
| 823 | 826 | if (fp == 0) return .end; // we reached the "sentinel" base pointer |
| ... | ... | @@ -845,7 +848,7 @@ const StackIterator = union(enum) { |
| 845 | 848 | it.fp = bp; |
| 846 | 849 | const ra = stripInstructionPtrAuthCode(ra_ptr.*); |
| 847 | 850 | if (ra <= 1) return .end; |
| 848 | return .{ .frame = ra - 1 }; | |
| 851 | return .{ .frame = ra }; | |
| 849 | 852 | }, |
| 850 | 853 | } |
| 851 | 854 | } |
lib/std/debug/SelfInfo.zig+20-15| ... | ... | @@ -53,7 +53,7 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void { |
| 53 | 53 | if (Module.LookupCache != void) self.lookup_cache.deinit(gpa); |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!void { | |
| 56 | pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { | |
| 57 | 57 | comptime assert(supports_unwinding); |
| 58 | 58 | const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); |
| 59 | 59 | const gop = try self.modules.getOrPut(gpa, module.key()); |
| ... | ... | @@ -124,15 +124,14 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) |
| 124 | 124 | /// /// pointer is unknown, 0 may be returned instead. |
| 125 | 125 | /// pub fn getFp(uc: *UnwindContext) usize; |
| 126 | 126 | /// }; |
| 127 | /// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame. | |
| 128 | /// /// The caller will read the new instruction poiter from the `pc` field. | |
| 129 | /// /// `pc = 0` indicates end of stack / no more frames. | |
| 127 | /// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame, and returns | |
| 128 | /// /// the frame's return address. | |
| 130 | 129 | /// pub fn unwindFrame( |
| 131 | 130 | /// mod: *const Module, |
| 132 | 131 | /// gpa: Allocator, |
| 133 | 132 | /// di: *DebugInfo, |
| 134 | 133 | /// ctx: *UnwindContext, |
| 135 | /// ) SelfInfo.Error!void; | |
| 134 | /// ) SelfInfo.Error!usize; | |
| 136 | 135 | /// ``` |
| 137 | 136 | const Module: type = Module: { |
| 138 | 137 | // Allow overriding the target-specific `SelfInfo` implementation by exposing `root.debug.Module`. |
| ... | ... | @@ -312,7 +311,7 @@ pub const DwarfUnwindContext = struct { |
| 312 | 311 | unwind: *const Dwarf.Unwind, |
| 313 | 312 | load_offset: usize, |
| 314 | 313 | explicit_fde_offset: ?usize, |
| 315 | ) Error!void { | |
| 314 | ) Error!usize { | |
| 316 | 315 | return unwindFrameInner(context, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) { |
| 317 | 316 | error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e, |
| 318 | 317 | |
| ... | ... | @@ -360,10 +359,10 @@ pub const DwarfUnwindContext = struct { |
| 360 | 359 | unwind: *const Dwarf.Unwind, |
| 361 | 360 | load_offset: usize, |
| 362 | 361 | explicit_fde_offset: ?usize, |
| 363 | ) !void { | |
| 362 | ) !usize { | |
| 364 | 363 | comptime assert(supports_unwinding); |
| 365 | 364 | |
| 366 | if (context.pc == 0) return; | |
| 365 | if (context.pc == 0) return 0; | |
| 367 | 366 | |
| 368 | 367 | const pc_vaddr = context.pc - load_offset; |
| 369 | 368 | |
| ... | ... | @@ -443,13 +442,19 @@ pub const DwarfUnwindContext = struct { |
| 443 | 442 | // The new CPU context is complete; flush changes. |
| 444 | 443 | context.cpu_context = new_cpu_context; |
| 445 | 444 | |
| 446 | // Also update the stored pc. However, because `return_address` points to the instruction | |
| 447 | // *after* the call, it could (in the case of noreturn functions) actually point outside of | |
| 448 | // the caller's address range, meaning an FDE lookup would fail. We can handle this by | |
| 449 | // subtracting 1 from `return_address` so that the next lookup is guaranteed to land inside | |
| 450 | // the `call` instruction. The exception to this rule is signal frames, where the return | |
| 451 | // address is the same instruction that triggered the handler. | |
| 452 | context.pc = if (cie.is_signal_frame) return_address else return_address -| 1; | |
| 445 | // The caller will subtract 1 from the return address to get an address corresponding to the | |
| 446 | // function call. However, if this is a signal frame, that's actually incorrect, because the | |
| 447 | // "return address" we have is the instruction which triggered the signal (if the signal | |
| 448 | // handler returned, the instruction would be re-run). Compensate for this by incrementing | |
| 449 | // the address in that case. | |
| 450 | const adjusted_ret_addr = if (cie.is_signal_frame) return_address +| 1 else return_address; | |
| 451 | ||
| 452 | // We also want to do that same subtraction here to get the PC for the next frame's FDE. | |
| 453 | // This is because if the callee was noreturn, then the function call might be the caller's | |
| 454 | // last instruction, so `return_address` might actually point outside of it! | |
| 455 | context.pc = adjusted_ret_addr -| 1; | |
| 456 | ||
| 457 | return adjusted_ret_addr; | |
| 453 | 458 | } |
| 454 | 459 | /// Since register rules are applied (usually) during a panic, |
| 455 | 460 | /// checked addition / subtraction is used so that we can return |
lib/std/debug/SelfInfo/DarwinModule.zig+9-3| ... | ... | @@ -324,7 +324,7 @@ pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext; |
| 324 | 324 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). |
| 325 | 325 | /// If the compact encoding can't encode a way to unwind a frame, it will |
| 326 | 326 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. |
| 327 | pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!void { | |
| 327 | pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize { | |
| 328 | 328 | return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) { |
| 329 | 329 | error.InvalidDebugInfo, |
| 330 | 330 | error.MissingDebugInfo, |
| ... | ... | @@ -340,7 +340,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, |
| 340 | 340 | => return error.InvalidDebugInfo, |
| 341 | 341 | }; |
| 342 | 342 | } |
| 343 | fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !void { | |
| 343 | fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { | |
| 344 | 344 | if (di.unwind == null) di.unwind = module.loadUnwindInfo(); |
| 345 | 345 | const unwind = &di.unwind.?; |
| 346 | 346 | |
| ... | ... | @@ -640,7 +640,13 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, |
| 640 | 640 | else => comptime unreachable, // unimplemented |
| 641 | 641 | }; |
| 642 | 642 | |
| 643 | context.pc = std.debug.stripInstructionPtrAuthCode(new_ip) -| 1; | |
| 643 | const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip); | |
| 644 | ||
| 645 | // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this | |
| 646 | // function's last instruction making `ret_addr` one byte past its end. | |
| 647 | context.pc = ret_addr -| 1; | |
| 648 | ||
| 649 | return ret_addr; | |
| 644 | 650 | } |
| 645 | 651 | pub const DebugInfo = struct { |
| 646 | 652 | unwind: ?Unwind, |
lib/std/debug/SelfInfo/ElfModule.zig+1-1| ... | ... | @@ -230,7 +230,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro |
| 230 | 230 | else => unreachable, |
| 231 | 231 | } |
| 232 | 232 | } |
| 233 | pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!void { | |
| 233 | pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize { | |
| 234 | 234 | if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di); |
| 235 | 235 | std.debug.assert(di.unwind[0] != null); |
| 236 | 236 | for (&di.unwind) |*opt_unwind| { |
lib/std/debug/SelfInfo/WindowsModule.zig+6-3| ... | ... | @@ -373,7 +373,7 @@ pub const UnwindContext = struct { |
| 373 | 373 | return ctx.cur.getRegs().bp; |
| 374 | 374 | } |
| 375 | 375 | }; |
| 376 | pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !void { | |
| 376 | pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { | |
| 377 | 377 | _ = module; |
| 378 | 378 | _ = gpa; |
| 379 | 379 | _ = di; |
| ... | ... | @@ -403,9 +403,12 @@ pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, |
| 403 | 403 | const tib = &windows.teb().NtTib; |
| 404 | 404 | if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { |
| 405 | 405 | context.pc = 0; |
| 406 | } else { | |
| 407 | context.pc = next_regs.ip -| 1; | |
| 406 | return 0; | |
| 408 | 407 | } |
| 408 | // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this | |
| 409 | // function's last instruction making `next_regs.ip` one byte past its end. | |
| 410 | context.pc = next_regs.ip -| 1; | |
| 411 | return next_regs.ip; | |
| 409 | 412 | } |
| 410 | 413 | |
| 411 | 414 | const WindowsModule = @This(); |
test/standalone/stack_iterator/unwind.zig+4-4| ... | ... | @@ -3,7 +3,7 @@ const builtin = @import("builtin"); |
| 3 | 3 | const fatal = std.process.fatal; |
| 4 | 4 | |
| 5 | 5 | noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { |
| 6 | expected[0] = @returnAddress() - 1; | |
| 6 | expected[0] = @returnAddress(); | |
| 7 | 7 | return std.debug.captureCurrentStackTrace(.{ |
| 8 | 8 | .first_address = @returnAddress(), |
| 9 | 9 | .allow_unsafe_unwind = true, |
| ... | ... | @@ -58,12 +58,12 @@ noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr |
| 58 | 58 | } |
| 59 | 59 | } |
| 60 | 60 | |
| 61 | expected[1] = @returnAddress() - 1; | |
| 61 | expected[1] = @returnAddress(); | |
| 62 | 62 | return frame3(expected, addr_buf); |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | 65 | noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { |
| 66 | expected[2] = @returnAddress() - 1; | |
| 66 | expected[2] = @returnAddress(); | |
| 67 | 67 | |
| 68 | 68 | // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding |
| 69 | 69 | // to exercise the stack-indirect encoding path |
| ... | ... | @@ -74,7 +74,7 @@ noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | 76 | noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { |
| 77 | expected[3] = @returnAddress() - 1; | |
| 77 | expected[3] = @returnAddress(); | |
| 78 | 78 | return frame1(expected, addr_buf); |
| 79 | 79 | } |
| 80 | 80 |
test/standalone/stack_iterator/unwind_freestanding.zig+4-4| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | const std = @import("std"); |
| 4 | 4 | |
| 5 | 5 | noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { |
| 6 | expected[0] = @returnAddress() - 1; | |
| 6 | expected[0] = @returnAddress(); | |
| 7 | 7 | return std.debug.captureCurrentStackTrace(.{ |
| 8 | 8 | .first_address = @returnAddress(), |
| 9 | 9 | .allow_unsafe_unwind = true, |
| ... | ... | @@ -11,12 +11,12 @@ noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr |
| 11 | 11 | } |
| 12 | 12 | |
| 13 | 13 | noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { |
| 14 | expected[1] = @returnAddress() - 1; | |
| 14 | expected[1] = @returnAddress(); | |
| 15 | 15 | return frame3(expected, addr_buf); |
| 16 | 16 | } |
| 17 | 17 | |
| 18 | 18 | noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { |
| 19 | expected[2] = @returnAddress() - 1; | |
| 19 | expected[2] = @returnAddress(); | |
| 20 | 20 | |
| 21 | 21 | // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding |
| 22 | 22 | // to exercise the stack-indirect encoding path |
| ... | ... | @@ -27,7 +27,7 @@ noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | 29 | noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { |
| 30 | expected[3] = @returnAddress() - 1; | |
| 30 | expected[3] = @returnAddress(); | |
| 31 | 31 | return frame1(expected, addr_buf); |
| 32 | 32 | } |
| 33 | 33 |