Commit 8263f55ab2

Alex Rønne Petersen <alex@alexrp.com>
2025-10-03 03:27:02
std.debug: add s390x-linux unwind support
1 parent 006bc5a
Changed files (4)
lib/std/debug/Dwarf/SelfUnwinder.zig
@@ -176,7 +176,11 @@ fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheE
             break :cfa try applyOffset(ptr.*, ro.offset);
         },
         .expression => |expr| cfa: {
-            // On all implemented architectures, the CFA is defined to be the previous frame's SP
+            // On most implemented architectures, the CFA is defined to be the previous frame's SP.
+            //
+            // On s390x, it's defined to be SP + 160 (ELF ABI s390x Supplement §1.6.3); however,
+            // what this actually means is that there will be a `def_cfa r15 + 160`, so nothing
+            // special for us to do.
             const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*;
             unwinder.expr_vm.reset();
             const value = try unwinder.expr_vm.run(expr, gpa, .{
lib/std/debug/SelfInfo/Elf.zig
@@ -90,6 +90,7 @@ pub const can_unwind: bool = s: {
             .loongarch64,
             .riscv32,
             .riscv64,
+            .s390x,
             .x86,
             .x86_64,
         },
lib/std/debug/cpu_context.zig
@@ -8,6 +8,7 @@ else switch (native_arch) {
     .arm, .armeb, .thumb, .thumbeb => Arm,
     .loongarch32, .loongarch64 => LoongArch,
     .riscv32, .riscv32be, .riscv64, .riscv64be => Riscv,
+    .s390x => S390x,
     .x86 => X86,
     .x86_64 => X86_64,
     else => noreturn,
@@ -189,6 +190,17 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
             },
             else => null,
         },
+        .s390x => switch (builtin.os.tag) {
+            .linux => .{
+                .r = uc.mcontext.gregs,
+                .f = uc.mcontext.fregs,
+                .psw = .{
+                    .mask = uc.mcontext.psw.mask,
+                    .addr = uc.mcontext.psw.addr,
+                },
+            },
+            else => null,
+        },
         else => null,
     };
 }
@@ -677,6 +689,81 @@ pub const Riscv = extern struct {
     }
 };
 
+/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
+pub const S390x = extern struct {
+    /// The numbered general-purpose registers r0 - r15.
+    r: [16]u64,
+    /// The numbered floating-point registers f0 - f15. Yes, really - they can be used in DWARF CFI.
+    f: [16]f64,
+    /// The program counter.
+    psw: extern struct {
+        mask: u64,
+        addr: u64,
+    },
+
+    pub inline fn current() S390x {
+        var ctx: S390x = undefined;
+        asm volatile (
+            \\ stmg %%r0, %%r15, 0(%%r2)
+            \\ std %%f0, 128(%%r2)
+            \\ std %%f1, 136(%%r2)
+            \\ std %%f2, 144(%%r2)
+            \\ std %%f3, 152(%%r2)
+            \\ std %%f4, 160(%%r2)
+            \\ std %%f5, 168(%%r2)
+            \\ std %%f6, 176(%%r2)
+            \\ std %%f7, 184(%%r2)
+            \\ std %%f8, 192(%%r2)
+            \\ std %%f9, 200(%%r2)
+            \\ std %%f10, 208(%%r2)
+            \\ std %%f11, 216(%%r2)
+            \\ std %%f12, 224(%%r2)
+            \\ std %%f13, 232(%%r2)
+            \\ std %%f14, 240(%%r2)
+            \\ std %%f15, 248(%%r2)
+            \\ epsw %%r0, %%r1
+            \\ stm %%r0, %%r1, 256(%%r2)
+            \\ larl %%r0, .
+            \\ stg %%r0, 264(%%r2)
+            \\ lg %%r0, 0(%%r2)
+            \\ lg %%r1, 8(%%r2)
+            :
+            : [gprs] "{r2}" (&ctx),
+            : .{ .memory = true });
+        return ctx;
+    }
+
+    pub fn dwarfRegisterBytes(ctx: *S390x, register_num: u16) DwarfRegisterError![]u8 {
+        switch (register_num) {
+            0...15 => return @ptrCast(&ctx.r[register_num]),
+            // Why???
+            16 => return @ptrCast(&ctx.f[0]),
+            17 => return @ptrCast(&ctx.f[2]),
+            18 => return @ptrCast(&ctx.f[4]),
+            19 => return @ptrCast(&ctx.f[6]),
+            20 => return @ptrCast(&ctx.f[1]),
+            21 => return @ptrCast(&ctx.f[3]),
+            22 => return @ptrCast(&ctx.f[5]),
+            23 => return @ptrCast(&ctx.f[7]),
+            24 => return @ptrCast(&ctx.f[8]),
+            25 => return @ptrCast(&ctx.f[10]),
+            26 => return @ptrCast(&ctx.f[12]),
+            27 => return @ptrCast(&ctx.f[14]),
+            28 => return @ptrCast(&ctx.f[9]),
+            29 => return @ptrCast(&ctx.f[11]),
+            30 => return @ptrCast(&ctx.f[13]),
+            31 => return @ptrCast(&ctx.f[15]),
+            64 => return @ptrCast(&ctx.psw.mask),
+            65 => return @ptrCast(&ctx.psw.addr),
+
+            48...63 => return error.UnsupportedRegister, // a0 - a15
+            68...83 => return error.UnsupportedRegister, // v16 - v31
+
+            else => return error.InvalidRegister,
+        }
+    }
+};
+
 const signal_ucontext_t = switch (native_os) {
     .linux => std.os.linux.ucontext_t,
     .emscripten => std.os.emscripten.ucontext_t,
lib/std/debug/Dwarf.zig
@@ -1433,6 +1433,7 @@ pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {
         .arm, .armeb, .thumb, .thumbeb => 15,
         .loongarch32, .loongarch64 => 32,
         .riscv32, .riscv32be, .riscv64, .riscv64be => 32,
+        .s390x => 65,
         .x86 => 8,
         .x86_64 => 16,
         else => null,
@@ -1445,6 +1446,7 @@ pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {
         .arm, .armeb, .thumb, .thumbeb => 11,
         .loongarch32, .loongarch64 => 22,
         .riscv32, .riscv32be, .riscv64, .riscv64be => 8,
+        .s390x => 11,
         .x86 => 5,
         .x86_64 => 6,
         else => unreachable,
@@ -1457,6 +1459,7 @@ pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
         .arm, .armeb, .thumb, .thumbeb => 13,
         .loongarch32, .loongarch64 => 3,
         .riscv32, .riscv32be, .riscv64, .riscv64be => 2,
+        .s390x => 15,
         .x86 => 4,
         .x86_64 => 7,
         else => unreachable,