@@ -728,7 +728,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
728728 }
729729 // `ret_addr` is the return address, which is *after* the function call.
730730 // Subtract 1 to get an address *in* the function call for a better source location.
731- try printSourceAtAddress (di_gpa , di , writer , ret_addr - | 1 , tty_config );
731+ try printSourceAtAddress (di_gpa , di , writer , ret_addr - | StackIterator . ra_call_offset , tty_config );
732732 printed_any_frame = true ;
733733 },
734734 };
@@ -777,7 +777,7 @@ pub fn writeStackTrace(st: *const std.builtin.StackTrace, writer: *Writer, tty_c
777777 for (st .instruction_addresses [0.. captured_frames ]) | ret_addr | {
778778 // `ret_addr` is the return address, which is *after* the function call.
779779 // Subtract 1 to get an address *in* the function call for a better source location.
780- try printSourceAtAddress (di_gpa , di , writer , ret_addr - | 1 , tty_config );
780+ try printSourceAtAddress (di_gpa , di , writer , ret_addr - | StackIterator . ra_call_offset , tty_config );
781781 }
782782 if (n_frames > captured_frames ) {
783783 tty_config .setColor (writer , .bold ) catch {};
@@ -807,14 +807,6 @@ const StackIterator = union(enum) {
807807 /// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and
808808 /// our own are one and the same.
809809 inline fn init (opt_context_ptr : ? CpuContextPtr ) error {CannotUnwindFromContext }! StackIterator {
810- if (builtin .cpu .arch .isSPARC ()) {
811- // Flush all the register windows on stack.
812- if (builtin .cpu .has (.sparc , .v9 )) {
813- asm volatile ("flushw" ::: .{ .memory = true });
814- } else {
815- asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
816- }
817- }
818810 if (opt_context_ptr ) | context_ptr | {
819811 if (SelfInfo == void or ! SelfInfo .can_unwind ) return error .CannotUnwindFromContext ;
820812 // Use `di_first` here so we report the PC in the context before unwinding any further.
@@ -833,7 +825,19 @@ const StackIterator = union(enum) {
833825 // in our caller's frame and above.
834826 return .{ .di = .init (&.current ()) };
835827 }
836- return .{ .fp = @frameAddress () };
828+ return .{
829+ // On SPARC, the frame pointer will point to the previous frame's save area,
830+ // meaning we will read the previous return address and thus miss a frame.
831+ // Instead, start at the stack pointer so we get the return address from the
832+ // current frame's save area. The addition of the stack bias cannot fail here
833+ // since we know we have a valid stack pointer.
834+ .fp = if (native_arch .isSPARC ()) sp : {
835+ flushSparcWindows ();
836+ break :sp asm (""
837+ : [_ ] "={o6}" (- > usize ),
838+ ) + stack_bias ;
839+ } else @frameAddress (),
840+ };
837841 }
838842 fn deinit (si : * StackIterator ) void {
839843 switch (si .* ) {
@@ -842,6 +846,15 @@ const StackIterator = union(enum) {
842846 }
843847 }
844848
849+ noinline fn flushSparcWindows () void {
850+ // Flush all register windows except the current one (hence `noinline`). This ensures that
851+ // we actually see meaningful data on the stack when we walk the frame chain.
852+ if (comptime builtin .target .cpu .has (.sparc , .v9 ))
853+ asm volatile ("flushw" ::: .{ .memory = true })
854+ else
855+ asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
856+ }
857+
845858 const FpUsability = enum {
846859 /// FP unwinding is impractical on this target. For example, due to its very silly ABI
847860 /// design decisions, it's not possible to do generic FP unwinding on MIPS without a
@@ -873,6 +886,8 @@ const StackIterator = union(enum) {
873886 .powerpcle ,
874887 .powerpc64 ,
875888 .powerpc64le ,
889+ .sparc ,
890+ .sparc64 ,
876891 = > .ideal ,
877892 // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Respect-the-purpose-of-specific-CPU-registers
878893 .aarch64 = > if (builtin .target .os .tag .isDarwin ()) .safe else .unsafe ,
@@ -923,7 +938,8 @@ const StackIterator = union(enum) {
923938 const di_gpa = getDebugInfoAllocator ();
924939 const ret_addr = di .unwindFrame (di_gpa , unwind_context ) catch | err | {
925940 const pc = unwind_context .pc ;
926- it .* = .{ .fp = unwind_context .getFp () };
941+ const fp = unwind_context .getFp ();
942+ it .* = .{ .fp = fp };
927943 return .{ .switch_to_fp = .{
928944 .address = pc ,
929945 .err = err ,
@@ -935,8 +951,8 @@ const StackIterator = union(enum) {
935951 .fp = > | fp | {
936952 if (fp == 0 ) return .end ; // we reached the "sentinel" base pointer
937953
938- const bp_addr = applyOffset (fp , bp_offset ) orelse return .end ;
939- const ra_addr = applyOffset (fp , ra_offset ) orelse return .end ;
954+ const bp_addr = applyOffset (fp , fp_to_bp_offset ) orelse return .end ;
955+ const ra_addr = applyOffset (fp , fp_to_ra_offset ) orelse return .end ;
940956
941957 if (bp_addr == 0 or ! mem .isAligned (bp_addr , @alignOf (usize )) or
942958 ra_addr == 0 or ! mem .isAligned (ra_addr , @alignOf (usize )))
@@ -947,7 +963,7 @@ const StackIterator = union(enum) {
947963
948964 const bp_ptr : * const usize = @ptrFromInt (bp_addr );
949965 const ra_ptr : * const usize = @ptrFromInt (ra_addr );
950- const bp = applyOffset (bp_ptr .* , bp_bias ) orelse return .end ;
966+ const bp = applyOffset (bp_ptr .* , stack_bias ) orelse return .end ;
951967
952968 // The stack grows downards, so `bp > fp` should always hold. If it doesn't, this
953969 // frame is invalid, so we'll treat it as though it we reached end of stack. The
@@ -964,33 +980,46 @@ const StackIterator = union(enum) {
964980 }
965981
966982 /// Offset of the saved base pointer (previous frame pointer) wrt the frame pointer.
967- const bp_offset = off : {
968- // On RISC-V the frame pointer points to the top of the saved register
969- // area, on pretty much every other architecture it points to the stack
970- // slot where the previous frame pointer is saved.
983+ const fp_to_bp_offset = off : {
984+ // On LoongArch and RISC-V, the frame pointer points to the top of the saved register area,
985+ // in which the base pointer is the first word.
971986 if (native_arch .isLoongArch () or native_arch .isRISCV ()) break :off -2 * @sizeOf (usize );
972- // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS.
987+ // On SPARC, the frame pointer points to the save area which holds 16 slots for the local
988+ // and incoming registers. The base pointer (i6) is stored in its customary save slot.
973989 if (native_arch .isSPARC ()) break :off 14 * @sizeOf (usize );
990+ // Everywhere else, the frame pointer points directly to the location of the base pointer.
974991 break :off 0 ;
975992 };
976993
977994 /// Offset of the saved return address wrt the frame pointer.
978- const ra_offset = off : {
979- if (native_arch .isLoongArch () or native_arch .isRISCV ()) break :off -1 * @sizeOf (usize );
980- if (native_arch .isSPARC ()) break :off 15 * @sizeOf (usize );
995+ const fp_to_ra_offset = off : {
996+ // On LoongArch and RISC-V, the frame pointer points to the top of the saved register area,
997+ // in which the return address is the second word.
998+ if (native_arch .isRISCV () or native_arch .isLoongArch ()) break :off -1 * @sizeOf (usize );
981999 if (native_arch .isPowerPC64 ()) break :off 2 * @sizeOf (usize );
9821000 // On s390x, r14 is the link register and we need to grab it from its customary slot in the
9831001 // register save area (ELF ABI s390x Supplement §1.2.2.2).
9841002 if (native_arch == .s390x ) break :off 14 * @sizeOf (usize );
1003+ // On SPARC, the frame pointer points to the save area which holds 16 slots for the local
1004+ // and incoming registers. The return address (i7) is stored in its customary save slot.
1005+ if (native_arch .isSPARC ()) break :off 15 * @sizeOf (usize );
9851006 break :off @sizeOf (usize );
9861007 };
9871008
988- /// Value to add to a base pointer after loading it from the stack. Yes, SPARC really does this.
989- const bp_bias = bias : {
990- if (native_arch .isSPARC ()) break :bias 2047 ;
1009+ /// Value to add to the stack pointer and frame/base pointers to get the real location being
1010+ /// pointed to. Yes, SPARC really does this.
1011+ const stack_bias = bias : {
1012+ if (native_arch == .sparc64 ) break :bias 2047 ;
9911013 break :bias 0 ;
9921014 };
9931015
1016+ /// On some oddball architectures, a return address points to the call instruction rather than
1017+ /// the instruction following it.
1018+ const ra_call_offset = off : {
1019+ if (native_arch .isSPARC ()) break :off 0 ;
1020+ break :off 1 ;
1021+ };
1022+
9941023 fn applyOffset (addr : usize , comptime off : comptime_int ) ? usize {
9951024 if (off >= 0 ) return math .add (usize , addr , off ) catch return null ;
9961025 return math .sub (usize , addr , - off ) catch return null ;
@@ -1429,6 +1458,22 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
14291458 break :info .{ addr , name };
14301459 };
14311460 const opt_cpu_context : ? cpu_context.Native = cpu_context .fromPosixSignalContext (ctx_ptr );
1461+
1462+ if (native_arch .isSPARC ()) {
1463+ // It's unclear to me whether this is a QEMU bug or also real kernel behavior, but in the
1464+ // former, I observed that the most recent register window wasn't getting spilled on the
1465+ // stack as expected when a signal arrived. A `flushw` from the signal handler does not
1466+ // appear to be sufficient either. On the other hand, when doing a synchronous stack trace
1467+ // and using `flushw`, this all appears to work as expected. So, *probably* a QEMU bug, but
1468+ // someone with real SPARC hardware should verify.
1469+ //
1470+ // In any case, the register save area exists specifically so that register windows can be
1471+ // spilled asynchronously. This means that it should be perfectly fine for us to manually do
1472+ // so here.
1473+ const ctx = opt_cpu_context .? ;
1474+ @as (* [16 ]usize , @ptrFromInt (ctx .o [6 ] + StackIterator .stack_bias )).* = ctx .l ++ ctx .i ;
1475+ }
1476+
14321477 handleSegfault (addr , name , if (opt_cpu_context ) | * ctx | ctx else null );
14331478}
14341479
0 commit comments