std: dwarf namespace reorg

std.debug.Dwarf is the parsing/decoding logic. std.dwarf remains the
unopinionated types and bits alone.

If you look at this diff you can see a lot less redundancy in
namespaces.
This commit is contained in:
Andrew Kelley
2024-08-01 13:40:47 -07:00
parent 377274ee9a
commit e5b46eab3b
7 changed files with 2793 additions and 2784 deletions

2709
lib/std/debug/Dwarf.zig Normal file

File diff suppressed because it is too large Load Diff

410
lib/std/debug/Dwarf/abi.zig Normal file
View File

@@ -0,0 +1,410 @@
const builtin = @import("builtin");
const std = @import("../../std.zig");
const mem = std.mem;
const native_os = builtin.os.tag;
const posix = std.posix;
pub fn supportsUnwinding(target: std.Target) bool {
return switch (target.cpu.arch) {
.x86 => switch (target.os.tag) {
.linux, .netbsd, .solaris, .illumos => true,
else => false,
},
.x86_64 => switch (target.os.tag) {
.linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
else => false,
},
.arm => switch (target.os.tag) {
.linux => true,
else => false,
},
.aarch64 => switch (target.os.tag) {
.linux, .netbsd, .freebsd, .macos, .ios => true,
else => false,
},
else => false,
};
}
pub fn ipRegNum() u8 {
return switch (builtin.cpu.arch) {
.x86 => 8,
.x86_64 => 16,
.arm => 15,
.aarch64 => 32,
else => unreachable,
};
}
pub fn fpRegNum(reg_context: RegisterContext) u8 {
return switch (builtin.cpu.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 => 11,
.aarch64 => 29,
else => unreachable,
};
}
pub fn spRegNum(reg_context: RegisterContext) u8 {
return switch (builtin.cpu.arch) {
.x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4,
.x86_64 => 7,
.arm => 13,
.aarch64 => 31,
else => unreachable,
};
}
/// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature.
/// This function clears these signature bits to make the pointer usable.
pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
if (builtin.cpu.arch == .aarch64) {
// `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it)
// The save / restore is because `xpaclri` operates on x30 (LR)
return asm (
\\mov x16, x30
\\mov x30, x15
\\hint 0x07
\\mov x15, x30
\\mov x30, x16
: [ret] "={x15}" (-> usize),
: [ptr] "{x15}" (ptr),
: "x16"
);
}
return ptr;
}
pub const RegisterContext = struct {
eh_frame: bool,
is_macho: bool,
};
pub const AbiError = error{
InvalidRegister,
UnimplementedArch,
UnimplementedOs,
RegisterContextRequired,
ThreadContextNotSupported,
};
fn RegValueReturnType(comptime ContextPtrType: type, comptime T: type) type {
const reg_bytes_type = comptime RegBytesReturnType(ContextPtrType);
const info = @typeInfo(reg_bytes_type).Pointer;
return @Type(.{
.Pointer = .{
.size = .One,
.is_const = info.is_const,
.is_volatile = info.is_volatile,
.is_allowzero = info.is_allowzero,
.alignment = info.alignment,
.address_space = info.address_space,
.child = T,
.sentinel = null,
},
});
}
/// Returns a pointer to a register stored in a ThreadContext, preserving the pointer attributes of the context.
pub fn regValueNative(
comptime T: type,
thread_context_ptr: anytype,
reg_number: u8,
reg_context: ?RegisterContext,
) !RegValueReturnType(@TypeOf(thread_context_ptr), T) {
const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context);
if (@sizeOf(T) != reg_bytes.len) return error.IncompatibleRegisterSize;
return mem.bytesAsValue(T, reg_bytes[0..@sizeOf(T)]);
}
fn RegBytesReturnType(comptime ContextPtrType: type) type {
const info = @typeInfo(ContextPtrType);
if (info != .Pointer or info.Pointer.child != std.debug.ThreadContext) {
@compileError("Expected a pointer to std.debug.ThreadContext, got " ++ @typeName(@TypeOf(ContextPtrType)));
}
return if (info.Pointer.is_const) return []const u8 else []u8;
}
/// Returns a slice containing the backing storage for `reg_number`.
///
/// `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: anytype,
reg_number: u8,
reg_context: ?RegisterContext,
) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) {
if (native_os == .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 => 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 (!std.debug.have_ucontext) return error.ThreadContextNotSupported;
const ucontext_ptr = thread_context_ptr;
return switch (builtin.cpu.arch) {
.x86 => switch (native_os) {
.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 (native_os) {
.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 (native_os.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 => switch (native_os) {
.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 => switch (native_os) {
.macos, .ios => 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 the ABI-defined default value this register has in the unwinding table
/// before running any of the CIE instructions. The DWARF spec defines these as having
/// the .undefined rule by default, but allows ABI authors to override that.
pub fn getRegDefaultValue(reg_number: u8, context: *std.debug.Dwarf.UnwindContext, out: []u8) !void {
switch (builtin.cpu.arch) {
.aarch64 => {
// Callee-saved registers are initialized as if they had the .same_value rule
if (reg_number >= 19 and reg_number <= 28) {
const src = try regBytes(context.thread_context, reg_number, context.reg_context);
if (src.len != out.len) return error.RegisterSizeMismatch;
@memcpy(out, src);
return;
}
},
else => {},
}
@memset(out, undefined);
}

View File

@@ -0,0 +1,687 @@
const builtin = @import("builtin");
const std = @import("../../std.zig");
const mem = std.mem;
const debug = std.debug;
const leb = std.leb;
const DW = std.dwarf;
const abi = std.debug.Dwarf.abi;
const assert = std.debug.assert;
const native_endian = builtin.cpu.arch.endian();
/// TODO merge with std.dwarf.CFA
const Opcode = enum(u8) {
advance_loc = 0x1 << 6,
offset = 0x2 << 6,
restore = 0x3 << 6,
nop = 0x00,
set_loc = 0x01,
advance_loc1 = 0x02,
advance_loc2 = 0x03,
advance_loc4 = 0x04,
offset_extended = 0x05,
restore_extended = 0x06,
undefined = 0x07,
same_value = 0x08,
register = 0x09,
remember_state = 0x0a,
restore_state = 0x0b,
def_cfa = 0x0c,
def_cfa_register = 0x0d,
def_cfa_offset = 0x0e,
def_cfa_expression = 0x0f,
expression = 0x10,
offset_extended_sf = 0x11,
def_cfa_sf = 0x12,
def_cfa_offset_sf = 0x13,
val_offset = 0x14,
val_offset_sf = 0x15,
val_expression = 0x16,
// These opcodes encode an operand in the lower 6 bits of the opcode itself
pub const lo_inline = @intFromEnum(Opcode.advance_loc);
pub const hi_inline = @intFromEnum(Opcode.restore) | 0b111111;
// These opcodes are trailed by zero or more operands
pub const lo_reserved = @intFromEnum(Opcode.nop);
pub const hi_reserved = @intFromEnum(Opcode.val_expression);
// Vendor-specific opcodes
pub const lo_user = 0x1c;
pub const hi_user = 0x3f;
};
fn readBlock(stream: *std.io.FixedBufferStream([]const u8)) ![]const u8 {
const reader = stream.reader();
const block_len = try leb.readUleb128(usize, reader);
if (stream.pos + block_len > stream.buffer.len) return error.InvalidOperand;
const block = stream.buffer[stream.pos..][0..block_len];
reader.context.pos += block_len;
return block;
}
pub const Instruction = union(Opcode) {
advance_loc: struct {
delta: u8,
},
offset: struct {
register: u8,
offset: u64,
},
restore: struct {
register: u8,
},
nop: void,
set_loc: struct {
address: u64,
},
advance_loc1: struct {
delta: u8,
},
advance_loc2: struct {
delta: u16,
},
advance_loc4: struct {
delta: u32,
},
offset_extended: struct {
register: u8,
offset: u64,
},
restore_extended: struct {
register: u8,
},
undefined: struct {
register: u8,
},
same_value: struct {
register: u8,
},
register: struct {
register: u8,
target_register: u8,
},
remember_state: void,
restore_state: void,
def_cfa: struct {
register: u8,
offset: u64,
},
def_cfa_register: struct {
register: u8,
},
def_cfa_offset: struct {
offset: u64,
},
def_cfa_expression: struct {
block: []const u8,
},
expression: struct {
register: u8,
block: []const u8,
},
offset_extended_sf: struct {
register: u8,
offset: i64,
},
def_cfa_sf: struct {
register: u8,
offset: i64,
},
def_cfa_offset_sf: struct {
offset: i64,
},
val_offset: struct {
register: u8,
offset: u64,
},
val_offset_sf: struct {
register: u8,
offset: i64,
},
val_expression: struct {
register: u8,
block: []const u8,
},
pub fn read(
stream: *std.io.FixedBufferStream([]const u8),
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !Instruction {
const reader = stream.reader();
switch (try reader.readByte()) {
Opcode.lo_inline...Opcode.hi_inline => |opcode| {
const e: Opcode = @enumFromInt(opcode & 0b11000000);
const value: u6 = @intCast(opcode & 0b111111);
return switch (e) {
.advance_loc => .{
.advance_loc = .{ .delta = value },
},
.offset => .{
.offset = .{
.register = value,
.offset = try leb.readUleb128(u64, reader),
},
},
.restore => .{
.restore = .{ .register = value },
},
else => unreachable,
};
},
Opcode.lo_reserved...Opcode.hi_reserved => |opcode| {
const e: Opcode = @enumFromInt(opcode);
return switch (e) {
.advance_loc,
.offset,
.restore,
=> unreachable,
.nop => .{ .nop = {} },
.set_loc => .{
.set_loc = .{
.address = switch (addr_size_bytes) {
2 => try reader.readInt(u16, endian),
4 => try reader.readInt(u32, endian),
8 => try reader.readInt(u64, endian),
else => return error.InvalidAddrSize,
},
},
},
.advance_loc1 => .{
.advance_loc1 = .{ .delta = try reader.readByte() },
},
.advance_loc2 => .{
.advance_loc2 = .{ .delta = try reader.readInt(u16, endian) },
},
.advance_loc4 => .{
.advance_loc4 = .{ .delta = try reader.readInt(u32, endian) },
},
.offset_extended => .{
.offset_extended = .{
.register = try leb.readUleb128(u8, reader),
.offset = try leb.readUleb128(u64, reader),
},
},
.restore_extended => .{
.restore_extended = .{
.register = try leb.readUleb128(u8, reader),
},
},
.undefined => .{
.undefined = .{
.register = try leb.readUleb128(u8, reader),
},
},
.same_value => .{
.same_value = .{
.register = try leb.readUleb128(u8, reader),
},
},
.register => .{
.register = .{
.register = try leb.readUleb128(u8, reader),
.target_register = try leb.readUleb128(u8, reader),
},
},
.remember_state => .{ .remember_state = {} },
.restore_state => .{ .restore_state = {} },
.def_cfa => .{
.def_cfa = .{
.register = try leb.readUleb128(u8, reader),
.offset = try leb.readUleb128(u64, reader),
},
},
.def_cfa_register => .{
.def_cfa_register = .{
.register = try leb.readUleb128(u8, reader),
},
},
.def_cfa_offset => .{
.def_cfa_offset = .{
.offset = try leb.readUleb128(u64, reader),
},
},
.def_cfa_expression => .{
.def_cfa_expression = .{
.block = try readBlock(stream),
},
},
.expression => .{
.expression = .{
.register = try leb.readUleb128(u8, reader),
.block = try readBlock(stream),
},
},
.offset_extended_sf => .{
.offset_extended_sf = .{
.register = try leb.readUleb128(u8, reader),
.offset = try leb.readIleb128(i64, reader),
},
},
.def_cfa_sf => .{
.def_cfa_sf = .{
.register = try leb.readUleb128(u8, reader),
.offset = try leb.readIleb128(i64, reader),
},
},
.def_cfa_offset_sf => .{
.def_cfa_offset_sf = .{
.offset = try leb.readIleb128(i64, reader),
},
},
.val_offset => .{
.val_offset = .{
.register = try leb.readUleb128(u8, reader),
.offset = try leb.readUleb128(u64, reader),
},
},
.val_offset_sf => .{
.val_offset_sf = .{
.register = try leb.readUleb128(u8, reader),
.offset = try leb.readIleb128(i64, reader),
},
},
.val_expression => .{
.val_expression = .{
.register = try leb.readUleb128(u8, reader),
.block = try readBlock(stream),
},
},
};
},
Opcode.lo_user...Opcode.hi_user => return error.UnimplementedUserOpcode,
else => return error.InvalidOpcode,
}
}
};
/// Since register rules are applied (usually) during a panic,
/// checked addition / subtraction is used so that we can return
/// an error and fall back to FP-based unwinding.
pub fn applyOffset(base: usize, offset: i64) !usize {
return if (offset >= 0)
try std.math.add(usize, base, @as(usize, @intCast(offset)))
else
try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
}
/// This is a virtual machine that runs DWARF call frame instructions.
pub const VirtualMachine = struct {
/// See section 6.4.1 of the DWARF5 specification for details on each
const RegisterRule = union(enum) {
// The spec says that the default rule for each column is the undefined rule.
// However, it also allows ABI / compiler authors to specify alternate defaults, so
// there is a distinction made here.
default: void,
undefined: void,
same_value: void,
// offset(N)
offset: i64,
// val_offset(N)
val_offset: i64,
// register(R)
register: u8,
// expression(E)
expression: []const u8,
// val_expression(E)
val_expression: []const u8,
// Augmenter-defined rule
architectural: void,
};
/// Each row contains unwinding rules for a set of registers.
pub const Row = struct {
/// Offset from `FrameDescriptionEntry.pc_begin`
offset: u64 = 0,
/// Special-case column that defines the CFA (Canonical Frame Address) rule.
/// The register field of this column defines the register that CFA is derived from.
cfa: Column = .{},
/// The register fields in these columns define the register the rule applies to.
columns: ColumnRange = .{},
/// Indicates that the next write to any column in this row needs to copy
/// the backing column storage first, as it may be referenced by previous rows.
copy_on_write: bool = false,
};
pub const Column = struct {
register: ?u8 = null,
rule: RegisterRule = .{ .default = {} },
/// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes)
pub fn resolveValue(
self: Column,
context: *std.debug.Dwarf.UnwindContext,
expression_context: std.debug.Dwarf.expression.Context,
ma: *debug.StackIterator.MemoryAccessor,
out: []u8,
) !void {
switch (self.rule) {
.default => {
const register = self.register orelse return error.InvalidRegister;
try abi.getRegDefaultValue(register, context, out);
},
.undefined => {
@memset(out, undefined);
},
.same_value => {
// TODO: This copy could be eliminated if callers always copy the state then call this function to update it
const register = self.register orelse return error.InvalidRegister;
const src = try abi.regBytes(context.thread_context, register, context.reg_context);
if (src.len != out.len) return error.RegisterSizeMismatch;
@memcpy(out, src);
},
.offset => |offset| {
if (context.cfa) |cfa| {
const addr = try applyOffset(cfa, offset);
if (ma.load(usize, addr) == null) return error.InvalidAddress;
const ptr: *const usize = @ptrFromInt(addr);
mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
} else return error.InvalidCFA;
},
.val_offset => |offset| {
if (context.cfa) |cfa| {
mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
} else return error.InvalidCFA;
},
.register => |register| {
const src = try abi.regBytes(context.thread_context, register, context.reg_context);
if (src.len != out.len) return error.RegisterSizeMismatch;
@memcpy(out, try abi.regBytes(context.thread_context, register, context.reg_context));
},
.expression => |expression| {
context.stack_machine.reset();
const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?);
const addr = if (value) |v| blk: {
if (v != .generic) return error.InvalidExpressionValue;
break :blk v.generic;
} else return error.NoExpressionValue;
if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress;
const ptr: *usize = @ptrFromInt(addr);
mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
},
.val_expression => |expression| {
context.stack_machine.reset();
const value = try context.stack_machine.run(expression, context.allocator, 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;
},
.architectural => return error.UnimplementedRegisterRule,
}
}
};
const ColumnRange = struct {
/// Index into `columns` of the first column in this row.
start: usize = undefined,
len: u8 = 0,
};
columns: std.ArrayListUnmanaged(Column) = .{},
stack: std.ArrayListUnmanaged(ColumnRange) = .{},
current_row: Row = .{},
/// The result of executing the CIE's initial_instructions
cie_row: ?Row = null,
pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void {
self.stack.deinit(allocator);
self.columns.deinit(allocator);
self.* = undefined;
}
pub fn reset(self: *VirtualMachine) void {
self.stack.clearRetainingCapacity();
self.columns.clearRetainingCapacity();
self.current_row = .{};
self.cie_row = null;
}
/// Return a slice backed by the row's non-CFA columns
pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
if (row.columns.len == 0) return &.{};
return self.columns.items[row.columns.start..][0..row.columns.len];
}
/// Either retrieves or adds a column for `register` (non-CFA) in the current row.
fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column {
for (self.rowColumns(self.current_row)) |*c| {
if (c.register == register) return c;
}
if (self.current_row.columns.len == 0) {
self.current_row.columns.start = self.columns.items.len;
}
self.current_row.columns.len += 1;
const column = try self.columns.addOne(allocator);
column.* = .{
.register = register,
};
return column;
}
/// Runs the CIE instructions, then the FDE instructions. Execution halts
/// once the row that corresponds to `pc` is known, and the row is returned.
pub fn runTo(
self: *VirtualMachine,
allocator: std.mem.Allocator,
pc: u64,
cie: std.debug.Dwarf.CommonInformationEntry,
fde: std.debug.Dwarf.FrameDescriptionEntry,
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !Row {
assert(self.cie_row == null);
if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange;
var prev_row: Row = self.current_row;
var cie_stream = std.io.fixedBufferStream(cie.initial_instructions);
var fde_stream = std.io.fixedBufferStream(fde.instructions);
var streams = [_]*std.io.FixedBufferStream([]const u8){
&cie_stream,
&fde_stream,
};
for (&streams, 0..) |stream, i| {
while (stream.pos < stream.buffer.len) {
const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian);
prev_row = try self.step(allocator, cie, i == 0, instruction);
if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
}
}
return self.current_row;
}
pub fn runToNative(
self: *VirtualMachine,
allocator: std.mem.Allocator,
pc: u64,
cie: std.debug.Dwarf.CommonInformationEntry,
fde: std.debug.Dwarf.FrameDescriptionEntry,
) !Row {
return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian());
}
fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void {
if (!self.current_row.copy_on_write) return;
const new_start = self.columns.items.len;
if (self.current_row.columns.len > 0) {
try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len);
self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
self.current_row.columns.start = new_start;
}
}
/// Executes a single instruction.
/// If this instruction is from the CIE, `is_initial` should be set.
/// Returns the value of `current_row` before executing this instruction.
pub fn step(
self: *VirtualMachine,
allocator: std.mem.Allocator,
cie: std.debug.Dwarf.CommonInformationEntry,
is_initial: bool,
instruction: Instruction,
) !Row {
// CIE instructions must be run before FDE instructions
assert(!is_initial or self.cie_row == null);
if (!is_initial and self.cie_row == null) {
self.cie_row = self.current_row;
self.current_row.copy_on_write = true;
}
const prev_row = self.current_row;
switch (instruction) {
.set_loc => |i| {
if (i.address <= self.current_row.offset) return error.InvalidOperation;
// TODO: Check cie.segment_selector_size != 0 for DWARFV4
self.current_row.offset = i.address;
},
inline .advance_loc,
.advance_loc1,
.advance_loc2,
.advance_loc4,
=> |i| {
self.current_row.offset += i.delta * cie.code_alignment_factor;
self.current_row.copy_on_write = true;
},
inline .offset,
.offset_extended,
.offset_extended_sf,
=> |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
},
inline .restore,
.restore_extended,
=> |i| {
try self.resolveCopyOnWrite(allocator);
if (self.cie_row) |cie_row| {
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = for (self.rowColumns(cie_row)) |cie_column| {
if (cie_column.register == i.register) break cie_column.rule;
} else .{ .default = {} };
} else return error.InvalidOperation;
},
.nop => {},
.undefined => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = .{ .undefined = {} };
},
.same_value => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = .{ .same_value = {} };
},
.register => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = .{ .register = i.target_register };
},
.remember_state => {
try self.stack.append(allocator, self.current_row.columns);
self.current_row.copy_on_write = true;
},
.restore_state => {
const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation;
self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
try self.columns.ensureUnusedCapacity(allocator, restored_columns.len);
self.current_row.columns.start = self.columns.items.len;
self.current_row.columns.len = restored_columns.len;
self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]);
},
.def_cfa => |i| {
try self.resolveCopyOnWrite(allocator);
self.current_row.cfa = .{
.register = i.register,
.rule = .{ .val_offset = @intCast(i.offset) },
};
},
.def_cfa_sf => |i| {
try self.resolveCopyOnWrite(allocator);
self.current_row.cfa = .{
.register = i.register,
.rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
};
},
.def_cfa_register => |i| {
try self.resolveCopyOnWrite(allocator);
if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
self.current_row.cfa.register = i.register;
},
.def_cfa_offset => |i| {
try self.resolveCopyOnWrite(allocator);
if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
self.current_row.cfa.rule = .{
.val_offset = @intCast(i.offset),
};
},
.def_cfa_offset_sf => |i| {
try self.resolveCopyOnWrite(allocator);
if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
self.current_row.cfa.rule = .{
.val_offset = i.offset * cie.data_alignment_factor,
};
},
.def_cfa_expression => |i| {
try self.resolveCopyOnWrite(allocator);
self.current_row.cfa.register = undefined;
self.current_row.cfa.rule = .{
.expression = i.block,
};
},
.expression => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = .{
.expression = i.block,
};
},
.val_offset => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = .{
.val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
};
},
.val_offset_sf => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = .{
.val_offset = i.offset * cie.data_alignment_factor,
};
},
.val_expression => |i| {
try self.resolveCopyOnWrite(allocator);
const column = try self.getOrAddColumn(allocator, i.register);
column.rule = .{
.val_expression = i.block,
};
},
}
return prev_row;
}
};

File diff suppressed because it is too large Load Diff