authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-18 13:32:47+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:55+01:00
log2ab650b4817cbb22244c17de828e82cbb0ccf15e
treedeebb1090f939f52a363de30179f2136f8819588
parent9434bab3134edadae7ae7e575f6b025cafc6a59a
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: go back to storing return addresses instead of call addresses

...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,14 +577,12 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize)
577 while (true) switch (it.next()) {577 while (true) switch (it.next()) {
578 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,578 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
579 .end => break,579 .end => break,
580 .frame => |pc_addr| {580 .frame => |ret_addr| {
581 if (wait_for) |target| {581 if (wait_for) |target| {
582 // Possible off-by-one error: `pc_addr` might be one less than the return address (so582 if (ret_addr != target) continue;
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;
585 wait_for = null;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 frame_idx += 1;586 frame_idx += 1;
589 },587 },
590 };588 };
...@@ -659,14 +657,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_...@@ -659,14 +657,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
659 }657 }
660 },658 },
661 .end => break,659 .end => break,
662 .frame => |pc_addr| {660 .frame => |ret_addr| {
663 if (wait_for) |target| {661 if (wait_for) |target| {
664 // Possible off-by-one error: `pc_addr` might be one less than the return address (so662 if (ret_addr != target) continue;
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;
667 wait_for = null;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 printed_any_frame = true;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,8 +710,10 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
712 },710 },
713 };711 };
714 const captured_frames = @min(n_frames, st.instruction_addresses.len);712 const captured_frames = @min(n_frames, st.instruction_addresses.len);
715 for (st.instruction_addresses[0..captured_frames]) |pc_addr| {713 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
716 try printSourceAtAddress(di_gpa, di, writer, pc_addr, tty_config);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 if (n_frames > captured_frames) {718 if (n_frames > captured_frames) {
719 tty_config.setColor(writer, .bold) catch {};719 tty_config.setColor(writer, .bold) catch {};
...@@ -787,7 +787,7 @@ const StackIterator = union(enum) {...@@ -787,7 +787,7 @@ const StackIterator = union(enum) {
787 }787 }
788788
789 const Result = union(enum) {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 frame: usize,791 frame: usize,
792 /// The end of the stack has been reached.792 /// The end of the stack has been reached.
793 end,793 end,
...@@ -797,18 +797,21 @@ const StackIterator = union(enum) {...@@ -797,18 +797,21 @@ const StackIterator = union(enum) {
797 err: SelfInfo.Error,797 err: SelfInfo.Error,
798 },798 },
799 };799 };
800
800 fn next(it: *StackIterator) Result {801 fn next(it: *StackIterator) Result {
801 switch (it.*) {802 switch (it.*) {
802 .di_first => |unwind_context| {803 .di_first => |unwind_context| {
803 const first_pc = unwind_context.pc;804 const first_pc = unwind_context.pc;
804 if (first_pc == 0) return .end;805 if (first_pc == 0) return .end;
805 it.* = .{ .di = unwind_context };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 .di => |*unwind_context| {811 .di => |*unwind_context| {
809 const di = getSelfDebugInfo() catch unreachable;812 const di = getSelfDebugInfo() catch unreachable;
810 const di_gpa = getDebugInfoAllocator();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 const pc = unwind_context.pc;815 const pc = unwind_context.pc;
813 it.* = .{ .fp = unwind_context.getFp() };816 it.* = .{ .fp = unwind_context.getFp() };
814 return .{ .switch_to_fp = .{817 return .{ .switch_to_fp = .{
...@@ -816,8 +819,8 @@ const StackIterator = union(enum) {...@@ -816,8 +819,8 @@ const StackIterator = union(enum) {
816 .err = err,819 .err = err,
817 } };820 } };
818 };821 };
819 const pc = unwind_context.pc;822 if (ret_addr <= 1) return .end;
820 return if (pc == 0) .end else .{ .frame = pc };823 return .{ .frame = ret_addr };
821 },824 },
822 .fp => |fp| {825 .fp => |fp| {
823 if (fp == 0) return .end; // we reached the "sentinel" base pointer826 if (fp == 0) return .end; // we reached the "sentinel" base pointer
...@@ -845,7 +848,7 @@ const StackIterator = union(enum) {...@@ -845,7 +848,7 @@ const StackIterator = union(enum) {
845 it.fp = bp;848 it.fp = bp;
846 const ra = stripInstructionPtrAuthCode(ra_ptr.*);849 const ra = stripInstructionPtrAuthCode(ra_ptr.*);
847 if (ra <= 1) return .end;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,7 +53,7 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void {
53 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);53 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);
54}54}
5555
56pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!void {56pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
57 comptime assert(supports_unwinding);57 comptime assert(supports_unwinding);
58 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);58 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
59 const gop = try self.modules.getOrPut(gpa, module.key());59 const gop = try self.modules.getOrPut(gpa, module.key());
...@@ -124,15 +124,14 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)...@@ -124,15 +124,14 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)
124/// /// pointer is unknown, 0 may be returned instead.124/// /// pointer is unknown, 0 may be returned instead.
125/// pub fn getFp(uc: *UnwindContext) usize;125/// pub fn getFp(uc: *UnwindContext) usize;
126/// };126/// };
127/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame.127/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame, and returns
128/// /// The caller will read the new instruction poiter from the `pc` field.128/// /// the frame's return address.
129/// /// `pc = 0` indicates end of stack / no more frames.
130/// pub fn unwindFrame(129/// pub fn unwindFrame(
131/// mod: *const Module,130/// mod: *const Module,
132/// gpa: Allocator,131/// gpa: Allocator,
133/// di: *DebugInfo,132/// di: *DebugInfo,
134/// ctx: *UnwindContext,133/// ctx: *UnwindContext,
135/// ) SelfInfo.Error!void;134/// ) SelfInfo.Error!usize;
136/// ```135/// ```
137const Module: type = Module: {136const Module: type = Module: {
138 // Allow overriding the target-specific `SelfInfo` implementation by exposing `root.debug.Module`.137 // Allow overriding the target-specific `SelfInfo` implementation by exposing `root.debug.Module`.
...@@ -312,7 +311,7 @@ pub const DwarfUnwindContext = struct {...@@ -312,7 +311,7 @@ pub const DwarfUnwindContext = struct {
312 unwind: *const Dwarf.Unwind,311 unwind: *const Dwarf.Unwind,
313 load_offset: usize,312 load_offset: usize,
314 explicit_fde_offset: ?usize,313 explicit_fde_offset: ?usize,
315 ) Error!void {314 ) Error!usize {
316 return unwindFrameInner(context, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {315 return unwindFrameInner(context, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {
317 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,316 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
318317
...@@ -360,10 +359,10 @@ pub const DwarfUnwindContext = struct {...@@ -360,10 +359,10 @@ pub const DwarfUnwindContext = struct {
360 unwind: *const Dwarf.Unwind,359 unwind: *const Dwarf.Unwind,
361 load_offset: usize,360 load_offset: usize,
362 explicit_fde_offset: ?usize,361 explicit_fde_offset: ?usize,
363 ) !void {362 ) !usize {
364 comptime assert(supports_unwinding);363 comptime assert(supports_unwinding);
365364
366 if (context.pc == 0) return;365 if (context.pc == 0) return 0;
367366
368 const pc_vaddr = context.pc - load_offset;367 const pc_vaddr = context.pc - load_offset;
369368
...@@ -443,13 +442,19 @@ pub const DwarfUnwindContext = struct {...@@ -443,13 +442,19 @@ pub const DwarfUnwindContext = struct {
443 // The new CPU context is complete; flush changes.442 // The new CPU context is complete; flush changes.
444 context.cpu_context = new_cpu_context;443 context.cpu_context = new_cpu_context;
445444
446 // Also update the stored pc. However, because `return_address` points to the instruction445 // The caller will subtract 1 from the return address to get an address corresponding to the
447 // *after* the call, it could (in the case of noreturn functions) actually point outside of446 // function call. However, if this is a signal frame, that's actually incorrect, because the
448 // the caller's address range, meaning an FDE lookup would fail. We can handle this by447 // "return address" we have is the instruction which triggered the signal (if the signal
449 // subtracting 1 from `return_address` so that the next lookup is guaranteed to land inside448 // handler returned, the instruction would be re-run). Compensate for this by incrementing
450 // the `call` instruction. The exception to this rule is signal frames, where the return449 // the address in that case.
451 // address is the same instruction that triggered the handler.450 const adjusted_ret_addr = if (cie.is_signal_frame) return_address +| 1 else return_address;
452 context.pc = if (cie.is_signal_frame) return_address else return_address -| 1;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 /// Since register rules are applied (usually) during a panic,459 /// Since register rules are applied (usually) during a panic,
455 /// checked addition / subtraction is used so that we can return460 /// 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,7 +324,7 @@ pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
324/// Unwind a frame using MachO compact unwind info (from __unwind_info).324/// Unwind a frame using MachO compact unwind info (from __unwind_info).
325/// If the compact encoding can't encode a way to unwind a frame, it will325/// If the compact encoding can't encode a way to unwind a frame, it will
326/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.326/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
327pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!void {327pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
328 return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {328 return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {
329 error.InvalidDebugInfo,329 error.InvalidDebugInfo,
330 error.MissingDebugInfo,330 error.MissingDebugInfo,
...@@ -340,7 +340,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -340,7 +340,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
340 => return error.InvalidDebugInfo,340 => return error.InvalidDebugInfo,
341 };341 };
342}342}
343fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !void {343fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
344 if (di.unwind == null) di.unwind = module.loadUnwindInfo();344 if (di.unwind == null) di.unwind = module.loadUnwindInfo();
345 const unwind = &di.unwind.?;345 const unwind = &di.unwind.?;
346346
...@@ -640,7 +640,13 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -640,7 +640,13 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
640 else => comptime unreachable, // unimplemented640 else => comptime unreachable, // unimplemented
641 };641 };
642642
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}
645pub const DebugInfo = struct {651pub const DebugInfo = struct {
646 unwind: ?Unwind,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,7 +230,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
230 else => unreachable,230 else => unreachable,
231 }231 }
232}232}
233pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!void {233pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
234 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);234 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
235 std.debug.assert(di.unwind[0] != null);235 std.debug.assert(di.unwind[0] != null);
236 for (&di.unwind) |*opt_unwind| {236 for (&di.unwind) |*opt_unwind| {
lib/std/debug/SelfInfo/WindowsModule.zig+6-3
...@@ -373,7 +373,7 @@ pub const UnwindContext = struct {...@@ -373,7 +373,7 @@ pub const UnwindContext = struct {
373 return ctx.cur.getRegs().bp;373 return ctx.cur.getRegs().bp;
374 }374 }
375};375};
376pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !void {376pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
377 _ = module;377 _ = module;
378 _ = gpa;378 _ = gpa;
379 _ = di;379 _ = di;
...@@ -403,9 +403,12 @@ pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo,...@@ -403,9 +403,12 @@ pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo,
403 const tib = &windows.teb().NtTib;403 const tib = &windows.teb().NtTib;
404 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {404 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {
405 context.pc = 0;405 context.pc = 0;
406 } else {406 return 0;
407 context.pc = next_regs.ip -| 1;
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}
410413
411const WindowsModule = @This();414const WindowsModule = @This();
test/standalone/stack_iterator/unwind.zig+4-4
...@@ -3,7 +3,7 @@ const builtin = @import("builtin");...@@ -3,7 +3,7 @@ const builtin = @import("builtin");
3const fatal = std.process.fatal;3const fatal = std.process.fatal;
44
5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
6 expected[0] = @returnAddress() - 1;6 expected[0] = @returnAddress();
7 return std.debug.captureCurrentStackTrace(.{7 return std.debug.captureCurrentStackTrace(.{
8 .first_address = @returnAddress(),8 .first_address = @returnAddress(),
9 .allow_unsafe_unwind = true,9 .allow_unsafe_unwind = true,
...@@ -58,12 +58,12 @@ noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr...@@ -58,12 +58,12 @@ noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr
58 }58 }
59 }59 }
6060
61 expected[1] = @returnAddress() - 1;61 expected[1] = @returnAddress();
62 return frame3(expected, addr_buf);62 return frame3(expected, addr_buf);
63}63}
6464
65noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {65noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
66 expected[2] = @returnAddress() - 1;66 expected[2] = @returnAddress();
6767
68 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding68 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
69 // to exercise the stack-indirect encoding path69 // 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,7 +74,7 @@ noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr
74}74}
7575
76noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {76noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
77 expected[3] = @returnAddress() - 1;77 expected[3] = @returnAddress();
78 return frame1(expected, addr_buf);78 return frame1(expected, addr_buf);
79}79}
8080
test/standalone/stack_iterator/unwind_freestanding.zig+4-4
...@@ -3,7 +3,7 @@...@@ -3,7 +3,7 @@
3const std = @import("std");3const std = @import("std");
44
5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
6 expected[0] = @returnAddress() - 1;6 expected[0] = @returnAddress();
7 return std.debug.captureCurrentStackTrace(.{7 return std.debug.captureCurrentStackTrace(.{
8 .first_address = @returnAddress(),8 .first_address = @returnAddress(),
9 .allow_unsafe_unwind = true,9 .allow_unsafe_unwind = true,
...@@ -11,12 +11,12 @@ noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr...@@ -11,12 +11,12 @@ noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr
11}11}
1212
13noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {13noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
14 expected[1] = @returnAddress() - 1;14 expected[1] = @returnAddress();
15 return frame3(expected, addr_buf);15 return frame3(expected, addr_buf);
16}16}
1717
18noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {18noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
19 expected[2] = @returnAddress() - 1;19 expected[2] = @returnAddress();
2020
21 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding21 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
22 // to exercise the stack-indirect encoding path22 // 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,7 +27,7 @@ noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTr
27}27}
2828
29noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {29noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
30 expected[3] = @returnAddress() - 1;30 expected[3] = @returnAddress();
31 return frame1(expected, addr_buf);31 return frame1(expected, addr_buf);
32}32}
3333