diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2025-09-17 18:38:11 +0100 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2025-09-30 13:44:54 +0100 |
| commit | a18fd41064493e742eacebc88e2afeadd54ff6f0 (patch) | |
| tree | 1081fbd6d3c64cf1f583ae3188ab05e0320f03d9 /lib/std/debug | |
| parent | b578cca022f4c9ce94439e2ee795639b3a23c8f5 (diff) | |
| download | zig-a18fd41064493e742eacebc88e2afeadd54ff6f0.tar.gz zig-a18fd41064493e742eacebc88e2afeadd54ff6f0.zip | |
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
Diffstat (limited to 'lib/std/debug')
| -rw-r--r-- | lib/std/debug/Dwarf.zig | 57 | ||||
| -rw-r--r-- | lib/std/debug/Dwarf/abi.zig | 351 | ||||
| -rw-r--r-- | lib/std/debug/Dwarf/expression.zig | 160 | ||||
| -rw-r--r-- | lib/std/debug/SelfInfo.zig | 229 | ||||
| -rw-r--r-- | lib/std/debug/SelfInfo/DarwinModule.zig | 57 | ||||
| -rw-r--r-- | lib/std/debug/SelfInfo/ElfModule.zig | 3 | ||||
| -rw-r--r-- | lib/std/debug/SelfInfo/WindowsModule.zig | 40 | ||||
| -rw-r--r-- | lib/std/debug/cpu_context.zig | 1019 |
8 files changed, 1320 insertions, 596 deletions
diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 9a70746b0a..655a65b709 100644 --- a/lib/std/debug/Dwarf.zig +++ b/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} { diff --git a/lib/std/debug/Dwarf/abi.zig b/lib/std/debug/Dwarf/abi.zig deleted file mode 100644 index 98a84392e6..0000000000 --- a/lib/std/debug/Dwarf/abi.zig +++ /dev/null @@ -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); -} diff --git a/lib/std/debug/Dwarf/expression.zig b/lib/std/debug/Dwarf/expression.zig index 68b49587c2..b1751af749 100644 --- a/lib/std/debug/Dwarf/expression.zig +++ b/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); } } diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index c3243edeb9..321e67bb7c 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/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); }; diff --git a/lib/std/debug/SelfInfo/DarwinModule.zig b/lib/std/debug/SelfInfo/DarwinModule.zig index 5bce65b89f..fc2f1c89bb 100644 --- a/lib/std/debug/SelfInfo/DarwinModule.zig +++ b/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(); diff --git a/lib/std/debug/SelfInfo/ElfModule.zig b/lib/std/debug/SelfInfo/ElfModule.zig index 8a0acf8bb0..fde61d8140 100644 --- a/lib/std/debug/SelfInfo/ElfModule.zig +++ b/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)); } } diff --git a/lib/std/debug/SelfInfo/WindowsModule.zig b/lib/std/debug/SelfInfo/WindowsModule.zig index 4bbc220c5b..8c88bd8b2f 100644 --- a/lib/std/debug/SelfInfo/WindowsModule.zig +++ b/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), }; } diff --git a/lib/std/debug/cpu_context.zig b/lib/std/debug/cpu_context.zig new file mode 100644 index 0000000000..9859575fa3 --- /dev/null +++ b/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; |
