aboutsummaryrefslogtreecommitdiff
path: root/lib/std/debug/Dwarf
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2025-09-17 18:38:11 +0100
committermlugg <mlugg@mlugg.co.uk>2025-09-30 13:44:54 +0100
commita18fd41064493e742eacebc88e2afeadd54ff6f0 (patch)
tree1081fbd6d3c64cf1f583ae3188ab05e0320f03d9 /lib/std/debug/Dwarf
parentb578cca022f4c9ce94439e2ee795639b3a23c8f5 (diff)
downloadzig-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/Dwarf')
-rw-r--r--lib/std/debug/Dwarf/abi.zig351
-rw-r--r--lib/std/debug/Dwarf/expression.zig160
2 files changed, 63 insertions, 448 deletions
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);
}
}