Commit a18fd41064

mlugg <mlugg@mlugg.co.uk>
2025-09-17 19:38:11
std: rework/remove ucontext_t
Our usage of `ucontext_t` in the standard library was kind of problematic. We unnecessarily mimiced libc-specific structures, and our `getcontext` implementation was overkill for our use case of stack tracing. This commit introduces a new namespace, `std.debug.cpu_context`, which contains "context" types for various architectures (currently x86, x86_64, ARM, and AARCH64) containing the general-purpose CPU registers; the ones needed in practice for stack unwinding. Each implementation has a function `current` which populates the structure using inline assembly. The structure is user-overrideable, though that should only be necessary if the standard library does not have an implementation for the *architecture*: that is to say, none of this is OS-dependent. Of course, in POSIX signal handlers, we get a `ucontext_t` from the kernel. The function `std.debug.cpu_context.fromPosixSignalContext` converts this to a `std.debug.cpu_context.Native` with a big ol' target switch. This functionality is not exposed from `std.c` or `std.posix`, and neither are `ucontext_t`, `mcontext_t`, or `getcontext`. The rationale is that these types and functions do not conform to a specific ABI, and in fact tend to get updated over time based on CPU features and extensions; in addition, different libcs use different structures which are "partially compatible" with the kernel structure. Overall, it's a mess, but all we need is the kernel context, so we can just define a kernel-compatible structure as long as we don't claim C compatibility by putting it in `std.c` or `std.posix`. This change resulted in a few nice `std.debug` simplifications, but nothing too noteworthy. However, the main benefit of this change is that DWARF unwinding---sometimes necessary for collecting stack traces reliably---now requires far less target-specific integration. Also fix a bug I noticed in `PageAllocator` (I found this due to a bug in my distro's QEMU distribution; thanks, broken QEMU patch!) and I think a couple of minor bugs in `std.debug`. Resolves: #23801 Resolves: #23802
1 parent b578cca
lib/std/c/darwin.zig
@@ -348,107 +348,6 @@ pub const VM = struct {
 
 pub const exception_type_t = c_int;
 
-pub const mcontext_t = switch (native_arch) {
-    .aarch64 => extern struct {
-        es: exception_state,
-        ss: thread_state,
-        ns: neon_state,
-    },
-    .x86_64 => extern struct {
-        es: exception_state,
-        ss: thread_state,
-        fs: float_state,
-    },
-    else => @compileError("unsupported arch"),
-};
-
-pub const exception_state = switch (native_arch) {
-    .aarch64 => extern struct {
-        far: u64, // Virtual Fault Address
-        esr: u32, // Exception syndrome
-        exception: u32, // Number of arm exception taken
-    },
-    .x86_64 => extern struct {
-        trapno: u16,
-        cpu: u16,
-        err: u32,
-        faultvaddr: u64,
-    },
-    else => @compileError("unsupported arch"),
-};
-
-pub const thread_state = switch (native_arch) {
-    .aarch64 => extern struct {
-        /// General purpose registers
-        regs: [29]u64,
-        /// Frame pointer x29
-        fp: u64,
-        /// Link register x30
-        lr: u64,
-        /// Stack pointer x31
-        sp: u64,
-        /// Program counter
-        pc: u64,
-        /// Current program status register
-        cpsr: u32,
-        __pad: u32,
-    },
-    .x86_64 => extern struct {
-        rax: u64,
-        rbx: u64,
-        rcx: u64,
-        rdx: u64,
-        rdi: u64,
-        rsi: u64,
-        rbp: u64,
-        rsp: u64,
-        r8: u64,
-        r9: u64,
-        r10: u64,
-        r11: u64,
-        r12: u64,
-        r13: u64,
-        r14: u64,
-        r15: u64,
-        rip: u64,
-        rflags: u64,
-        cs: u64,
-        fs: u64,
-        gs: u64,
-    },
-    else => @compileError("unsupported arch"),
-};
-
-pub const neon_state = extern struct {
-    q: [32]u128,
-    fpsr: u32,
-    fpcr: u32,
-};
-
-pub const float_state = extern struct {
-    reserved: [2]c_int,
-    fcw: u16,
-    fsw: u16,
-    ftw: u8,
-    rsrv1: u8,
-    fop: u16,
-    ip: u32,
-    cs: u16,
-    rsrv2: u16,
-    dp: u32,
-    ds: u16,
-    rsrv3: u16,
-    mxcsr: u32,
-    mxcsrmask: u32,
-    stmm: [8]stmm_reg,
-    xmm: [16]xmm_reg,
-    rsrv4: [96]u8,
-    reserved1: c_int,
-};
-
-pub const stmm_reg = [16]u8;
-pub const xmm_reg = [16]u8;
-
 pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;
 pub extern "c" fn _NSGetExecutablePath(buf: [*:0]u8, bufsize: *u32) c_int;
 pub extern "c" fn _dyld_image_count() u32;
lib/std/c/dragonfly.zig
@@ -13,46 +13,6 @@ pub extern "c" fn ptrace(request: c_int, pid: pid_t, addr: caddr_t, data: c_int)
 pub extern "c" fn umtx_sleep(ptr: *const volatile c_int, value: c_int, timeout: c_int) c_int;
 pub extern "c" fn umtx_wakeup(ptr: *const volatile c_int, count: c_int) c_int;
 
-pub const mcontext_t = extern struct {
-    onstack: register_t, // XXX - sigcontext compat.
-    rdi: register_t,
-    rsi: register_t,
-    rdx: register_t,
-    rcx: register_t,
-    r8: register_t,
-    r9: register_t,
-    rax: register_t,
-    rbx: register_t,
-    rbp: register_t,
-    r10: register_t,
-    r11: register_t,
-    r12: register_t,
-    r13: register_t,
-    r14: register_t,
-    r15: register_t,
-    xflags: register_t,
-    trapno: register_t,
-    addr: register_t,
-    flags: register_t,
-    err: register_t,
-    rip: register_t,
-    cs: register_t,
-    rflags: register_t,
-    rsp: register_t, // machine state
-    ss: register_t,
-
-    len: c_uint, // sizeof(mcontext_t)
-    fpformat: c_uint,
-    ownedfp: c_uint,
-    reserved: c_uint,
-    unused: [8]c_uint,
-
-    // NOTE! 64-byte aligned as of here. Also must match savefpu structure.
-    fpregs: [256]c_int align(64),
-};
-
-pub const register_t = isize;
-
 pub const E = enum(u16) {
     /// No error occurred.
     SUCCESS = 0,
lib/std/c/haiku.zig
@@ -273,269 +273,6 @@ pub const E = enum(i32) {
 
 pub const status_t = i32;
 
-pub const mcontext_t = switch (builtin.cpu.arch) {
-    .arm, .thumb => extern struct {
-        r0: u32,
-        r1: u32,
-        r2: u32,
-        r3: u32,
-        r4: u32,
-        r5: u32,
-        r6: u32,
-        r7: u32,
-        r8: u32,
-        r9: u32,
-        r10: u32,
-        r11: u32,
-        r12: u32,
-        r13: u32,
-        r14: u32,
-        r15: u32,
-        cpsr: u32,
-    },
-    .aarch64 => extern struct {
-        x: [10]u64,
-        lr: u64,
-        sp: u64,
-        elr: u64,
-        spsr: u64,
-        fp_q: [32]u128,
-        fpsr: u32,
-        fpcr: u32,
-    },
-    .m68k => extern struct {
-        pc: u32,
-        d0: u32,
-        d1: u32,
-        d2: u32,
-        d3: u32,
-        d4: u32,
-        d5: u32,
-        d6: u32,
-        d7: u32,
-        a0: u32,
-        a1: u32,
-        a2: u32,
-        a3: u32,
-        a4: u32,
-        a5: u32,
-        a6: u32,
-        a7: u32,
-        ccr: u8,
-        f0: f64,
-        f1: f64,
-        f2: f64,
-        f3: f64,
-        f4: f64,
-        f5: f64,
-        f6: f64,
-        f7: f64,
-        f8: f64,
-        f9: f64,
-        f10: f64,
-        f11: f64,
-        f12: f64,
-        f13: f64,
-    },
-    .mipsel => extern struct {
-        r0: u32,
-    },
-    .powerpc => extern struct {
-        pc: u32,
-        r0: u32,
-        r1: u32,
-        r2: u32,
-        r3: u32,
-        r4: u32,
-        r5: u32,
-        r6: u32,
-        r7: u32,
-        r8: u32,
-        r9: u32,
-        r10: u32,
-        r11: u32,
-        r12: u32,
-        f0: f64,
-        f1: f64,
-        f2: f64,
-        f3: f64,
-        f4: f64,
-        f5: f64,
-        f6: f64,
-        f7: f64,
-        f8: f64,
-        f9: f64,
-        f10: f64,
-        f11: f64,
-        f12: f64,
-        f13: f64,
-        reserved: u32,
-        fpscr: u32,
-        ctr: u32,
-        xer: u32,
-        cr: u32,
-        msr: u32,
-        lr: u32,
-    },
-    .riscv64 => extern struct {
-        x: [31]u64,
-        pc: u64,
-        f: [32]f64,
-        fcsr: u64,
-    },
-    .sparc64 => extern struct {
-        g1: u64,
-        g2: u64,
-        g3: u64,
-        g4: u64,
-        g5: u64,
-        g6: u64,
-        g7: u64,
-        o0: u64,
-        o1: u64,
-        o2: u64,
-        o3: u64,
-        o4: u64,
-        o5: u64,
-        sp: u64,
-        o7: u64,
-        l0: u64,
-        l1: u64,
-        l2: u64,
-        l3: u64,
-        l4: u64,
-        l5: u64,
-        l6: u64,
-        l7: u64,
-        i0: u64,
-        i1: u64,
-        i2: u64,
-        i3: u64,
-        i4: u64,
-        i5: u64,
-        fp: u64,
-        i7: u64,
-    },
-    .x86 => extern struct {
-        pub const old_extended_regs = extern struct {
-            control: u16,
-            reserved1: u16,
-            status: u16,
-            reserved2: u16,
-            tag: u16,
-            reserved3: u16,
-            eip: u32,
-            cs: u16,
-            opcode: u16,
-            datap: u32,
-            ds: u16,
-            reserved4: u16,
-            fp_mmx: [8][10]u8,
-        };
-
-        pub const fp_register = extern struct { value: [10]u8, reserved: [6]u8 };
-
-        pub const xmm_register = extern struct { value: [16]u8 };
-
-        pub const new_extended_regs = extern struct {
-            control: u16,
-            status: u16,
-            tag: u16,
-            opcode: u16,
-            eip: u32,
-            cs: u16,
-            reserved1: u16,
-            datap: u32,
-            ds: u16,
-            reserved2: u16,
-            mxcsr: u32,
-            reserved3: u32,
-            fp_mmx: [8]fp_register,
-            xmmx: [8]xmm_register,
-            reserved4: [224]u8,
-        };
-
-        pub const extended_regs = extern struct {
-            state: extern union {
-                old_format: old_extended_regs,
-                new_format: new_extended_regs,
-            },
-            format: u32,
-        };
-
-        eip: u32,
-        eflags: u32,
-        eax: u32,
-        ecx: u32,
-        edx: u32,
-        esp: u32,
-        ebp: u32,
-        reserved: u32,
-        xregs: extended_regs,
-        edi: u32,
-        esi: u32,
-        ebx: u32,
-    },
-    .x86_64 => extern struct {
-        pub const fp_register = extern struct {
-            value: [10]u8,
-            reserved: [6]u8,
-        };
-
-        pub const xmm_register = extern struct {
-            value: [16]u8,
-        };
-
-        pub const fpu_state = extern struct {
-            control: u16,
-            status: u16,
-            tag: u16,
-            opcode: u16,
-            rip: u64,
-            rdp: u64,
-            mxcsr: u32,
-            mscsr_mask: u32,
-
-            fp_mmx: [8]fp_register,
-            xmm: [16]xmm_register,
-            reserved: [96]u8,
-        };
-
-        pub const xstate_hdr = extern struct {
-            bv: u64,
-            xcomp_bv: u64,
-            reserved: [48]u8,
-        };
-
-        pub const savefpu = extern struct {
-            fxsave: fpu_state,
-            xstate: xstate_hdr,
-            ymm: [16]xmm_register,
-        };
-
-        rax: u64,
-        rbx: u64,
-        rcx: u64,
-        rdx: u64,
-        rdi: u64,
-        rsi: u64,
-        rbp: u64,
-        r8: u64,
-        r9: u64,
-        r10: u64,
-        r11: u64,
-        r12: u64,
-        r13: u64,
-        r14: u64,
-        r15: u64,
-        rsp: u64,
-        rip: u64,
-        rflags: u64,
-        fpu: savefpu,
-    },
-    else => void,
-};
-
 pub const DirEnt = extern struct {
     /// device
     dev: dev_t,
lib/std/c/openbsd.zig
@@ -144,53 +144,6 @@ pub const TCIO = enum(u32) {
     ION = 4,
 };
 
-pub const ucontext_t = switch (builtin.cpu.arch) {
-    .x86_64 => extern struct {
-        sc_rdi: c_long,
-        sc_rsi: c_long,
-        sc_rdx: c_long,
-        sc_rcx: c_long,
-        sc_r8: c_long,
-        sc_r9: c_long,
-        sc_r10: c_long,
-        sc_r11: c_long,
-        sc_r12: c_long,
-        sc_r13: c_long,
-        sc_r14: c_long,
-        sc_r15: c_long,
-        sc_rbp: c_long,
-        sc_rbx: c_long,
-        sc_rax: c_long,
-        sc_gs: c_long,
-        sc_fs: c_long,
-        sc_es: c_long,
-        sc_ds: c_long,
-        sc_trapno: c_long,
-        sc_err: c_long,
-        sc_rip: c_long,
-        sc_cs: c_long,
-        sc_rflags: c_long,
-        sc_rsp: c_long,
-        sc_ss: c_long,
-
-        sc_fpstate: *anyopaque, // struct fxsave64 *
-        __sc_unused: c_int,
-        sc_mask: c_int,
-        sc_cookie: c_long,
-    },
-    .aarch64 => extern struct {
-        __sc_unused: c_int,
-        sc_mask: c_int,
-        sc_sp: c_ulong,
-        sc_lr: c_ulong,
-        sc_elr: c_ulong,
-        sc_spsr: c_ulong,
-        sc_x: [30]c_ulong,
-        sc_cookie: c_long,
-    },
-    else => @compileError("missing ucontext_t type definition"),
-};
-
 pub const E = enum(u16) {
     /// No error occurred.
     SUCCESS = 0,
lib/std/debug/Dwarf/abi.zig
@@ -1,351 +0,0 @@
-const builtin = @import("builtin");
-
-const std = @import("../../std.zig");
-const mem = std.mem;
-const posix = std.posix;
-const Arch = std.Target.Cpu.Arch;
-
-/// Tells whether unwinding for this target is supported by the Dwarf standard.
-///
-/// See also `std.debug.SelfInfo.supports_unwinding` which tells whether the Zig
-/// standard library has a working implementation of unwinding for this target.
-pub fn supportsUnwinding(target: *const std.Target) bool {
-    return switch (target.cpu.arch) {
-        .amdgcn,
-        .nvptx,
-        .nvptx64,
-        .spirv32,
-        .spirv64,
-        => false,
-
-        // Enabling this causes relocation errors such as:
-        // error: invalid relocation type R_RISCV_SUB32 at offset 0x20
-        .riscv64, .riscv64be, .riscv32, .riscv32be => false,
-
-        // Conservative guess. Feel free to update this logic with any targets
-        // that are known to not support Dwarf unwinding.
-        else => true,
-    };
-}
-
-/// Returns `null` for CPU architectures without an instruction pointer register.
-pub fn ipRegNum(arch: Arch) ?u8 {
-    return switch (arch) {
-        .x86 => 8,
-        .x86_64 => 16,
-        .arm, .armeb, .thumb, .thumbeb => 15,
-        .aarch64, .aarch64_be => 32,
-        else => null,
-    };
-}
-
-pub fn fpRegNum(arch: Arch, reg_context: RegisterContext) u8 {
-    return switch (arch) {
-        // GCC on OS X historically did the opposite of ELF for these registers
-        // (only in .eh_frame), and that is now the convention for MachO
-        .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5,
-        .x86_64 => 6,
-        .arm, .armeb, .thumb, .thumbeb => 11,
-        .aarch64, .aarch64_be => 29,
-        else => unreachable,
-    };
-}
-
-pub fn spRegNum(arch: Arch, reg_context: RegisterContext) u8 {
-    return switch (arch) {
-        .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4,
-        .x86_64 => 7,
-        .arm, .armeb, .thumb, .thumbeb => 13,
-        .aarch64, .aarch64_be => 31,
-        else => unreachable,
-    };
-}
-
-pub const RegisterContext = struct {
-    eh_frame: bool,
-    is_macho: bool,
-};
-
-pub const RegBytesError = error{
-    InvalidRegister,
-    UnimplementedArch,
-    UnimplementedOs,
-    RegisterContextRequired,
-    ThreadContextNotSupported,
-};
-
-/// Returns a slice containing the backing storage for `reg_number`.
-///
-/// This function assumes the Dwarf information corresponds not necessarily to
-/// the current executable, but at least with a matching CPU architecture and
-/// OS. It is planned to lift this limitation with a future enhancement.
-///
-/// `reg_context` describes in what context the register number is used, as it can have different
-/// meanings depending on the DWARF container. It is only required when getting the stack or
-/// frame pointer register on some architectures.
-pub fn regBytes(
-    thread_context_ptr: *std.debug.ThreadContext,
-    reg_number: u8,
-    reg_context: ?RegisterContext,
-) RegBytesError![]u8 {
-    if (builtin.os.tag == .windows) {
-        return switch (builtin.cpu.arch) {
-            .x86 => switch (reg_number) {
-                0 => mem.asBytes(&thread_context_ptr.Eax),
-                1 => mem.asBytes(&thread_context_ptr.Ecx),
-                2 => mem.asBytes(&thread_context_ptr.Edx),
-                3 => mem.asBytes(&thread_context_ptr.Ebx),
-                4 => mem.asBytes(&thread_context_ptr.Esp),
-                5 => mem.asBytes(&thread_context_ptr.Ebp),
-                6 => mem.asBytes(&thread_context_ptr.Esi),
-                7 => mem.asBytes(&thread_context_ptr.Edi),
-                8 => mem.asBytes(&thread_context_ptr.Eip),
-                9 => mem.asBytes(&thread_context_ptr.EFlags),
-                10 => mem.asBytes(&thread_context_ptr.SegCs),
-                11 => mem.asBytes(&thread_context_ptr.SegSs),
-                12 => mem.asBytes(&thread_context_ptr.SegDs),
-                13 => mem.asBytes(&thread_context_ptr.SegEs),
-                14 => mem.asBytes(&thread_context_ptr.SegFs),
-                15 => mem.asBytes(&thread_context_ptr.SegGs),
-                else => error.InvalidRegister,
-            },
-            .x86_64 => switch (reg_number) {
-                0 => mem.asBytes(&thread_context_ptr.Rax),
-                1 => mem.asBytes(&thread_context_ptr.Rdx),
-                2 => mem.asBytes(&thread_context_ptr.Rcx),
-                3 => mem.asBytes(&thread_context_ptr.Rbx),
-                4 => mem.asBytes(&thread_context_ptr.Rsi),
-                5 => mem.asBytes(&thread_context_ptr.Rdi),
-                6 => mem.asBytes(&thread_context_ptr.Rbp),
-                7 => mem.asBytes(&thread_context_ptr.Rsp),
-                8 => mem.asBytes(&thread_context_ptr.R8),
-                9 => mem.asBytes(&thread_context_ptr.R9),
-                10 => mem.asBytes(&thread_context_ptr.R10),
-                11 => mem.asBytes(&thread_context_ptr.R11),
-                12 => mem.asBytes(&thread_context_ptr.R12),
-                13 => mem.asBytes(&thread_context_ptr.R13),
-                14 => mem.asBytes(&thread_context_ptr.R14),
-                15 => mem.asBytes(&thread_context_ptr.R15),
-                16 => mem.asBytes(&thread_context_ptr.Rip),
-                else => error.InvalidRegister,
-            },
-            .aarch64, .aarch64_be => switch (reg_number) {
-                0...30 => mem.asBytes(&thread_context_ptr.DUMMYUNIONNAME.X[reg_number]),
-                31 => mem.asBytes(&thread_context_ptr.Sp),
-                32 => mem.asBytes(&thread_context_ptr.Pc),
-                else => error.InvalidRegister,
-            },
-            else => error.UnimplementedArch,
-        };
-    }
-
-    if (posix.ucontext_t == void) return error.ThreadContextNotSupported;
-
-    const ucontext_ptr = thread_context_ptr;
-    return switch (builtin.cpu.arch) {
-        .x86 => switch (builtin.os.tag) {
-            .linux, .netbsd, .solaris, .illumos => switch (reg_number) {
-                0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]),
-                1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]),
-                2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDX]),
-                3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBX]),
-                4...5 => if (reg_context) |r| bytes: {
-                    if (reg_number == 4) {
-                        break :bytes if (r.eh_frame and r.is_macho)
-                            mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP])
-                        else
-                            mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]);
-                    } else {
-                        break :bytes if (r.eh_frame and r.is_macho)
-                            mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP])
-                        else
-                            mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]);
-                    }
-                } else error.RegisterContextRequired,
-                6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESI]),
-                7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDI]),
-                8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EIP]),
-                9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EFL]),
-                10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.CS]),
-                11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.SS]),
-                12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.DS]),
-                13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ES]),
-                14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.FS]),
-                15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.GS]),
-                16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs
-                32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs
-                else => error.InvalidRegister,
-            },
-            else => error.UnimplementedOs,
-        },
-        .x86_64 => switch (builtin.os.tag) {
-            .linux, .solaris, .illumos => switch (reg_number) {
-                0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]),
-                1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]),
-                2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RCX]),
-                3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBX]),
-                4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSI]),
-                5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDI]),
-                6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBP]),
-                7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSP]),
-                8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R8]),
-                9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R9]),
-                10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R10]),
-                11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R11]),
-                12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R12]),
-                13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R13]),
-                14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]),
-                15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]),
-                16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]),
-                17...32 => |i| if (builtin.os.tag.isSolarish())
-                    mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17])
-                else
-                    mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]),
-                else => error.InvalidRegister,
-            },
-            .freebsd => switch (reg_number) {
-                0 => mem.asBytes(&ucontext_ptr.mcontext.rax),
-                1 => mem.asBytes(&ucontext_ptr.mcontext.rdx),
-                2 => mem.asBytes(&ucontext_ptr.mcontext.rcx),
-                3 => mem.asBytes(&ucontext_ptr.mcontext.rbx),
-                4 => mem.asBytes(&ucontext_ptr.mcontext.rsi),
-                5 => mem.asBytes(&ucontext_ptr.mcontext.rdi),
-                6 => mem.asBytes(&ucontext_ptr.mcontext.rbp),
-                7 => mem.asBytes(&ucontext_ptr.mcontext.rsp),
-                8 => mem.asBytes(&ucontext_ptr.mcontext.r8),
-                9 => mem.asBytes(&ucontext_ptr.mcontext.r9),
-                10 => mem.asBytes(&ucontext_ptr.mcontext.r10),
-                11 => mem.asBytes(&ucontext_ptr.mcontext.r11),
-                12 => mem.asBytes(&ucontext_ptr.mcontext.r12),
-                13 => mem.asBytes(&ucontext_ptr.mcontext.r13),
-                14 => mem.asBytes(&ucontext_ptr.mcontext.r14),
-                15 => mem.asBytes(&ucontext_ptr.mcontext.r15),
-                16 => mem.asBytes(&ucontext_ptr.mcontext.rip),
-                // TODO: Extract xmm state from mcontext.fpstate?
-                else => error.InvalidRegister,
-            },
-            .openbsd => switch (reg_number) {
-                0 => mem.asBytes(&ucontext_ptr.sc_rax),
-                1 => mem.asBytes(&ucontext_ptr.sc_rdx),
-                2 => mem.asBytes(&ucontext_ptr.sc_rcx),
-                3 => mem.asBytes(&ucontext_ptr.sc_rbx),
-                4 => mem.asBytes(&ucontext_ptr.sc_rsi),
-                5 => mem.asBytes(&ucontext_ptr.sc_rdi),
-                6 => mem.asBytes(&ucontext_ptr.sc_rbp),
-                7 => mem.asBytes(&ucontext_ptr.sc_rsp),
-                8 => mem.asBytes(&ucontext_ptr.sc_r8),
-                9 => mem.asBytes(&ucontext_ptr.sc_r9),
-                10 => mem.asBytes(&ucontext_ptr.sc_r10),
-                11 => mem.asBytes(&ucontext_ptr.sc_r11),
-                12 => mem.asBytes(&ucontext_ptr.sc_r12),
-                13 => mem.asBytes(&ucontext_ptr.sc_r13),
-                14 => mem.asBytes(&ucontext_ptr.sc_r14),
-                15 => mem.asBytes(&ucontext_ptr.sc_r15),
-                16 => mem.asBytes(&ucontext_ptr.sc_rip),
-                // TODO: Extract xmm state from sc_fpstate?
-                else => error.InvalidRegister,
-            },
-            .macos, .ios => switch (reg_number) {
-                0 => mem.asBytes(&ucontext_ptr.mcontext.ss.rax),
-                1 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdx),
-                2 => mem.asBytes(&ucontext_ptr.mcontext.ss.rcx),
-                3 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbx),
-                4 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsi),
-                5 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdi),
-                6 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbp),
-                7 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsp),
-                8 => mem.asBytes(&ucontext_ptr.mcontext.ss.r8),
-                9 => mem.asBytes(&ucontext_ptr.mcontext.ss.r9),
-                10 => mem.asBytes(&ucontext_ptr.mcontext.ss.r10),
-                11 => mem.asBytes(&ucontext_ptr.mcontext.ss.r11),
-                12 => mem.asBytes(&ucontext_ptr.mcontext.ss.r12),
-                13 => mem.asBytes(&ucontext_ptr.mcontext.ss.r13),
-                14 => mem.asBytes(&ucontext_ptr.mcontext.ss.r14),
-                15 => mem.asBytes(&ucontext_ptr.mcontext.ss.r15),
-                16 => mem.asBytes(&ucontext_ptr.mcontext.ss.rip),
-                else => error.InvalidRegister,
-            },
-            else => error.UnimplementedOs,
-        },
-        .arm, .armeb, .thumb, .thumbeb => switch (builtin.os.tag) {
-            .linux => switch (reg_number) {
-                0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0),
-                1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1),
-                2 => mem.asBytes(&ucontext_ptr.mcontext.arm_r2),
-                3 => mem.asBytes(&ucontext_ptr.mcontext.arm_r3),
-                4 => mem.asBytes(&ucontext_ptr.mcontext.arm_r4),
-                5 => mem.asBytes(&ucontext_ptr.mcontext.arm_r5),
-                6 => mem.asBytes(&ucontext_ptr.mcontext.arm_r6),
-                7 => mem.asBytes(&ucontext_ptr.mcontext.arm_r7),
-                8 => mem.asBytes(&ucontext_ptr.mcontext.arm_r8),
-                9 => mem.asBytes(&ucontext_ptr.mcontext.arm_r9),
-                10 => mem.asBytes(&ucontext_ptr.mcontext.arm_r10),
-                11 => mem.asBytes(&ucontext_ptr.mcontext.arm_fp),
-                12 => mem.asBytes(&ucontext_ptr.mcontext.arm_ip),
-                13 => mem.asBytes(&ucontext_ptr.mcontext.arm_sp),
-                14 => mem.asBytes(&ucontext_ptr.mcontext.arm_lr),
-                15 => mem.asBytes(&ucontext_ptr.mcontext.arm_pc),
-                // CPSR is not allocated a register number (See: https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst, Section 4.1)
-                else => error.InvalidRegister,
-            },
-            else => error.UnimplementedOs,
-        },
-        .aarch64, .aarch64_be => switch (builtin.os.tag) {
-            .macos, .ios, .watchos => switch (reg_number) {
-                0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]),
-                29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp),
-                30 => mem.asBytes(&ucontext_ptr.mcontext.ss.lr),
-                31 => mem.asBytes(&ucontext_ptr.mcontext.ss.sp),
-                32 => mem.asBytes(&ucontext_ptr.mcontext.ss.pc),
-
-                // TODO: Find storage for this state
-                //34 => mem.asBytes(&ucontext_ptr.ra_sign_state),
-
-                // V0-V31
-                64...95 => mem.asBytes(&ucontext_ptr.mcontext.ns.q[reg_number - 64]),
-                else => error.InvalidRegister,
-            },
-            .netbsd => switch (reg_number) {
-                0...34 => mem.asBytes(&ucontext_ptr.mcontext.gregs[reg_number]),
-                else => error.InvalidRegister,
-            },
-            .freebsd => switch (reg_number) {
-                0...29 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.x[reg_number]),
-                30 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.lr),
-                31 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.sp),
-
-                // TODO: This seems wrong, but it was in the previous debug.zig code for mapping PC, check this
-                32 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.elr),
-
-                else => error.InvalidRegister,
-            },
-            .openbsd => switch (reg_number) {
-                0...30 => mem.asBytes(&ucontext_ptr.sc_x[reg_number]),
-                31 => mem.asBytes(&ucontext_ptr.sc_sp),
-                32 => mem.asBytes(&ucontext_ptr.sc_lr),
-                33 => mem.asBytes(&ucontext_ptr.sc_elr),
-                34 => mem.asBytes(&ucontext_ptr.sc_spsr),
-                else => error.InvalidRegister,
-            },
-            else => switch (reg_number) {
-                0...30 => mem.asBytes(&ucontext_ptr.mcontext.regs[reg_number]),
-                31 => mem.asBytes(&ucontext_ptr.mcontext.sp),
-                32 => mem.asBytes(&ucontext_ptr.mcontext.pc),
-                else => error.InvalidRegister,
-            },
-        },
-        else => error.UnimplementedArch,
-    };
-}
-
-/// Returns a pointer to a register stored in a ThreadContext, preserving the
-/// pointer attributes of the context.
-pub fn regValueNative(
-    thread_context_ptr: *std.debug.ThreadContext,
-    reg_number: u8,
-    reg_context: ?RegisterContext,
-) !*align(1) usize {
-    const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context);
-    if (@sizeOf(usize) != reg_bytes.len) return error.IncompatibleRegisterSize;
-    return @ptrCast(reg_bytes);
-}
lib/std/debug/Dwarf/expression.zig
@@ -5,12 +5,17 @@ const native_endian = native_arch.endian();
 const std = @import("std");
 const leb = std.leb;
 const OP = std.dwarf.OP;
