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/Dwarf | |
| 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/Dwarf')
| -rw-r--r-- | lib/std/debug/Dwarf/abi.zig | 351 | ||||
| -rw-r--r-- | lib/std/debug/Dwarf/expression.zig | 160 |
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); } } |
