authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-10-16 10:14:05+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-10-16 10:14:05+02:00
log48f8133beaef31121caea82e39190291bdfdc633
treebdef017a179e3ef0450b2cb6dd71e15543ffd8e9
parent493ad58ff72ac79528cbd20e9506daca964ec37b
parente0f10da2703fa4a1390c9daf91c0997270920c4a
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25569 from alexrp/std-debug-sparc

`std.debug`: implement `sparc*-linux` unwinding

5 files changed, 235 insertions(+), 57 deletions(-)

lib/std/debug.zig+71-26
......@@ -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
lib/std/debug/Dwarf.zig+3
......@@ -1437,6 +1437,7 @@ pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {
14371437 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 67,
14381438 .riscv32, .riscv32be, .riscv64, .riscv64be => 65,
14391439 .s390x => 65,
1440 .sparc, .sparc64 => 32,
14401441 .x86 => 8,
14411442 .x86_64 => 16,
14421443 else => null,
......@@ -1453,6 +1454,7 @@ pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {
14531454 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 1,
14541455 .riscv32, .riscv32be, .riscv64, .riscv64be => 8,
14551456 .s390x => 11,
1457 .sparc, .sparc64 => 30,
14561458 .x86 => 5,
14571459 .x86_64 => 6,
14581460 else => unreachable,
......@@ -1469,6 +1471,7 @@ pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
14691471 .powerpc, .powerpcle, .powerpc64, .powerpc64le => 1,
14701472 .riscv32, .riscv32be, .riscv64, .riscv64be => 2,
14711473 .s390x => 15,
1474 .sparc, .sparc64 => 14,
14721475 .x86 => 4,
14731476 .x86_64 => 7,
14741477 else => unreachable,
lib/std/debug/SelfInfo/Elf.zig+4-16
......@@ -94,28 +94,22 @@ pub const can_unwind: bool = s: {
9494 // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through
9595 // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format.
9696 const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
97 // Not supported yet: arm, m68k, sparc64
97 // Not supported yet: arm, m68k
9898 .haiku => &.{
9999 .aarch64,
100 .powerpc,
101100 .riscv64,
102101 .x86,
103102 .x86_64,
104103 },
105 // Not supported yet: arc, arm/armeb/thumb/thumbeb, csky, m68k, or1k, sparc/sparc64, xtensa
104 // Not supported yet: arc, arm/armeb/thumb/thumbeb, csky, m68k, or1k, xtensa
106105 .linux => &.{
107106 .aarch64,
108107 .aarch64_be,
109 .hexagon,
110108 .loongarch64,
111109 .mips,
112110 .mipsel,
113111 .mips64,
114112 .mips64el,
115 .powerpc,
116 .powerpcle,
117 .powerpc64,
118 .powerpc64le,
119113 .riscv32,
120114 .riscv64,
121115 .s390x,
......@@ -134,28 +128,23 @@ pub const can_unwind: bool = s: {
134128 // Not supported yet: arm
135129 .freebsd => &.{
136130 .aarch64,
137 .powerpc64,
138 .powerpc64le,
139131 .riscv64,
140132 .x86_64,
141133 },
142 // Not supported yet: arm/armeb, m68k, mips64/mips64el, sparc/sparc64
134 // Not supported yet: arm/armeb, m68k, mips64/mips64el
143135 .netbsd => &.{
144136 .aarch64,
145137 .aarch64_be,
146138 .mips,
147139 .mipsel,
148 .powerpc,
149140 .x86,
150141 .x86_64,
151142 },
152 // Not supported yet: arm, sparc64
143 // Not supported yet: arm
153144 .openbsd => &.{
154145 .aarch64,
155146 .mips64,
156147 .mips64el,
157 .powerpc,
158 .powerpc64,
159148 .riscv64,
160149 .x86,
161150 .x86_64,
......@@ -165,7 +154,6 @@ pub const can_unwind: bool = s: {
165154 .x86,
166155 .x86_64,
167156 },
168 // Not supported yet: sparc64
169157 .solaris => &.{
170158 .x86_64,
171159 },
lib/std/debug/cpu_context.zig+155-13
......@@ -10,6 +10,7 @@ else switch (native_arch) {
1010 .loongarch32, .loongarch64 => LoongArch,
1111 .mips, .mipsel, .mips64, .mips64el => Mips,
1212 .powerpc, .powerpcle, .powerpc64, .powerpc64le => Powerpc,
13 .sparc, .sparc64 => Sparc,
1314 .riscv32, .riscv32be, .riscv64, .riscv64be => Riscv,
1415 .s390x => S390x,
1516 .x86 => X86,
......@@ -39,6 +40,26 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
3940 },
4041 .pc = @truncate(uc.mcontext.pc),
4142 };
43 } else if (native_arch.isSPARC() and native_os == .linux) {
44 const SparcStackFrame = extern struct {
45 l: [8]usize,
46 i: [8]usize,
47 _x: [8]usize,
48 };
49
50 // When invoking a signal handler, the kernel builds an `rt_signal_frame` structure on the
51 // stack and passes a pointer to its `info` field to the signal handler. This implies that
52 // prior to said `info` field, we will find the `ss` field which, among other things,
53 // contains the incoming and local registers of the interrupted code.
54 const frame = @as(*const SparcStackFrame, @ptrFromInt(@as(usize, @intFromPtr(ctx_ptr)) - @sizeOf(SparcStackFrame)));
55
56 return .{
57 .g = uc.mcontext.g,
58 .o = uc.mcontext.o,
59 .l = frame.l,
60 .i = frame.i,
61 .pc = uc.mcontext.pc,
62 };
4263 }
4364
4465 // Only unified conversions from here.
......@@ -858,6 +879,104 @@ const Powerpc = extern struct {
858879 }
859880};
860881
882/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
883const Sparc = extern struct {
884 g: [8]Gpr,
885 o: [8]Gpr,
886 l: [8]Gpr,
887 i: [8]Gpr,
888 pc: Gpr,
889
890 pub const Gpr = if (native_arch == .sparc64) u64 else u32;
891
892 pub inline fn current() Sparc {
893 flushWindows();
894
895 var ctx: Sparc = undefined;
896 asm volatile (if (Gpr == u64)
897 \\ stx %g0, [%l0 + 0]
898 \\ stx %g1, [%l0 + 8]
899 \\ stx %g2, [%l0 + 16]
900 \\ stx %g3, [%l0 + 24]
901 \\ stx %g4, [%l0 + 32]
902 \\ stx %g5, [%l0 + 40]
903 \\ stx %g6, [%l0 + 48]
904 \\ stx %g7, [%l0 + 56]
905 \\ stx %o0, [%l0 + 64]
906 \\ stx %o1, [%l0 + 72]
907 \\ stx %o2, [%l0 + 80]
908 \\ stx %o3, [%l0 + 88]
909 \\ stx %o4, [%l0 + 96]
910 \\ stx %o5, [%l0 + 104]
911 \\ stx %o6, [%l0 + 112]
912 \\ stx %o7, [%l0 + 120]
913 \\ stx %l0, [%l0 + 128]
914 \\ stx %l1, [%l0 + 136]
915 \\ stx %l2, [%l0 + 144]
916 \\ stx %l3, [%l0 + 152]
917 \\ stx %l4, [%l0 + 160]
918 \\ stx %l5, [%l0 + 168]
919 \\ stx %l6, [%l0 + 176]
920 \\ stx %l7, [%l0 + 184]
921 \\ stx %i0, [%l0 + 192]
922 \\ stx %i1, [%l0 + 200]
923 \\ stx %i2, [%l0 + 208]
924 \\ stx %i3, [%l0 + 216]
925 \\ stx %i4, [%l0 + 224]
926 \\ stx %i5, [%l0 + 232]
927 \\ stx %i6, [%l0 + 240]
928 \\ stx %i7, [%l0 + 248]
929 \\ call 1f
930 \\ stx %o7, [%l0 + 256]
931 \\1:
932 else
933 \\ std %g0, [%l0 + 0]
934 \\ std %g2, [%l0 + 8]
935 \\ std %g4, [%l0 + 16]
936 \\ std %g6, [%l0 + 24]
937 \\ std %o0, [%l0 + 32]
938 \\ std %o2, [%l0 + 40]
939 \\ std %o4, [%l0 + 48]
940 \\ std %o6, [%l0 + 56]
941 \\ std %l0, [%l0 + 64]
942 \\ std %l2, [%l0 + 72]
943 \\ std %l4, [%l0 + 80]
944 \\ std %l6, [%l0 + 88]
945 \\ std %i0, [%l0 + 96]
946 \\ std %i2, [%l0 + 104]
947 \\ std %i4, [%l0 + 112]
948 \\ std %i6, [%l0 + 120]
949 \\ call 1f
950 \\ st %o7, [%l0 + 128]
951 \\1:
952 :
953 : [gprs] "{l0}" (&ctx),
954 : .{ .o7 = true, .memory = true });
955 return ctx;
956 }
957
958 noinline fn flushWindows() void {
959 // Flush all register windows except the current one (hence `noinline`). This ensures that
960 // we actually see meaningful data on the stack when we walk the frame chain.
961 if (comptime builtin.target.cpu.has(.sparc, .v9))
962 asm volatile ("flushw" ::: .{ .memory = true })
963 else
964 asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
965 }
966
967 pub fn dwarfRegisterBytes(ctx: *Sparc, register_num: u16) DwarfRegisterError![]u8 {
968 switch (register_num) {
969 0...7 => return @ptrCast(&ctx.g[register_num]),
970 8...15 => return @ptrCast(&ctx.o[register_num - 8]),
971 16...23 => return @ptrCast(&ctx.l[register_num - 16]),
972 24...31 => return @ptrCast(&ctx.i[register_num - 24]),
973 32 => return @ptrCast(&ctx.pc),
974
975 else => return error.InvalidRegister,
976 }
977 }
978};
979
861980/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
862981const Riscv = extern struct {
863982 /// The numbered general-purpose registers r0 - r31. r0 must be zero.
......@@ -1271,9 +1390,32 @@ const signal_ucontext_t = switch (native_os) {
12711390 lr: u32,
12721391 },
12731392 },
1274 // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/sparc/include/uapi/asm/uctx.h
1275 .sparc => @compileError("sparc-linux ucontext_t missing"),
1276 .sparc64 => @compileError("sparc64-linux ucontext_t missing"),
1393 // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/sparc/kernel/signal_32.c#L48-L49
1394 .sparc => extern struct {
1395 // Not actually a `ucontext_t` at all because, uh, reasons?
1396
1397 _info: std.os.linux.siginfo_t,
1398 mcontext: extern struct {
1399 _psr: u32,
1400 pc: u32,
1401 _npc: u32,
1402 _y: u32,
1403 g: [8]u32,
1404 o: [8]u32,
1405 },
1406 },
1407 // https://github.com/torvalds/linux/blob/cd5a0afbdf8033dc83786315d63f8b325bdba2fd/arch/sparc/kernel/signal_64.c#L247-L248
1408 .sparc64 => extern struct {
1409 // Ditto...
1410
1411 _info: std.os.linux.siginfo_t,
1412 mcontext: extern struct {
1413 g: [8]u64,
1414 o: [8]u64,
1415 _tstate: u64,
1416 pc: u64,
1417 },
1418 },
12771419 else => unreachable,
12781420 },
12791421 // https://github.com/freebsd/freebsd-src/blob/55c28005f544282b984ae0e15dacd0c108d8ab12/sys/sys/_ucontext.h
......@@ -1398,14 +1540,14 @@ const signal_ucontext_t = switch (native_os) {
13981540 },
13991541 },
14001542 // This needs to be audited by someone with access to the Solaris headers.
1401 .solaris => extern struct {
1402 _flags: u64,
1403 _link: ?*signal_ucontext_t,
1404 _sigmask: std.c.sigset_t,
1405 _stack: std.c.stack_t,
1406 mcontext: switch (native_arch) {
1407 .sparc64 => @compileError("sparc64-solaris mcontext_t missing"),
1408 .x86_64 => extern struct {
1543 .solaris => switch (native_arch) {
1544 .sparc64 => @compileError("sparc64-solaris ucontext_t missing"),
1545 .x86_64 => extern struct {
1546 _flags: u64,
1547 _link: ?*signal_ucontext_t,
1548 _sigmask: std.c.sigset_t,
1549 _stack: std.c.stack_t,
1550 mcontext: extern struct {
14091551 r15: u64,
14101552 r14: u64,
14111553 r13: u64,
......@@ -1425,8 +1567,8 @@ const signal_ucontext_t = switch (native_os) {
14251567 _err: i64,
14261568 rip: u64,
14271569 },
1428 else => unreachable,
14291570 },
1571 else => unreachable,
14301572 },
14311573 // https://github.com/illumos/illumos-gate/blob/d4ce137bba3bd16823db6374d9e9a643264ce245/usr/src/uts/intel/sys/ucontext.h
14321574 .illumos => extern struct {
......@@ -1538,7 +1680,7 @@ const signal_ucontext_t = switch (native_os) {
15381680 },
15391681 },
15401682 // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/sparc64/include/signal.h
1541 .sparc64 => @compileError("sparc64-openbsd mcontext_t missing"),
1683 .sparc64 => @compileError("sparc64-openbsd ucontext_t missing"),
15421684 // https://github.com/openbsd/src/blob/42468faed8369d07ae49ae02dd71ec34f59b66cd/sys/arch/i386/include/signal.h
15431685 .x86 => extern struct {
15441686 mcontext: extern struct {
lib/std/os/windows.zig+2-2
......@@ -4229,8 +4229,8 @@ pub const CONTEXT = switch (native_arch) {
42294229 SegSs: DWORD,
42304230 ExtendedRegisters: [512]BYTE,
42314231
4232 pub fn getRegs(ctx: *const CONTEXT) struct { bp: usize, ip: usize } {
4233 return .{ .bp = ctx.Ebp, .ip = ctx.Eip };
4232 pub fn getRegs(ctx: *const CONTEXT) struct { bp: usize, ip: usize, sp: usize } {
4233 return .{ .bp = ctx.Ebp, .ip = ctx.Eip, .sp = ctx.Esp };
42344234 }
42354235 },
42364236 .x86_64 => extern struct {