-const abi = std.debug.Dwarf.abi;
 const mem = std.mem;
 const assert = std.debug.assert;
 const testing = std.testing;
 const Writer = std.Io.Writer;
 
+const regNative = std.debug.SelfInfo.DwarfUnwindContext.regNative;
+
+const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?;
+const fp_reg_num = std.debug.Dwarf.fpRegNum(native_arch);
+const sp_reg_num = std.debug.Dwarf.spRegNum(native_arch);
+
 /// Expressions can be evaluated in different contexts, each requiring its own set of inputs.
 /// Callers should specify all the fields relevant to their context. If a field is required
 /// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned.
@@ -23,9 +28,7 @@ pub const Context = struct {
     object_address: ?*const anyopaque = null,
     /// .debug_addr section
     debug_addr: ?[]const u8 = null,
-    /// Thread context
-    thread_context: ?*std.debug.ThreadContext = null,
-    reg_context: ?abi.RegisterContext = null,
+    cpu_context: ?*std.debug.cpu_context.Native = null,
     /// Call frame address, if in a CFI context
     cfa: ?usize = null,
     /// This expression is a sub-expression from an OP.entry_value instruction
@@ -62,7 +65,9 @@ pub const Error = error{
     InvalidTypeLength,
 
     TruncatedIntegralType,
-} || abi.RegBytesError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero, ReadFailed };
+
+    IncompatibleRegisterSize,
+} || std.debug.cpu_context.DwarfRegisterError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero, ReadFailed };
 
 /// A stack machine that can decode and run DWARF expressions.
 /// Expressions can be decoded for non-native address size and endianness,
@@ -369,29 +374,20 @@ pub fn StackMachine(comptime options: Options) type {
                 OP.breg0...OP.breg31,
                 OP.bregx,
                 => {
-                    if (context.thread_context == null) return error.IncompleteExpressionContext;
-
-                    const base_register = operand.?.base_register;
-                    var value: i64 = @intCast(mem.readInt(usize, (try abi.regBytes(
-                        context.thread_context.?,
-                        base_register.base_register,
-                        context.reg_context,
-                    ))[0..@sizeOf(usize)], native_endian));
-                    value += base_register.offset;
-                    try self.stack.append(allocator, .{ .generic = @intCast(value) });
+                    const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
+
+                    const br = operand.?.base_register;
+                    const value: i64 = @intCast((try regNative(cpu_context, br.base_register)).*);
+                    try self.stack.append(allocator, .{ .generic = @intCast(value + br.offset) });
                 },
                 OP.regval_type => {
-                    const register_type = operand.?.register_type;
-                    const value = mem.readInt(usize, (try abi.regBytes(
-                        context.thread_context.?,
-                        register_type.register,
-                        context.reg_context,
-                    ))[0..@sizeOf(usize)], native_endian);
+                    const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
+                    const rt = operand.?.register_type;
                     try self.stack.append(allocator, .{
                         .regval_type = .{
-                            .type_offset = register_type.type_offset,
+                            .type_offset = rt.type_offset,
                             .type_size = @sizeOf(addr_type),
-                            .value = value,
+                            .value = (try regNative(cpu_context, rt.register)).*,
                         },
                     });
                 },
@@ -734,14 +730,14 @@ pub fn StackMachine(comptime options: Options) type {
 
                     // TODO: The spec states that this sub-expression needs to observe the state (ie. registers)
                     //       as it was upon entering the current subprogram. If this isn't being called at the
-                    //       end of a frame unwind operation, an additional ThreadContext with this state will be needed.
+                    //       end of a frame unwind operation, an additional cpu_context.Native with this state will be needed.
 
                     if (isOpcodeRegisterLocation(block[0])) {
-                        if (context.thread_context == null) return error.IncompleteExpressionContext;
+                        const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
 
                         var block_stream: std.Io.Reader = .fixed(block);
                         const register = (try readOperand(&block_stream, block[0], context)).?.register;
-                        const value = mem.readInt(usize, (try abi.regBytes(context.thread_context.?, register, context.reg_context))[0..@sizeOf(usize)], native_endian);
+                        const value = (try regNative(cpu_context, register)).*;
                         try self.stack.append(allocator, .{ .generic = value });
                     } else {
                         var stack_machine: Self = .{};
@@ -1149,55 +1145,39 @@ test "basics" {
     }
 
     // Register values
-    if (@sizeOf(std.debug.ThreadContext) != 0) {
+    if (std.debug.cpu_context.Native != noreturn) {
         stack_machine.reset();
         program.clearRetainingCapacity();
 
-        const reg_context = abi.RegisterContext{
-            .eh_frame = true,
-            .is_macho = builtin.os.tag == .macos,
-        };
-        var thread_context: std.debug.ThreadContext = undefined;
-        std.debug.relocateContext(&thread_context);
+        var cpu_context: std.debug.cpu_context.Native = undefined;
         const context = Context{
-            .thread_context = &thread_context,
-            .reg_context = reg_context,
+            .cpu_context = &cpu_context,
         };
 
-        // Only test register operations on arch / os that have them implemented
-        if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {
-
-            // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it
-
-            mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
-            (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1;
-            (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2;
-            (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3;
-
-            try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100));
-            try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200));
-            try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300));
-            try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));
-
-            _ = try stack_machine.run(program.written(), allocator, context, 0);
-
-            const regval_type = stack_machine.stack.pop().?.regval_type;
-            try testing.expectEqual(@as(usize, 400), regval_type.type_offset);
-            try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size);
-            try testing.expectEqual(@as(usize, 0xee), regval_type.value);
-
-            try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic);
-            try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic);
-            try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic);
-        } else |err| {
-            switch (err) {
-                error.UnimplementedArch,
-                error.UnimplementedOs,
-                error.ThreadContextNotSupported,
-                => {},
-                else => return err,
-            }
-        }
+        const reg_bytes = try cpu_context.dwarfRegisterBytes(0);
+
+        // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it
+
+        mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
+        (try regNative(&cpu_context, fp_reg_num)).* = 1;
+        (try regNative(&cpu_context, sp_reg_num)).* = 2;
+        (try regNative(&cpu_context, ip_reg_num)).* = 3;
+
+        try b.writeBreg(writer, fp_reg_num, @as(usize, 100));
+        try b.writeBreg(writer, sp_reg_num, @as(usize, 200));
+        try b.writeBregx(writer, ip_reg_num, @as(usize, 300));
+        try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));
+
+        _ = try stack_machine.run(program.written(), allocator, context, 0);
+
+        const regval_type = stack_machine.stack.pop().?.regval_type;
+        try testing.expectEqual(@as(usize, 400), regval_type.type_offset);
+        try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size);
+        try testing.expectEqual(@as(usize, 0xee), regval_type.value);
+
+        try testing.expectEqual(@as(usize, 303), stack_machine.stack.pop().?.generic);
+        try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic);
+        try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic);
     }
 
     // Stack operations
@@ -1585,38 +1565,24 @@ test "basics" {
         }
 
         // Register location description
-        const reg_context = abi.RegisterContext{
-            .eh_frame = true,
-            .is_macho = builtin.os.tag == .macos,
-        };
-        var thread_context: std.debug.ThreadContext = undefined;
-        std.debug.relocateContext(&thread_context);
+        var cpu_context: std.debug.cpu_context.Native = undefined;
+        std.debug.relocateContext(&cpu_context);
         context = Context{
-            .thread_context = &thread_context,
-            .reg_context = reg_context,
+            .cpu_context = &cpu_context,
         };
 
-        if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {
-            mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
+        const reg_bytes = try cpu_context.dwarfRegisterBytes(0);
+        mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
 
-            var sub_program: std.Io.Writer.Allocating = .init(allocator);
-            defer sub_program.deinit();
-            const sub_writer = &sub_program.writer;
-            try b.writeReg(sub_writer, 0);
+        var sub_program: std.Io.Writer.Allocating = .init(allocator);
+        defer sub_program.deinit();
+        const sub_writer = &sub_program.writer;
+        try b.writeReg(sub_writer, 0);
 
-            stack_machine.reset();
-            program.clearRetainingCapacity();
-            try b.writeEntryValue(writer, sub_program.written());
-            _ = try stack_machine.run(program.written(), allocator, context, null);
-            try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic);
-        } else |err| {
-            switch (err) {
-                error.UnimplementedArch,
-                error.UnimplementedOs,
-                error.ThreadContextNotSupported,
-                => {},
-                else => return err,
-            }
-        }
+        stack_machine.reset();
+        program.clearRetainingCapacity();
+        try b.writeEntryValue(writer, sub_program.written());
+        _ = try stack_machine.run(program.written(), allocator, context, null);
+        try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic);
     }
 }
lib/std/debug/SelfInfo/DarwinModule.zig
@@ -265,12 +265,9 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
         error.OutOfMemory,
         error.Unexpected,
         => |e| return e,
-        error.UnimplementedArch,
-        error.UnimplementedOs,
-        error.ThreadContextNotSupported,
+        error.UnsupportedRegister,
         => return error.UnsupportedDebugInfo,
         error.InvalidRegister,
-        error.RegisterContextRequired,
         error.IncompatibleRegisterSize,
         => return error.InvalidDebugInfo,
     };
@@ -396,7 +393,6 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
     };
 
     if (entry.raw_encoding == 0) return error.MissingDebugInfo;
-    const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
 
     const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
     const new_ip = switch (builtin.cpu.arch) {
@@ -405,16 +401,16 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
             .RBP_FRAME => ip: {
                 const frame = encoding.value.x86_64.frame;
 
-                const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
+                const fp = (try dwarfRegNative(&context.cpu_context, fp_reg_num)).*;
                 const new_sp = fp + 2 * @sizeOf(usize);
 
                 const ip_ptr = fp + @sizeOf(usize);
                 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
                 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
 
-                (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
-                (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
-                (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
+                (try dwarfRegNative(&context.cpu_context, fp_reg_num)).* = new_fp;
+                (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp;
+                (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip;
 
                 const regs: [5]u3 = .{
                     frame.reg0,
@@ -427,7 +423,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
                     if (reg == 0) continue;
                     const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
                     const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
-                    (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
+                    (try dwarfRegNative(&context.cpu_context, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*;
                 }
 
                 break :ip new_ip;
@@ -437,7 +433,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
             => ip: {
                 const frameless = encoding.value.x86_64.frameless;
 
-                const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
+                const sp = (try dwarfRegNative(&context.cpu_context, sp_reg_num)).*;
                 const stack_size: usize = stack_size: {
                     if (encoding.mode.x86_64 == .STACK_IMMD) {
                         break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
@@ -487,7 +483,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
                     var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
                     for (0..reg_count) |i| {
                         const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
-                        (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+                        (try dwarfRegNative(&context.cpu_context, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
                         reg_addr += @sizeOf(usize);
                     }
 
@@ -497,8 +493,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
                 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
                 const new_sp = ip_ptr + @sizeOf(usize);
 
-                (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
-                (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
+                (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp;
+                (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip;
 
                 break :ip new_ip;
             },
@@ -516,10 +512,10 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
         .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
             .OLD => return error.UnsupportedDebugInfo,
             .FRAMELESS => ip: {
-                const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
+                const sp = (try dwarfRegNative(&context.cpu_context, sp_reg_num)).*;
                 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
-                const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
-                (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
+                const new_ip = (try dwarfRegNative(&context.cpu_context, 30)).*;
+                (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp;
                 break :ip new_ip;
             },
             .DWARF => {
@@ -535,15 +531,15 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
             .FRAME => ip: {
                 const frame = encoding.value.arm64.frame;
 
-                const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
+                const fp = (try dwarfRegNative(&context.cpu_context, fp_reg_num)).*;
                 const ip_ptr = fp + @sizeOf(usize);
 
                 var reg_addr = fp - @sizeOf(usize);
                 inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
                     if (@field(frame.x_reg_pairs, field.name) != 0) {
-                        (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+                        (try dwarfRegNative(&context.cpu_context, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
                         reg_addr += @sizeOf(usize);
-                        (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
+                        (try dwarfRegNative(&context.cpu_context, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
                         reg_addr += @sizeOf(usize);
                     }
                 }
@@ -552,12 +548,12 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
                     if (@field(frame.d_reg_pairs, field.name) != 0) {
                         // Only the lower half of the 128-bit V registers are restored during unwinding
                         {
-                            const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 8 + i, context.reg_context));
+                            const dest: *align(1) usize = @ptrCast(try context.cpu_context.dwarfRegisterBytes(64 + 8 + i));
                             dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
                         }
                         reg_addr += @sizeOf(usize);
                         {
-                            const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 9 + i, context.reg_context));
+                            const dest: *align(1) usize = @ptrCast(try context.cpu_context.dwarfRegisterBytes(64 + 9 + i));
                             dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
                         }
                         reg_addr += @sizeOf(usize);
@@ -567,8 +563,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
                 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
                 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
 
-                (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
-                (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
+                (try dwarfRegNative(&context.cpu_context, fp_reg_num)).* = new_fp;
+                (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip;
 
                 break :ip new_ip;
             },
@@ -782,13 +778,9 @@ test {
     _ = MachoSymbol;
 }
 
-fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
-    return Dwarf.abi.fpRegNum(builtin.target.cpu.arch, reg_context);
-}
-fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
-    return Dwarf.abi.spRegNum(builtin.target.cpu.arch, reg_context);
-}
-const ip_reg_num = Dwarf.abi.ipRegNum(builtin.target.cpu.arch).?;
+const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?;
+const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch);
+const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch);
 
 /// Uses `mmap` to map the file at `path` into memory.
 fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
@@ -821,8 +813,7 @@ const mem = std.mem;
 const posix = std.posix;
 const testing = std.testing;
 const Error = std.debug.SelfInfo.Error;
-const regBytes = Dwarf.abi.regBytes;
-const regValueNative = Dwarf.abi.regValueNative;
+const dwarfRegNative = std.debug.SelfInfo.DwarfUnwindContext.regNative;
 
 const builtin = @import("builtin");
 const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo/ElfModule.zig
@@ -26,7 +26,6 @@ pub fn key(m: ElfModule) usize {
 pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModule {
     _ = cache;
     _ = gpa;
-    if (builtin.target.os.tag == .haiku) @panic("TODO implement lookup module for Haiku");
     const DlIterContext = struct {
         /// input
         address: usize,
@@ -261,7 +260,7 @@ pub const supports_unwinding: bool = s: {
 };
 comptime {
     if (supports_unwinding) {
-        std.debug.assert(Dwarf.abi.supportsUnwinding(&builtin.target));
+        std.debug.assert(Dwarf.supportsUnwinding(&builtin.target));
     }
 }
 
lib/std/debug/SelfInfo/WindowsModule.zig
@@ -295,11 +295,45 @@ pub const UnwindContext = struct {
     pc: usize,
     cur: windows.CONTEXT,
     history_table: windows.UNWIND_HISTORY_TABLE,
-    pub fn init(ctx: *const windows.CONTEXT, gpa: Allocator) Allocator.Error!UnwindContext {
-        _ = gpa;
+    pub fn init(ctx: *const std.debug.cpu_context.Native) UnwindContext {
         return .{
             .pc = @returnAddress(),
-            .cur = ctx.*,
+            .cur = switch (builtin.cpu.arch) {
+                .x86_64 => std.mem.zeroInit(windows.CONTEXT, .{
+                    .Rax = ctx.gprs.get(.rax),
+                    .Rcx = ctx.gprs.get(.rcx),
+                    .Rdx = ctx.gprs.get(.rdx),
+                    .Rbx = ctx.gprs.get(.rbx),
+                    .Rsp = ctx.gprs.get(.rsp),
+                    .Rbp = ctx.gprs.get(.rbp),
+                    .Rsi = ctx.gprs.get(.rsi),
+                    .Rdi = ctx.gprs.get(.rdi),
+                    .R8 = ctx.gprs.get(.r8),
+                    .R9 = ctx.gprs.get(.r9),
+                    .R10 = ctx.gprs.get(.r10),
+                    .R11 = ctx.gprs.get(.r11),
+                    .R12 = ctx.gprs.get(.r12),
+                    .R13 = ctx.gprs.get(.r13),
+                    .R14 = ctx.gprs.get(.r14),
+                    .R15 = ctx.gprs.get(.r15),
+                    .Rip = ctx.gprs.get(.rip),
+                }),
+                .aarch64, .aarch64_be => .{
+                    .ContextFlags = 0,
+                    .Cpsr = 0,
+                    .DUMMYUNIONNAME = .{ .X = ctx.x },
+                    .Sp = ctx.sp,
+                    .Pc = ctx.pc,
+                    .V = @splat(.{ .B = @splat(0) }),
+                    .Fpcr = 0,
+                    .Fpsr = 0,
+                    .Bcr = @splat(0),
+                    .Bvr = @splat(0),
+                    .Wcr = @splat(0),
+                    .Wvr = @splat(0),
+                },
+                else => comptime unreachable,
+            },
             .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE),
         };
     }
lib/std/debug/cpu_context.zig
@@ -0,0 +1,1019 @@
+/// Register state for the native architecture, used by `std.debug` for stack unwinding.
+/// `noreturn` if there is no implementation for the native architecture.
+/// This can be overriden by exposing a declaration `root.debug.CpuContext`.
+pub const Native = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "CpuContext"))
+    root.debug.CpuContext
+else switch (native_arch) {
+    .x86 => X86,
+    .x86_64 => X86_64,
+    .arm, .armeb, .thumb, .thumbeb => Arm,
+    .aarch64, .aarch64_be => Aarch64,
+    else => noreturn,
+};
+
+pub const DwarfRegisterError = error{
+    InvalidRegister,
+    UnsupportedRegister,
+};
+
+pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
+    if (signal_ucontext_t == void) return null;
+    const uc: *const signal_ucontext_t = @ptrCast(@alignCast(ctx_ptr));
+    return switch (native_arch) {
+        .x86 => switch (native_os) {
+            .linux, .netbsd, .solaris, .illumos => .{ .gprs = .init(.{
+                .eax = uc.mcontext.gregs[std.posix.REG.EAX],
+                .ecx = uc.mcontext.gregs[std.posix.REG.ECX],
+                .edx = uc.mcontext.gregs[std.posix.REG.EDX],
+                .ebx = uc.mcontext.gregs[std.posix.REG.EBX],
+                .esp = uc.mcontext.gregs[std.posix.REG.ESP],
+                .ebp = uc.mcontext.gregs[std.posix.REG.EBP],
+                .esi = uc.mcontext.gregs[std.posix.REG.ESI],
+                .edi = uc.mcontext.gregs[std.posix.REG.EDI],
+                .eip = uc.mcontext.gregs[std.posix.REG.EIP],
+            }) },
+            else => null,
+        },
+        .x86_64 => switch (native_os) {
+            .linux, .solaris, .illumos => .{ .gprs = .init(.{
+                .rax = uc.mcontext.gregs[std.posix.REG.RAX],
+                .rdx = uc.mcontext.gregs[std.posix.REG.RDX],
+                .rcx = uc.mcontext.gregs[std.posix.REG.RCX],
+                .rbx = uc.mcontext.gregs[std.posix.REG.RBX],
+                .rsi = uc.mcontext.gregs[std.posix.REG.RSI],
+                .rdi = uc.mcontext.gregs[std.posix.REG.RDI],
+                .rbp = uc.mcontext.gregs[std.posix.REG.RBP],
+                .rsp = uc.mcontext.gregs[std.posix.REG.RSP],
+                .r8 = uc.mcontext.gregs[std.posix.REG.R8],
+                .r9 = uc.mcontext.gregs[std.posix.REG.R9],
+                .r10 = uc.mcontext.gregs[std.posix.REG.R10],
+                .r11 = uc.mcontext.gregs[std.posix.REG.R11],
+                .r12 = uc.mcontext.gregs[std.posix.REG.R12],
+                .r13 = uc.mcontext.gregs[std.posix.REG.R13],
+                .r14 = uc.mcontext.gregs[std.posix.REG.R14],
+                .r15 = uc.mcontext.gregs[std.posix.REG.R15],
+                .rip = uc.mcontext.gregs[std.posix.REG.RIP],
+            }) },
+            .freebsd => .{ .gprs = .init(.{
+                .rax = uc.mcontext.rax,
+                .rdx = uc.mcontext.rdx,
+                .rcx = uc.mcontext.rcx,
+                .rbx = uc.mcontext.rbx,
+                .rsi = uc.mcontext.rsi,
+                .rdi = uc.mcontext.rdi,
+                .rbp = uc.mcontext.rbp,
+                .rsp = uc.mcontext.rsp,
+                .r8 = uc.mcontext.r8,
+                .r9 = uc.mcontext.r9,
+                .r10 = uc.mcontext.r10,
+                .r11 = uc.mcontext.r11,
+                .r12 = uc.mcontext.r12,
+                .r13 = uc.mcontext.r13,
+                .r14 = uc.mcontext.r14,
+                .r15 = uc.mcontext.r15,
+                .rip = uc.mcontext.rip,
+            }) },
+            .openbsd => .{ .gprs = .init(.{
+                .rax = @bitCast(uc.sc_rax),
+                .rdx = @bitCast(uc.sc_rdx),
+                .rcx = @bitCast(uc.sc_rcx),
+                .rbx = @bitCast(uc.sc_rbx),
+                .rsi = @bitCast(uc.sc_rsi),
+                .rdi = @bitCast(uc.sc_rdi),
+                .rbp = @bitCast(uc.sc_rbp),
+                .rsp = @bitCast(uc.sc_rsp),
+                .r8 = @bitCast(uc.sc_r8),
+                .r9 = @bitCast(uc.sc_r9),
+                .r10 = @bitCast(uc.sc_r10),
+                .r11 = @bitCast(uc.sc_r11),
+                .r12 = @bitCast(uc.sc_r12),
+                .r13 = @bitCast(uc.sc_r13),
+                .r14 = @bitCast(uc.sc_r14),
+                .r15 = @bitCast(uc.sc_r15),
+                .rip = @bitCast(uc.sc_rip),
+            }) },
+            .macos, .ios => .{ .gprs = .init(.{
+                .rax = uc.mcontext.ss.rax,
+                .rdx = uc.mcontext.ss.rdx,
+                .rcx = uc.mcontext.ss.rcx,
+                .rbx = uc.mcontext.ss.rbx,
+                .rsi = uc.mcontext.ss.rsi,
+                .rdi = uc.mcontext.ss.rdi,
+                .rbp = uc.mcontext.ss.rbp,
+                .rsp = uc.mcontext.ss.rsp,
+                .r8 = uc.mcontext.ss.r8,
+                .r9 = uc.mcontext.ss.r9,
+                .r10 = uc.mcontext.ss.r10,
+                .r11 = uc.mcontext.ss.r11,
+                .r12 = uc.mcontext.ss.r12,
+                .r13 = uc.mcontext.ss.r13,
+                .r14 = uc.mcontext.ss.r14,
+                .r15 = uc.mcontext.ss.r15,
+                .rip = uc.mcontext.ss.rip,
+            }) },
+            else => null,
+        },
+        .arm, .armeb, .thumb, .thumbeb => switch (builtin.os.tag) {
+            .linux => .{
+                .r = .{
+                    uc.mcontext.arm_r0,
+                    uc.mcontext.arm_r1,
+                    uc.mcontext.arm_r2,
+                    uc.mcontext.arm_r3,
+                    uc.mcontext.arm_r4,
+                    uc.mcontext.arm_r5,
+                    uc.mcontext.arm_r6,
+                    uc.mcontext.arm_r7,
+                    uc.mcontext.arm_r8,
+                    uc.mcontext.arm_r9,
+                    uc.mcontext.arm_r10,
+                    uc.mcontext.arm_fp, // r11 = fp
+                    uc.mcontext.arm_ip, // r12 = ip
+                    uc.mcontext.arm_sp, // r13 = sp
+                    uc.mcontext.arm_lr, // r14 = lr
+                    uc.mcontext.arm_pc, // r15 = pc
+                },
+            },
+            else => null,
+        },
+        .aarch64, .aarch64_be => switch (builtin.os.tag) {
+            .macos, .ios, .tvos, .watchos, .visionos => .{
+                .x = uc.mcontext.ss.regs ++ @as([2]u64, .{
+                    uc.mcontext.ss.fp, // x29 = fp
+                    uc.mcontext.ss.lr, // x30 = lr
+                }),
+                .sp = uc.mcontext.ss.sp,
+                .pc = uc.mcontext.ss.pc,
+            },
+            .netbsd => .{
+                .x = uc.mcontext.gregs[0..31],
+                .sp = uc.mcontext.gregs[31],
+                .pc = uc.mcontext.gregs[32],
+            },
+            .freebsd => .{
+                .x = uc.mcontext.gpregs.x ++ @as([1]u64, .{
+                    uc.mcontext.gpregs.lr, // x30 = lr
+                }),
+                .sp = uc.mcontext.gpregs.sp,
+                // On aarch64, the register ELR_LR1 defines the address to return to after handling
+                // a CPU exception (ELR is "Exception Link Register"). FreeBSD's ucontext_t uses
+                // this as the field name, but it's the same thing as the context's PC.
+                .pc = uc.mcontext.gpregs.elr,
+            },
+            .openbsd => .{
+                .x = uc.sc_x ++ .{uc.sc_lr},
+                .sp = uc.sc_sp,
+                // Not a bug; see freebsd above for explanation.
+                .pc = uc.sc_elr,
+            },
+            .linux => .{
+                .x = uc.mcontext.regs,
+                .sp = uc.mcontext.sp,
+                .pc = uc.mcontext.pc,
+            },
+            else => null,
+        },
+        else => null,
+    };
+}
+
+pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native {
+    return switch (native_arch) {
+        .x86 => .{ .gprs = .init(.{
+            .eax = ctx.Eax,
+            .ecx = ctx.Ecx,
+            .edx = ctx.Edx,
+            .ebx = ctx.Ebx,
+            .esp = ctx.Esp,
+            .ebp = ctx.Ebp,
+            .esi = ctx.Esi,
+            .edi = ctx.Edi,
+            .eip = ctx.Eip,
+        }) },
+        .x86_64 => .{ .gprs = .init(.{
+            .rax = ctx.Rax,
+            .rdx = ctx.Rdx,
+            .rcx = ctx.Rcx,
+            .rbx = ctx.Rbx,
+            .rsi = ctx.Rsi,
+            .rdi = ctx.Rdi,
+            .rbp = ctx.Rbp,
+            .rsp = ctx.Rsp,
+            .r8 = ctx.R8,
+            .r9 = ctx.R9,
+            .r10 = ctx.R10,
+            .r11 = ctx.R11,
+            .r12 = ctx.R12,
+            .r13 = ctx.R13,
+            .r14 = ctx.R14,
+            .r15 = ctx.R15,
+            .rip = ctx.Rip,
+        }) },
+        .aarch64, .aarch64_be => .{
+            .x = ctx.DUMMYUNIONNAME.X[0..31].*,
+            .sp = ctx.Sp,
+            .pc = ctx.Pc,
+        },
+        else => comptime unreachable,
+    };
+}
+
+pub const X86 = struct {
+    /// The first 8 registers here intentionally match the order of registers pushed
+    /// by PUSHA, which is also the order used by the DWARF register mappings.
+    pub const Gpr = enum {
+        // zig fmt: off
+        eax, ecx, edx, ebx,
+        esp, ebp, esi, edi,
+        eip,
+        // zig fmt: on
+    };
+    gprs: std.enums.EnumArray(Gpr, u32),
+
+    pub inline fn current() X86 {
+        var ctx: X86 = undefined;
+        asm volatile (
+            \\movl %%eax, 0x00(%%edi)
+            \\movl %%ecx, 0x04(%%edi)
+            \\movl %%edx, 0x08(%%edi)
+            \\movl %%ebx, 0x0c(%%edi)
+            \\movl %%esp, 0x10(%%edi)
+            \\movl %%ebp, 0x14(%%edi)
+            \\movl %%esi, 0x18(%%edi)
+            \\movl %%edi, 0x1c(%%edi)
+            \\call 1f
+            \\1:
+            \\popl 0x20(%%edi)
+            :
+            : [gprs] "{edi}" (&ctx.gprs.values),
+            : .{ .memory = true });
+        return ctx;
+    }
+
+    pub fn dwarfRegisterBytes(ctx: *X86, register_num: u16) DwarfRegisterError![]u8 {
+        // System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.1
+        //   ยง 2.4.2 "DWARF Register Number Mapping"
+        switch (register_num) {
+            // The order of `Gpr` intentionally matches DWARF's mappings.
+            //
+            // x86-macos sometimes uses different mappings (ebp and esp are reversed when the unwind
+            // information is from `__eh_frame`). This deviation is not considered here, because
+            // x86-macos is a deprecated target which is not supported by the Zig Standard Library.
+            0...8 => return @ptrCast(&ctx.gprs.values[register_num]),
+
+            9 => return error.UnsupportedRegister, // rflags
+            11...18 => return error.UnsupportedRegister, // st0 - st7
+            21...28 => return error.UnsupportedRegister, // xmm0 - xmm7
+            29...36 => return error.UnsupportedRegister, // mm0 - mm7
+            39 => return error.UnsupportedRegister, // mxcsr
+            40...45 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs
+            48 => return error.UnsupportedRegister, // tr
+            49 => return error.UnsupportedRegister, // ldtr
+            93...94 => return error.UnsupportedRegister, // fs.base, gs.base
+
+            else => return error.InvalidRegister,
+        }
+    }
+};
+
+pub const X86_64 = struct {
+    /// MLUGG TODO: explain this order. why does DWARF have this?
+    pub const Gpr = enum {
+        // zig fmt: off
+        rax, rdx, rcx, rbx,
+        rsi, rdi, rbp, rsp,
+        r8,  r9,  r10, r11,
+        r12, r13, r14, r15,
+        rip,
+        // zig fmt: on
+    };
+    gprs: std.enums.EnumArray(Gpr, u64),
+
+    pub inline fn current() X86_64 {
+        var ctx: X86_64 = undefined;
+        asm volatile (
+            \\movq %%rax, 0x00(%%rdi)
+            \\movq %%rdx, 0x08(%%rdi)
+            \\movq %%rcx, 0x10(%%rdi)
+            \\movq %%rbx, 0x18(%%rdi)
+            \\movq %%rsi, 0x20(%%rdi)
+            \\movq %%rdi, 0x28(%%rdi)
+            \\movq %%rbp, 0x30(%%rdi)
+            \\movq %%rsp, 0x38(%%rdi)
+            \\movq %%r8,  0x40(%%rdi)
+            \\movq %%r9,  0x48(%%rdi)
+            \\movq %%r10, 0x50(%%rdi)
+            \\movq %%r11, 0x58(%%rdi)
+            \\movq %%r12, 0x60(%%rdi)
+            \\movq %%r13, 0x68(%%rdi)
+            \\movq %%r14, 0x70(%%rdi)
+            \\movq %%r15, 0x78(%%rdi)
+            \\leaq (%%rip), %%rax
+            \\movq %%rax, 0x80(%%rdi)
+            \\movq 0x00(%%rdi), %%rax  // restore saved rax
+            :
+            : [gprs] "{rdi}" (&ctx.gprs.values),
+            : .{ .memory = true });
+        return ctx;
+    }
+
+    pub fn dwarfRegisterBytes(ctx: *X86_64, register_num: u16) DwarfRegisterError![]u8 {
+        // System V Application Binary Interface AMD64 Architecture Processor Supplement
+        //   ยง 3.6.2 "DWARF Register Number Mapping"
+        switch (register_num) {
+            // The order of `Gpr` intentionally matches DWARF's mappings.
+            0...16 => return @ptrCast(&ctx.gprs.values[register_num]),
+
+            17...32 => return error.UnsupportedRegister, // xmm0 - xmm15
+            33...40 => return error.UnsupportedRegister, // st0 - st7
+            41...48 => return error.UnsupportedRegister, // mm0 - mm7
+            49 => return error.UnsupportedRegister, // rflags
+            50...55 => return error.UnsupportedRegister, // es, cs, ss, ds, fs, gs
+            58...59 => return error.UnsupportedRegister, // fs.base, gs.base
+            62 => return error.UnsupportedRegister, // tr
+            63 => return error.UnsupportedRegister, // ldtr
+            64 => return error.UnsupportedRegister, // mxcsr
+            65 => return error.UnsupportedRegister, // fcw
+            66 => return error.UnsupportedRegister, // fsw
+
+            else => return error.InvalidRegister,
+        }
+    }
+};
+
+pub const Arm = struct {
+    /// The numbered general-purpose registers R0 - R15.
+    r: [16]u32,
+
+    pub inline fn current() Arm {
+        var ctx: Arm = undefined;
+        asm volatile (
+            \\// For compatibility with Thumb, we can't write r13 (sp) or r15 (pc) with stm.
+            \\stm r0, {r0-r12}
+            \\str r13, [r0, #0x34]
+            \\str r14, [r0, #0x38]
+            \\str r15, [r0, #0x3c]
+            :
+            : [r] "{r0}" (&ctx.r),
+            : .{ .memory = true });
+        return ctx;
+    }
+
+    pub fn dwarfRegisterBytes(ctx: *Arm, register_num: u16) DwarfRegisterError![]u8 {
+        // DWARF for the Arm(r) Architecture ยง 4.1 "DWARF register names"
+        switch (register_num) {
+            // The order of `Gpr` intentionally matches DWARF's mappings.
+            0...15 => return @ptrCast(&ctx.r[register_num]),
+
+            64...95 => return error.UnsupportedRegister, // S0 - S31
+            96...103 => return error.UnsupportedRegister, // F0 - F7
+            104...111 => return error.UnsupportedRegister, // wCGR0 - wCGR7, or ACC0 - ACC7
+            112...127 => return error.UnsupportedRegister, // wR0 - wR15
+            128 => return error.UnsupportedRegister, // SPSR
+            129 => return error.UnsupportedRegister, // SPSR_FIQ
+            130 => return error.UnsupportedRegister, // SPSR_IRQ
+            131 => return error.UnsupportedRegister, // SPSR_ABT
+            132 => return error.UnsupportedRegister, // SPSR_UND
+            133 => return error.UnsupportedRegister, // SPSR_SVC
+            143 => return error.UnsupportedRegister, // RA_AUTH_CODE
+            144...150 => return error.UnsupportedRegister, // R8_USR - R14_USR
+            151...157 => return error.UnsupportedRegister, // R8_FIQ - R14_FIQ
+            158...159 => return error.UnsupportedRegister, // R13_IRQ - R14_IRQ
+            160...161 => return error.UnsupportedRegister, // R13_ABT - R14_ABT
+            162...163 => return error.UnsupportedRegister, // R13_UND - R14_UND
+            164...165 => return error.UnsupportedRegister, // R13_SVC - R14_SVC
+            192...199 => return error.UnsupportedRegister, // wC0 - wC7
+            256...287 => return error.UnsupportedRegister, // D0 - D31
+            320 => return error.UnsupportedRegister, // TPIDRURO
+            321 => return error.UnsupportedRegister, // TPIDRURW
+            322 => return error.UnsupportedRegister, // TPIDPR
+            323 => return error.UnsupportedRegister, // HTPIDPR
+            8192...16383 => return error.UnsupportedRegister, // Unspecified vendor co-processor register
+
+            else => return error.InvalidRegister,
+        }
+    }
+};
+
+/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
+pub const Aarch64 = extern struct {
+    /// The numbered general-purpose registers X0 - X30.
+    x: [31]u64,
+    sp: u64,
+    pc: u64,
+
+    pub inline fn current() Aarch64 {
+        var ctx: Aarch64 = undefined;
+        asm volatile (
+            \\stp x0,  x1,  [x0, #0x000]
+            \\stp x2,  x3,  [x0, #0x010]
+            \\stp x4,  x5,  [x0, #0x020]
+            \\stp x6,  x7,  [x0, #0x030]
+            \\stp x8,  x9,  [x0, #0x040]
+            \\stp x10, x11, [x0, #0x050]
+            \\stp x12, x13, [x0, #0x060]
+            \\stp x14, x15, [x0, #0x070]
+            \\stp x16, x17, [x0, #0x080]
+            \\stp x18, x19, [x0, #0x090]
+            \\stp x20, x21, [x0, #0x0a0]
+            \\stp x22, x23, [x0, #0x0b0]
+            \\stp x24, x25, [x0, #0x0c0]
+            \\stp x26, x27, [x0, #0x0d0]
+            \\stp x28, x29, [x0, #0x0e0]
+            \\str x30, [x0, #0x0f0]
+            \\mov x1, sp
+            \\str x1, [x0, #0x0f8]
+            \\adr x1, .
+            \\str x1, [x0, #0x100]
+            \\ldr x1, [x0, #0x008] // restore saved x1
+            :
+            : [gprs] "{x0}" (&ctx),
+            : .{ .memory = true });
+        return ctx;
+    }
+
+    pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 {
+        // DWARF for the Arm(r) 64-bit Architecture (AArch64) ยง 4.1 "DWARF register names"
+        switch (register_num) {
+            // The order of `Gpr` intentionally matches DWARF's mappings.
+            0...30 => return @ptrCast(&ctx.x[register_num]),
+            31 => return @ptrCast(&ctx.sp),
+            32 => return @ptrCast(&ctx.pc),
+
+            33 => return error.UnsupportedRegister, // ELF_mode
+            34 => return error.UnsupportedRegister, // RA_SIGN_STATE
+            35 => return error.UnsupportedRegister, // TPIDRRO_ELO
+            36 => return error.UnsupportedRegister, // RPIDR_ELO
+            37 => return error.UnsupportedRegister, // RPIDR_EL1
+            38 => return error.UnsupportedRegister, // RPIDR_EL2
+            39 => return error.UnsupportedRegister, // RPIDR_EL3
+            46 => return error.UnsupportedRegister, // VG
+            47 => return error.UnsupportedRegister, // FFR
+            48...63 => return error.UnsupportedRegister, // P0 - P15
+            64...95 => return error.UnsupportedRegister, // V0 - V31
+            96...127 => return error.UnsupportedRegister, // Z0 - Z31
+
+            else => return error.InvalidRegister,
+        }
+    }
+};
+
+const signal_ucontext_t = switch (native_os) {
+    .linux => std.os.linux.ucontext_t,
+    .emscripten => std.os.emscripten.ucontext_t,
+    .freebsd => std.os.freebsd.ucontext_t,
+    .macos, .ios, .tvos, .watchos, .visionos => extern struct {
+        onstack: c_int,
+        sigmask: std.c.sigset_t,
+        stack: std.c.stack_t,
+        link: ?*signal_ucontext_t,
+        mcsize: u64,
+        mcontext: *mcontext_t,
+        const mcontext_t = switch (native_arch) {
+            .aarch64 => extern struct {
+                es: extern struct {
+                    far: u64, // Virtual Fault Address
+                    esr: u32, // Exception syndrome
+                    exception: u32, // Number of arm exception taken
+                },
+                ss: extern struct {
+                    /// General purpose registers
+                    regs: [29]u64,
+                    /// Frame pointer x29
+                    fp: u64,
+                    /// Link register x30
+                    lr: u64,
+                    /// Stack pointer x31
+                    sp: u64,
+                    /// Program counter
+                    pc: u64,
+                    /// Current program status register
+                    cpsr: u32,
+                    __pad: u32,
+                },
+                ns: extern struct {
+                    q: [32]u128,
+                    fpsr: u32,
+                    fpcr: u32,
+                },
+            },
+            .x86_64 => extern struct {
+                es: extern struct {
+                    trapno: u16,
+                    cpu: u16,
+                    err: u32,
+                    faultvaddr: u64,
+                },
+                ss: extern struct {
+                    rax: u64,
+                    rbx: u64,
+                    rcx: u64,
+                    rdx: u64,
+                    rdi: u64,
+                    rsi: u64,
+                    rbp: u64,
+                    rsp: u64,
+                    r8: u64,
+                    r9: u64,
+                    r10: u64,
+                    r11: u64,
+                    r12: u64,
+                    r13: u64,
+                    r14: u64,
+                    r15: u64,
+                    rip: u64,
+                    rflags: u64,
+                    cs: u64,
+                    fs: u64,
+                    gs: u64,
+                },
+                fs: extern struct {
+                    reserved: [2]c_int,
+                    fcw: u16,
+                    fsw: u16,
+                    ftw: u8,
+                    rsrv1: u8,
+                    fop: u16,
+                    ip: u32,
+                    cs: u16,
+                    rsrv2: u16,
+                    dp: u32,
+                    ds: u16,
+                    rsrv3: u16,
+                    mxcsr: u32,
+                    mxcsrmask: u32,
+                    stmm: [8]stmm_reg,
+                    xmm: [16]xmm_reg,
+                    rsrv4: [96]u8,
+                    reserved1: c_int,
+
+                    const stmm_reg = [16]u8;
+                    const xmm_reg = [16]u8;
+                },
+            },
+            else => void,
+        };
+    },
+    .solaris, .illumos => extern struct {
+        flags: u64,
+        link: ?*signal_ucontext_t,
+        sigmask: std.c.sigset_t,
+        stack: std.c.stack_t,
+        mcontext: mcontext_t,
+        brand_data: [3]?*anyopaque,
+        filler: [2]i64,
+        const mcontext_t = extern struct {
+            gregs: [28]u64,
+            fpregs: std.c.fpregset_t,
+        };
+    },
+    .openbsd => switch (builtin.cpu.arch) {
+        .x86_64 => extern struct {
+            sc_rdi: c_long,
+            sc_rsi: c_long,
+            sc_rdx: c_long,
+            sc_rcx: c_long,
+            sc_r8: c_long,
+            sc_r9: c_long,
+            sc_r10: c_long,
+            sc_r11: c_long,
+            sc_r12: c_long,
+            sc_r13: c_long,
+            sc_r14: c_long,
+            sc_r15: c_long,
+            sc_rbp: c_long,
+            sc_rbx: c_long,
+            sc_rax: c_long,
+            sc_gs: c_long,
+            sc_fs: c_long,
+            sc_es: c_long,
+            sc_ds: c_long,
+            sc_trapno: c_long,
+            sc_err: c_long,
+            sc_rip: c_long,
+            sc_cs: c_long,
+            sc_rflags: c_long,
+            sc_rsp: c_long,
+            sc_ss: c_long,
+
+            sc_fpstate: *anyopaque, // struct fxsave64 *
+            __sc_unused: c_int,
+            sc_mask: c_int,
+            sc_cookie: c_long,
+        },
+        .aarch64 => extern struct {
+            __sc_unused: c_int,
+            sc_mask: c_int,
+            sc_sp: c_ulong,
+            sc_lr: c_ulong,
+            sc_elr: c_ulong,
+            sc_spsr: c_ulong,
+            sc_x: [30]c_ulong,
+            sc_cookie: c_long,
+        },
+        else => void,
+    },
+    .netbsd => extern struct {
+        flags: u32,
+        link: ?*signal_ucontext_t,
+        sigmask: std.c.sigset_t,
+        stack: std.c.stack_t,
+        mcontext: mcontext_t,
+        __pad: [
+            switch (builtin.cpu.arch) {
+                .x86 => 4,
+                .mips, .mipsel, .mips64, .mips64el => 14,
+                .arm, .armeb, .thumb, .thumbeb => 1,
+                .sparc, .sparc64 => if (@sizeOf(usize) == 4) 43 else 8,
+                else => 0,
+            }
+        ]u32,
+        const mcontext_t = switch (builtin.cpu.arch) {
+            .aarch64, .aarch64_be => extern struct {
+                gregs: [35]u64,
+                fregs: [528]u8 align(16),
+                spare: [8]u64,
+            },
+            .x86 => extern struct {
+                gregs: [19]u32,
+                fpregs: [161]u32,
+                mc_tlsbase: u32,
+            },
+            .x86_64 => extern struct {
+                gregs: [26]u64,
+                mc_tlsbase: u64,
+                fpregs: [512]u8 align(8),
+            },
+            else => void,
+        };
+    },
+    .dragonfly => extern struct {
+        sigmask: std.c.sigset_t,
+        mcontext: mcontext_t,
+        link: ?*signal_ucontext_t,
+        stack: std.c.stack_t,
+        cofunc: ?*fn (?*signal_ucontext_t, ?*anyopaque) void,
+        arg: ?*void,
+        _spare: [4]c_int,
+        const mcontext_t = extern struct {
+            const register_t = isize;
+            onstack: register_t, // XXX - sigcontext compat.
+            rdi: register_t,
+            rsi: register_t,
+            rdx: register_t,
+            rcx: register_t,
+            r8: register_t,
+            r9: register_t,
+            rax: register_t,
+            rbx: register_t,
+            rbp: register_t,
+            r10: register_t,
+            r11: register_t,
+            r12: register_t,
+            r13: register_t,
+            r14: register_t,
+            r15: register_t,
+            xflags: register_t,
+            trapno: register_t,
+            addr: register_t,
+            flags: register_t,
+            err: register_t,
+            rip: register_t,
+            cs: register_t,
+            rflags: register_t,
+            rsp: register_t, // machine state
+            ss: register_t,
+
+            len: c_uint, // sizeof(mcontext_t)
+            fpformat: c_uint,
+            ownedfp: c_uint,
+            reserved: c_uint,
+            unused: [8]c_uint,
+
+            // NOTE! 64-byte aligned as of here. Also must match savefpu structure.
+            fpregs: [256]c_int align(64),
+        };
+    },
+    .serenity => extern struct {
+        link: ?*signal_ucontext_t,
+        sigmask: std.c.sigset_t,
+        stack: std.c.stack_t,
+        mcontext: mcontext_t,
+        const mcontext_t = switch (builtin.cpu.arch) {
+            // https://github.com/SerenityOS/serenity/blob/200e91cd7f1ec5453799a2720d4dc114a59cc289/Kernel/Arch/aarch64/mcontext.h#L15-L19
+            .aarch64 => extern struct {
+                x: [31]u64,
+                sp: u64,
+                pc: u64,
+            },
+            // https://github.com/SerenityOS/serenity/blob/66f8d0f031ef25c409dbb4fecaa454800fecae0f/Kernel/Arch/riscv64/mcontext.h#L15-L18
+            .riscv64 => extern struct {
+                x: [31]u64,
+                pc: u64,
+            },
+            // https://github.com/SerenityOS/serenity/blob/7b9ea3efdec9f86a1042893e8107d0b23aad8727/Kernel/Arch/x86_64/mcontext.h#L15-L40
+            .x86_64 => extern struct {
+                rax: u64,
+                rcx: u64,
+                rdx: u64,
+                rbx: u64,
+                rsp: u64,
+                rbp: u64,
+                rsi: u64,
+                rdi: u64,
+                rip: u64,
+                r8: u64,
+                r9: u64,
+                r10: u64,
+                r11: u64,
+                r12: u64,
+                r13: u64,
+                r14: u64,
+                r15: u64,
+                rflags: u64,
+                cs: u32,
+                ss: u32,
+                ds: u32,
+                es: u32,
+                fs: u32,
+                gs: u32,
+            },
+            else => void,
+        };
+    },
+    .haiku => extern struct {
+        link: ?*signal_ucontext_t,
+        sigmask: std.c.sigset_t,
+        stack: std.c.stack_t,
+        mcontext: mcontext_t,
+        const mcontext_t = switch (builtin.cpu.arch) {
+            .arm, .thumb => extern struct {
+                r0: u32,
+                r1: u32,
+                r2: u32,
+                r3: u32,
+                r4: u32,
+                r5: u32,
+                r6: u32,
+                r7: u32,
+                r8: u32,
+                r9: u32,
+                r10: u32,
+                r11: u32,
+                r12: u32,
+                r13: u32,
+                r14: u32,
+                r15: u32,
+                cpsr: u32,
+            },
+            .aarch64 => extern struct {
+                x: [10]u64,
+                lr: u64,
+                sp: u64,
+                elr: u64,
+                spsr: u64,
+                fp_q: [32]u128,
+                fpsr: u32,
+                fpcr: u32,
+            },
+            .m68k => extern struct {
+                pc: u32,
+                d0: u32,
+                d1: u32,
+                d2: u32,
+                d3: u32,
+                d4: u32,
+                d5: u32,
+                d6: u32,
+                d7: u32,
+                a0: u32,
+                a1: u32,
+                a2: u32,
+                a3: u32,
+                a4: u32,
+                a5: u32,
+                a6: u32,
+                a7: u32,
+                ccr: u8,
+                f0: f64,
+                f1: f64,
+                f2: f64,
+                f3: f64,
+                f4: f64,
+                f5: f64,
+                f6: f64,
+                f7: f64,
+                f8: f64,
+                f9: f64,
+                f10: f64,
+                f11: f64,
+                f12: f64,
+                f13: f64,
+            },
+            .mipsel => extern struct {
+                r0: u32,
+            },
+            .powerpc => extern struct {
+                pc: u32,
+                r0: u32,
+                r1: u32,
+                r2: u32,
+                r3: u32,
+                r4: u32,
+                r5: u32,
+                r6: u32,
+                r7: u32,
+                r8: u32,
+                r9: u32,
+                r10: u32,
+                r11: u32,
+                r12: u32,
+                f0: f64,
+                f1: f64,
+                f2: f64,
+                f3: f64,
+                f4: f64,
+                f5: f64,
+                f6: f64,
+                f7: f64,
+                f8: f64,
+                f9: f64,
+                f10: f64,
+                f11: f64,
+                f12: f64,
+                f13: f64,
+                reserved: u32,
+                fpscr: u32,
+                ctr: u32,
+                xer: u32,
+                cr: u32,
+                msr: u32,
+                lr: u32,
+            },
+            .riscv64 => extern struct {
+                x: [31]u64,
+                pc: u64,
+                f: [32]f64,
+                fcsr: u64,
+            },
+            .sparc64 => extern struct {
+                g1: u64,
+                g2: u64,
+                g3: u64,
+                g4: u64,
+                g5: u64,
+                g6: u64,
+                g7: u64,
+                o0: u64,
+                o1: u64,
+                o2: u64,
+                o3: u64,
+                o4: u64,
+                o5: u64,
+                sp: u64,
+                o7: u64,
+                l0: u64,
+                l1: u64,
+                l2: u64,
+                l3: u64,
+                l4: u64,
+                l5: u64,
+                l6: u64,
+                l7: u64,
+                i0: u64,
+                i1: u64,
+                i2: u64,
+                i3: u64,
+                i4: u64,
+                i5: u64,
+                fp: u64,
+                i7: u64,
+            },
+            .x86 => extern struct {
+                pub const old_extended_regs = extern struct {
+                    control: u16,
+                    reserved1: u16,
+                    status: u16,
+                    reserved2: u16,
+                    tag: u16,
+                    reserved3: u16,
+                    eip: u32,
+                    cs: u16,
+                    opcode: u16,
+                    datap: u32,
+                    ds: u16,
+                    reserved4: u16,
+                    fp_mmx: [8][10]u8,
+                };
+
+                pub const fp_register = extern struct { value: [10]u8, reserved: [6]u8 };
+
+                pub const xmm_register = extern struct { value: [16]u8 };
+
+                pub const new_extended_regs = extern struct {
+                    control: u16,
+                    status: u16,
+                    tag: u16,
+                    opcode: u16,
+                    eip: u32,
+                    cs: u16,
+                    reserved1: u16,
+                    datap: u32,
+                    ds: u16,
+                    reserved2: u16,
+                    mxcsr: u32,
+                    reserved3: u32,
+                    fp_mmx: [8]fp_register,
+                    xmmx: [8]xmm_register,
+                    reserved4: [224]u8,
+                };
+
+                pub const extended_regs = extern struct {
+                    state: extern union {
+                        old_format: old_extended_regs,
+                        new_format: new_extended_regs,
+                    },
+                    format: u32,
+                };
+
+                eip: u32,
+                eflags: u32,
+                eax: u32,
+                ecx: u32,
+                edx: u32,
+                esp: u32,
+                ebp: u32,
+                reserved: u32,
+                xregs: extended_regs,
+                edi: u32,
+                esi: u32,
+                ebx: u32,
+            },
+            .x86_64 => extern struct {
+                pub const fp_register = extern struct {
+                    value: [10]u8,
+                    reserved: [6]u8,
+                };
+
+                pub const xmm_register = extern struct {
+                    value: [16]u8,
+                };
+
+                pub const fpu_state = extern struct {
+                    control: u16,
+                    status: u16,
+                    tag: u16,
+                    opcode: u16,
+                    rip: u64,
+                    rdp: u64,
+                    mxcsr: u32,
+                    mscsr_mask: u32,
+
+                    fp_mmx: [8]fp_register,
+                    xmm: [16]xmm_register,
+                    reserved: [96]u8,
+                };
+
+                pub const xstate_hdr = extern struct {
+                    bv: u64,
+                    xcomp_bv: u64,
+                    reserved: [48]u8,
+                };
+
+                pub const savefpu = extern struct {
+                    fxsave: fpu_state,
+                    xstate: xstate_hdr,
+                    ymm: [16]xmm_register,
+                };
+
+                rax: u64,
+                rbx: u64,
+                rcx: u64,
+                rdx: u64,
+                rdi: u64,
+                rsi: u64,
+                rbp: u64,
+                r8: u64,
+                r9: u64,
+                r10: u64,
+                r11: u64,
+                r12: u64,
+                r13: u64,
+                r14: u64,
+                r15: u64,
+                rsp: u64,
+                rip: u64,
+                rflags: u64,
+                fpu: savefpu,
+            },
+            else => void,
+        };
+    },
+    else => void,
+};
+
+const std = @import("../std.zig");
+const root = @import("root");
+const builtin = @import("builtin");
+const native_arch = @import("builtin").target.cpu.arch;
+const native_os = @import("builtin").target.os.tag;
lib/std/debug/Dwarf.zig
@@ -27,7 +27,6 @@ const Reader = std.Io.Reader;
 const Dwarf = @This();
 
 pub const expression = @import("Dwarf/expression.zig");
-pub const abi = @import("Dwarf/abi.zig");
 pub const call_frame = @import("Dwarf/call_frame.zig");
 pub const Unwind = @import("Dwarf/Unwind.zig");
 
@@ -1415,7 +1414,7 @@ pub fn readUnitHeader(r: *Reader, endian: Endian) ScanError!UnitHeader {
 }
 
 /// Returns the DWARF register number for an x86_64 register number found in compact unwind info
-pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
+pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 {
     return switch (unwind_reg_number) {
         1 => 3, // RBX
         2 => 12, // R12
@@ -1427,6 +1426,60 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
     };
 }
 
+/// Returns `null` for CPU architectures without an instruction pointer register.
+pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {
+    return switch (arch) {
+        .x86 => 8,
+        .x86_64 => 16,
+        .arm, .armeb, .thumb, .thumbeb => 15,
+        .aarch64, .aarch64_be => 32,
+        else => null,
+    };
+}
+
+pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {
+    return switch (arch) {
+        .x86 => 5,
+        .x86_64 => 6,
+        .arm, .armeb, .thumb, .thumbeb => 11,
+        .aarch64, .aarch64_be => 29,
+        else => unreachable,
+    };
+}
+
+pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
+    return switch (arch) {
+        .x86 => 4,
+        .x86_64 => 7,
+        .arm, .armeb, .thumb, .thumbeb => 13,
+        .aarch64, .aarch64_be => 31,
+        else => unreachable,
+    };
+}
+
+/// Tells whether unwinding for this target is supported by the Dwarf standard.
+///
+/// See also `std.debug.SelfInfo.supports_unwinding` which tells whether the Zig
+/// standard library has a working implementation of unwinding for this target.
+pub fn supportsUnwinding(target: *const std.Target) bool {
+    return switch (target.cpu.arch) {
+        .amdgcn,
+        .nvptx,
+        .nvptx64,
+        .spirv32,
+        .spirv64,
+        => false,
+
+        // Enabling this causes relocation errors such as:
+        // error: invalid relocation type R_RISCV_SUB32 at offset 0x20
+        .riscv64, .riscv64be, .riscv32, .riscv32be => false,
+
+        // Conservative guess. Feel free to update this logic with any targets
+        // that are known to not support Dwarf unwinding.
+        else => true,
+    };
+}
+
 /// This function is to make it handy to comment out the return and make it
 /// into a crash when working on this file.
 pub fn bad() error{InvalidDebugInfo} {
lib/std/debug/SelfInfo.zig
@@ -11,8 +11,7 @@ const mem = std.mem;
 const Allocator = std.mem.Allocator;
 const assert = std.debug.assert;
 const Dwarf = std.debug.Dwarf;
-const regBytes = Dwarf.abi.regBytes;
-const regValueNative = Dwarf.abi.regValueNative;
+const CpuContext = std.debug.cpu_context.Native;
 
 const root = @import("root");
 
@@ -38,8 +37,6 @@ pub const Error = error{
 pub const target_supported: bool = Module != void;
 
 /// Indicates whether the `SelfInfo` implementation has support for unwinding on this target.
-///
-/// For whether DWARF unwinding is *theoretically* possible, see `Dwarf.abi.supportsUnwinding`.
 pub const supports_unwinding: bool = target_supported and Module.supports_unwinding;
 
 pub const UnwindContext = if (supports_unwinding) Module.UnwindContext;
@@ -120,7 +117,7 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)
 /// pub const UnwindContext = struct {
 ///     /// A PC value inside the function of the last unwound frame.
 ///     pc: usize,
-///     pub fn init(tc: *std.debug.ThreadContext, gpa: Allocator) Allocator.Error!UnwindContext;
+///     pub fn init(ctx: *std.debug.cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext;
 ///     pub fn deinit(uc: *UnwindContext, gpa: Allocator) void;
 ///     /// Returns the frame pointer associated with the last unwound stack frame. If the frame
 ///     /// pointer is unknown, 0 may be returned instead.
@@ -141,9 +138,26 @@ const Module: type = Module: {
         break :Module root.debug.Module;
     }
     break :Module switch (native_os) {
-        .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => @import("SelfInfo/ElfModule.zig"),
-        .macos, .ios, .watchos, .tvos, .visionos => @import("SelfInfo/DarwinModule.zig"),
-        .uefi, .windows => @import("SelfInfo/WindowsModule.zig"),
+        .linux,
+        .netbsd,
+        .freebsd,
+        .dragonfly,
+        .openbsd,
+        .solaris,
+        .illumos,
+        => @import("SelfInfo/ElfModule.zig"),
+
+        .macos,
+        .ios,
+        .watchos,
+        .tvos,
+        .visionos,
+        => @import("SelfInfo/DarwinModule.zig"),
+
+        .uefi,
+        .windows,
+        => @import("SelfInfo/WindowsModule.zig"),
+
         else => void,
     };
 };
@@ -153,26 +167,25 @@ const Module: type = Module: {
 pub const DwarfUnwindContext = struct {
     cfa: ?usize,
     pc: usize,
-    thread_context: *std.debug.ThreadContext,
-    reg_context: Dwarf.abi.RegisterContext,
+    cpu_context: CpuContext,
     vm: Dwarf.Unwind.VirtualMachine,
     stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
 
-    pub fn init(thread_context: *std.debug.ThreadContext, gpa: Allocator) error{}!DwarfUnwindContext {
+    pub fn init(cpu_context: *const CpuContext) DwarfUnwindContext {
         comptime assert(supports_unwinding);
-        _ = gpa;
 
-        const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
-        const raw_pc_ptr = regValueNative(thread_context, ip_reg_num, null) catch {
-            unreachable; // error means unsupported, in which case `supports_unwinding` should have been `false`
+        // `@constCast` is safe because we aren't going to store to the resulting pointer.
+        const raw_pc_ptr = regNative(@constCast(cpu_context), ip_reg_num) catch |err| switch (err) {
+            error.InvalidRegister => unreachable, // `ip_reg_num` is definitely valid
+            error.UnsupportedRegister => unreachable, // the implementation needs to support ip
+            error.IncompatibleRegisterSize => unreachable, // ip is definitely `usize`-sized
         };
         const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*);
 
         return .{
             .cfa = null,
             .pc = pc,
-            .thread_context = thread_context,
-            .reg_context = undefined,
+            .cpu_context = cpu_context.*,
             .vm = .{},
             .stack_machine = .{},
         };
@@ -185,17 +198,25 @@ pub const DwarfUnwindContext = struct {
     }
 
     pub fn getFp(self: *const DwarfUnwindContext) usize {
-        return (regValueNative(self.thread_context, Dwarf.abi.fpRegNum(native_arch, self.reg_context), self.reg_context) catch return 0).*;
+        // `@constCast` is safe because we aren't going to store to the resulting pointer.
+        const ptr = regNative(@constCast(&self.cpu_context), fp_reg_num) catch |err| switch (err) {
+            error.InvalidRegister => unreachable, // `fp_reg_num` is definitely valid
+            error.UnsupportedRegister => unreachable, // the implementation needs to support fp
+            error.IncompatibleRegisterSize => unreachable, // fp is a pointer so is `usize`-sized
+        };
+        return ptr.*;
     }
 
-    /// Resolves the register rule and places the result into `out` (see regBytes)
+    /// Resolves the register rule and places the result into `out` (see regBytes). Returns `true`
+    /// iff the rule was undefined. This is *not* the same as `col.rule == .undefined`, because the
+    /// default rule may be undefined.
     pub fn resolveRegisterRule(
         context: *DwarfUnwindContext,
         gpa: Allocator,
         col: Dwarf.Unwind.VirtualMachine.Column,
         expression_context: std.debug.Dwarf.expression.Context,
         out: []u8,
-    ) !void {
+    ) !bool {
         switch (col.rule) {
             .default => {
                 const register = col.register orelse return error.InvalidRegister;
@@ -203,58 +224,74 @@ pub const DwarfUnwindContext = struct {
                 // See the doc comment on `Dwarf.Unwind.VirtualMachine.RegisterRule.default`.
                 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 18) {
                     // Callee-saved registers are initialized as if they had the .same_value rule
-                    const src = try regBytes(context.thread_context, register, context.reg_context);
+                    const src = try context.cpu_context.dwarfRegisterBytes(register);
                     if (src.len != out.len) return error.RegisterSizeMismatch;
                     @memcpy(out, src);
-                    return;
+                    return false;
                 }
                 @memset(out, undefined);
+                return true;
             },
             .undefined => {
                 @memset(out, undefined);
+                return true;
             },
             .same_value => {
                 // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
                 const register = col.register orelse return error.InvalidRegister;
-                const src = try regBytes(context.thread_context, register, context.reg_context);
+                const src = try context.cpu_context.dwarfRegisterBytes(register);
                 if (src.len != out.len) return error.RegisterSizeMismatch;
                 @memcpy(out, src);
+                return false;
             },
             .offset => |offset| {
-                if (context.cfa) |cfa| {
-                    const addr = try applyOffset(cfa, offset);
-                    const ptr: *const usize = @ptrFromInt(addr);
-                    mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
-                } else return error.InvalidCFA;
+                const cfa = context.cfa orelse return error.InvalidCFA;
+                const addr = try applyOffset(cfa, offset);
+                const ptr: *const usize = @ptrFromInt(addr);
+                mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
+                return false;
             },
             .val_offset => |offset| {
-                if (context.cfa) |cfa| {
-                    mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
-                } else return error.InvalidCFA;
+                const cfa = context.cfa orelse return error.InvalidCFA;
+                mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
+                return false;
             },
             .register => |register| {
-                const src = try regBytes(context.thread_context, register, context.reg_context);
+                const src = try context.cpu_context.dwarfRegisterBytes(register);
                 if (src.len != out.len) return error.RegisterSizeMismatch;
                 @memcpy(out, src);
+                return false;
             },
             .expression => |expression| {
                 context.stack_machine.reset();
-                const value = try context.stack_machine.run(expression, gpa, expression_context, context.cfa.?);
-                const addr = if (value) |v| blk: {
-                    if (v != .generic) return error.InvalidExpressionValue;
-                    break :blk v.generic;
-                } else return error.NoExpressionValue;
-
+                const value = try context.stack_machine.run(
+                    expression,
+                    gpa,
+                    expression_context,
+                    context.cfa.?,
+                ) orelse return error.NoExpressionValue;
+                const addr = switch (value) {
+                    .generic => |addr| addr,
+                    else => return error.InvalidExpressionValue,
+                };
                 const ptr: *usize = @ptrFromInt(addr);
                 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
+                return false;
             },
             .val_expression => |expression| {
                 context.stack_machine.reset();
-                const value = try context.stack_machine.run(expression, gpa, expression_context, context.cfa.?);
-                if (value) |v| {
-                    if (v != .generic) return error.InvalidExpressionValue;
-                    mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
-                } else return error.NoExpressionValue;
+                const value = try context.stack_machine.run(
+                    expression,
+                    gpa,
+                    expression_context,
+                    context.cfa.?,
+                ) orelse return error.NoExpressionValue;
+                const val_raw = switch (value) {
+                    .generic => |raw| raw,
+                    else => return error.InvalidExpressionValue,
+                };
+                mem.writeInt(usize, out[0..@sizeOf(usize)], val_raw, native_endian);
+                return false;
             },
             .architectural => return error.UnimplementedRegisterRule,
         }
@@ -277,9 +314,6 @@ pub const DwarfUnwindContext = struct {
         return unwindFrameInner(context, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {
             error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
 
-            error.UnimplementedArch,
-            error.UnimplementedOs,
-            error.ThreadContextNotSupported,
             error.UnimplementedRegisterRule,
             error.UnsupportedAddrSize,
             error.UnsupportedDwarfVersion,
@@ -289,10 +323,10 @@ pub const DwarfUnwindContext = struct {
             error.UnimplementedTypedComparison,
             error.UnimplementedTypeConversion,
             error.UnknownExpressionOpcode,
+            error.UnsupportedRegister,
             => return error.UnsupportedDebugInfo,
 
             error.InvalidRegister,
-            error.RegisterContextRequired,
             error.ReadFailed,
             error.EndOfStream,
             error.IncompatibleRegisterSize,
@@ -346,20 +380,17 @@ pub const DwarfUnwindContext = struct {
         // may not reference other debug sections anyway.
         var expression_context: Dwarf.expression.Context = .{
             .format = format,
-            .thread_context = context.thread_context,
-            .reg_context = context.reg_context,
+            .cpu_context = &context.cpu_context,
             .cfa = context.cfa,
         };
 
         context.vm.reset();
-        context.reg_context.eh_frame = cie.version != 4;
-        context.reg_context.is_macho = native_os.isDarwin();
 
         const row = try context.vm.runTo(gpa, pc_vaddr, cie, fde, @sizeOf(usize), native_endian);
         context.cfa = switch (row.cfa.rule) {
             .val_offset => |offset| blk: {
                 const register = row.cfa.register orelse return error.InvalidCFARule;
-                const value = (try regValueNative(context.thread_context, register, context.reg_context)).*;
+                const value = (try regNative(&context.cpu_context, register)).*;
                 break :blk try applyOffset(value, offset);
             },
             .expression => |expr| blk: {
@@ -381,73 +412,41 @@ pub const DwarfUnwindContext = struct {
 
         expression_context.cfa = context.cfa;
 
-        // Buffering the modifications is done because copying the thread context is not portable,
-        // some implementations (ie. darwin) use internal pointers to the mcontext.
-        var arena: std.heap.ArenaAllocator = .init(gpa);
-        defer arena.deinit();
-        const update_arena = arena.allocator();
-
-        const RegisterUpdate = struct {
-            // Backed by thread_context
-            dest: []u8,
-            // Backed by arena
-            src: []const u8,
-            prev: ?*@This(),
-        };
-
-        var update_tail: ?*RegisterUpdate = null;
         var has_return_address = true;
+
+        // Create a copy of the CPU context, to which we will apply the new rules.
+        var new_cpu_context = context.cpu_context;
+
+        // On all implemented architectures, the CFA is defined as being the previous frame's SP
+        (try regNative(&new_cpu_context, sp_reg_num)).* = context.cfa.?;
+
         for (context.vm.rowColumns(row)) |column| {
             if (column.register) |register| {
+                const dest = try new_cpu_context.dwarfRegisterBytes(register);
+                const rule_undef = try context.resolveRegisterRule(gpa, column, expression_context, dest);
                 if (register == cie.return_address_register) {
-                    has_return_address = column.rule != .undefined;
+                    has_return_address = !rule_undef;
                 }
-
-                const dest = try regBytes(context.thread_context, register, context.reg_context);
-                const src = try update_arena.alloc(u8, dest.len);
-                try context.resolveRegisterRule(gpa, column, expression_context, src);
-
-                const new_update = try update_arena.create(RegisterUpdate);
-                new_update.* = .{
-                    .dest = dest,
-                    .src = src,
-                    .prev = update_tail,
-                };
-                update_tail = new_update;
             }
         }
 
-        // On all implemented architectures, the CFA is defined as being the previous frame's SP
-        (try regValueNative(context.thread_context, Dwarf.abi.spRegNum(native_arch, context.reg_context), context.reg_context)).* = context.cfa.?;
-
-        while (update_tail) |tail| {
-            @memcpy(tail.dest, tail.src);
-            update_tail = tail.prev;
-        }
+        const return_address: u64 = if (has_return_address) pc: {
+            const raw_ptr = try regNative(&new_cpu_context, cie.return_address_register);
+            break :pc stripInstructionPtrAuthCode(raw_ptr.*);
+        } else 0;
 
-        if (has_return_address) {
-            context.pc = stripInstructionPtrAuthCode((try regValueNative(
-                context.thread_context,
-                cie.return_address_register,
-                context.reg_context,
-            )).*);
-        } else {
-            context.pc = 0;
-        }
+        (try regNative(new_cpu_context, ip_reg_num)).* = return_address;
 
-        const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
-        (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc;
+        // The new CPU context is complete; flush changes.
+        context.cpu_context = new_cpu_context;
 
-        // The call instruction will have pushed the address of the instruction that follows the call as the return address.
-        // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in
-        // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up
-        // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this,
-        // we subtract one so that the next lookup is guaranteed to land inside the
-        //
-        // The exception to this rule is signal frames, where we return execution would be returned to the instruction
-        // that triggered the handler.
-        const return_address = context.pc;
-        if (context.pc > 0 and !cie.is_signal_frame) context.pc -= 1;
+        // Also update the stored pc. However, because `return_address` points to the instruction
+        // *after* the call, it could (in the case of noreturn functions) actually point outside of
+        // the caller's address range, meaning an FDE lookup would fail. We can handle this by
+        // subtracting 1 from `return_address` so that the next lookup is guaranteed to land inside
+        // the `call` instruction`. The exception to this rule is signal frames, where the return
+        // address is the same instruction that triggered the handler.
+        context.pc = if (cie.is_signal_frame) return_address else return_address -| 1;
 
         return return_address;
     }
@@ -479,4 +478,18 @@ pub const DwarfUnwindContext = struct {
 
         return ptr;
     }
+
+    pub fn regNative(ctx: *CpuContext, num: u16) error{
+        InvalidRegister,
+        UnsupportedRegister,
+        IncompatibleRegisterSize,
+    }!*align(1) usize {
+        const bytes = try ctx.dwarfRegisterBytes(num);
+        if (bytes.len != @sizeOf(usize)) return error.IncompatibleRegisterSize;
+        return @ptrCast(bytes);
+    }
+
+    const ip_reg_num = Dwarf.ipRegNum(native_arch).?;
+    const fp_reg_num = Dwarf.fpRegNum(native_arch);
+    const sp_reg_num = Dwarf.spRegNum(native_arch);
 };
lib/std/heap/PageAllocator.zig
@@ -183,7 +183,7 @@ pub fn realloc(uncasted_memory: []u8, new_len: usize, may_move: bool) ?[*]u8 {
 
     if (posix.MREMAP != void) {
         // TODO: if the next_mmap_addr_hint is within the remapped range, update it
-        const new_memory = posix.mremap(memory.ptr, memory.len, new_len, .{ .MAYMOVE = may_move }, null) catch return null;
+        const new_memory = posix.mremap(memory.ptr, page_aligned_len, new_size_aligned, .{ .MAYMOVE = may_move }, null) catch return null;
         return new_memory.ptr;
     }
 
lib/std/os/linux/aarch64.zig
@@ -260,7 +260,4 @@ pub const ucontext_t = extern struct {
     mcontext: mcontext_t,
 };
 
-/// TODO
-pub const getcontext = {};
-
 pub const Elf_Symndx = u32;
lib/std/os/linux/arm.zig
@@ -310,7 +310,4 @@ pub const ucontext_t = extern struct {
     regspace: [64]u64,
 };
 
-/// TODO
-pub const getcontext = {};
-
 pub const Elf_Symndx = u32;
lib/std/os/linux/hexagon.zig
@@ -237,6 +237,3 @@ pub const VDSO = void;
 
 /// TODO
 pub const ucontext_t = void;
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/loongarch64.zig
@@ -250,6 +250,3 @@ pub const ucontext_t = extern struct {
 };
 
 pub const Elf_Symndx = u32;
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/m68k.zig
@@ -258,6 +258,3 @@ pub const VDSO = void;
 
 /// TODO
 pub const ucontext_t = void;
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/mips.zig
@@ -349,6 +349,3 @@ pub const Elf_Symndx = u32;
 
 /// TODO
 pub const ucontext_t = void;
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/mips64.zig
@@ -328,6 +328,3 @@ pub const Elf_Symndx = u32;
 
 /// TODO
 pub const ucontext_t = void;
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/powerpc.zig
@@ -381,6 +381,3 @@ pub const ucontext_t = extern struct {
 };
 
 pub const Elf_Symndx = u32;
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/powerpc64.zig
@@ -376,6 +376,3 @@ pub const ucontext_t = extern struct {
 };
 
 pub const Elf_Symndx = u32;
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/riscv32.zig
@@ -255,6 +255,3 @@ pub const ucontext_t = extern struct {
     sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
     mcontext: mcontext_t,
 };
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/riscv64.zig
@@ -255,6 +255,3 @@ pub const ucontext_t = extern struct {
     sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
     mcontext: mcontext_t,
 };
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/s390x.zig
@@ -273,6 +273,3 @@ pub const mcontext_t = extern struct {
     __regs2: [18]u32,
     __regs3: [16]f64,
 };
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/sparc64.zig
@@ -426,6 +426,3 @@ pub const ucontext_t = extern struct {
     stack: stack_t,
     sigset: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a libc-compatible (1024-bit) sigmask
 };
-
-/// TODO
-pub const getcontext = {};
lib/std/os/linux/x86.zig
@@ -436,17 +436,3 @@ pub fn getContextInternal() callconv(.naked) usize {
           [sigset_size] "i" (linux.NSIG / 8),
         : .{ .cc = true, .memory = true, .eax = true, .ecx = true, .edx = true });
 }
-
-pub inline fn getcontext(context: *ucontext_t) usize {
-    // This method is used so that getContextInternal can control
-    // its prologue in order to read ESP from a constant offset.
-    // An aligned stack is not needed for getContextInternal.
-    var clobber_edx: usize = undefined;
-    return asm volatile (
-        \\ calll %[getContextInternal:P]
-        : [_] "={eax}" (-> usize),
-          [_] "={edx}" (clobber_edx),
-        : [_] "{edx}" (context),
-          [getContextInternal] "X" (&getContextInternal),
-        : .{ .cc = true, .memory = true, .ecx = true });
-}
lib/std/os/linux/x86_64.zig
@@ -352,98 +352,3 @@ pub const ucontext_t = extern struct {
     sigmask: [1024 / @bitSizeOf(c_ulong)]c_ulong, // Currently a glibc-compatible (1024-bit) sigmask.
     fpregs_mem: [64]usize, // Not part of kernel ABI, only part of glibc ucontext_t
 };
-
-fn gpRegisterOffset(comptime reg_index: comptime_int) usize {
-    return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index;
-}
-
-fn getContextInternal() callconv(.naked) usize {
-    // TODO: Read GS/FS registers?
-    asm volatile (
-        \\ movq $0, %[flags_offset:c](%%rdi)
-        \\ movq $0, %[link_offset:c](%%rdi)
-        \\ movq %%r8, %[r8_offset:c](%%rdi)
-        \\ movq %%r9, %[r9_offset:c](%%rdi)
-        \\ movq %%r10, %[r10_offset:c](%%rdi)
-        \\ movq %%r11, %[r11_offset:c](%%rdi)
-        \\ movq %%r12, %[r12_offset:c](%%rdi)
-        \\ movq %%r13, %[r13_offset:c](%%rdi)
-        \\ movq %%r14, %[r14_offset:c](%%rdi)
-        \\ movq %%r15, %[r15_offset:c](%%rdi)
-        \\ movq %%rdi, %[rdi_offset:c](%%rdi)
-        \\ movq %%rsi, %[rsi_offset:c](%%rdi)
-        \\ movq %%rbp, %[rbp_offset:c](%%rdi)
-        \\ movq %%rbx, %[rbx_offset:c](%%rdi)
-        \\ movq %%rdx, %[rdx_offset:c](%%rdi)
-        \\ movq %%rax, %[rax_offset:c](%%rdi)
-        \\ movq %%rcx, %[rcx_offset:c](%%rdi)
-        \\ movq (%%rsp), %%rcx
-        \\ movq %%rcx, %[rip_offset:c](%%rdi)
-        \\ leaq 8(%%rsp), %%rcx
-        \\ movq %%rcx, %[rsp_offset:c](%%rdi)
-        \\ pushfq
-        \\ popq %[efl_offset:c](%%rdi)
-        \\ leaq %[fpmem_offset:c](%%rdi), %%rcx
-        \\ movq %%rcx, %[fpstate_offset:c](%%rdi)
-        \\ fnstenv (%%rcx)
-        \\ fldenv (%%rcx)
-        \\ stmxcsr %[mxcsr_offset:c](%%rdi)
-        \\ leaq %[stack_offset:c](%%rdi), %%rsi
-        \\ movq %%rdi, %%r8
-        \\ xorl %%edi, %%edi
-        \\ movl %[sigaltstack], %%eax
-        \\ syscall
-        \\ testq %%rax, %%rax
-        \\ jnz 0f
-        \\ movl %[sigprocmask], %%eax
-        \\ xorl %%esi, %%esi
-        \\ leaq %[sigmask_offset:c](%%r8), %%rdx
-        \\ movl %[sigset_size], %%r10d
-        \\ syscall
-        \\0:
-        \\ retq
-        :
-        : [flags_offset] "i" (@offsetOf(ucontext_t, "flags")),
-          [link_offset] "i" (@offsetOf(ucontext_t, "link")),
-          [r8_offset] "i" (comptime gpRegisterOffset(REG.R8)),
-          [r9_offset] "i" (comptime gpRegisterOffset(REG.R9)),
-          [r10_offset] "i" (comptime gpRegisterOffset(REG.R10)),
-          [r11_offset] "i" (comptime gpRegisterOffset(REG.R11)),
-          [r12_offset] "i" (comptime gpRegisterOffset(REG.R12)),
-          [r13_offset] "i" (comptime gpRegisterOffset(REG.R13)),
-          [r14_offset] "i" (comptime gpRegisterOffset(REG.R14)),
-          [r15_offset] "i" (comptime gpRegisterOffset(REG.R15)),
-          [rdi_offset] "i" (comptime gpRegisterOffset(REG.RDI)),
-          [rsi_offset] "i" (comptime gpRegisterOffset(REG.RSI)),
-          [rbp_offset] "i" (comptime gpRegisterOffset(REG.RBP)),
-          [rbx_offset] "i" (comptime gpRegisterOffset(REG.RBX)),
-          [rdx_offset] "i" (comptime gpRegisterOffset(REG.RDX)),
-          [rax_offset] "i" (comptime gpRegisterOffset(REG.RAX)),
-          [rcx_offset] "i" (comptime gpRegisterOffset(REG.RCX)),
-          [rsp_offset] "i" (comptime gpRegisterOffset(REG.RSP)),
-          [rip_offset] "i" (comptime gpRegisterOffset(REG.RIP)),
-          [efl_offset] "i" (comptime gpRegisterOffset(REG.EFL)),
-          [fpstate_offset] "i" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")),
-          [fpmem_offset] "i" (@offsetOf(ucontext_t, "fpregs_mem")),
-          [mxcsr_offset] "i" (@offsetOf(ucontext_t, "fpregs_mem") + @offsetOf(fpstate, "mxcsr")),
-          [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)),
-          [stack_offset] "i" (@offsetOf(ucontext_t, "stack")),
-          [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)),
-          [sigmask_offset] "i" (@offsetOf(ucontext_t, "sigmask")),
-          [sigset_size] "i" (@sizeOf(sigset_t)),
-        : .{ .cc = true, .memory = true, .rax = true, .rcx = true, .rdx = true, .rdi = true, .rsi = true, .r8 = true, .r10 = true, .r11 = true });
-}
-
-pub inline fn getcontext(context: *ucontext_t) usize {
-    // This method is used so that getContextInternal can control
-    // its prologue in order to read RSP from a constant offset
-    // An aligned stack is not needed for getContextInternal.
-    var clobber_rdi: usize = undefined;
-    return asm volatile (
-        \\ callq %[getContextInternal:P]
-        : [_] "={rax}" (-> usize),
-          [_] "={rdi}" (clobber_rdi),
-        : [_] "{rdi}" (context),
-          [getContextInternal] "X" (&getContextInternal),
-        : .{ .cc = true, .memory = true, .rcx = true, .rdx = true, .rsi = true, .r8 = true, .r10 = true, .r11 = true });
-}
lib/std/os/freebsd.zig
@@ -3,6 +3,7 @@ const fd_t = std.c.fd_t;
 const off_t = std.c.off_t;
 const unexpectedErrno = std.posix.unexpectedErrno;
 const errno = std.posix.errno;
+const builtin = @import("builtin");
 
 pub const CopyFileRangeError = std.posix.UnexpectedError || error{
     /// If infd is not open for reading or outfd is not open for writing, or
@@ -47,3 +48,75 @@ pub fn copy_file_range(fd_in: fd_t, off_in: ?*i64, fd_out: fd_t, off_out: ?*i64,
         else => |err| return unexpectedErrno(err),
     }
 }
+
+pub const ucontext_t = extern struct {
+    sigmask: std.c.sigset_t,
+    mcontext: mcontext_t,
+    link: ?*ucontext_t,
+    stack: std.c.stack_t,
+    flags: c_int,
+    __spare__: [4]c_int,
+    const mcontext_t = switch (builtin.cpu.arch) {
+        .x86_64 => extern struct {
+            onstack: u64,
+            rdi: u64,
+            rsi: u64,
+            rdx: u64,
+            rcx: u64,
+            r8: u64,
+            r9: u64,
+            rax: u64,
+            rbx: u64,
+            rbp: u64,
+            r10: u64,
+            r11: u64,
+            r12: u64,
+            r13: u64,
+            r14: u64,
+            r15: u64,
+            trapno: u32,
+            fs: u16,
+            gs: u16,
+            addr: u64,
+            flags: u32,
+            es: u16,
+            ds: u16,
+            err: u64,
+            rip: u64,
+            cs: u64,
+            rflags: u64,
+            rsp: u64,
+            ss: u64,
+            len: u64,
+            fpformat: u64,
+            ownedfp: u64,
+            fpstate: [64]u64 align(16),
+            fsbase: u64,
+            gsbase: u64,
+            xfpustate: u64,
+            xfpustate_len: u64,
+            spare: [4]u64,
+        },
+        .aarch64 => extern struct {
+            gpregs: extern struct {
+                x: [30]u64,
+                lr: u64,
+                sp: u64,
+                elr: u64,
+                spsr: u32,
+                _pad: u32,
+            },
+            fpregs: extern struct {
+                q: [32]u128,
+                sr: u32,
+                cr: u32,
+                flags: u32,
+                _pad: u32,
+            },
+            flags: u32,
+            _pad: u32,
+            _spare: [8]u64,
+        },
+        else => void,
+    };
+};
lib/std/os/linux.zig
@@ -49,7 +49,6 @@ const arch_bits = switch (native_arch) {
     .s390x => @import("linux/s390x.zig"),
     else => struct {
         pub const ucontext_t = void;
-        pub const getcontext = {};
     },
 };
 
@@ -112,7 +111,6 @@ pub const timeval = arch_bits.timeval;
 pub const timezone = arch_bits.timezone;
 pub const ucontext_t = arch_bits.ucontext_t;
 pub const user_desc = arch_bits.user_desc;
-pub const getcontext = arch_bits.getcontext;
 
 pub const tls = @import("linux/tls.zig");
 pub const BPF = @import("linux/bpf.zig");
lib/std/c.zig
@@ -7035,205 +7035,6 @@ pub const timezone = switch (native_os) {
     else => void,
 };
 
-pub const ucontext_t = switch (native_os) {
-    .linux => linux.ucontext_t, // std.os.linux.ucontext_t is currently glibc-compatible, but it should probably not be.
-    .emscripten => emscripten.ucontext_t,
-    .macos, .ios, .tvos, .watchos, .visionos => extern struct {
-        onstack: c_int,
-        sigmask: sigset_t,
-        stack: stack_t,
-        link: ?*ucontext_t,
-        mcsize: u64,
-        mcontext: *mcontext_t,
-        __mcontext_data: mcontext_t,
-    },
-    .freebsd => extern struct {
-        sigmask: sigset_t,
-        mcontext: mcontext_t,
-        link: ?*ucontext_t,
-        stack: stack_t,
-        flags: c_int,
-        __spare__: [4]c_int,
-    },
-    .solaris, .illumos => extern struct {
-        flags: u64,
-        link: ?*ucontext_t,
-        sigmask: sigset_t,
-        stack: stack_t,
-        mcontext: mcontext_t,
-        brand_data: [3]?*anyopaque,
-        filler: [2]i64,
-    },
-    .netbsd => extern struct {
-        flags: u32,
-        link: ?*ucontext_t,
-        sigmask: sigset_t,
-        stack: stack_t,
-        mcontext: mcontext_t,
-        __pad: [
-            switch (builtin.cpu.arch) {
-                .x86 => 4,
-                .mips, .mipsel, .mips64, .mips64el => 14,
-                .arm, .armeb, .thumb, .thumbeb => 1,
-                .sparc, .sparc64 => if (@sizeOf(usize) == 4) 43 else 8,
-                else => 0,
-            }
-        ]u32,
-    },
-    .dragonfly => extern struct {
-        sigmask: sigset_t,
-        mcontext: mcontext_t,
-        link: ?*ucontext_t,
-        stack: stack_t,
-        cofunc: ?*fn (?*ucontext_t, ?*anyopaque) void,
-        arg: ?*void,
-        _spare: [4]c_int,
-    },
-    // https://github.com/SerenityOS/serenity/blob/87eac0e424cff4a1f941fb704b9362a08654c24d/Kernel/API/POSIX/ucontext.h#L19-L24
-    .haiku, .serenity => extern struct {
-        link: ?*ucontext_t,
-        sigmask: sigset_t,
-        stack: stack_t,
-        mcontext: mcontext_t,
-    },
-    .openbsd => openbsd.ucontext_t,
-    else => void,
-};
-pub const mcontext_t = switch (native_os) {
-    .linux => linux.mcontext_t,
-    .emscripten => emscripten.mcontext_t,
-    .macos, .ios, .tvos, .watchos, .visionos => darwin.mcontext_t,
-    .freebsd => switch (builtin.cpu.arch) {
-        .x86_64 => extern struct {
-            onstack: u64,
-            rdi: u64,
-            rsi: u64,
-            rdx: u64,
-            rcx: u64,
-            r8: u64,
-            r9: u64,
-            rax: u64,
-            rbx: u64,
-            rbp: u64,
-            r10: u64,
-            r11: u64,
-            r12: u64,
-            r13: u64,
-            r14: u64,
-            r15: u64,
-            trapno: u32,
-            fs: u16,
-            gs: u16,
-            addr: u64,
-            flags: u32,
-            es: u16,
-            ds: u16,
-            err: u64,
-            rip: u64,
-            cs: u64,
-            rflags: u64,
-            rsp: u64,
-            ss: u64,
-            len: u64,
-            fpformat: u64,
-            ownedfp: u64,
-            fpstate: [64]u64 align(16),
-            fsbase: u64,
-            gsbase: u64,
-            xfpustate: u64,
-            xfpustate_len: u64,
-            spare: [4]u64,
-        },
-        .aarch64 => extern struct {
-            gpregs: extern struct {
-                x: [30]u64,
-                lr: u64,
-                sp: u64,
-                elr: u64,
-                spsr: u32,
-                _pad: u32,
-            },
-            fpregs: extern struct {
-                q: [32]u128,
-                sr: u32,
-                cr: u32,
-                flags: u32,
-                _pad: u32,
-            },
-            flags: u32,
-            _pad: u32,
-            _spare: [8]u64,
-        },
-        else => struct {},
-    },
-    .solaris, .illumos => extern struct {
-        gregs: [28]u64,
-        fpregs: solaris.fpregset_t,
-    },
-    .netbsd => switch (builtin.cpu.arch) {
-        .aarch64, .aarch64_be => extern struct {
-            gregs: [35]u64,
-            fregs: [528]u8 align(16),
-            spare: [8]u64,
-        },
-        .x86 => extern struct {
-            gregs: [19]u32,
-            fpregs: [161]u32,
-            mc_tlsbase: u32,
-        },
-        .x86_64 => extern struct {
-            gregs: [26]u64,
-            mc_tlsbase: u64,
-            fpregs: [512]u8 align(8),
-        },
-        else => struct {},
-    },
-    .dragonfly => dragonfly.mcontext_t,
-    .haiku => haiku.mcontext_t,
-    .serenity => switch (native_arch) {
-        // https://github.com/SerenityOS/serenity/blob/200e91cd7f1ec5453799a2720d4dc114a59cc289/Kernel/Arch/aarch64/mcontext.h#L15-L19
-        .aarch64 => extern struct {
-            x: [31]u64,
-            sp: u64,
-            pc: u64,
-        },
-        // https://github.com/SerenityOS/serenity/blob/66f8d0f031ef25c409dbb4fecaa454800fecae0f/Kernel/Arch/riscv64/mcontext.h#L15-L18
-        .riscv64 => extern struct {
-            x: [31]u64,
-            pc: u64,
-        },
-        // https://github.com/SerenityOS/serenity/blob/7b9ea3efdec9f86a1042893e8107d0b23aad8727/Kernel/Arch/x86_64/mcontext.h#L15-L40
-        .x86_64 => extern struct {
-            rax: u64,
-            rcx: u64,
-            rdx: u64,
-            rbx: u64,
-            rsp: u64,
-            rbp: u64,
-            rsi: u64,
-            rdi: u64,
-            rip: u64,
-            r8: u64,
-            r9: u64,
-            r10: u64,
-            r11: u64,
-            r12: u64,
-            r13: u64,
-            r14: u64,
-            r15: u64,
-            rflags: u64,
-            cs: u32,
-            ss: u32,
-            ds: u32,
-            es: u32,
-            fs: u32,
-            gs: u32,
-        },
-        else => struct {},
-    },
-    else => void,
-};
-
 pub const user_desc = switch (native_os) {
     .linux => linux.user_desc,
     else => void,
@@ -11238,13 +11039,6 @@ pub const LC = enum(c_int) {
 
 pub extern "c" fn setlocale(category: LC, locale: ?[*:0]const u8) ?[*:0]const u8;
 
-pub const getcontext = if (builtin.target.abi.isAndroid() or builtin.target.os.tag == .openbsd or builtin.target.os.tag == .haiku)
-{} // libc does not implement getcontext
-    else if (native_os == .linux and builtin.target.abi.isMusl())
-        linux.getcontext
-    else
-        private.getcontext;
-
 pub const max_align_t = if (native_abi == .msvc or native_abi == .itanium)
     f64
 else if (native_os.isDarwin())
@@ -11668,7 +11462,6 @@ const private = struct {
     extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int;
 
     extern "c" fn pthread_setname_np(thread: pthread_t, name: [*:0]const u8) c_int;
-    extern "c" fn getcontext(ucp: *ucontext_t) c_int;
 
     extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;
     extern "c" fn getentropy(buffer: [*]u8, size: usize) c_int;
lib/std/debug.zig
@@ -22,6 +22,7 @@ pub const ElfFile = @import("debug/ElfFile.zig");
 pub const SelfInfo = @import("debug/SelfInfo.zig");
 pub const Info = @import("debug/Info.zig");
 pub const Coverage = @import("debug/Coverage.zig");
+pub const cpu_context = @import("debug/cpu_context.zig");
 
 pub const simple_panic = @import("debug/simple_panic.zig");
 pub const no_panic = @import("debug/no_panic.zig");
@@ -331,66 +332,8 @@ test dumpHexFallible {
     try std.testing.expectEqualStrings(expected, aw.written());
 }
 
-/// Platform-specific thread state. This contains register state, and on some platforms
-/// information about the stack. This is not safe to trivially copy, because some platforms
-/// use internal pointers within this structure. After copying, call `relocateContext`.
-pub const ThreadContext = ThreadContext: {
-    // Allow overriding the target's `ThreadContext` by exposing `root.debug.ThreadContext`.
-    if (@hasDecl(root, "debug") and @hasDecl(root.debug, "ThreadContext")) {
-        break :ThreadContext root.debug.ThreadContext;
-    }
-
-    if (native_os == .windows) break :ThreadContext windows.CONTEXT;
-    if (posix.ucontext_t != void) break :ThreadContext posix.ucontext_t;
-
-    break :ThreadContext noreturn;
-};
-/// Updates any internal pointers of a `ThreadContext` after the caller copies it.
-pub fn relocateContext(dest: *ThreadContext) void {
-    switch (native_os) {
-        .macos => dest.mcontext = &dest.__mcontext_data,
-        else => {},
-    }
-}
-/// The value which is placed on the stack to make a copy of a `ThreadContext`.
-const ThreadContextBuf = if (ThreadContext == noreturn) void else ThreadContext;
-/// The pointer through which a `ThreadContext` is received from callers of stack tracing logic.
-pub const ThreadContextPtr = if (ThreadContext == noreturn) noreturn else *const ThreadContext;
-
-/// Capture the current context. The register values in the context will reflect the
-/// state after the platform `getcontext` function returns.
-///
-/// It is valid to call this if the platform doesn't have context capturing support,
-/// in that case `false` will be returned. This function is `inline` so that the `false`
-/// is comptime-known at the call site in that case.
-pub inline fn getContext(context: *ThreadContextBuf) bool {
-    // Allow overriding the target's `getContext` by exposing `root.debug.getContext`.
-    if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getContext")) {
-        return root.debug.getContext(context);
-    }
-
-    if (native_os == .windows) {
-        context.* = std.mem.zeroes(windows.CONTEXT);
-        windows.ntdll.RtlCaptureContext(context);
-        return true;
-    }
-
-    if (@TypeOf(posix.system.getcontext) != void) {
-        if (posix.system.getcontext(context) != 0) return false;
-        if (native_os == .macos) {
-            assert(context.mcsize == @sizeOf(std.c.mcontext_t));
-
-            // On aarch64-macos, the system getcontext doesn't write anything into the pc
-            // register slot, it only writes lr. This makes the context consistent with
-            // other aarch64 getcontext implementations which write the current lr
-            // (where getcontext will return to) into both the lr and pc slot of the context.
-            if (native_arch == .aarch64) context.mcontext.ss.pc = context.mcontext.ss.lr;
-        }
-        return true;
-    }
-
-    return false;
-}
+/// The pointer through which a `cpu_context.Native` is received from callers of stack tracing logic.
+pub const CpuContextPtr = if (cpu_context.Native == noreturn) noreturn else *const cpu_context.Native;
 
 /// Invokes detectable illegal behavior when `ok` is `false`.
 ///
@@ -616,10 +559,10 @@ pub const StackUnwindOptions = struct {
     /// used to omit intermediate handling code (for instance, a panic handler and its machinery)
     /// from stack traces.
     first_address: ?usize = null,
-    /// If not `null`, we will unwind from this `ThreadContext` instead of the current top of the
-    /// stack. The main use case here is printing stack traces from signal handlers, where the
-    /// kernel provides a `*const ThreadContext` of the state before the signal.
-    context: ?ThreadContextPtr = null,
+    /// If not `null`, we will unwind from this `cpu_context.Native` instead of the current top of
+    /// the stack. The main use case here is printing stack traces from signal handlers, where the
+    /// kernel provides a `*const cpu_context.Native` of the state before the signal.
+    context: ?CpuContextPtr = null,
     /// If `true`, stack unwinding strategies which may cause crashes are used as a last resort.
     /// If `false`, only known-safe mechanisms will be attempted.
     allow_unsafe_unwind: bool = false,
@@ -630,8 +573,7 @@ pub const StackUnwindOptions = struct {
 ///
 /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
 pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {
-    var context_buf: ThreadContextBuf = undefined;
-    var it = StackIterator.init(options.context, &context_buf) catch {
+    var it = StackIterator.init(options.context) catch {
         return .{ .index = 0, .instruction_addresses = &.{} };
     };
     defer it.deinit();
@@ -670,14 +612,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
             return;
         },
     };
-    var context_buf: ThreadContextBuf = undefined;
-    var it = StackIterator.init(options.context, &context_buf) catch |err| switch (err) {
-        error.OutOfMemory => {
-            tty_config.setColor(writer, .dim) catch {};
-            try writer.print("Cannot print stack trace: out of memory\n", .{});
-            tty_config.setColor(writer, .reset) catch {};
-            return;
-        },
+    var it = StackIterator.init(options.context) catch |err| switch (err) {
         error.CannotUnwindFromContext => {
             tty_config.setColor(writer, .dim) catch {};
             try writer.print("Cannot print stack trace: context unwind unavailable for target\n", .{});
@@ -794,9 +729,9 @@ const StackIterator = union(enum) {
     fp: usize,
 
     /// It is important that this function is marked `inline` so that it can safely use
-    /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one
-    /// and the same.
-    inline fn init(context_opt: ?ThreadContextPtr, context_buf: *ThreadContextBuf) error{ OutOfMemory, CannotUnwindFromContext }!StackIterator {
+    /// `@frameAddress` and `cpu_context.Native.current` as the caller's stack frame and
+    /// our own are one and the same.
+    inline fn init(opt_context_ptr: ?CpuContextPtr) error{CannotUnwindFromContext}!StackIterator {
         if (builtin.cpu.arch.isSPARC()) {
             // Flush all the register windows on stack.
             if (builtin.cpu.has(.sparc, .v9)) {
@@ -805,14 +740,12 @@ const StackIterator = union(enum) {
                 asm volatile ("ta 3" ::: .{ .memory = true }); // ST_FLUSH_WINDOWS
             }
         }
-        if (context_opt) |context| {
+        if (opt_context_ptr) |context_ptr| {
             if (!SelfInfo.supports_unwinding) return error.CannotUnwindFromContext;
-            context_buf.* = context.*;
-            relocateContext(context_buf);
-            return .{ .di = try .init(context_buf, getDebugInfoAllocator()) };
+            return .{ .di = .init(context_ptr) };
         }
-        if (SelfInfo.supports_unwinding and getContext(context_buf)) {
-            return .{ .di = try .init(context_buf, getDebugInfoAllocator()) };
+        if (SelfInfo.supports_unwinding and cpu_context.Native != noreturn) {
+            return .{ .di = .init(&.current()) };
         }
         return .{ .fp = @frameAddress() };
     }
@@ -1212,7 +1145,7 @@ pub const have_segfault_handling_support = switch (native_os) {
     .windows,
     => true,
 
-    .freebsd, .openbsd => ThreadContext != noreturn,
+    .freebsd, .openbsd => cpu_context.Native != noreturn,
     else => false,
 };
 
@@ -1309,33 +1242,8 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa
         };
         break :info .{ addr, name };
     };
-
-    if (ThreadContext == noreturn) return handleSegfault(addr, name, null);
-
-    // Some kernels don't align `ctx_ptr` properly, so we'll copy it into a local buffer.
-    var copied_ctx: posix.ucontext_t = undefined;
-    const orig_ctx: *align(1) posix.ucontext_t = @ptrCast(ctx_ptr);
-    copied_ctx = orig_ctx.*;
-    if (builtin.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) {
-        // The kernel incorrectly writes the contents of `__mcontext_data` right after `mcontext`,
-        // rather than after the 8 bytes of padding that are supposed to sit between the two. Copy the
-        // contents to the right place so that the `mcontext` pointer will be correct after the
-        // `relocateContext` call below.
-        const WrittenContext = extern struct {
-            onstack: c_int,
-            sigmask: std.c.sigset_t,
-            stack: std.c.stack_t,
-            link: ?*std.c.ucontext_t,
-            mcsize: u64,
-            mcontext: *std.c.mcontext_t,
-            __mcontext_data: std.c.mcontext_t align(@sizeOf(usize)), // Disable padding after `mcontext`.
-        };
-        const written_ctx: *align(1) WrittenContext = @ptrCast(ctx_ptr);
-        copied_ctx.__mcontext_data = written_ctx.__mcontext_data;
-    }
-    relocateContext(&copied_ctx);
-
-    handleSegfault(addr, name, &copied_ctx);
+    const opt_cpu_context: ?cpu_context.Native = cpu_context.fromPosixSignalContext(ctx_ptr);
+    handleSegfault(addr, name, if (opt_cpu_context) |*ctx| ctx else null);
 }
 
 fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_long {
@@ -1347,10 +1255,10 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(.winapi) c_
         windows.EXCEPTION_STACK_OVERFLOW => .{ "Stack overflow", null },
         else => return windows.EXCEPTION_CONTINUE_SEARCH,
     };
-    handleSegfault(addr, name, info.ContextRecord);
+    handleSegfault(addr, name, &cpu_context.fromWindowsContext(info.ContextRecord));
 }
 
-fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?ThreadContextPtr) noreturn {
+fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
     // Allow overriding the target-agnostic segfault handler by exposing `root.debug.handleSegfault`.
     if (@hasDecl(root, "debug") and @hasDecl(root.debug, "handleSegfault")) {
         return root.debug.handleSegfault(addr, name, opt_ctx);
@@ -1358,7 +1266,7 @@ fn handleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?ThreadContextPtr) no
     return defaultHandleSegfault(addr, name, opt_ctx);
 }
 
-pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?ThreadContextPtr) noreturn {
+pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContextPtr) noreturn {
     // There is very similar logic to the following in `defaultPanic`.
     switch (panic_stage) {
         0 => {
lib/std/posix.zig
@@ -47,8 +47,6 @@ else switch (native_os) {
     .linux => linux,
     .plan9 => std.os.plan9,
     else => struct {
-        pub const getcontext = {};
-        pub const ucontext_t = void;
         pub const pid_t = void;
         pub const pollfd = void;
         pub const fd_t = void;
@@ -142,7 +140,6 @@ pub const in_pktinfo = system.in_pktinfo;
 pub const in6_pktinfo = system.in6_pktinfo;
 pub const ino_t = system.ino_t;
 pub const linger = system.linger;
-pub const mcontext_t = system.mcontext_t;
 pub const mode_t = system.mode_t;
 pub const msghdr = system.msghdr;
 pub const msghdr_const = system.msghdr_const;
@@ -171,7 +168,6 @@ pub const timespec = system.timespec;
 pub const timestamp_t = system.timestamp_t;
 pub const timeval = system.timeval;
 pub const timezone = system.timezone;
-pub const ucontext_t = system.ucontext_t;
 pub const uid_t = system.uid_t;
 pub const user_desc = system.user_desc;
 pub const utsname = system.utsname;