mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 19:09:47 +03:00
Merge pull request #15823 from kcbanner/dwarf_unwind
Add DWARF unwinding, and an external debug info loader for ELF
This commit is contained in:
@@ -413,6 +413,13 @@ pub extern "c" fn timer_delete(timerid: c.timer_t) c_int;
|
||||
pub extern "c" fn timer_settime(timerid: c.timer_t, flags: c_int, new_value: *const c.itimerspec, old_value: *c.itimerspec) c_int;
|
||||
pub extern "c" fn timer_gettime(timerid: c.timer_t, flags: c_int, curr_value: *c.itimerspec) c_int;
|
||||
|
||||
pub usingnamespace if (builtin.os.tag == .linux and builtin.target.isMusl()) struct {
|
||||
// musl does not implement getcontext
|
||||
pub const getcontext = std.os.linux.getcontext;
|
||||
} else struct {
|
||||
pub extern "c" fn getcontext(ucp: *std.os.ucontext_t) c_int;
|
||||
};
|
||||
|
||||
pub const max_align_t = if (builtin.abi == .msvc)
|
||||
f64
|
||||
else if (builtin.target.isDarwin())
|
||||
|
||||
@@ -148,12 +148,10 @@ pub const ucontext_t = extern struct {
|
||||
link: ?*ucontext_t,
|
||||
mcsize: u64,
|
||||
mcontext: *mcontext_t,
|
||||
__mcontext_data: mcontext_t,
|
||||
};
|
||||
|
||||
pub const mcontext_t = extern struct {
|
||||
es: arch_bits.exception_state,
|
||||
ss: arch_bits.thread_state,
|
||||
};
|
||||
pub const mcontext_t = arch_bits.mcontext_t;
|
||||
|
||||
extern "c" fn __error() *c_int;
|
||||
pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
// See C headers in
|
||||
// lib/libc/include/aarch64-macos.12-gnu/mach/arm/_structs.h
|
||||
// lib/libc/include/aarch64-macos.13-none/arm/_mcontext.h
|
||||
|
||||
pub const mcontext_t = extern struct {
|
||||
es: exception_state,
|
||||
ss: thread_state,
|
||||
ns: neon_state,
|
||||
};
|
||||
|
||||
pub const exception_state = extern struct {
|
||||
far: u64, // Virtual Fault Address
|
||||
@@ -17,6 +24,12 @@ pub const thread_state = extern struct {
|
||||
__pad: u32,
|
||||
};
|
||||
|
||||
pub const neon_state = extern struct {
|
||||
q: [32]u128,
|
||||
fpsr: u32,
|
||||
fpcr: u32,
|
||||
};
|
||||
|
||||
pub const EXC_TYPES_COUNT = 14;
|
||||
pub const EXC_MASK_MACHINE = 0;
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
const c = @import("../darwin.zig");
|
||||
|
||||
pub const mcontext_t = extern struct {
|
||||
es: exception_state,
|
||||
ss: thread_state,
|
||||
fs: float_state,
|
||||
};
|
||||
|
||||
pub const exception_state = extern struct {
|
||||
trapno: u16,
|
||||
cpu: u16,
|
||||
@@ -31,6 +37,29 @@ pub const thread_state = extern struct {
|
||||
gs: u64,
|
||||
};
|
||||
|
||||
const stmm_reg = [16]u8;
|
||||
const xmm_reg = [16]u8;
|
||||
pub const float_state = extern struct {
|
||||
reserved: [2]c_int,
|
||||
fcw: u16,
|
||||
fsw: u16,
|
||||
ftw: u8,
|
||||
rsrv1: u8,
|
||||
fop: u16,
|
||||
ip: u32,
|
||||
cs: u16,
|
||||
rsrv2: u16,
|
||||
dp: u32,
|
||||
ds: u16,
|
||||
rsrv3: u16,
|
||||
mxcsr: u32,
|
||||
mxcsrmask: u32,
|
||||
stmm: [8]stmm_reg,
|
||||
xmm: [16]xmm_reg,
|
||||
rsrv4: [96]u8,
|
||||
reserved1: c_int,
|
||||
};
|
||||
|
||||
pub const THREAD_STATE = 4;
|
||||
pub const THREAD_STATE_COUNT: c.mach_msg_type_number_t = @sizeOf(thread_state) / @sizeOf(c_int);
|
||||
|
||||
|
||||
+8
-5
@@ -1214,6 +1214,11 @@ pub const Coff = struct {
|
||||
return Strtab{ .buffer = self.data[offset..][0..size] };
|
||||
}
|
||||
|
||||
pub fn strtabRequired(self: *const Coff) bool {
|
||||
for (self.getSectionHeaders()) |*sect_hdr| if (sect_hdr.getName() == null) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn getSectionHeaders(self: *const Coff) []align(1) const SectionHeader {
|
||||
const coff_header = self.getCoffHeader();
|
||||
const offset = self.coff_header_offset + @sizeOf(CoffHeader) + coff_header.size_of_optional_header;
|
||||
@@ -1248,14 +1253,12 @@ pub const Coff = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getSectionData(self: *const Coff, comptime name: []const u8) ![]const u8 {
|
||||
const sec = self.getSectionByName(name) orelse return error.MissingCoffSection;
|
||||
pub fn getSectionData(self: *const Coff, sec: *align(1) const SectionHeader) []const u8 {
|
||||
return self.data[sec.pointer_to_raw_data..][0..sec.virtual_size];
|
||||
}
|
||||
|
||||
// Return an owned slice full of the section data
|
||||
pub fn getSectionDataAlloc(self: *const Coff, comptime name: []const u8, allocator: mem.Allocator) ![]u8 {
|
||||
const section_data = try self.getSectionData(name);
|
||||
pub fn getSectionDataAlloc(self: *const Coff, sec: *align(1) const SectionHeader, allocator: mem.Allocator) ![]u8 {
|
||||
const section_data = self.getSectionData(sec);
|
||||
return allocator.dupe(u8, section_data);
|
||||
}
|
||||
};
|
||||
|
||||
+764
-331
@@ -133,10 +133,80 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub const have_ucontext = @hasDecl(os.system, "ucontext_t") and
|
||||
(builtin.os.tag != .linux or switch (builtin.cpu.arch) {
|
||||
.mips, .mipsel, .mips64, .mips64el, .riscv64 => false,
|
||||
else => true,
|
||||
});
|
||||
|
||||
/// Platform-specific thread state. This contains register state, and on some platforms
|
||||
/// information about the stack. This is not safe to trivially copy, because some platforms
|
||||
/// use internal pointers within this structure. To make a copy, use `copyContext`.
|
||||
pub const ThreadContext = blk: {
|
||||
if (native_os == .windows) {
|
||||
break :blk std.os.windows.CONTEXT;
|
||||
} else if (have_ucontext) {
|
||||
break :blk os.ucontext_t;
|
||||
} else {
|
||||
break :blk void;
|
||||
}
|
||||
};
|
||||
|
||||
/// Copies one context to another, updating any internal pointers
|
||||
pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void {
|
||||
if (!have_ucontext) return {};
|
||||
dest.* = source.*;
|
||||
relocateContext(dest);
|
||||
}
|
||||
|
||||
/// Updates any internal pointers in the context to reflect its current location
|
||||
pub fn relocateContext(context: *ThreadContext) void {
|
||||
return switch (native_os) {
|
||||
.macos => {
|
||||
context.mcontext = &context.__mcontext_data;
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
}
|
||||
|
||||
pub const have_getcontext = @hasDecl(os.system, "getcontext") and
|
||||
(builtin.os.tag != .linux or switch (builtin.cpu.arch) {
|
||||
.x86,
|
||||
.x86_64,
|
||||
=> true,
|
||||
else => builtin.link_libc and !builtin.target.isMusl(),
|
||||
});
|
||||
|
||||
/// Capture the current context. The register values in the context will reflect the
|
||||
/// state after the platform `getcontext` function returns.
|
||||
///
|
||||
/// It is valid to call this if the platform doesn't have context capturing support,
|
||||
/// in that case false will be returned.
|
||||
pub inline fn getContext(context: *ThreadContext) bool {
|
||||
if (native_os == .windows) {
|
||||
context.* = std.mem.zeroes(windows.CONTEXT);
|
||||
windows.ntdll.RtlCaptureContext(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
const result = have_getcontext and os.system.getcontext(context) == 0;
|
||||
if (native_os == .macos) {
|
||||
assert(context.mcsize == @sizeOf(std.c.mcontext_t));
|
||||
|
||||
// On aarch64-macos, the system getcontext doesn't write anything into the pc
|
||||
// register slot, it only writes lr. This makes the context consistent with
|
||||
// other aarch64 getcontext implementations which write the current lr
|
||||
// (where getcontext will return to) into both the lr and pc slot of the context.
|
||||
if (native_arch == .aarch64) context.mcontext.ss.pc = context.mcontext.ss.lr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Tries to print the stack trace starting from the supplied base pointer to stderr,
|
||||
/// unbuffered, and ignores any error returned.
|
||||
/// TODO multithreaded awareness
|
||||
pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
|
||||
pub fn dumpStackTraceFromBase(context: *const ThreadContext) void {
|
||||
nosuspend {
|
||||
if (comptime builtin.target.isWasm()) {
|
||||
if (native_os == .wasi) {
|
||||
@@ -156,13 +226,25 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
|
||||
};
|
||||
const tty_config = io.tty.detectConfig(io.getStdErr());
|
||||
if (native_os == .windows) {
|
||||
writeCurrentStackTraceWindows(stderr, debug_info, tty_config, ip) catch return;
|
||||
// On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context
|
||||
// provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace
|
||||
// will be captured and frames prior to the exception will be filtered.
|
||||
// The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame,
|
||||
// which is where the IP in `context` points to, so it can't be used as start_addr.
|
||||
// Instead, start_addr is recovered from the stack.
|
||||
const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null;
|
||||
writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return;
|
||||
return;
|
||||
}
|
||||
|
||||
printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return;
|
||||
var it = StackIterator.init(null, bp);
|
||||
var it = StackIterator.initWithContext(null, debug_info, context) catch return;
|
||||
defer it.deinit();
|
||||
printSourceAtAddress(debug_info, stderr, it.unwind_state.?.dwarf_context.pc, tty_config) catch return;
|
||||
|
||||
while (it.next()) |return_address| {
|
||||
if (it.getLastError()) |unwind_error|
|
||||
printUnwindError(debug_info, stderr, unwind_error.address, unwind_error.err, tty_config) catch {};
|
||||
|
||||
// On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
|
||||
// therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
|
||||
// an overflow. We do not need to signal `StackIterator` as it will correctly detect this
|
||||
@@ -184,12 +266,12 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
|
||||
if (native_os == .windows) {
|
||||
const addrs = stack_trace.instruction_addresses;
|
||||
const first_addr = first_address orelse {
|
||||
stack_trace.index = walkStackWindows(addrs[0..]);
|
||||
stack_trace.index = walkStackWindows(addrs[0..], null);
|
||||
return;
|
||||
};
|
||||
var addr_buf_stack: [32]usize = undefined;
|
||||
const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs;
|
||||
const n = walkStackWindows(addr_buf[0..]);
|
||||
const n = walkStackWindows(addr_buf[0..], null);
|
||||
const first_index = for (addr_buf[0..n], 0..) |addr, i| {
|
||||
if (addr == first_addr) {
|
||||
break i;
|
||||
@@ -206,7 +288,11 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
|
||||
}
|
||||
stack_trace.index = slice.len;
|
||||
} else {
|
||||
// TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required).
|
||||
// A new path for loading DebugInfo needs to be created which will only attempt to parse in-memory sections, because
|
||||
// stopping to load other debug info (ie. source line info) from disk here is not required for unwinding.
|
||||
var it = StackIterator.init(first_address, null);
|
||||
defer it.deinit();
|
||||
for (stack_trace.instruction_addresses, 0..) |*addr, i| {
|
||||
addr.* = it.next() orelse {
|
||||
stack_trace.index = i;
|
||||
@@ -399,12 +485,27 @@ pub fn writeStackTrace(
|
||||
}
|
||||
}
|
||||
|
||||
pub const UnwindError = if (have_ucontext)
|
||||
@typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).Fn.return_type.?).ErrorUnion.error_set
|
||||
else
|
||||
void;
|
||||
|
||||
pub const StackIterator = struct {
|
||||
// Skip every frame before this address is found.
|
||||
first_address: ?usize,
|
||||
// Last known value of the frame pointer register.
|
||||
fp: usize,
|
||||
|
||||
// When DebugInfo and a register context is available, this iterator can unwind
|
||||
// stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer),
|
||||
// using DWARF and MachO unwind info.
|
||||
unwind_state: if (have_ucontext) ?struct {
|
||||
debug_info: *DebugInfo,
|
||||
dwarf_context: DW.UnwindContext,
|
||||
last_error: ?UnwindError = null,
|
||||
failed: bool = false,
|
||||
} else void = if (have_ucontext) null else {},
|
||||
|
||||
pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
|
||||
if (native_arch == .sparc64) {
|
||||
// Flush all the register windows on stack.
|
||||
@@ -419,6 +520,44 @@ pub const StackIterator = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const os.ucontext_t) !StackIterator {
|
||||
// The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that
|
||||
// the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder.
|
||||
if (comptime builtin.target.isDarwin() and native_arch == .aarch64) {
|
||||
return init(first_address, context.mcontext.ss.fp);
|
||||
} else {
|
||||
var iterator = init(first_address, null);
|
||||
iterator.unwind_state = .{
|
||||
.debug_info = debug_info,
|
||||
.dwarf_context = try DW.UnwindContext.init(debug_info.allocator, context, &isValidMemory),
|
||||
};
|
||||
|
||||
return iterator;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *StackIterator) void {
|
||||
if (have_ucontext and self.unwind_state != null) self.unwind_state.?.dwarf_context.deinit();
|
||||
}
|
||||
|
||||
pub fn getLastError(self: *StackIterator) ?struct {
|
||||
err: UnwindError,
|
||||
address: usize,
|
||||
} {
|
||||
if (!have_ucontext) return null;
|
||||
if (self.unwind_state) |*unwind_state| {
|
||||
if (unwind_state.last_error) |err| {
|
||||
unwind_state.last_error = null;
|
||||
return .{
|
||||
.err = err,
|
||||
.address = unwind_state.dwarf_context.pc,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Offset of the saved BP wrt the frame pointer.
|
||||
const fp_offset = if (native_arch.isRISCV())
|
||||
// On RISC-V the frame pointer points to the top of the saved register
|
||||
@@ -461,6 +600,7 @@ pub const StackIterator = struct {
|
||||
if (native_os == .freestanding) return true;
|
||||
|
||||
const aligned_address = address & ~@as(usize, @intCast((mem.page_size - 1)));
|
||||
if (aligned_address == 0) return false;
|
||||
const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size];
|
||||
|
||||
if (native_os != .windows) {
|
||||
@@ -500,7 +640,49 @@ pub const StackIterator = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn next_unwind(self: *StackIterator) !usize {
|
||||
const unwind_state = &self.unwind_state.?;
|
||||
const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc);
|
||||
switch (native_os) {
|
||||
.macos, .ios, .watchos, .tvos => {
|
||||
// __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
|
||||
// via DWARF before attempting to use the compact unwind info will produce incorrect results.
|
||||
if (module.unwind_info) |unwind_info| {
|
||||
if (DW.unwindFrameMachO(&unwind_state.dwarf_context, unwind_info, module.eh_frame, module.base_address)) |return_address| {
|
||||
return return_address;
|
||||
} else |err| {
|
||||
if (err != error.RequiresDWARFUnwind) return err;
|
||||
}
|
||||
} else return error.MissingUnwindInfo;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| {
|
||||
return di.unwindFrame(&unwind_state.dwarf_context, null);
|
||||
} else return error.MissingDebugInfo;
|
||||
}
|
||||
|
||||
fn next_internal(self: *StackIterator) ?usize {
|
||||
if (have_ucontext) {
|
||||
if (self.unwind_state) |*unwind_state| {
|
||||
if (!unwind_state.failed) {
|
||||
if (unwind_state.dwarf_context.pc == 0) return null;
|
||||
if (self.next_unwind()) |return_address| {
|
||||
self.fp = unwind_state.dwarf_context.getFp() catch 0;
|
||||
return return_address;
|
||||
} else |err| {
|
||||
unwind_state.last_error = err;
|
||||
unwind_state.failed = true;
|
||||
|
||||
// Fall back to fp-based unwinding on the first failure.
|
||||
// We can't attempt it again for other modules higher in the
|
||||
// stack because the full register state won't have been unwound.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fp = if (comptime native_arch.isSPARC())
|
||||
// On SPARC the offset is positive. (!)
|
||||
math.add(usize, self.fp, fp_offset) catch return null
|
||||
@@ -537,11 +719,21 @@ pub fn writeCurrentStackTrace(
|
||||
tty_config: io.tty.Config,
|
||||
start_addr: ?usize,
|
||||
) !void {
|
||||
var context: ThreadContext = undefined;
|
||||
const has_context = getContext(&context);
|
||||
if (native_os == .windows) {
|
||||
return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);
|
||||
return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr);
|
||||
}
|
||||
var it = StackIterator.init(start_addr, null);
|
||||
|
||||
var it = (if (has_context) blk: {
|
||||
break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null;
|
||||
} else null) orelse StackIterator.init(start_addr, null);
|
||||
defer it.deinit();
|
||||
|
||||
while (it.next()) |return_address| {
|
||||
if (it.getLastError()) |unwind_error|
|
||||
try printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config);
|
||||
|
||||
// On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
|
||||
// therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
|
||||
// an overflow. We do not need to signal `StackIterator` as it will correctly detect this
|
||||
@@ -552,7 +744,7 @@ pub fn writeCurrentStackTrace(
|
||||
}
|
||||
}
|
||||
|
||||
pub noinline fn walkStackWindows(addresses: []usize) usize {
|
||||
pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize {
|
||||
if (builtin.cpu.arch == .x86) {
|
||||
// RtlVirtualUnwind doesn't exist on x86
|
||||
return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null);
|
||||
@@ -560,8 +752,13 @@ pub noinline fn walkStackWindows(addresses: []usize) usize {
|
||||
|
||||
const tib = @as(*const windows.NT_TIB, @ptrCast(&windows.teb().Reserved1));
|
||||
|
||||
var context: windows.CONTEXT = std.mem.zeroes(windows.CONTEXT);
|
||||
windows.ntdll.RtlCaptureContext(&context);
|
||||
var context: windows.CONTEXT = undefined;
|
||||
if (existing_context) |context_ptr| {
|
||||
context = context_ptr.*;
|
||||
} else {
|
||||
context = std.mem.zeroes(windows.CONTEXT);
|
||||
windows.ntdll.RtlCaptureContext(&context);
|
||||
}
|
||||
|
||||
var i: usize = 0;
|
||||
var image_base: usize = undefined;
|
||||
@@ -603,14 +800,15 @@ pub noinline fn walkStackWindows(addresses: []usize) usize {
|
||||
return i;
|
||||
}
|
||||
|
||||
pub fn writeCurrentStackTraceWindows(
|
||||
pub fn writeStackTraceWindows(
|
||||
out_stream: anytype,
|
||||
debug_info: *DebugInfo,
|
||||
tty_config: io.tty.Config,
|
||||
context: *const windows.CONTEXT,
|
||||
start_addr: ?usize,
|
||||
) !void {
|
||||
var addr_buf: [1024]usize = undefined;
|
||||
const n = walkStackWindows(addr_buf[0..]);
|
||||
const n = walkStackWindows(addr_buf[0..], context);
|
||||
const addrs = addr_buf[0..n];
|
||||
var start_i: usize = if (start_addr) |saddr| blk: {
|
||||
for (addrs, 0..) |addr, i| {
|
||||
@@ -681,6 +879,13 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz
|
||||
);
|
||||
}
|
||||
|
||||
pub fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void {
|
||||
const module_name = debug_info.getModuleNameForAddress(address) orelse "???";
|
||||
try tty_config.setColor(out_stream, .dim);
|
||||
try out_stream.print("Unwind information for `{s}:0x{x}` was not available ({}), trace may be incomplete\n\n", .{ module_name, address, err });
|
||||
try tty_config.setColor(out_stream, .reset);
|
||||
}
|
||||
|
||||
pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
|
||||
const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
|
||||
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
|
||||
@@ -779,12 +984,8 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI
|
||||
}
|
||||
}
|
||||
|
||||
fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDebugInfo {
|
||||
fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo {
|
||||
nosuspend {
|
||||
const coff_obj = try allocator.create(coff.Coff);
|
||||
defer allocator.destroy(coff_obj);
|
||||
coff_obj.* = try coff.Coff.init(coff_bytes);
|
||||
|
||||
var di = ModuleDebugInfo{
|
||||
.base_address = undefined,
|
||||
.coff_image_base = coff_obj.getImageBase(),
|
||||
@@ -792,62 +993,35 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe
|
||||
.debug_data = undefined,
|
||||
};
|
||||
|
||||
if (coff_obj.getSectionByName(".debug_info")) |sec| {
|
||||
if (coff_obj.getSectionByName(".debug_info")) |_| {
|
||||
// This coff file has embedded DWARF debug info
|
||||
_ = sec;
|
||||
var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
|
||||
errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
|
||||
|
||||
const debug_info = coff_obj.getSectionDataAlloc(".debug_info", allocator) catch return error.MissingDebugInfo;
|
||||
errdefer allocator.free(debug_info);
|
||||
const debug_abbrev = coff_obj.getSectionDataAlloc(".debug_abbrev", allocator) catch return error.MissingDebugInfo;
|
||||
errdefer allocator.free(debug_abbrev);
|
||||
const debug_str = coff_obj.getSectionDataAlloc(".debug_str", allocator) catch return error.MissingDebugInfo;
|
||||
errdefer allocator.free(debug_str);
|
||||
const debug_line = coff_obj.getSectionDataAlloc(".debug_line", allocator) catch return error.MissingDebugInfo;
|
||||
errdefer allocator.free(debug_line);
|
||||
|
||||
const debug_str_offsets = coff_obj.getSectionDataAlloc(".debug_str_offsets", allocator) catch null;
|
||||
const debug_line_str = coff_obj.getSectionDataAlloc(".debug_line_str", allocator) catch null;
|
||||
const debug_ranges = coff_obj.getSectionDataAlloc(".debug_ranges", allocator) catch null;
|
||||
const debug_loclists = coff_obj.getSectionDataAlloc(".debug_loclists", allocator) catch null;
|
||||
const debug_rnglists = coff_obj.getSectionDataAlloc(".debug_rnglists", allocator) catch null;
|
||||
const debug_addr = coff_obj.getSectionDataAlloc(".debug_addr", allocator) catch null;
|
||||
const debug_names = coff_obj.getSectionDataAlloc(".debug_names", allocator) catch null;
|
||||
const debug_frame = coff_obj.getSectionDataAlloc(".debug_frame", allocator) catch null;
|
||||
inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| {
|
||||
sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
|
||||
break :blk .{
|
||||
.data = try coff_obj.getSectionDataAlloc(section_header, allocator),
|
||||
.virtual_address = section_header.virtual_address,
|
||||
.owned = true,
|
||||
};
|
||||
} else null;
|
||||
}
|
||||
|
||||
var dwarf = DW.DwarfInfo{
|
||||
.endian = native_endian,
|
||||
.debug_info = debug_info,
|
||||
.debug_abbrev = debug_abbrev,
|
||||
.debug_str = debug_str,
|
||||
.debug_str_offsets = debug_str_offsets,
|
||||
.debug_line = debug_line,
|
||||
.debug_line_str = debug_line_str,
|
||||
.debug_ranges = debug_ranges,
|
||||
.debug_loclists = debug_loclists,
|
||||
.debug_rnglists = debug_rnglists,
|
||||
.debug_addr = debug_addr,
|
||||
.debug_names = debug_names,
|
||||
.debug_frame = debug_frame,
|
||||
};
|
||||
|
||||
DW.openDwarfDebugInfo(&dwarf, allocator) catch |err| {
|
||||
if (debug_str_offsets) |d| allocator.free(d);
|
||||
if (debug_line_str) |d| allocator.free(d);
|
||||
if (debug_ranges) |d| allocator.free(d);
|
||||
if (debug_loclists) |d| allocator.free(d);
|
||||
if (debug_rnglists) |d| allocator.free(d);
|
||||
if (debug_addr) |d| allocator.free(d);
|
||||
if (debug_names) |d| allocator.free(d);
|
||||
if (debug_frame) |d| allocator.free(d);
|
||||
return err;
|
||||
.sections = sections,
|
||||
.is_macho = false,
|
||||
};
|
||||
|
||||
try DW.openDwarfDebugInfo(&dwarf, allocator);
|
||||
di.debug_data = PdbOrDwarf{ .dwarf = dwarf };
|
||||
return di;
|
||||
}
|
||||
|
||||
// Only used by pdb path
|
||||
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
|
||||
errdefer allocator.free(di.coff_section_headers);
|
||||
|
||||
var path_buf: [windows.MAX_PATH]u8 = undefined;
|
||||
const len = try coff_obj.getPdbPath(path_buf[0..]);
|
||||
@@ -877,13 +1051,35 @@ fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8
|
||||
return ptr[start..end];
|
||||
}
|
||||
|
||||
/// This takes ownership of elf_file: users of this function should not close
|
||||
/// it themselves, even on error.
|
||||
/// TODO it's weird to take ownership even on error, rework this code.
|
||||
pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugInfo {
|
||||
/// Reads debug info from an ELF file, or the current binary if none in specified.
|
||||
/// If the required sections aren't present but a reference to external debug info is,
|
||||
/// then this this function will recurse to attempt to load the debug sections from
|
||||
/// an external file.
|
||||
pub fn readElfDebugInfo(
|
||||
allocator: mem.Allocator,
|
||||
elf_filename: ?[]const u8,
|
||||
build_id: ?[]const u8,
|
||||
expected_crc: ?u32,
|
||||
parent_sections: *DW.DwarfInfo.SectionArray,
|
||||
parent_mapped_mem: ?[]align(mem.page_size) const u8,
|
||||
) !ModuleDebugInfo {
|
||||
nosuspend {
|
||||
|
||||
// TODO https://github.com/ziglang/zig/issues/5525
|
||||
const elf_file = (if (elf_filename) |filename| blk: {
|
||||
break :blk if (fs.path.isAbsolute(filename))
|
||||
fs.openFileAbsolute(filename, .{ .intended_io_mode = .blocking })
|
||||
else
|
||||
fs.cwd().openFile(filename, .{ .intended_io_mode = .blocking });
|
||||
} else fs.openSelfExe(.{ .intended_io_mode = .blocking })) catch |err| switch (err) {
|
||||
error.FileNotFound => return error.MissingDebugInfo,
|
||||
else => return err,
|
||||
};
|
||||
|
||||
const mapped_mem = try mapWholeFile(elf_file);
|
||||
const hdr = @as(*const elf.Ehdr, @ptrCast(&mapped_mem[0]));
|
||||
if (expected_crc) |crc| if (crc != std.hash.crc.Crc32SmallWithPoly(.IEEE).hash(mapped_mem)) return error.InvalidDebugInfo;
|
||||
|
||||
const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
|
||||
if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
|
||||
if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
|
||||
|
||||
@@ -896,73 +1092,152 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn
|
||||
|
||||
const shoff = hdr.e_shoff;
|
||||
const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
|
||||
const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(
|
||||
&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow],
|
||||
));
|
||||
const header_strings = mapped_mem[str_shdr.sh_offset .. str_shdr.sh_offset + str_shdr.sh_size];
|
||||
const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
|
||||
const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
|
||||
const shdrs = @as(
|
||||
[*]const elf.Shdr,
|
||||
@ptrCast(@alignCast(&mapped_mem[shoff])),
|
||||
)[0..hdr.e_shnum];
|
||||
|
||||
var opt_debug_info: ?[]const u8 = null;
|
||||
var opt_debug_abbrev: ?[]const u8 = null;
|
||||
var opt_debug_str: ?[]const u8 = null;
|
||||
var opt_debug_str_offsets: ?[]const u8 = null;
|
||||
var opt_debug_line: ?[]const u8 = null;
|
||||
var opt_debug_line_str: ?[]const u8 = null;
|
||||
var opt_debug_ranges: ?[]const u8 = null;
|
||||
var opt_debug_loclists: ?[]const u8 = null;
|
||||
var opt_debug_rnglists: ?[]const u8 = null;
|
||||
var opt_debug_addr: ?[]const u8 = null;
|
||||
var opt_debug_names: ?[]const u8 = null;
|
||||
var opt_debug_frame: ?[]const u8 = null;
|
||||
var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
|
||||
|
||||
// Combine section list. This takes ownership over any owned sections from the parent scope.
|
||||
for (parent_sections, §ions) |*parent, *section| {
|
||||
if (parent.*) |*p| {
|
||||
section.* = p.*;
|
||||
p.owned = false;
|
||||
}
|
||||
}
|
||||
errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
|
||||
|
||||
var separate_debug_filename: ?[]const u8 = null;
|
||||
var separate_debug_crc: ?u32 = null;
|
||||
|
||||
for (shdrs) |*shdr| {
|
||||
if (shdr.sh_type == elf.SHT_NULL) continue;
|
||||
|
||||
if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
|
||||
const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
|
||||
if (mem.eql(u8, name, ".debug_info")) {
|
||||
opt_debug_info = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_abbrev")) {
|
||||
opt_debug_abbrev = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_str")) {
|
||||
opt_debug_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_str_offsets")) {
|
||||
opt_debug_str_offsets = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_line")) {
|
||||
opt_debug_line = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_line_str")) {
|
||||
opt_debug_line_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_ranges")) {
|
||||
opt_debug_ranges = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_loclists")) {
|
||||
opt_debug_loclists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_rnglists")) {
|
||||
opt_debug_rnglists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_addr")) {
|
||||
opt_debug_addr = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_names")) {
|
||||
opt_debug_names = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
} else if (mem.eql(u8, name, ".debug_frame")) {
|
||||
opt_debug_frame = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
|
||||
if (mem.eql(u8, name, ".gnu_debuglink")) {
|
||||
const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
|
||||
const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
|
||||
const crc_bytes = gnu_debuglink[crc_offset .. crc_offset + 4];
|
||||
separate_debug_crc = mem.readIntSliceNative(u32, crc_bytes);
|
||||
separate_debug_filename = debug_filename;
|
||||
continue;
|
||||
}
|
||||
|
||||
var section_index: ?usize = null;
|
||||
inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| {
|
||||
if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
|
||||
}
|
||||
if (section_index == null) continue;
|
||||
if (sections[section_index.?] != null) continue;
|
||||
|
||||
const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
|
||||
sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
|
||||
var section_stream = io.fixedBufferStream(section_bytes);
|
||||
var section_reader = section_stream.reader();
|
||||
const chdr = section_reader.readStruct(elf.Chdr) catch continue;
|
||||
if (chdr.ch_type != .ZLIB) continue;
|
||||
|
||||
var zlib_stream = std.compress.zlib.decompressStream(allocator, section_stream.reader()) catch continue;
|
||||
defer zlib_stream.deinit();
|
||||
|
||||
var decompressed_section = try allocator.alloc(u8, chdr.ch_size);
|
||||
errdefer allocator.free(decompressed_section);
|
||||
|
||||
const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
|
||||
assert(read == decompressed_section.len);
|
||||
|
||||
break :blk .{
|
||||
.data = decompressed_section,
|
||||
.virtual_address = shdr.sh_addr,
|
||||
.owned = true,
|
||||
};
|
||||
} else .{
|
||||
.data = section_bytes,
|
||||
.virtual_address = shdr.sh_addr,
|
||||
.owned = false,
|
||||
};
|
||||
}
|
||||
|
||||
const missing_debug_info =
|
||||
sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or
|
||||
sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or
|
||||
sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or
|
||||
sections[@intFromEnum(DW.DwarfSection.debug_line)] == null;
|
||||
|
||||
// Attempt to load debug info from an external file
|
||||
// See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
|
||||
if (missing_debug_info) {
|
||||
|
||||
// Only allow one level of debug info nesting
|
||||
if (parent_mapped_mem) |_| {
|
||||
return error.MissingDebugInfo;
|
||||
}
|
||||
|
||||
const global_debug_directories = [_][]const u8{
|
||||
"/usr/lib/debug",
|
||||
};
|
||||
|
||||
// <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
|
||||
if (build_id) |id| blk: {
|
||||
if (id.len < 3) break :blk;
|
||||
|
||||
// Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
|
||||
const extension = ".debug";
|
||||
var id_prefix_buf: [2]u8 = undefined;
|
||||
var filename_buf: [38 + extension.len]u8 = undefined;
|
||||
|
||||
_ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
|
||||
const filename = std.fmt.bufPrint(
|
||||
&filename_buf,
|
||||
"{s}" ++ extension,
|
||||
.{std.fmt.fmtSliceHexLower(id[1..])},
|
||||
) catch break :blk;
|
||||
|
||||
for (global_debug_directories) |global_directory| {
|
||||
const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
|
||||
defer allocator.free(path);
|
||||
|
||||
return readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem) catch continue;
|
||||
}
|
||||
}
|
||||
|
||||
// use the path from .gnu_debuglink, in the same search order as gdb
|
||||
if (separate_debug_filename) |separate_filename| blk: {
|
||||
if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
|
||||
|
||||
// <cwd>/<gnu_debuglink>
|
||||
if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
|
||||
|
||||
// <cwd>/.debug/<gnu_debuglink>
|
||||
{
|
||||
const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
|
||||
defer allocator.free(path);
|
||||
|
||||
if (readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
|
||||
}
|
||||
|
||||
var cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const cwd_path = fs.cwd().realpath("", &cwd_buf) catch break :blk;
|
||||
|
||||
// <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
|
||||
for (global_debug_directories) |global_directory| {
|
||||
const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
|
||||
defer allocator.free(path);
|
||||
if (readElfDebugInfo(allocator, path, null, separate_debug_crc, §ions, mapped_mem)) |debug_info| return debug_info else |_| {}
|
||||
}
|
||||
}
|
||||
|
||||
return error.MissingDebugInfo;
|
||||
}
|
||||
|
||||
var di = DW.DwarfInfo{
|
||||
.endian = endian,
|
||||
.debug_info = opt_debug_info orelse return error.MissingDebugInfo,
|
||||
.debug_abbrev = opt_debug_abbrev orelse return error.MissingDebugInfo,
|
||||
.debug_str = opt_debug_str orelse return error.MissingDebugInfo,
|
||||
.debug_str_offsets = opt_debug_str_offsets,
|
||||
.debug_line = opt_debug_line orelse return error.MissingDebugInfo,
|
||||
.debug_line_str = opt_debug_line_str,
|
||||
.debug_ranges = opt_debug_ranges,
|
||||
.debug_loclists = opt_debug_loclists,
|
||||
.debug_rnglists = opt_debug_rnglists,
|
||||
.debug_addr = opt_debug_addr,
|
||||
.debug_names = opt_debug_names,
|
||||
.debug_frame = opt_debug_frame,
|
||||
.sections = sections,
|
||||
.is_macho = false,
|
||||
};
|
||||
|
||||
try DW.openDwarfDebugInfo(&di, allocator);
|
||||
@@ -970,7 +1245,8 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn
|
||||
return ModuleDebugInfo{
|
||||
.base_address = undefined,
|
||||
.dwarf = di,
|
||||
.mapped_memory = mapped_mem,
|
||||
.mapped_memory = parent_mapped_mem orelse mapped_mem,
|
||||
.external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1094,6 +1370,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn
|
||||
|
||||
return ModuleDebugInfo{
|
||||
.base_address = undefined,
|
||||
.vmaddr_slide = undefined,
|
||||
.mapped_memory = mapped_mem,
|
||||
.ofiles = ModuleDebugInfo.OFileTable.init(allocator),
|
||||
.symbols = symbols,
|
||||
@@ -1180,6 +1457,21 @@ pub const WindowsModuleInfo = struct {
|
||||
base_address: usize,
|
||||
size: u32,
|
||||
name: []const u8,
|
||||
handle: windows.HMODULE,
|
||||
|
||||
// Set when the image file needed to be mapped from disk
|
||||
mapped_file: ?struct {
|
||||
file: File,
|
||||
section_handle: windows.HANDLE,
|
||||
section_view: []const u8,
|
||||
|
||||
pub fn deinit(self: @This()) void {
|
||||
const process_handle = windows.kernel32.GetCurrentProcess();
|
||||
assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS);
|
||||
windows.CloseHandle(self.section_handle);
|
||||
self.file.close();
|
||||
}
|
||||
} = null,
|
||||
};
|
||||
|
||||
pub const DebugInfo = struct {
|
||||
@@ -1195,6 +1487,8 @@ pub const DebugInfo = struct {
|
||||
};
|
||||
|
||||
if (native_os == .windows) {
|
||||
errdefer debug_info.modules.deinit(allocator);
|
||||
|
||||
const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
|
||||
if (handle == windows.INVALID_HANDLE_VALUE) {
|
||||
switch (windows.kernel32.GetLastError()) {
|
||||
@@ -1212,9 +1506,16 @@ pub const DebugInfo = struct {
|
||||
var module_valid = true;
|
||||
while (module_valid) {
|
||||
const module_info = try debug_info.modules.addOne(allocator);
|
||||
module_info.base_address = @intFromPtr(module_entry.modBaseAddr);
|
||||
module_info.size = module_entry.modBaseSize;
|
||||
module_info.name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
|
||||
const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
|
||||
errdefer allocator.free(name);
|
||||
|
||||
module_info.* = .{
|
||||
.base_address = @intFromPtr(module_entry.modBaseAddr),
|
||||
.size = module_entry.modBaseSize,
|
||||
.name = name,
|
||||
.handle = module_entry.hModule,
|
||||
};
|
||||
|
||||
module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
|
||||
}
|
||||
}
|
||||
@@ -1233,6 +1534,7 @@ pub const DebugInfo = struct {
|
||||
if (native_os == .windows) {
|
||||
for (self.modules.items) |module| {
|
||||
self.allocator.free(module.name);
|
||||
if (module.mapped_file) |mapped_file| mapped_file.deinit();
|
||||
}
|
||||
self.modules.deinit(self.allocator);
|
||||
}
|
||||
@@ -1252,9 +1554,12 @@ pub const DebugInfo = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the module name for a given address.
|
||||
// This can be called when getModuleForAddress fails, so implementations should provide
|
||||
// a path that doesn't rely on any side-effects of a prior successful module lookup.
|
||||
pub fn getModuleNameForAddress(self: *DebugInfo, address: usize) ?[]const u8 {
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
return null;
|
||||
return self.lookupModuleNameDyld(address);
|
||||
} else if (native_os == .windows) {
|
||||
return self.lookupModuleNameWin32(address);
|
||||
} else if (native_os == .haiku) {
|
||||
@@ -1262,7 +1567,7 @@ pub const DebugInfo = struct {
|
||||
} else if (comptime builtin.target.isWasm()) {
|
||||
return null;
|
||||
} else {
|
||||
return null;
|
||||
return self.lookupModuleNameDl(address);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1271,11 +1576,10 @@ pub const DebugInfo = struct {
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < image_count) : (i += 1) {
|
||||
const base_address = std.c._dyld_get_image_vmaddr_slide(i);
|
||||
|
||||
if (address < base_address) continue;
|
||||
|
||||
const header = std.c._dyld_get_image_header(i) orelse continue;
|
||||
const base_address = @intFromPtr(header);
|
||||
if (address < base_address) continue;
|
||||
const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
|
||||
|
||||
var it = macho.LoadCommandIterator{
|
||||
.ncmds = header.ncmds,
|
||||
@@ -1284,18 +1588,29 @@ pub const DebugInfo = struct {
|
||||
@ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
|
||||
)[0..header.sizeofcmds]),
|
||||
};
|
||||
|
||||
var unwind_info: ?[]const u8 = null;
|
||||
var eh_frame: ?[]const u8 = null;
|
||||
while (it.next()) |cmd| switch (cmd.cmd()) {
|
||||
.SEGMENT_64 => {
|
||||
const segment_cmd = cmd.cast(macho.segment_command_64).?;
|
||||
const rebased_address = address - base_address;
|
||||
const seg_start = segment_cmd.vmaddr;
|
||||
const seg_end = seg_start + segment_cmd.vmsize;
|
||||
if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
|
||||
|
||||
if (rebased_address >= seg_start and rebased_address < seg_end) {
|
||||
const seg_start = segment_cmd.vmaddr + vmaddr_slide;
|
||||
const seg_end = seg_start + segment_cmd.vmsize;
|
||||
if (address >= seg_start and address < seg_end) {
|
||||
if (self.address_map.get(base_address)) |obj_di| {
|
||||
return obj_di;
|
||||
}
|
||||
|
||||
for (cmd.getSections()) |sect| {
|
||||
if (mem.eql(u8, "__unwind_info", sect.sectName())) {
|
||||
unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
|
||||
} else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
|
||||
eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
|
||||
}
|
||||
}
|
||||
|
||||
const obj_di = try self.allocator.create(ModuleDebugInfo);
|
||||
errdefer self.allocator.destroy(obj_di);
|
||||
|
||||
@@ -1308,6 +1623,9 @@ pub const DebugInfo = struct {
|
||||
};
|
||||
obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
|
||||
obj_di.base_address = base_address;
|
||||
obj_di.vmaddr_slide = vmaddr_slide;
|
||||
obj_di.unwind_info = unwind_info;
|
||||
obj_di.eh_frame = eh_frame;
|
||||
|
||||
try self.address_map.putNoClobber(base_address, obj_di);
|
||||
|
||||
@@ -1321,18 +1639,124 @@ pub const DebugInfo = struct {
|
||||
return error.MissingDebugInfo;
|
||||
}
|
||||
|
||||
fn lookupModuleNameDyld(self: *DebugInfo, address: usize) ?[]const u8 {
|
||||
_ = self;
|
||||
const image_count = std.c._dyld_image_count();
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < image_count) : (i += 1) {
|
||||
const header = std.c._dyld_get_image_header(i) orelse continue;
|
||||
const base_address = @intFromPtr(header);
|
||||
if (address < base_address) continue;
|
||||
const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
|
||||
|
||||
var it = macho.LoadCommandIterator{
|
||||
.ncmds = header.ncmds,
|
||||
.buffer = @alignCast(@as(
|
||||
[*]u8,
|
||||
@ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
|
||||
)[0..header.sizeofcmds]),
|
||||
};
|
||||
|
||||
while (it.next()) |cmd| switch (cmd.cmd()) {
|
||||
.SEGMENT_64 => {
|
||||
const segment_cmd = cmd.cast(macho.segment_command_64).?;
|
||||
if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
|
||||
|
||||
const original_address = address - vmaddr_slide;
|
||||
const seg_start = segment_cmd.vmaddr;
|
||||
const seg_end = seg_start + segment_cmd.vmsize;
|
||||
if (original_address >= seg_start and original_address < seg_end) {
|
||||
return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
|
||||
for (self.modules.items) |module| {
|
||||
for (self.modules.items) |*module| {
|
||||
if (address >= module.base_address and address < module.base_address + module.size) {
|
||||
if (self.address_map.get(module.base_address)) |obj_di| {
|
||||
return obj_di;
|
||||
}
|
||||
|
||||
const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
|
||||
const obj_di = try self.allocator.create(ModuleDebugInfo);
|
||||
errdefer self.allocator.destroy(obj_di);
|
||||
|
||||
obj_di.* = try readCoffDebugInfo(self.allocator, mapped_module);
|
||||
const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
|
||||
var coff_obj = try coff.Coff.init(mapped_module);
|
||||
|
||||
// The string table is not mapped into memory by the loader, so if a section name is in the
|
||||
// string table then we have to map the full image file from disk. This can happen when
|
||||
// a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
|
||||
if (coff_obj.strtabRequired()) {
|
||||
var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
|
||||
// openFileAbsoluteW requires the prefix to be present
|
||||
mem.copy(u16, name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
|
||||
|
||||
const process_handle = windows.kernel32.GetCurrentProcess();
|
||||
const len = windows.kernel32.K32GetModuleFileNameExW(
|
||||
process_handle,
|
||||
module.handle,
|
||||
@ptrCast(&name_buffer[4]),
|
||||
windows.PATH_MAX_WIDE,
|
||||
);
|
||||
|
||||
if (len == 0) return error.MissingDebugInfo;
|
||||
const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
|
||||
error.FileNotFound => return error.MissingDebugInfo,
|
||||
else => return err,
|
||||
};
|
||||
errdefer coff_file.close();
|
||||
|
||||
var section_handle: windows.HANDLE = undefined;
|
||||
const create_section_rc = windows.ntdll.NtCreateSection(
|
||||
§ion_handle,
|
||||
windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
|
||||
null,
|
||||
null,
|
||||
windows.PAGE_READONLY,
|
||||
// The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
|
||||
// In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
|
||||
windows.SEC_COMMIT,
|
||||
coff_file.handle,
|
||||
);
|
||||
if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
||||
errdefer windows.CloseHandle(section_handle);
|
||||
|
||||
var coff_len: usize = 0;
|
||||
var base_ptr: usize = 0;
|
||||
const map_section_rc = windows.ntdll.NtMapViewOfSection(
|
||||
section_handle,
|
||||
process_handle,
|
||||
@ptrCast(&base_ptr),
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
&coff_len,
|
||||
.ViewUnmap,
|
||||
0,
|
||||
windows.PAGE_READONLY,
|
||||
);
|
||||
if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
||||
errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
|
||||
|
||||
const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
|
||||
coff_obj = try coff.Coff.init(section_view);
|
||||
|
||||
module.mapped_file = .{
|
||||
.file = coff_file,
|
||||
.section_handle = section_handle,
|
||||
.section_view = section_view,
|
||||
};
|
||||
}
|
||||
errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
|
||||
|
||||
obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
|
||||
obj_di.base_address = module.base_address;
|
||||
|
||||
try self.address_map.putNoClobber(module.base_address, obj_di);
|
||||
@@ -1352,6 +1776,44 @@ pub const DebugInfo = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn lookupModuleNameDl(self: *DebugInfo, address: usize) ?[]const u8 {
|
||||
_ = self;
|
||||
|
||||
var ctx: struct {
|
||||
// Input
|
||||
address: usize,
|
||||
// Output
|
||||
name: []const u8 = "",
|
||||
} = .{ .address = address };
|
||||
const CtxTy = @TypeOf(ctx);
|
||||
|
||||
if (os.dl_iterate_phdr(&ctx, error{Found}, struct {
|
||||
fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void {
|
||||
_ = size;
|
||||
if (context.address < info.dlpi_addr) return;
|
||||
const phdrs = info.dlpi_phdr[0..info.dlpi_phnum];
|
||||
for (phdrs) |*phdr| {
|
||||
if (phdr.p_type != elf.PT_LOAD) continue;
|
||||
|
||||
const seg_start = info.dlpi_addr +% phdr.p_vaddr;
|
||||
const seg_end = seg_start + phdr.p_memsz;
|
||||
if (context.address >= seg_start and context.address < seg_end) {
|
||||
context.name = mem.sliceTo(info.dlpi_name, 0) orelse "";
|
||||
break;
|
||||
}
|
||||
} else return;
|
||||
|
||||
return error.Found;
|
||||
}
|
||||
}.callback)) {
|
||||
return null;
|
||||
} else |err| switch (err) {
|
||||
error.Found => return fs.path.basename(ctx.name),
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
|
||||
var ctx: struct {
|
||||
// Input
|
||||
@@ -1359,6 +1821,8 @@ pub const DebugInfo = struct {
|
||||
// Output
|
||||
base_address: usize = undefined,
|
||||
name: []const u8 = undefined,
|
||||
build_id: ?[]const u8 = null,
|
||||
gnu_eh_frame: ?[]const u8 = null,
|
||||
} = .{ .address = address };
|
||||
const CtxTy = @TypeOf(ctx);
|
||||
|
||||
@@ -1373,18 +1837,40 @@ pub const DebugInfo = struct {
|
||||
for (phdrs) |*phdr| {
|
||||
if (phdr.p_type != elf.PT_LOAD) continue;
|
||||
|
||||
const seg_start = info.dlpi_addr + phdr.p_vaddr;
|
||||
// Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
|
||||
const seg_start = info.dlpi_addr +% phdr.p_vaddr;
|
||||
const seg_end = seg_start + phdr.p_memsz;
|
||||
|
||||
if (context.address >= seg_start and context.address < seg_end) {
|
||||
// Android libc uses NULL instead of an empty string to mark the
|
||||
// main program
|
||||
context.name = mem.sliceTo(info.dlpi_name, 0) orelse "";
|
||||
context.base_address = info.dlpi_addr;
|
||||
// Stop the iteration
|
||||
return error.Found;
|
||||
break;
|
||||
}
|
||||
} else return;
|
||||
|
||||
for (info.dlpi_phdr[0..info.dlpi_phnum]) |phdr| {
|
||||
switch (phdr.p_type) {
|
||||
elf.PT_NOTE => {
|
||||
// Look for .note.gnu.build-id
|
||||
const note_bytes = @as([*]const u8, @ptrFromInt(info.dlpi_addr + phdr.p_vaddr))[0..phdr.p_memsz];
|
||||
const name_size = mem.readIntSliceNative(u32, note_bytes[0..4]);
|
||||
if (name_size != 4) continue;
|
||||
const desc_size = mem.readIntSliceNative(u32, note_bytes[4..8]);
|
||||
const note_type = mem.readIntSliceNative(u32, note_bytes[8..12]);
|
||||
if (note_type != elf.NT_GNU_BUILD_ID) continue;
|
||||
if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
|
||||
context.build_id = note_bytes[16..][0..desc_size];
|
||||
},
|
||||
elf.PT_GNU_EH_FRAME => {
|
||||
context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.dlpi_addr + phdr.p_vaddr))[0..phdr.p_memsz];
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the iteration
|
||||
return error.Found;
|
||||
}
|
||||
}.callback)) {
|
||||
return error.MissingDebugInfo;
|
||||
@@ -1399,20 +1885,24 @@ pub const DebugInfo = struct {
|
||||
const obj_di = try self.allocator.create(ModuleDebugInfo);
|
||||
errdefer self.allocator.destroy(obj_di);
|
||||
|
||||
// TODO https://github.com/ziglang/zig/issues/5525
|
||||
const copy = if (ctx.name.len > 0)
|
||||
fs.cwd().openFile(ctx.name, .{ .intended_io_mode = .blocking })
|
||||
else
|
||||
fs.openSelfExe(.{ .intended_io_mode = .blocking });
|
||||
var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
|
||||
if (ctx.gnu_eh_frame) |eh_frame_hdr| {
|
||||
// This is a special case - pointer offsets inside .eh_frame_hdr
|
||||
// are encoded relative to its base address, so we must use the
|
||||
// version that is already memory mapped, and not the one that
|
||||
// will be mapped separately from the ELF file.
|
||||
sections[@intFromEnum(DW.DwarfSection.eh_frame_hdr)] = .{
|
||||
.data = eh_frame_hdr,
|
||||
.owned = false,
|
||||
};
|
||||
}
|
||||
|
||||
const elf_file = copy catch |err| switch (err) {
|
||||
error.FileNotFound => return error.MissingDebugInfo,
|
||||
else => return err,
|
||||
};
|
||||
|
||||
obj_di.* = try readElfDebugInfo(self.allocator, elf_file);
|
||||
obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, §ions, null);
|
||||
obj_di.base_address = ctx.base_address;
|
||||
|
||||
// Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
|
||||
obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
|
||||
|
||||
try self.address_map.putNoClobber(ctx.base_address, obj_di);
|
||||
|
||||
return obj_di;
|
||||
@@ -1434,11 +1924,16 @@ pub const DebugInfo = struct {
|
||||
pub const ModuleDebugInfo = switch (native_os) {
|
||||
.macos, .ios, .watchos, .tvos => struct {
|
||||
base_address: usize,
|
||||
vmaddr_slide: usize,
|
||||
mapped_memory: []align(mem.page_size) const u8,
|
||||
symbols: []const MachoSymbol,
|
||||
strings: [:0]const u8,
|
||||
ofiles: OFileTable,
|
||||
|
||||
// Backed by the in-memory sections mapped by the loader
|
||||
unwind_info: ?[]const u8 = null,
|
||||
eh_frame: ?[]const u8 = null,
|
||||
|
||||
const OFileTable = std.StringHashMap(OFileInfo);
|
||||
const OFileInfo = struct {
|
||||
di: DW.DwarfInfo,
|
||||
@@ -1457,7 +1952,7 @@ pub const ModuleDebugInfo = switch (native_os) {
|
||||
os.munmap(self.mapped_memory);
|
||||
}
|
||||
|
||||
fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !OFileInfo {
|
||||
fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo {
|
||||
const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking });
|
||||
const mapped_mem = try mapWholeFile(o_file);
|
||||
|
||||
@@ -1500,95 +1995,40 @@ pub const ModuleDebugInfo = switch (native_os) {
|
||||
addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
|
||||
}
|
||||
|
||||
var opt_debug_line: ?macho.section_64 = null;
|
||||
var opt_debug_info: ?macho.section_64 = null;
|
||||
var opt_debug_abbrev: ?macho.section_64 = null;
|
||||
var opt_debug_str: ?macho.section_64 = null;
|
||||
var opt_debug_str_offsets: ?macho.section_64 = null;
|
||||
var opt_debug_line_str: ?macho.section_64 = null;
|
||||
var opt_debug_ranges: ?macho.section_64 = null;
|
||||
var opt_debug_loclists: ?macho.section_64 = null;
|
||||
var opt_debug_rnglists: ?macho.section_64 = null;
|
||||
var opt_debug_addr: ?macho.section_64 = null;
|
||||
var opt_debug_names: ?macho.section_64 = null;
|
||||
var opt_debug_frame: ?macho.section_64 = null;
|
||||
var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
|
||||
if (self.eh_frame) |eh_frame| sections[@intFromEnum(DW.DwarfSection.eh_frame)] = .{
|
||||
.data = eh_frame,
|
||||
.owned = false,
|
||||
};
|
||||
|
||||
for (segcmd.?.getSections()) |sect| {
|
||||
const name = sect.sectName();
|
||||
if (mem.eql(u8, name, "__debug_line")) {
|
||||
opt_debug_line = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_info")) {
|
||||
opt_debug_info = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_abbrev")) {
|
||||
opt_debug_abbrev = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_str")) {
|
||||
opt_debug_str = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_str_offsets")) {
|
||||
opt_debug_str_offsets = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_line_str")) {
|
||||
opt_debug_line_str = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_ranges")) {
|
||||
opt_debug_ranges = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_loclists")) {
|
||||
opt_debug_loclists = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_rnglists")) {
|
||||
opt_debug_rnglists = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_addr")) {
|
||||
opt_debug_addr = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_names")) {
|
||||
opt_debug_names = sect;
|
||||
} else if (mem.eql(u8, name, "__debug_frame")) {
|
||||
opt_debug_frame = sect;
|
||||
if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
|
||||
|
||||
var section_index: ?usize = null;
|
||||
inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| {
|
||||
if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
|
||||
}
|
||||
if (section_index == null) continue;
|
||||
|
||||
const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);
|
||||
sections[section_index.?] = .{
|
||||
.data = section_bytes,
|
||||
.virtual_address = sect.addr,
|
||||
.owned = false,
|
||||
};
|
||||
}
|
||||
|
||||
const debug_line = opt_debug_line orelse
|
||||
return error.MissingDebugInfo;
|
||||
const debug_info = opt_debug_info orelse
|
||||
return error.MissingDebugInfo;
|
||||
const debug_str = opt_debug_str orelse
|
||||
return error.MissingDebugInfo;
|
||||
const debug_abbrev = opt_debug_abbrev orelse
|
||||
return error.MissingDebugInfo;
|
||||
const missing_debug_info =
|
||||
sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or
|
||||
sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or
|
||||
sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or
|
||||
sections[@intFromEnum(DW.DwarfSection.debug_line)] == null;
|
||||
if (missing_debug_info) return error.MissingDebugInfo;
|
||||
|
||||
var di = DW.DwarfInfo{
|
||||
.endian = .Little,
|
||||
.debug_info = try chopSlice(mapped_mem, debug_info.offset, debug_info.size),
|
||||
.debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.offset, debug_abbrev.size),
|
||||
.debug_str = try chopSlice(mapped_mem, debug_str.offset, debug_str.size),
|
||||
.debug_str_offsets = if (opt_debug_str_offsets) |debug_str_offsets|
|
||||
try chopSlice(mapped_mem, debug_str_offsets.offset, debug_str_offsets.size)
|
||||
else
|
||||
null,
|
||||
.debug_line = try chopSlice(mapped_mem, debug_line.offset, debug_line.size),
|
||||
.debug_line_str = if (opt_debug_line_str) |debug_line_str|
|
||||
try chopSlice(mapped_mem, debug_line_str.offset, debug_line_str.size)
|
||||
else
|
||||
null,
|
||||
.debug_ranges = if (opt_debug_ranges) |debug_ranges|
|
||||
try chopSlice(mapped_mem, debug_ranges.offset, debug_ranges.size)
|
||||
else
|
||||
null,
|
||||
.debug_loclists = if (opt_debug_loclists) |debug_loclists|
|
||||
try chopSlice(mapped_mem, debug_loclists.offset, debug_loclists.size)
|
||||
else
|
||||
null,
|
||||
.debug_rnglists = if (opt_debug_rnglists) |debug_rnglists|
|
||||
try chopSlice(mapped_mem, debug_rnglists.offset, debug_rnglists.size)
|
||||
else
|
||||
null,
|
||||
.debug_addr = if (opt_debug_addr) |debug_addr|
|
||||
try chopSlice(mapped_mem, debug_addr.offset, debug_addr.size)
|
||||
else
|
||||
null,
|
||||
.debug_names = if (opt_debug_names) |debug_names|
|
||||
try chopSlice(mapped_mem, debug_names.offset, debug_names.size)
|
||||
else
|
||||
null,
|
||||
.debug_frame = if (opt_debug_frame) |debug_frame|
|
||||
try chopSlice(mapped_mem, debug_frame.offset, debug_frame.size)
|
||||
else
|
||||
null,
|
||||
.sections = sections,
|
||||
.is_macho = true,
|
||||
};
|
||||
|
||||
try DW.openDwarfDebugInfo(&di, allocator);
|
||||
@@ -1598,52 +2038,38 @@ pub const ModuleDebugInfo = switch (native_os) {
|
||||
};
|
||||
|
||||
// Add the debug info to the cache
|
||||
try self.ofiles.putNoClobber(o_file_path, info);
|
||||
const result = try self.ofiles.getOrPut(o_file_path);
|
||||
assert(!result.found_existing);
|
||||
result.value_ptr.* = info;
|
||||
|
||||
return info;
|
||||
return result.value_ptr;
|
||||
}
|
||||
|
||||
pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
|
||||
nosuspend {
|
||||
// Translate the VA into an address into this object
|
||||
const relocated_address = address - self.base_address;
|
||||
|
||||
// Find the .o file where this symbol is defined
|
||||
const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse
|
||||
return SymbolInfo{};
|
||||
const addr_off = relocated_address - symbol.addr;
|
||||
const result = try self.getOFileInfoForAddress(allocator, address);
|
||||
if (result.symbol == null) return .{};
|
||||
|
||||
// Take the symbol name from the N_FUN STAB entry, we're going to
|
||||
// use it if we fail to find the DWARF infos
|
||||
const stab_symbol = mem.sliceTo(self.strings[symbol.strx..], 0);
|
||||
const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
|
||||
|
||||
// Check if its debug infos are already in the cache
|
||||
var o_file_info = self.ofiles.get(o_file_path) orelse
|
||||
(self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
|
||||
error.FileNotFound,
|
||||
error.MissingDebugInfo,
|
||||
error.InvalidDebugInfo,
|
||||
=> {
|
||||
return SymbolInfo{ .symbol_name = stab_symbol };
|
||||
},
|
||||
else => return err,
|
||||
});
|
||||
const o_file_di = &o_file_info.di;
|
||||
const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
|
||||
if (result.o_file_info == null) return .{ .symbol_name = stab_symbol };
|
||||
|
||||
// Translate again the address, this time into an address inside the
|
||||
// .o file
|
||||
const relocated_address_o = o_file_info.addr_table.get(stab_symbol) orelse return SymbolInfo{
|
||||
const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
|
||||
.symbol_name = "???",
|
||||
};
|
||||
|
||||
const addr_off = result.relocated_address - result.symbol.?.addr;
|
||||
const o_file_di = &result.o_file_info.?.di;
|
||||
if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
|
||||
return SymbolInfo{
|
||||
.symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
|
||||
.compile_unit_name = compile_unit.die.getAttrString(
|
||||
o_file_di,
|
||||
DW.AT.name,
|
||||
o_file_di.debug_str,
|
||||
o_file_di.section(.debug_str),
|
||||
compile_unit.*,
|
||||
) catch |err| switch (err) {
|
||||
error.MissingDebugInfo, error.InvalidDebugInfo => "???",
|
||||
@@ -1663,39 +2089,61 @@ pub const ModuleDebugInfo = switch (native_os) {
|
||||
},
|
||||
else => return err,
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getOFileInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !struct {
|
||||
relocated_address: usize,
|
||||
symbol: ?*const MachoSymbol = null,
|
||||
o_file_info: ?*OFileInfo = null,
|
||||
} {
|
||||
nosuspend {
|
||||
// Translate the VA into an address into this object
|
||||
const relocated_address = address - self.vmaddr_slide;
|
||||
|
||||
// Find the .o file where this symbol is defined
|
||||
const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
|
||||
.relocated_address = relocated_address,
|
||||
};
|
||||
|
||||
// Check if its debug infos are already in the cache
|
||||
const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
|
||||
var o_file_info = self.ofiles.getPtr(o_file_path) orelse
|
||||
(self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
|
||||
error.FileNotFound,
|
||||
error.MissingDebugInfo,
|
||||
error.InvalidDebugInfo,
|
||||
=> return .{
|
||||
.relocated_address = relocated_address,
|
||||
.symbol = symbol,
|
||||
},
|
||||
else => return err,
|
||||
});
|
||||
|
||||
return .{
|
||||
.relocated_address = relocated_address,
|
||||
.symbol = symbol,
|
||||
.o_file_info = o_file_info,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo {
|
||||
return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
|
||||
}
|
||||
},
|
||||
.uefi, .windows => struct {
|
||||
base_address: usize,
|
||||
debug_data: PdbOrDwarf,
|
||||
coff_image_base: u64,
|
||||
/// Only used if debug_data is .pdb
|
||||
coff_section_headers: []coff.SectionHeader,
|
||||
|
||||
fn deinit(self: *@This(), allocator: mem.Allocator) void {
|
||||
switch (self.debug_data) {
|
||||
.dwarf => |*dwarf| {
|
||||
allocator.free(dwarf.debug_info);
|
||||
allocator.free(dwarf.debug_abbrev);
|
||||
allocator.free(dwarf.debug_str);
|
||||
allocator.free(dwarf.debug_line);
|
||||
if (dwarf.debug_str_offsets) |d| allocator.free(d);
|
||||
if (dwarf.debug_line_str) |d| allocator.free(d);
|
||||
if (dwarf.debug_ranges) |d| allocator.free(d);
|
||||
if (dwarf.debug_loclists) |d| allocator.free(d);
|
||||
if (dwarf.debug_rnglists) |d| allocator.free(d);
|
||||
if (dwarf.debug_addr) |d| allocator.free(d);
|
||||
if (dwarf.debug_names) |d| allocator.free(d);
|
||||
if (dwarf.debug_frame) |d| allocator.free(d);
|
||||
},
|
||||
.pdb => {
|
||||
allocator.free(self.coff_section_headers);
|
||||
},
|
||||
}
|
||||
|
||||
self.debug_data.deinit(allocator);
|
||||
if (self.debug_data == .pdb) {
|
||||
allocator.free(self.coff_section_headers);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
|
||||
@@ -1747,15 +2195,27 @@ pub const ModuleDebugInfo = switch (native_os) {
|
||||
.line_info = opt_line_info,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo {
|
||||
_ = allocator;
|
||||
_ = address;
|
||||
|
||||
return switch (self.debug_data) {
|
||||
.dwarf => |*dwarf| dwarf,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
},
|
||||
.linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris => struct {
|
||||
base_address: usize,
|
||||
dwarf: DW.DwarfInfo,
|
||||
mapped_memory: []align(mem.page_size) const u8,
|
||||
external_mapped_memory: ?[]align(mem.page_size) const u8,
|
||||
|
||||
fn deinit(self: *@This(), allocator: mem.Allocator) void {
|
||||
self.dwarf.deinit(allocator);
|
||||
os.munmap(self.mapped_memory);
|
||||
if (self.external_mapped_memory) |m| os.munmap(m);
|
||||
}
|
||||
|
||||
pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
|
||||
@@ -1763,6 +2223,12 @@ pub const ModuleDebugInfo = switch (native_os) {
|
||||
const relocated_address = address - self.base_address;
|
||||
return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
|
||||
}
|
||||
|
||||
pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo {
|
||||
_ = allocator;
|
||||
_ = address;
|
||||
return &self.dwarf;
|
||||
}
|
||||
},
|
||||
.wasi => struct {
|
||||
fn deinit(self: *@This(), allocator: mem.Allocator) void {
|
||||
@@ -1776,6 +2242,13 @@ pub const ModuleDebugInfo = switch (native_os) {
|
||||
_ = address;
|
||||
return SymbolInfo{};
|
||||
}
|
||||
|
||||
pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo {
|
||||
_ = self;
|
||||
_ = allocator;
|
||||
_ = address;
|
||||
return null;
|
||||
}
|
||||
},
|
||||
else => DW.DwarfInfo,
|
||||
};
|
||||
@@ -1784,7 +2257,7 @@ fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *DW.DwarfInfo)
|
||||
if (nosuspend di.findCompileUnit(address)) |compile_unit| {
|
||||
return SymbolInfo{
|
||||
.symbol_name = nosuspend di.getSymbolName(address) orelse "???",
|
||||
.compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.debug_str, compile_unit.*) catch |err| switch (err) {
|
||||
.compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
|
||||
error.MissingDebugInfo, error.InvalidDebugInfo => "???",
|
||||
},
|
||||
.line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
|
||||
@@ -1932,52 +2405,13 @@ fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void
|
||||
} catch os.abort();
|
||||
|
||||
switch (native_arch) {
|
||||
.x86 => {
|
||||
.x86,
|
||||
.x86_64,
|
||||
.arm,
|
||||
.aarch64,
|
||||
=> {
|
||||
const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||
const ip = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EIP]));
|
||||
const bp = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EBP]));
|
||||
dumpStackTraceFromBase(bp, ip);
|
||||
},
|
||||
.x86_64 => {
|
||||
const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||
const ip = switch (native_os) {
|
||||
.linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RIP])),
|
||||
.freebsd => @as(usize, @intCast(ctx.mcontext.rip)),
|
||||
.openbsd => @as(usize, @intCast(ctx.sc_rip)),
|
||||
.macos => @as(usize, @intCast(ctx.mcontext.ss.rip)),
|
||||
else => unreachable,
|
||||
};
|
||||
const bp = switch (native_os) {
|
||||
.linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RBP])),
|
||||
.openbsd => @as(usize, @intCast(ctx.sc_rbp)),
|
||||
.freebsd => @as(usize, @intCast(ctx.mcontext.rbp)),
|
||||
.macos => @as(usize, @intCast(ctx.mcontext.ss.rbp)),
|
||||
else => unreachable,
|
||||
};
|
||||
dumpStackTraceFromBase(bp, ip);
|
||||
},
|
||||
.arm => {
|
||||
const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||
const ip = @as(usize, @intCast(ctx.mcontext.arm_pc));
|
||||
const bp = @as(usize, @intCast(ctx.mcontext.arm_fp));
|
||||
dumpStackTraceFromBase(bp, ip);
|
||||
},
|
||||
.aarch64 => {
|
||||
const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||
const ip = switch (native_os) {
|
||||
.macos => @as(usize, @intCast(ctx.mcontext.ss.pc)),
|
||||
.netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.PC])),
|
||||
.freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.elr)),
|
||||
else => @as(usize, @intCast(ctx.mcontext.pc)),
|
||||
};
|
||||
// x29 is the ABI-designated frame pointer
|
||||
const bp = switch (native_os) {
|
||||
.macos => @as(usize, @intCast(ctx.mcontext.ss.fp)),
|
||||
.netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.FP])),
|
||||
.freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.x[os.REG.FP])),
|
||||
else => @as(usize, @intCast(ctx.mcontext.regs[29])),
|
||||
};
|
||||
dumpStackTraceFromBase(bp, ip);
|
||||
dumpStackTraceFromBase(ctx);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@@ -2036,16 +2470,15 @@ fn handleSegfaultWindowsExtra(
|
||||
}
|
||||
|
||||
fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void {
|
||||
const regs = info.ContextRecord.getRegs();
|
||||
const stderr = io.getStdErr().writer();
|
||||
_ = switch (msg) {
|
||||
0 => stderr.print("{s}\n", .{label.?}),
|
||||
1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
|
||||
2 => stderr.print("Illegal instruction at address 0x{x}\n", .{regs.ip}),
|
||||
2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}),
|
||||
else => unreachable,
|
||||
} catch os.abort();
|
||||
|
||||
dumpStackTraceFromBase(regs.bp, regs.ip);
|
||||
dumpStackTraceFromBase(info.ContextRecord);
|
||||
}
|
||||
|
||||
pub fn dumpStackPointerAddr(prefix: []const u8) void {
|
||||
|
||||
+1462
-116
@@ -3,9 +3,11 @@ const std = @import("std.zig");
|
||||
const debug = std.debug;
|
||||
const fs = std.fs;
|
||||
const io = std.io;
|
||||
const os = std.os;
|
||||
const mem = std.mem;
|
||||
const math = std.math;
|
||||
const leb = @import("leb128.zig");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub const TAG = @import("dwarf/TAG.zig");
|
||||
pub const AT = @import("dwarf/AT.zig");
|
||||
@@ -13,6 +15,10 @@ pub const OP = @import("dwarf/OP.zig");
|
||||
pub const LANG = @import("dwarf/LANG.zig");
|
||||
pub const FORM = @import("dwarf/FORM.zig");
|
||||
pub const ATE = @import("dwarf/ATE.zig");
|
||||
pub const EH = @import("dwarf/EH.zig");
|
||||
pub const abi = @import("dwarf/abi.zig");
|
||||
pub const call_frame = @import("dwarf/call_frame.zig");
|
||||
pub const expressions = @import("dwarf/expressions.zig");
|
||||
|
||||
pub const LLE = struct {
|
||||
pub const end_of_list = 0x00;
|
||||
@@ -140,11 +146,11 @@ pub const CC = enum(u8) {
|
||||
pass_by_reference = 0x4,
|
||||
pass_by_value = 0x5,
|
||||
|
||||
lo_user = 0x40,
|
||||
hi_user = 0xff,
|
||||
|
||||
GNU_renesas_sh = 0x40,
|
||||
GNU_borland_fastcall_i386 = 0x41,
|
||||
|
||||
pub const lo_user = 0x40;
|
||||
pub const hi_user = 0xff;
|
||||
};
|
||||
|
||||
pub const Format = enum { @"32", @"64" };
|
||||
@@ -157,15 +163,9 @@ const PcRange = struct {
|
||||
const Func = struct {
|
||||
pc_range: ?PcRange,
|
||||
name: ?[]const u8,
|
||||
|
||||
fn deinit(func: *Func, allocator: mem.Allocator) void {
|
||||
if (func.name) |name| {
|
||||
allocator.free(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const CompileUnit = struct {
|
||||
pub const CompileUnit = struct {
|
||||
version: u16,
|
||||
is_64: bool,
|
||||
die: *Die,
|
||||
@@ -175,6 +175,7 @@ const CompileUnit = struct {
|
||||
addr_base: usize,
|
||||
rnglists_base: usize,
|
||||
loclists_base: usize,
|
||||
frame_base: ?*const FormValue,
|
||||
};
|
||||
|
||||
const AbbrevTable = std.ArrayList(AbbrevTableEntry);
|
||||
@@ -210,7 +211,7 @@ const AbbrevAttr = struct {
|
||||
payload: i64,
|
||||
};
|
||||
|
||||
const FormValue = union(enum) {
|
||||
pub const FormValue = union(enum) {
|
||||
Address: u64,
|
||||
AddrOffset: usize,
|
||||
Block: []u8,
|
||||
@@ -292,7 +293,7 @@ const Die = struct {
|
||||
|
||||
fn getAttrAddr(
|
||||
self: *const Die,
|
||||
di: *DwarfInfo,
|
||||
di: *const DwarfInfo,
|
||||
id: u64,
|
||||
compile_unit: CompileUnit,
|
||||
) error{ InvalidDebugInfo, MissingDebugInfo }!u64 {
|
||||
@@ -337,7 +338,7 @@ const Die = struct {
|
||||
FormValue.String => |value| return value,
|
||||
FormValue.StrPtr => |offset| return di.getString(offset),
|
||||
FormValue.StrOffset => |index| {
|
||||
const debug_str_offsets = di.debug_str_offsets orelse return badDwarf();
|
||||
const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf();
|
||||
if (compile_unit.str_offsets_base == 0) return badDwarf();
|
||||
if (compile_unit.is_64) {
|
||||
const byte_offset = compile_unit.str_offsets_base + 8 * index;
|
||||
@@ -642,27 +643,75 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con
|
||||
return null;
|
||||
}
|
||||
|
||||
pub const DwarfSection = enum {
|
||||
debug_info,
|
||||
debug_abbrev,
|
||||
debug_str,
|
||||
debug_str_offsets,
|
||||
debug_line,
|
||||
debug_line_str,
|
||||
debug_ranges,
|
||||
debug_loclists,
|
||||
debug_rnglists,
|
||||
debug_addr,
|
||||
debug_names,
|
||||
debug_frame,
|
||||
eh_frame,
|
||||
eh_frame_hdr,
|
||||
};
|
||||
|
||||
pub const DwarfInfo = struct {
|
||||
pub const Section = struct {
|
||||
data: []const u8,
|
||||
// Module-relative virtual address.
|
||||
// Only set if the section data was loaded from disk.
|
||||
virtual_address: ?usize = null,
|
||||
// If `data` is owned by this DwarfInfo.
|
||||
owned: bool,
|
||||
|
||||
// For sections that are not memory mapped by the loader, this is an offset
|
||||
// from `data.ptr` to where the section would have been mapped. Otherwise,
|
||||
// `data` is directly backed by the section and the offset is zero.
|
||||
pub fn virtualOffset(self: Section, base_address: usize) i64 {
|
||||
return if (self.virtual_address) |va|
|
||||
@as(i64, @intCast(base_address + va)) -
|
||||
@as(i64, @intCast(@intFromPtr(self.data.ptr)))
|
||||
else
|
||||
0;
|
||||
}
|
||||
};
|
||||
|
||||
const num_sections = std.enums.directEnumArrayLen(DwarfSection, 0);
|
||||
pub const SectionArray = [num_sections]?Section;
|
||||
pub const null_section_array = [_]?Section{null} ** num_sections;
|
||||
|
||||
endian: std.builtin.Endian,
|
||||
// No memory is owned by the DwarfInfo
|
||||
debug_info: []const u8,
|
||||
debug_abbrev: []const u8,
|
||||
debug_str: []const u8,
|
||||
debug_str_offsets: ?[]const u8,
|
||||
debug_line: []const u8,
|
||||
debug_line_str: ?[]const u8,
|
||||
debug_ranges: ?[]const u8,
|
||||
debug_loclists: ?[]const u8,
|
||||
debug_rnglists: ?[]const u8,
|
||||
debug_addr: ?[]const u8,
|
||||
debug_names: ?[]const u8,
|
||||
debug_frame: ?[]const u8,
|
||||
sections: SectionArray = null_section_array,
|
||||
is_macho: bool,
|
||||
|
||||
// Filled later by the initializer
|
||||
abbrev_table_list: std.ArrayListUnmanaged(AbbrevTableHeader) = .{},
|
||||
compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{},
|
||||
func_list: std.ArrayListUnmanaged(Func) = .{},
|
||||
|
||||
eh_frame_hdr: ?ExceptionFrameHeader = null,
|
||||
// These lookup tables are only used if `eh_frame_hdr` is null
|
||||
cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{},
|
||||
// Sorted by start_pc
|
||||
fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{},
|
||||
|
||||
pub fn section(di: DwarfInfo, dwarf_section: DwarfSection) ?[]const u8 {
|
||||
return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
|
||||
}
|
||||
|
||||
pub fn sectionVirtualOffset(di: DwarfInfo, dwarf_section: DwarfSection, base_address: usize) ?i64 {
|
||||
return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null;
|
||||
}
|
||||
|
||||
pub fn deinit(di: *DwarfInfo, allocator: mem.Allocator) void {
|
||||
for (di.sections) |opt_section| {
|
||||
if (opt_section) |s| if (s.owned) allocator.free(s.data);
|
||||
}
|
||||
for (di.abbrev_table_list.items) |*abbrev| {
|
||||
abbrev.deinit();
|
||||
}
|
||||
@@ -672,10 +721,9 @@ pub const DwarfInfo = struct {
|
||||
allocator.destroy(cu.die);
|
||||
}
|
||||
di.compile_unit_list.deinit(allocator);
|
||||
for (di.func_list.items) |*func| {
|
||||
func.deinit(allocator);
|
||||
}
|
||||
di.func_list.deinit(allocator);
|
||||
di.cie_map.deinit(allocator);
|
||||
di.fde_list.deinit(allocator);
|
||||
}
|
||||
|
||||
pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 {
|
||||
@@ -691,7 +739,7 @@ pub const DwarfInfo = struct {
|
||||
}
|
||||
|
||||
fn scanAllFunctions(di: *DwarfInfo, allocator: mem.Allocator) !void {
|
||||
var stream = io.fixedBufferStream(di.debug_info);
|
||||
var stream = io.fixedBufferStream(di.section(.debug_info).?);
|
||||
const in = stream.reader();
|
||||
const seekable = &stream.seekableStream();
|
||||
var this_unit_offset: u64 = 0;
|
||||
@@ -755,6 +803,7 @@ pub const DwarfInfo = struct {
|
||||
.addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0,
|
||||
.rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0,
|
||||
.loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0,
|
||||
.frame_base = die_obj.getAttr(AT.frame_base),
|
||||
};
|
||||
},
|
||||
TAG.subprogram, TAG.inlined_subroutine, TAG.subroutine, TAG.entry_point => {
|
||||
@@ -764,8 +813,7 @@ pub const DwarfInfo = struct {
|
||||
// Prevent endless loops
|
||||
while (depth > 0) : (depth -= 1) {
|
||||
if (this_die_obj.getAttr(AT.name)) |_| {
|
||||
const name = try this_die_obj.getAttrString(di, AT.name, di.debug_str, compile_unit);
|
||||
break :x try allocator.dupe(u8, name);
|
||||
break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit);
|
||||
} else if (this_die_obj.getAttr(AT.abstract_origin)) |_| {
|
||||
// Follow the DIE it points to and repeat
|
||||
const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin);
|
||||
@@ -796,34 +844,58 @@ pub const DwarfInfo = struct {
|
||||
break :x null;
|
||||
};
|
||||
|
||||
const pc_range = x: {
|
||||
if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| {
|
||||
if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
|
||||
const pc_end = switch (high_pc_value.*) {
|
||||
FormValue.Address => |value| value,
|
||||
FormValue.Const => |value| b: {
|
||||
const offset = try value.asUnsignedLe();
|
||||
break :b (low_pc + offset);
|
||||
},
|
||||
else => return badDwarf(),
|
||||
};
|
||||
break :x PcRange{
|
||||
var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: {
|
||||
if (die_obj.getAttr(AT.high_pc)) |high_pc_value| {
|
||||
const pc_end = switch (high_pc_value.*) {
|
||||
FormValue.Address => |value| value,
|
||||
FormValue.Const => |value| b: {
|
||||
const offset = try value.asUnsignedLe();
|
||||
break :b (low_pc + offset);
|
||||
},
|
||||
else => return badDwarf(),
|
||||
};
|
||||
|
||||
try di.func_list.append(allocator, Func{
|
||||
.name = fn_name,
|
||||
.pc_range = .{
|
||||
.start = low_pc,
|
||||
.end = pc_end,
|
||||
};
|
||||
} else {
|
||||
break :x null;
|
||||
}
|
||||
} else |err| {
|
||||
if (err != error.MissingDebugInfo) return err;
|
||||
break :x null;
|
||||
},
|
||||
});
|
||||
|
||||
break :blk true;
|
||||
}
|
||||
|
||||
break :blk false;
|
||||
} else |err| blk: {
|
||||
if (err != error.MissingDebugInfo) return err;
|
||||
break :blk false;
|
||||
};
|
||||
|
||||
try di.func_list.append(allocator, Func{
|
||||
.name = fn_name,
|
||||
.pc_range = pc_range,
|
||||
});
|
||||
if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: {
|
||||
var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| {
|
||||
if (err != error.MissingDebugInfo) return err;
|
||||
break :blk;
|
||||
};
|
||||
|
||||
while (try iter.next()) |range| {
|
||||
range_added = true;
|
||||
try di.func_list.append(allocator, Func{
|
||||
.name = fn_name,
|
||||
.pc_range = .{
|
||||
.start = range.start_addr,
|
||||
.end = range.end_addr,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (fn_name != null and !range_added) {
|
||||
try di.func_list.append(allocator, Func{
|
||||
.name = fn_name,
|
||||
.pc_range = null,
|
||||
});
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
@@ -836,7 +908,7 @@ pub const DwarfInfo = struct {
|
||||
}
|
||||
|
||||
fn scanAllCompileUnits(di: *DwarfInfo, allocator: mem.Allocator) !void {
|
||||
var stream = io.fixedBufferStream(di.debug_info);
|
||||
var stream = io.fixedBufferStream(di.section(.debug_info).?);
|
||||
const in = &stream.reader();
|
||||
const seekable = &stream.seekableStream();
|
||||
var this_unit_offset: u64 = 0;
|
||||
@@ -892,6 +964,7 @@ pub const DwarfInfo = struct {
|
||||
.addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0,
|
||||
.rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0,
|
||||
.loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0,
|
||||
.frame_base = compile_unit_die.getAttr(AT.frame_base),
|
||||
};
|
||||
|
||||
compile_unit.pc_range = x: {
|
||||
@@ -924,17 +997,18 @@ pub const DwarfInfo = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit {
|
||||
for (di.compile_unit_list.items) |*compile_unit| {
|
||||
if (compile_unit.pc_range) |range| {
|
||||
if (target_address >= range.start and target_address < range.end) return compile_unit;
|
||||
}
|
||||
const DebugRangeIterator = struct {
|
||||
base_address: u64,
|
||||
section_type: DwarfSection,
|
||||
di: *const DwarfInfo,
|
||||
compile_unit: *const CompileUnit,
|
||||
stream: io.FixedBufferStream([]const u8),
|
||||
|
||||
const opt_debug_ranges = if (compile_unit.version >= 5) di.debug_rnglists else di.debug_ranges;
|
||||
const debug_ranges = opt_debug_ranges orelse continue;
|
||||
pub fn init(ranges_value: *const FormValue, di: *const DwarfInfo, compile_unit: *const CompileUnit) !@This() {
|
||||
const section_type = if (compile_unit.version >= 5) DwarfSection.debug_rnglists else DwarfSection.debug_ranges;
|
||||
const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo;
|
||||
|
||||
const ranges_val = compile_unit.die.getAttr(AT.ranges) orelse continue;
|
||||
const ranges_offset = switch (ranges_val.*) {
|
||||
const ranges_offset = switch (ranges_value.*) {
|
||||
.SecOffset => |off| off,
|
||||
.Const => |c| try c.asUnsignedLe(),
|
||||
.RangeListOffset => |idx| off: {
|
||||
@@ -954,8 +1028,7 @@ pub const DwarfInfo = struct {
|
||||
};
|
||||
|
||||
var stream = io.fixedBufferStream(debug_ranges);
|
||||
const in = &stream.reader();
|
||||
const seekable = &stream.seekableStream();
|
||||
try stream.seekTo(ranges_offset);
|
||||
|
||||
// All the addresses in the list are relative to the value
|
||||
// specified by DW_AT.low_pc or to some other value encoded
|
||||
@@ -966,86 +1039,122 @@ pub const DwarfInfo = struct {
|
||||
else => return err,
|
||||
};
|
||||
|
||||
try seekable.seekTo(ranges_offset);
|
||||
return .{
|
||||
.base_address = base_address,
|
||||
.section_type = section_type,
|
||||
.di = di,
|
||||
.compile_unit = compile_unit,
|
||||
.stream = stream,
|
||||
};
|
||||
}
|
||||
|
||||
if (compile_unit.version >= 5) {
|
||||
while (true) {
|
||||
// Returns the next range in the list, or null if the end was reached.
|
||||
pub fn next(self: *@This()) !?struct { start_addr: u64, end_addr: u64 } {
|
||||
const in = self.stream.reader();
|
||||
switch (self.section_type) {
|
||||
.debug_rnglists => {
|
||||
const kind = try in.readByte();
|
||||
switch (kind) {
|
||||
RLE.end_of_list => break,
|
||||
RLE.end_of_list => return null,
|
||||
RLE.base_addressx => {
|
||||
const index = try leb.readULEB128(usize, in);
|
||||
base_address = try di.readDebugAddr(compile_unit.*, index);
|
||||
self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index);
|
||||
return try self.next();
|
||||
},
|
||||
RLE.startx_endx => {
|
||||
const start_index = try leb.readULEB128(usize, in);
|
||||
const start_addr = try di.readDebugAddr(compile_unit.*, start_index);
|
||||
const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
|
||||
|
||||
const end_index = try leb.readULEB128(usize, in);
|
||||
const end_addr = try di.readDebugAddr(compile_unit.*, end_index);
|
||||
const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index);
|
||||
|
||||
if (target_address >= start_addr and target_address < end_addr) {
|
||||
return compile_unit;
|
||||
}
|
||||
return .{
|
||||
.start_addr = start_addr,
|
||||
.end_addr = end_addr,
|
||||
};
|
||||
},
|
||||
RLE.startx_length => {
|
||||
const start_index = try leb.readULEB128(usize, in);
|
||||
const start_addr = try di.readDebugAddr(compile_unit.*, start_index);
|
||||
const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index);
|
||||
|
||||
const len = try leb.readULEB128(usize, in);
|
||||
const end_addr = start_addr + len;
|
||||
|
||||
if (target_address >= start_addr and target_address < end_addr) {
|
||||
return compile_unit;
|
||||
}
|
||||
return .{
|
||||
.start_addr = start_addr,
|
||||
.end_addr = end_addr,
|
||||
};
|
||||
},
|
||||
RLE.offset_pair => {
|
||||
const start_addr = try leb.readULEB128(usize, in);
|
||||
const end_addr = try leb.readULEB128(usize, in);
|
||||
|
||||
// This is the only kind that uses the base address
|
||||
if (target_address >= base_address + start_addr and target_address < base_address + end_addr) {
|
||||
return compile_unit;
|
||||
}
|
||||
return .{
|
||||
.start_addr = self.base_address + start_addr,
|
||||
.end_addr = self.base_address + end_addr,
|
||||
};
|
||||
},
|
||||
RLE.base_address => {
|
||||
base_address = try in.readInt(usize, di.endian);
|
||||
self.base_address = try in.readInt(usize, self.di.endian);
|
||||
return try self.next();
|
||||
},
|
||||
RLE.start_end => {
|
||||
const start_addr = try in.readInt(usize, di.endian);
|
||||
const end_addr = try in.readInt(usize, di.endian);
|
||||
if (target_address >= start_addr and target_address < end_addr) {
|
||||
return compile_unit;
|
||||
}
|
||||
const start_addr = try in.readInt(usize, self.di.endian);
|
||||
const end_addr = try in.readInt(usize, self.di.endian);
|
||||
|
||||
return .{
|
||||
.start_addr = start_addr,
|
||||
.end_addr = end_addr,
|
||||
};
|
||||
},
|
||||
RLE.start_length => {
|
||||
const start_addr = try in.readInt(usize, di.endian);
|
||||
const start_addr = try in.readInt(usize, self.di.endian);
|
||||
const len = try leb.readULEB128(usize, in);
|
||||
const end_addr = start_addr + len;
|
||||
if (target_address >= start_addr and target_address < end_addr) {
|
||||
return compile_unit;
|
||||
}
|
||||
|
||||
return .{
|
||||
.start_addr = start_addr,
|
||||
.end_addr = end_addr,
|
||||
};
|
||||
},
|
||||
else => return badDwarf(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (true) {
|
||||
const begin_addr = try in.readInt(usize, di.endian);
|
||||
const end_addr = try in.readInt(usize, di.endian);
|
||||
if (begin_addr == 0 and end_addr == 0) {
|
||||
break;
|
||||
}
|
||||
},
|
||||
.debug_ranges => {
|
||||
const start_addr = try in.readInt(usize, self.di.endian);
|
||||
const end_addr = try in.readInt(usize, self.di.endian);
|
||||
if (start_addr == 0 and end_addr == 0) return null;
|
||||
|
||||
// This entry selects a new value for the base address
|
||||
if (begin_addr == math.maxInt(usize)) {
|
||||
base_address = end_addr;
|
||||
continue;
|
||||
if (start_addr == math.maxInt(usize)) {
|
||||
self.base_address = end_addr;
|
||||
return try self.next();
|
||||
}
|
||||
if (target_address >= base_address + begin_addr and target_address < base_address + end_addr) {
|
||||
return compile_unit;
|
||||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.start_addr = self.base_address + start_addr,
|
||||
.end_addr = self.base_address + end_addr,
|
||||
};
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn findCompileUnit(di: *const DwarfInfo, target_address: u64) !*const CompileUnit {
|
||||
for (di.compile_unit_list.items) |*compile_unit| {
|
||||
if (compile_unit.pc_range) |range| {
|
||||
if (target_address >= range.start and target_address < range.end) return compile_unit;
|
||||
}
|
||||
|
||||
const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue;
|
||||
var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue;
|
||||
while (try iter.next()) |range| {
|
||||
if (target_address >= range.start_addr and target_address < range.end_addr) return compile_unit;
|
||||
}
|
||||
}
|
||||
|
||||
return missingDwarf();
|
||||
}
|
||||
|
||||
@@ -1065,7 +1174,7 @@ pub const DwarfInfo = struct {
|
||||
}
|
||||
|
||||
fn parseAbbrevTable(di: *DwarfInfo, allocator: mem.Allocator, offset: u64) !AbbrevTable {
|
||||
var stream = io.fixedBufferStream(di.debug_abbrev);
|
||||
var stream = io.fixedBufferStream(di.section(.debug_abbrev).?);
|
||||
const in = &stream.reader();
|
||||
const seekable = &stream.seekableStream();
|
||||
|
||||
@@ -1146,11 +1255,11 @@ pub const DwarfInfo = struct {
|
||||
compile_unit: CompileUnit,
|
||||
target_address: u64,
|
||||
) !debug.LineInfo {
|
||||
var stream = io.fixedBufferStream(di.debug_line);
|
||||
var stream = io.fixedBufferStream(di.section(.debug_line).?);
|
||||
const in = &stream.reader();
|
||||
const seekable = &stream.seekableStream();
|
||||
|
||||
const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.debug_line_str, compile_unit);
|
||||
const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit);
|
||||
const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
|
||||
|
||||
try seekable.seekTo(line_info_offset);
|
||||
@@ -1416,15 +1525,15 @@ pub const DwarfInfo = struct {
|
||||
}
|
||||
|
||||
fn getString(di: DwarfInfo, offset: u64) ![]const u8 {
|
||||
return getStringGeneric(di.debug_str, offset);
|
||||
return getStringGeneric(di.section(.debug_str), offset);
|
||||
}
|
||||
|
||||
fn getLineString(di: DwarfInfo, offset: u64) ![]const u8 {
|
||||
return getStringGeneric(di.debug_line_str, offset);
|
||||
return getStringGeneric(di.section(.debug_line_str), offset);
|
||||
}
|
||||
|
||||
fn readDebugAddr(di: DwarfInfo, compile_unit: CompileUnit, index: u64) !u64 {
|
||||
const debug_addr = di.debug_addr orelse return badDwarf();
|
||||
const debug_addr = di.section(.debug_addr) orelse return badDwarf();
|
||||
|
||||
// addr_base points to the first item after the header, however we
|
||||
// need to read the header to know the size of each item. Empirically,
|
||||
@@ -1448,10 +1557,689 @@ pub const DwarfInfo = struct {
|
||||
else => badDwarf(),
|
||||
};
|
||||
}
|
||||
|
||||
/// If .eh_frame_hdr is present, then only the header needs to be parsed.
|
||||
///
|
||||
/// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list
|
||||
/// of FDEs is built for binary searching during unwinding.
|
||||
pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void {
|
||||
if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
|
||||
var stream = io.fixedBufferStream(eh_frame_hdr);
|
||||
const reader = stream.reader();
|
||||
|
||||
const version = try reader.readByte();
|
||||
if (version != 1) break :blk;
|
||||
|
||||
const eh_frame_ptr_enc = try reader.readByte();
|
||||
if (eh_frame_ptr_enc == EH.PE.omit) break :blk;
|
||||
const fde_count_enc = try reader.readByte();
|
||||
if (fde_count_enc == EH.PE.omit) break :blk;
|
||||
const table_enc = try reader.readByte();
|
||||
if (table_enc == EH.PE.omit) break :blk;
|
||||
|
||||
const eh_frame_ptr = std.math.cast(usize, try readEhPointer(reader, eh_frame_ptr_enc, @sizeOf(usize), .{
|
||||
.pc_rel_base = @intFromPtr(&eh_frame_hdr[stream.pos]),
|
||||
.follow_indirect = true,
|
||||
}, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf();
|
||||
|
||||
const fde_count = std.math.cast(usize, try readEhPointer(reader, fde_count_enc, @sizeOf(usize), .{
|
||||
.pc_rel_base = @intFromPtr(&eh_frame_hdr[stream.pos]),
|
||||
.follow_indirect = true,
|
||||
}, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf();
|
||||
|
||||
const entry_size = try ExceptionFrameHeader.entrySize(table_enc);
|
||||
const entries_len = fde_count * entry_size;
|
||||
if (entries_len > eh_frame_hdr.len - stream.pos) return badDwarf();
|
||||
|
||||
di.eh_frame_hdr = .{
|
||||
.eh_frame_ptr = eh_frame_ptr,
|
||||
.table_enc = table_enc,
|
||||
.fde_count = fde_count,
|
||||
.entries = eh_frame_hdr[stream.pos..][0..entries_len],
|
||||
};
|
||||
|
||||
// No need to scan .eh_frame, we have a binary search table already
|
||||
return;
|
||||
}
|
||||
|
||||
const frame_sections = [2]DwarfSection{ .eh_frame, .debug_frame };
|
||||
for (frame_sections) |frame_section| {
|
||||
if (di.section(frame_section)) |section_data| {
|
||||
var stream = io.fixedBufferStream(section_data);
|
||||
while (stream.pos < stream.buffer.len) {
|
||||
const entry_header = try EntryHeader.read(&stream, frame_section, di.endian);
|
||||
switch (entry_header.type) {
|
||||
.cie => {
|
||||
const cie = try CommonInformationEntry.parse(
|
||||
entry_header.entry_bytes,
|
||||
di.sectionVirtualOffset(frame_section, base_address).?,
|
||||
true,
|
||||
entry_header.is_64,
|
||||
frame_section,
|
||||
entry_header.length_offset,
|
||||
@sizeOf(usize),
|
||||
di.endian,
|
||||
);
|
||||
try di.cie_map.put(allocator, entry_header.length_offset, cie);
|
||||
},
|
||||
.fde => |cie_offset| {
|
||||
const cie = di.cie_map.get(cie_offset) orelse return badDwarf();
|
||||
const fde = try FrameDescriptionEntry.parse(
|
||||
entry_header.entry_bytes,
|
||||
di.sectionVirtualOffset(frame_section, base_address).?,
|
||||
true,
|
||||
cie,
|
||||
@sizeOf(usize),
|
||||
di.endian,
|
||||
);
|
||||
try di.fde_list.append(allocator, fde);
|
||||
},
|
||||
.terminator => break,
|
||||
}
|
||||
}
|
||||
|
||||
std.mem.sort(FrameDescriptionEntry, di.fde_list.items, {}, struct {
|
||||
fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool {
|
||||
_ = ctx;
|
||||
return a.pc_begin < b.pc_begin;
|
||||
}
|
||||
}.lessThan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwind a stack frame using DWARF unwinding info, updating the register context.
|
||||
///
|
||||
/// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE.
|
||||
/// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE.
|
||||
///
|
||||
/// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
|
||||
/// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
|
||||
pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, explicit_fde_offset: ?usize) !usize {
|
||||
if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture;
|
||||
if (context.pc == 0) return 0;
|
||||
|
||||
// Find the FDE and CIE
|
||||
var cie: CommonInformationEntry = undefined;
|
||||
var fde: FrameDescriptionEntry = undefined;
|
||||
|
||||
if (explicit_fde_offset) |fde_offset| {
|
||||
const dwarf_section: DwarfSection = .eh_frame;
|
||||
const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
|
||||
if (fde_offset >= frame_section.len) return error.MissingFDE;
|
||||
|
||||
var stream = io.fixedBufferStream(frame_section);
|
||||
try stream.seekTo(fde_offset);
|
||||
|
||||
const fde_entry_header = try EntryHeader.read(&stream, dwarf_section, di.endian);
|
||||
if (fde_entry_header.type != .fde) return error.MissingFDE;
|
||||
|
||||
const cie_offset = fde_entry_header.type.fde;
|
||||
try stream.seekTo(cie_offset);
|
||||
|
||||
const cie_entry_header = try EntryHeader.read(&stream, dwarf_section, builtin.cpu.arch.endian());
|
||||
if (cie_entry_header.type != .cie) return badDwarf();
|
||||
|
||||
cie = try CommonInformationEntry.parse(
|
||||
cie_entry_header.entry_bytes,
|
||||
0,
|
||||
true,
|
||||
cie_entry_header.is_64,
|
||||
dwarf_section,
|
||||
cie_entry_header.length_offset,
|
||||
@sizeOf(usize),
|
||||
builtin.cpu.arch.endian(),
|
||||
);
|
||||
|
||||
fde = try FrameDescriptionEntry.parse(
|
||||
fde_entry_header.entry_bytes,
|
||||
0,
|
||||
true,
|
||||
cie,
|
||||
@sizeOf(usize),
|
||||
builtin.cpu.arch.endian(),
|
||||
);
|
||||
} else if (di.eh_frame_hdr) |header| {
|
||||
const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null;
|
||||
try header.findEntry(
|
||||
context.isValidMemory,
|
||||
eh_frame_len,
|
||||
@intFromPtr(di.section(.eh_frame_hdr).?.ptr),
|
||||
context.pc,
|
||||
&cie,
|
||||
&fde,
|
||||
);
|
||||
} else {
|
||||
const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct {
|
||||
pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
|
||||
if (pc < mid_item.pc_begin) return .lt;
|
||||
|
||||
const range_end = mid_item.pc_begin + mid_item.pc_range;
|
||||
if (pc < range_end) return .eq;
|
||||
|
||||
return .gt;
|
||||
}
|
||||
}.compareFn);
|
||||
|
||||
fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;
|
||||
cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
|
||||
}
|
||||
|
||||
var expression_context = .{
|
||||
.is_64 = cie.is_64,
|
||||
.isValidMemory = context.isValidMemory,
|
||||
.compile_unit = di.findCompileUnit(fde.pc_begin) catch null,
|
||||
.thread_context = context.thread_context,
|
||||
.reg_context = context.reg_context,
|
||||
.cfa = context.cfa,
|
||||
};
|
||||
|
||||
context.vm.reset();
|
||||
context.reg_context.eh_frame = cie.version != 4;
|
||||
context.reg_context.is_macho = di.is_macho;
|
||||
|
||||
const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
|
||||
context.cfa = switch (row.cfa.rule) {
|
||||
.val_offset => |offset| blk: {
|
||||
const register = row.cfa.register orelse return error.InvalidCFARule;
|
||||
const value = mem.readIntSliceNative(usize, try abi.regBytes(context.thread_context, register, context.reg_context));
|
||||
break :blk try call_frame.applyOffset(value, offset);
|
||||
},
|
||||
.expression => |expression| blk: {
|
||||
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;
|
||||
break :blk v.generic;
|
||||
} else return error.NoExpressionValue;
|
||||
},
|
||||
else => return error.InvalidCFARule,
|
||||
};
|
||||
|
||||
if (!context.isValidMemory(context.cfa.?)) return error.InvalidCFA;
|
||||
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(context.allocator);
|
||||
defer arena.deinit();
|
||||
const update_allocator = 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;
|
||||
for (context.vm.rowColumns(row)) |column| {
|
||||
if (column.register) |register| {
|
||||
if (register == cie.return_address_register) {
|
||||
has_return_address = column.rule != .undefined;
|
||||
}
|
||||
|
||||
const dest = try abi.regBytes(context.thread_context, register, context.reg_context);
|
||||
const src = try update_allocator.alloc(u8, dest.len);
|
||||
|
||||
const prev = update_tail;
|
||||
update_tail = try update_allocator.create(RegisterUpdate);
|
||||
update_tail.?.* = .{
|
||||
.dest = dest,
|
||||
.src = src,
|
||||
.prev = prev,
|
||||
};
|
||||
|
||||
try column.resolveValue(
|
||||
context,
|
||||
expression_context,
|
||||
src,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// On all implemented architectures, the CFA is defined as being the previous frame's SP
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?;
|
||||
|
||||
while (update_tail) |tail| {
|
||||
@memcpy(tail.dest, tail.src);
|
||||
update_tail = tail.prev;
|
||||
}
|
||||
|
||||
if (has_return_address) {
|
||||
context.pc = abi.stripInstructionPtrAuthCode(mem.readIntSliceNative(usize, try abi.regBytes(
|
||||
context.thread_context,
|
||||
cie.return_address_register,
|
||||
context.reg_context,
|
||||
)));
|
||||
} else {
|
||||
context.pc = 0;
|
||||
}
|
||||
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc;
|
||||
|
||||
// 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.isSignalFrame()) context.pc -= 1;
|
||||
|
||||
return return_address;
|
||||
}
|
||||
};
|
||||
|
||||
/// Returns the DWARF register number for an x86_64 register number found in compact unwind info
|
||||
fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
|
||||
return switch (unwind_reg_number) {
|
||||
1 => 3, // RBX
|
||||
2 => 12, // R12
|
||||
3 => 13, // R13
|
||||
4 => 14, // R14
|
||||
5 => 15, // R15
|
||||
6 => 6, // RBP
|
||||
else => error.InvalidUnwindRegisterNumber,
|
||||
};
|
||||
}
|
||||
|
||||
const macho = std.macho;
|
||||
|
||||
/// Unwind a frame using MachO compact unwind info (from __unwind_info).
|
||||
/// If the compact encoding can't encode a way to unwind a frame, it will
|
||||
/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
|
||||
pub fn unwindFrameMachO(context: *UnwindContext, unwind_info: []const u8, eh_frame: ?[]const u8, module_base_address: usize) !usize {
|
||||
const header = mem.bytesAsValue(
|
||||
macho.unwind_info_section_header,
|
||||
unwind_info[0..@sizeOf(macho.unwind_info_section_header)],
|
||||
);
|
||||
const indices = mem.bytesAsSlice(
|
||||
macho.unwind_info_section_header_index_entry,
|
||||
unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)],
|
||||
);
|
||||
if (indices.len == 0) return error.MissingUnwindInfo;
|
||||
|
||||
const mapped_pc = context.pc - module_base_address;
|
||||
const second_level_index = blk: {
|
||||
var left: usize = 0;
|
||||
var len: usize = indices.len;
|
||||
|
||||
while (len > 1) {
|
||||
const mid = left + len / 2;
|
||||
const offset = indices[mid].functionOffset;
|
||||
if (mapped_pc < offset) {
|
||||
len /= 2;
|
||||
} else {
|
||||
left = mid;
|
||||
if (mapped_pc == offset) break;
|
||||
len -= len / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Last index is a sentinel containing the highest address as its functionOffset
|
||||
if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo;
|
||||
break :blk &indices[left];
|
||||
};
|
||||
|
||||
const common_encodings = mem.bytesAsSlice(
|
||||
macho.compact_unwind_encoding_t,
|
||||
unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)],
|
||||
);
|
||||
|
||||
const start_offset = second_level_index.secondLevelPagesSectionOffset;
|
||||
const kind = mem.bytesAsValue(
|
||||
macho.UNWIND_SECOND_LEVEL,
|
||||
unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)],
|
||||
);
|
||||
|
||||
const entry: struct {
|
||||
function_offset: usize,
|
||||
raw_encoding: u32,
|
||||
} = switch (kind.*) {
|
||||
.REGULAR => blk: {
|
||||
const page_header = mem.bytesAsValue(
|
||||
macho.unwind_info_regular_second_level_page_header,
|
||||
unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)],
|
||||
);
|
||||
|
||||
const entries = mem.bytesAsSlice(
|
||||
macho.unwind_info_regular_second_level_entry,
|
||||
unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)],
|
||||
);
|
||||
if (entries.len == 0) return error.InvalidUnwindInfo;
|
||||
|
||||
var left: usize = 0;
|
||||
var len: usize = entries.len;
|
||||
while (len > 1) {
|
||||
const mid = left + len / 2;
|
||||
const offset = entries[mid].functionOffset;
|
||||
if (mapped_pc < offset) {
|
||||
len /= 2;
|
||||
} else {
|
||||
left = mid;
|
||||
if (mapped_pc == offset) break;
|
||||
len -= len / 2;
|
||||
}
|
||||
}
|
||||
|
||||
break :blk .{
|
||||
.function_offset = entries[left].functionOffset,
|
||||
.raw_encoding = entries[left].encoding,
|
||||
};
|
||||
},
|
||||
.COMPRESSED => blk: {
|
||||
const page_header = mem.bytesAsValue(
|
||||
macho.unwind_info_compressed_second_level_page_header,
|
||||
unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)],
|
||||
);
|
||||
|
||||
const entries = mem.bytesAsSlice(
|
||||
macho.UnwindInfoCompressedEntry,
|
||||
unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)],
|
||||
);
|
||||
if (entries.len == 0) return error.InvalidUnwindInfo;
|
||||
|
||||
var left: usize = 0;
|
||||
var len: usize = entries.len;
|
||||
while (len > 1) {
|
||||
const mid = left + len / 2;
|
||||
const offset = second_level_index.functionOffset + entries[mid].funcOffset;
|
||||
if (mapped_pc < offset) {
|
||||
len /= 2;
|
||||
} else {
|
||||
left = mid;
|
||||
if (mapped_pc == offset) break;
|
||||
len -= len / 2;
|
||||
}
|
||||
}
|
||||
|
||||
const entry = entries[left];
|
||||
const function_offset = second_level_index.functionOffset + entry.funcOffset;
|
||||
if (entry.encodingIndex < header.commonEncodingsArrayCount) {
|
||||
if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo;
|
||||
break :blk .{
|
||||
.function_offset = function_offset,
|
||||
.raw_encoding = common_encodings[entry.encodingIndex],
|
||||
};
|
||||
} else {
|
||||
const local_index = try std.math.sub(
|
||||
u8,
|
||||
entry.encodingIndex,
|
||||
std.math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo,
|
||||
);
|
||||
const local_encodings = mem.bytesAsSlice(
|
||||
macho.compact_unwind_encoding_t,
|
||||
unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)],
|
||||
);
|
||||
if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
|
||||
break :blk .{
|
||||
.function_offset = function_offset,
|
||||
.raw_encoding = local_encodings[local_index],
|
||||
};
|
||||
}
|
||||
},
|
||||
else => return error.InvalidUnwindInfo,
|
||||
};
|
||||
|
||||
if (entry.raw_encoding == 0) return error.NoUnwindInfo;
|
||||
const reg_context = abi.RegisterContext{
|
||||
.eh_frame = false,
|
||||
.is_macho = true,
|
||||
};
|
||||
|
||||
const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
|
||||
const new_ip = switch (builtin.cpu.arch) {
|
||||
.x86_64 => switch (encoding.mode.x86_64) {
|
||||
.OLD => return error.UnimplementedUnwindEncoding,
|
||||
.RBP_FRAME => blk: {
|
||||
const regs: [5]u3 = .{
|
||||
encoding.value.x86_64.frame.reg0,
|
||||
encoding.value.x86_64.frame.reg1,
|
||||
encoding.value.x86_64.frame.reg2,
|
||||
encoding.value.x86_64.frame.reg3,
|
||||
encoding.value.x86_64.frame.reg4,
|
||||
};
|
||||
|
||||
const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize);
|
||||
var max_reg: usize = 0;
|
||||
inline for (regs, 0..) |reg, i| {
|
||||
if (reg > 0) max_reg = i;
|
||||
}
|
||||
|
||||
const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
|
||||
const new_sp = fp + 2 * @sizeOf(usize);
|
||||
|
||||
// Verify the stack range we're about to read register values from
|
||||
if (!context.isValidMemory(new_sp) or !context.isValidMemory(fp - frame_offset + max_reg * @sizeOf(usize))) return error.InvalidUnwindInfo;
|
||||
|
||||
const ip_ptr = fp + @sizeOf(usize);
|
||||
const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
|
||||
const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
|
||||
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
|
||||
|
||||
for (regs, 0..) |reg, i| {
|
||||
if (reg == 0) continue;
|
||||
const addr = fp - frame_offset + i * @sizeOf(usize);
|
||||
const reg_number = try compactUnwindToDwarfRegNumber(reg);
|
||||
(try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
|
||||
}
|
||||
|
||||
break :blk new_ip;
|
||||
},
|
||||
.STACK_IMMD,
|
||||
.STACK_IND,
|
||||
=> blk: {
|
||||
const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*;
|
||||
const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD)
|
||||
@as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize)
|
||||
else stack_size: {
|
||||
// In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
|
||||
const sub_offset_addr =
|
||||
module_base_address +
|
||||
entry.function_offset +
|
||||
encoding.value.x86_64.frameless.stack.indirect.sub_offset;
|
||||
if (!context.isValidMemory(sub_offset_addr)) return error.InvalidUnwindInfo;
|
||||
|
||||
// `sub_offset_addr` points to the offset of the literal within the instruction
|
||||
const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
|
||||
break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust);
|
||||
};
|
||||
|
||||
// Decode the Lehmer-coded sequence of registers.
|
||||
// For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
|
||||
|
||||
// Decode the variable-based permutation number into its digits. Each digit represents
|
||||
// an index into the list of register numbers that weren't yet used in the sequence at
|
||||
// the time the digit was added.
|
||||
const reg_count = encoding.value.x86_64.frameless.stack_reg_count;
|
||||
const ip_ptr = if (reg_count > 0) reg_blk: {
|
||||
var digits: [6]u3 = undefined;
|
||||
var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation;
|
||||
var base: usize = 2;
|
||||
for (0..reg_count) |i| {
|
||||
const div = accumulator / base;
|
||||
digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
|
||||
accumulator = div;
|
||||
base += 1;
|
||||
}
|
||||
|
||||
const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 };
|
||||
var registers: [reg_numbers.len]u3 = undefined;
|
||||
var used_indices = [_]bool{false} ** reg_numbers.len;
|
||||
for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
|
||||
var unused_count: u8 = 0;
|
||||
const unused_index = for (used_indices, 0..) |used, index| {
|
||||
if (!used) {
|
||||
if (target_unused_index == unused_count) break index;
|
||||
unused_count += 1;
|
||||
}
|
||||
} else unreachable;
|
||||
|
||||
registers[i] = reg_numbers[unused_index];
|
||||
used_indices[unused_index] = true;
|
||||
}
|
||||
|
||||
var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
|
||||
if (!context.isValidMemory(reg_addr)) return error.InvalidUnwindInfo;
|
||||
for (0..reg_count) |i| {
|
||||
const reg_number = try compactUnwindToDwarfRegNumber(registers[i]);
|
||||
(try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
|
||||
reg_addr += @sizeOf(usize);
|
||||
}
|
||||
|
||||
break :reg_blk reg_addr;
|
||||
} else sp + stack_size - @sizeOf(usize);
|
||||
|
||||
const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
|
||||
const new_sp = ip_ptr + @sizeOf(usize);
|
||||
if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo;
|
||||
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
|
||||
|
||||
break :blk new_ip;
|
||||
},
|
||||
.DWARF => {
|
||||
return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf));
|
||||
},
|
||||
},
|
||||
.aarch64 => switch (encoding.mode.arm64) {
|
||||
.OLD => return error.UnimplementedUnwindEncoding,
|
||||
.FRAMELESS => blk: {
|
||||
const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*;
|
||||
const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
|
||||
const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*;
|
||||
if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo;
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp;
|
||||
break :blk new_ip;
|
||||
},
|
||||
.DWARF => {
|
||||
return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf));
|
||||
},
|
||||
.FRAME => blk: {
|
||||
const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*;
|
||||
const new_sp = fp + 16;
|
||||
const ip_ptr = fp + @sizeOf(usize);
|
||||
|
||||
const num_restored_pairs: usize =
|
||||
@popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) +
|
||||
@popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs)));
|
||||
const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize);
|
||||
|
||||
if (!context.isValidMemory(new_sp) or !context.isValidMemory(min_reg_addr)) return error.InvalidUnwindInfo;
|
||||
|
||||
var reg_addr = fp - @sizeOf(usize);
|
||||
inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| {
|
||||
if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) {
|
||||
(try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
|
||||
reg_addr += @sizeOf(usize);
|
||||
(try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
|
||||
reg_addr += @sizeOf(usize);
|
||||
}
|
||||
}
|
||||
|
||||
inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| {
|
||||
if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) {
|
||||
// Only the lower half of the 128-bit V registers are restored during unwinding
|
||||
@memcpy(
|
||||
try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context),
|
||||
mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
|
||||
);
|
||||
reg_addr += @sizeOf(usize);
|
||||
@memcpy(
|
||||
try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context),
|
||||
mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))),
|
||||
);
|
||||
reg_addr += @sizeOf(usize);
|
||||
}
|
||||
}
|
||||
|
||||
const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
|
||||
const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
|
||||
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp;
|
||||
(try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip;
|
||||
|
||||
break :blk new_ip;
|
||||
},
|
||||
},
|
||||
else => return error.UnimplementedArch,
|
||||
};
|
||||
|
||||
context.pc = abi.stripInstructionPtrAuthCode(new_ip);
|
||||
if (context.pc > 0) context.pc -= 1;
|
||||
return new_ip;
|
||||
}
|
||||
|
||||
fn unwindFrameMachODwarf(context: *UnwindContext, eh_frame: []const u8, fde_offset: usize) !usize {
|
||||
var di = DwarfInfo{
|
||||
.endian = builtin.cpu.arch.endian(),
|
||||
.is_macho = true,
|
||||
};
|
||||
defer di.deinit(context.allocator);
|
||||
|
||||
di.sections[@intFromEnum(DwarfSection.eh_frame)] = .{
|
||||
.data = eh_frame,
|
||||
.owned = false,
|
||||
};
|
||||
|
||||
return di.unwindFrame(context, fde_offset);
|
||||
}
|
||||
|
||||
pub const UnwindContext = struct {
|
||||
allocator: mem.Allocator,
|
||||
cfa: ?usize,
|
||||
pc: usize,
|
||||
thread_context: *debug.ThreadContext,
|
||||
reg_context: abi.RegisterContext,
|
||||
isValidMemory: *const fn (address: usize) bool,
|
||||
vm: call_frame.VirtualMachine = .{},
|
||||
stack_machine: expressions.StackMachine(.{ .call_frame_context = true }) = .{},
|
||||
|
||||
pub fn init(allocator: mem.Allocator, thread_context: *const debug.ThreadContext, isValidMemory: *const fn (address: usize) bool) !UnwindContext {
|
||||
const pc = abi.stripInstructionPtrAuthCode((try abi.regValueNative(usize, thread_context, abi.ipRegNum(), null)).*);
|
||||
|
||||
const context_copy = try allocator.create(debug.ThreadContext);
|
||||
debug.copyContext(thread_context, context_copy);
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.cfa = null,
|
||||
.pc = pc,
|
||||
.thread_context = context_copy,
|
||||
.reg_context = undefined,
|
||||
.isValidMemory = isValidMemory,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *UnwindContext) void {
|
||||
self.vm.deinit(self.allocator);
|
||||
self.stack_machine.deinit(self.allocator);
|
||||
self.allocator.destroy(self.thread_context);
|
||||
}
|
||||
|
||||
pub fn getFp(self: *const UnwindContext) !usize {
|
||||
return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*;
|
||||
}
|
||||
};
|
||||
|
||||
/// Initialize DWARF info. The caller has the responsibility to initialize most
|
||||
/// the DwarfInfo fields before calling.
|
||||
/// the DwarfInfo fields before calling. `binary_mem` is the raw bytes of the
|
||||
/// main binary file (not the secondary debug info file).
|
||||
pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void {
|
||||
try di.scanAllFunctions(allocator);
|
||||
try di.scanAllCompileUnits(allocator);
|
||||
@@ -1477,3 +2265,561 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
|
||||
const last = mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf();
|
||||
return str[casted_offset..last :0];
|
||||
}
|
||||
|
||||
const EhPointerContext = struct {
|
||||
// The address of the pointer field itself
|
||||
pc_rel_base: u64,
|
||||
|
||||
// Whether or not to follow indirect pointers. This should only be
|
||||
// used when decoding pointers at runtime using the current process's
|
||||
// debug info
|
||||
follow_indirect: bool,
|
||||
|
||||
// These relative addressing modes are only used in specific cases, and
|
||||
// might not be available / required in all parsing contexts
|
||||
data_rel_base: ?u64 = null,
|
||||
text_rel_base: ?u64 = null,
|
||||
function_rel_base: ?u64 = null,
|
||||
};
|
||||
|
||||
fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: std.builtin.Endian) !?u64 {
|
||||
if (enc == EH.PE.omit) return null;
|
||||
|
||||
const value: union(enum) {
|
||||
signed: i64,
|
||||
unsigned: u64,
|
||||
} = switch (enc & EH.PE.type_mask) {
|
||||
EH.PE.absptr => .{
|
||||
.unsigned = 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,
|
||||
},
|
||||
},
|
||||
EH.PE.uleb128 => .{ .unsigned = try leb.readULEB128(u64, reader) },
|
||||
EH.PE.udata2 => .{ .unsigned = try reader.readInt(u16, endian) },
|
||||
EH.PE.udata4 => .{ .unsigned = try reader.readInt(u32, endian) },
|
||||
EH.PE.udata8 => .{ .unsigned = try reader.readInt(u64, endian) },
|
||||
EH.PE.sleb128 => .{ .signed = try leb.readILEB128(i64, reader) },
|
||||
EH.PE.sdata2 => .{ .signed = try reader.readInt(i16, endian) },
|
||||
EH.PE.sdata4 => .{ .signed = try reader.readInt(i32, endian) },
|
||||
EH.PE.sdata8 => .{ .signed = try reader.readInt(i64, endian) },
|
||||
else => return badDwarf(),
|
||||
};
|
||||
|
||||
var base = switch (enc & EH.PE.rel_mask) {
|
||||
EH.PE.pcrel => ctx.pc_rel_base,
|
||||
EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified,
|
||||
EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified,
|
||||
EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified,
|
||||
else => null,
|
||||
};
|
||||
|
||||
const ptr: u64 = if (base) |b| switch (value) {
|
||||
.signed => |s| @intCast(try math.add(i64, s, @as(i64, @intCast(b)))),
|
||||
// absptr can actually contain signed values in some cases (aarch64 MachO)
|
||||
.unsigned => |u| u +% b,
|
||||
} else switch (value) {
|
||||
.signed => |s| @as(u64, @intCast(s)),
|
||||
.unsigned => |u| u,
|
||||
};
|
||||
|
||||
if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) {
|
||||
if (@sizeOf(usize) != addr_size_bytes) {
|
||||
// See the documentation for `follow_indirect`
|
||||
return error.NonNativeIndirection;
|
||||
}
|
||||
|
||||
const native_ptr = math.cast(usize, ptr) orelse return error.PointerOverflow;
|
||||
return switch (addr_size_bytes) {
|
||||
2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*,
|
||||
else => return error.UnsupportedAddrSize,
|
||||
};
|
||||
} else {
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/// This represents the decoded .eh_frame_hdr header
|
||||
pub const ExceptionFrameHeader = struct {
|
||||
eh_frame_ptr: usize,
|
||||
table_enc: u8,
|
||||
fde_count: usize,
|
||||
entries: []const u8,
|
||||
|
||||
pub fn entrySize(table_enc: u8) !u8 {
|
||||
return switch (table_enc & EH.PE.type_mask) {
|
||||
EH.PE.udata2,
|
||||
EH.PE.sdata2,
|
||||
=> 4,
|
||||
EH.PE.udata4,
|
||||
EH.PE.sdata4,
|
||||
=> 8,
|
||||
EH.PE.udata8,
|
||||
EH.PE.sdata8,
|
||||
=> 16,
|
||||
// This is a binary search table, so all entries must be the same length
|
||||
else => return badDwarf(),
|
||||
};
|
||||
}
|
||||
|
||||
fn isValidPtr(
|
||||
self: ExceptionFrameHeader,
|
||||
ptr: usize,
|
||||
isValidMemory: *const fn (address: usize) bool,
|
||||
eh_frame_len: ?usize,
|
||||
) bool {
|
||||
if (eh_frame_len) |len| {
|
||||
return ptr >= self.eh_frame_ptr and ptr < self.eh_frame_ptr + len;
|
||||
} else {
|
||||
return isValidMemory(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// Find an entry by binary searching the eh_frame_hdr section.
|
||||
///
|
||||
/// Since the length of the eh_frame section (`eh_frame_len`) may not be known by the caller,
|
||||
/// `isValidMemory` will be called before accessing any memory referenced by
|
||||
/// the header entries. If `eh_frame_len` is provided, then these checks can be skipped.
|
||||
pub fn findEntry(
|
||||
self: ExceptionFrameHeader,
|
||||
isValidMemory: *const fn (address: usize) bool,
|
||||
eh_frame_len: ?usize,
|
||||
eh_frame_hdr_ptr: usize,
|
||||
pc: usize,
|
||||
cie: *CommonInformationEntry,
|
||||
fde: *FrameDescriptionEntry,
|
||||
) !void {
|
||||
const entry_size = try entrySize(self.table_enc);
|
||||
|
||||
var left: usize = 0;
|
||||
var len: usize = self.fde_count;
|
||||
|
||||
var stream = io.fixedBufferStream(self.entries);
|
||||
const reader = stream.reader();
|
||||
|
||||
while (len > 1) {
|
||||
const mid = left + len / 2;
|
||||
|
||||
try stream.seekTo(mid * entry_size);
|
||||
const pc_begin = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
|
||||
.pc_rel_base = @intFromPtr(&self.entries[stream.pos]),
|
||||
.follow_indirect = true,
|
||||
.data_rel_base = eh_frame_hdr_ptr,
|
||||
}, builtin.cpu.arch.endian()) orelse return badDwarf();
|
||||
|
||||
if (pc < pc_begin) {
|
||||
len /= 2;
|
||||
} else {
|
||||
left = mid;
|
||||
if (pc == pc_begin) break;
|
||||
len -= len / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == 0) return badDwarf();
|
||||
try stream.seekTo(left * entry_size);
|
||||
|
||||
// Read past the pc_begin field of the entry
|
||||
_ = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
|
||||
.pc_rel_base = @intFromPtr(&self.entries[stream.pos]),
|
||||
.follow_indirect = true,
|
||||
.data_rel_base = eh_frame_hdr_ptr,
|
||||
}, builtin.cpu.arch.endian()) orelse return badDwarf();
|
||||
|
||||
const fde_ptr = math.cast(usize, try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
|
||||
.pc_rel_base = @intFromPtr(&self.entries[stream.pos]),
|
||||
.follow_indirect = true,
|
||||
.data_rel_base = eh_frame_hdr_ptr,
|
||||
}, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf();
|
||||
|
||||
// Verify the length fields of the FDE header are readable
|
||||
if (!self.isValidPtr(fde_ptr, isValidMemory, eh_frame_len) or fde_ptr < self.eh_frame_ptr) return badDwarf();
|
||||
|
||||
var fde_entry_header_len: usize = 4;
|
||||
if (!self.isValidPtr(fde_ptr + 3, isValidMemory, eh_frame_len)) return badDwarf();
|
||||
if (self.isValidPtr(fde_ptr + 11, isValidMemory, eh_frame_len)) fde_entry_header_len = 12;
|
||||
|
||||
// Even if eh_frame_len is not specified, all ranges accssed are checked by isValidPtr
|
||||
const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse math.maxInt(u32)];
|
||||
|
||||
const fde_offset = fde_ptr - self.eh_frame_ptr;
|
||||
var eh_frame_stream = io.fixedBufferStream(eh_frame);
|
||||
try eh_frame_stream.seekTo(fde_offset);
|
||||
|
||||
const fde_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian());
|
||||
if (!self.isValidPtr(@intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf();
|
||||
if (fde_entry_header.type != .fde) return badDwarf();
|
||||
|
||||
// CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable
|
||||
const cie_offset = fde_entry_header.type.fde;
|
||||
try eh_frame_stream.seekTo(cie_offset);
|
||||
const cie_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian());
|
||||
if (!self.isValidPtr(@intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf();
|
||||
if (cie_entry_header.type != .cie) return badDwarf();
|
||||
|
||||
cie.* = try CommonInformationEntry.parse(
|
||||
cie_entry_header.entry_bytes,
|
||||
0,
|
||||
true,
|
||||
cie_entry_header.is_64,
|
||||
.eh_frame,
|
||||
cie_entry_header.length_offset,
|
||||
@sizeOf(usize),
|
||||
builtin.cpu.arch.endian(),
|
||||
);
|
||||
|
||||
fde.* = try FrameDescriptionEntry.parse(
|
||||
fde_entry_header.entry_bytes,
|
||||
0,
|
||||
true,
|
||||
cie.*,
|
||||
@sizeOf(usize),
|
||||
builtin.cpu.arch.endian(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
pub const EntryHeader = struct {
|
||||
/// Offset of the length field in the backing buffer
|
||||
length_offset: usize,
|
||||
is_64: bool,
|
||||
type: union(enum) {
|
||||
cie,
|
||||
/// Value is the offset of the corresponding CIE
|
||||
fde: u64,
|
||||
terminator: void,
|
||||
},
|
||||
/// The entry's contents, not including the ID field
|
||||
entry_bytes: []const u8,
|
||||
|
||||
/// Reads a header for either an FDE or a CIE, then advances the stream to the position after the trailing structure.
|
||||
/// `stream` must be a stream backed by either the .eh_frame or .debug_frame sections.
|
||||
pub fn read(stream: *std.io.FixedBufferStream([]const u8), dwarf_section: DwarfSection, endian: std.builtin.Endian) !EntryHeader {
|
||||
assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame);
|
||||
|
||||
const reader = stream.reader();
|
||||
const length_offset = stream.pos;
|
||||
|
||||
var is_64: bool = undefined;
|
||||
const length = math.cast(usize, try readUnitLength(reader, endian, &is_64)) orelse return badDwarf();
|
||||
if (length == 0) return .{
|
||||
.length_offset = length_offset,
|
||||
.is_64 = is_64,
|
||||
.type = .{ .terminator = {} },
|
||||
.entry_bytes = &.{},
|
||||
};
|
||||
|
||||
const id_len = @as(u8, if (is_64) 8 else 4);
|
||||
const id = if (is_64) try reader.readInt(u64, endian) else try reader.readInt(u32, endian);
|
||||
const entry_bytes = stream.buffer[stream.pos..][0 .. length - id_len];
|
||||
const cie_id: u64 = switch (dwarf_section) {
|
||||
.eh_frame => CommonInformationEntry.eh_id,
|
||||
.debug_frame => if (is_64) CommonInformationEntry.dwarf64_id else CommonInformationEntry.dwarf32_id,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
const result = EntryHeader{
|
||||
.length_offset = length_offset,
|
||||
.is_64 = is_64,
|
||||
.type = if (id == cie_id) .{ .cie = {} } else .{
|
||||
.fde = switch (dwarf_section) {
|
||||
.eh_frame => try std.math.sub(u64, stream.pos - id_len, id),
|
||||
.debug_frame => id,
|
||||
else => unreachable,
|
||||
},
|
||||
},
|
||||
.entry_bytes = entry_bytes,
|
||||
};
|
||||
|
||||
stream.pos += entry_bytes.len;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// The length of the entry including the ID field, but not the length field itself
|
||||
pub fn entryLength(self: EntryHeader) usize {
|
||||
return self.entry_bytes.len + @as(u8, if (self.is_64) 8 else 4);
|
||||
}
|
||||
};
|
||||
|
||||
pub const CommonInformationEntry = struct {
|
||||
// Used in .eh_frame
|
||||
pub const eh_id = 0;
|
||||
|
||||
// Used in .debug_frame (DWARF32)
|
||||
pub const dwarf32_id = math.maxInt(u32);
|
||||
|
||||
// Used in .debug_frame (DWARF64)
|
||||
pub const dwarf64_id = math.maxInt(u64);
|
||||
|
||||
// Offset of the length field of this entry in the eh_frame section.
|
||||
// This is the key that FDEs use to reference CIEs.
|
||||
length_offset: u64,
|
||||
version: u8,
|
||||
address_size: u8,
|
||||
is_64: bool,
|
||||
|
||||
// Only present in version 4
|
||||
segment_selector_size: ?u8,
|
||||
|
||||
code_alignment_factor: u32,
|
||||
data_alignment_factor: i32,
|
||||
return_address_register: u8,
|
||||
|
||||
aug_str: []const u8,
|
||||
aug_data: []const u8,
|
||||
lsda_pointer_enc: u8,
|
||||
personality_enc: ?u8,
|
||||
personality_routine_pointer: ?u64,
|
||||
fde_pointer_enc: u8,
|
||||
initial_instructions: []const u8,
|
||||
|
||||
pub fn isSignalFrame(self: CommonInformationEntry) bool {
|
||||
for (self.aug_str) |c| if (c == 'S') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool {
|
||||
for (self.aug_str) |c| if (c == 'B') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn mteTaggedFrame(self: CommonInformationEntry) bool {
|
||||
for (self.aug_str) |c| if (c == 'G') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// This function expects to read the CIE starting with the version field.
|
||||
/// The returned struct references memory backed by cie_bytes.
|
||||
///
|
||||
/// See the FrameDescriptionEntry.parse documentation for the description
|
||||
/// of `pc_rel_offset` and `is_runtime`.
|
||||
///
|
||||
/// `length_offset` specifies the offset of this CIE's length field in the
|
||||
/// .eh_frame / .debug_frame section.
|
||||
pub fn parse(
|
||||
cie_bytes: []const u8,
|
||||
pc_rel_offset: i64,
|
||||
is_runtime: bool,
|
||||
is_64: bool,
|
||||
dwarf_section: DwarfSection,
|
||||
length_offset: u64,
|
||||
addr_size_bytes: u8,
|
||||
endian: std.builtin.Endian,
|
||||
) !CommonInformationEntry {
|
||||
if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
|
||||
|
||||
var stream = io.fixedBufferStream(cie_bytes);
|
||||
const reader = stream.reader();
|
||||
|
||||
const version = try reader.readByte();
|
||||
switch (dwarf_section) {
|
||||
.eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion,
|
||||
.debug_frame => if (version != 4) return error.UnsupportedDwarfVersion,
|
||||
else => return error.UnsupportedDwarfSection,
|
||||
}
|
||||
|
||||
var has_eh_data = false;
|
||||
var has_aug_data = false;
|
||||
|
||||
var aug_str_len: usize = 0;
|
||||
var aug_str_start = stream.pos;
|
||||
var aug_byte = try reader.readByte();
|
||||
while (aug_byte != 0) : (aug_byte = try reader.readByte()) {
|
||||
switch (aug_byte) {
|
||||
'z' => {
|
||||
if (aug_str_len != 0) return badDwarf();
|
||||
has_aug_data = true;
|
||||
},
|
||||
'e' => {
|
||||
if (has_aug_data or aug_str_len != 0) return badDwarf();
|
||||
if (try reader.readByte() != 'h') return badDwarf();
|
||||
has_eh_data = true;
|
||||
},
|
||||
else => if (has_eh_data) return badDwarf(),
|
||||
}
|
||||
|
||||
aug_str_len += 1;
|
||||
}
|
||||
|
||||
if (has_eh_data) {
|
||||
// legacy data created by older versions of gcc - unsupported here
|
||||
for (0..addr_size_bytes) |_| _ = try reader.readByte();
|
||||
}
|
||||
|
||||
const address_size = if (version == 4) try reader.readByte() else addr_size_bytes;
|
||||
const segment_selector_size = if (version == 4) try reader.readByte() else null;
|
||||
|
||||
const code_alignment_factor = try leb.readULEB128(u32, reader);
|
||||
const data_alignment_factor = try leb.readILEB128(i32, reader);
|
||||
const return_address_register = if (version == 1) try reader.readByte() else try leb.readULEB128(u8, reader);
|
||||
|
||||
var lsda_pointer_enc: u8 = EH.PE.omit;
|
||||
var personality_enc: ?u8 = null;
|
||||
var personality_routine_pointer: ?u64 = null;
|
||||
var fde_pointer_enc: u8 = EH.PE.absptr;
|
||||
|
||||
var aug_data: []const u8 = &[_]u8{};
|
||||
const aug_str = if (has_aug_data) blk: {
|
||||
const aug_data_len = try leb.readULEB128(usize, reader);
|
||||
const aug_data_start = stream.pos;
|
||||
aug_data = cie_bytes[aug_data_start..][0..aug_data_len];
|
||||
|
||||
const aug_str = cie_bytes[aug_str_start..][0..aug_str_len];
|
||||
for (aug_str[1..]) |byte| {
|
||||
switch (byte) {
|
||||
'L' => {
|
||||
lsda_pointer_enc = try reader.readByte();
|
||||
},
|
||||
'P' => {
|
||||
personality_enc = try reader.readByte();
|
||||
personality_routine_pointer = try readEhPointer(
|
||||
reader,
|
||||
personality_enc.?,
|
||||
addr_size_bytes,
|
||||
.{
|
||||
.pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[stream.pos]), pc_rel_offset),
|
||||
.follow_indirect = is_runtime,
|
||||
},
|
||||
endian,
|
||||
);
|
||||
},
|
||||
'R' => {
|
||||
fde_pointer_enc = try reader.readByte();
|
||||
},
|
||||
'S', 'B', 'G' => {},
|
||||
else => return badDwarf(),
|
||||
}
|
||||
}
|
||||
|
||||
// aug_data_len can include padding so the CIE ends on an address boundary
|
||||
try stream.seekTo(aug_data_start + aug_data_len);
|
||||
break :blk aug_str;
|
||||
} else &[_]u8{};
|
||||
|
||||
const initial_instructions = cie_bytes[stream.pos..];
|
||||
return .{
|
||||
.length_offset = length_offset,
|
||||
.version = version,
|
||||
.address_size = address_size,
|
||||
.is_64 = is_64,
|
||||
.segment_selector_size = segment_selector_size,
|
||||
.code_alignment_factor = code_alignment_factor,
|
||||
.data_alignment_factor = data_alignment_factor,
|
||||
.return_address_register = return_address_register,
|
||||
.aug_str = aug_str,
|
||||
.aug_data = aug_data,
|
||||
.lsda_pointer_enc = lsda_pointer_enc,
|
||||
.personality_enc = personality_enc,
|
||||
.personality_routine_pointer = personality_routine_pointer,
|
||||
.fde_pointer_enc = fde_pointer_enc,
|
||||
.initial_instructions = initial_instructions,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const FrameDescriptionEntry = struct {
|
||||
// Offset into eh_frame where the CIE for this FDE is stored
|
||||
cie_length_offset: u64,
|
||||
|
||||
pc_begin: u64,
|
||||
pc_range: u64,
|
||||
lsda_pointer: ?u64,
|
||||
aug_data: []const u8,
|
||||
instructions: []const u8,
|
||||
|
||||
/// This function expects to read the FDE starting at the PC Begin field.
|
||||
/// The returned struct references memory backed by `fde_bytes`.
|
||||
///
|
||||
/// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values
|
||||
/// used when decoding pointers. This should be set to zero if fde_bytes is
|
||||
/// backed by the memory of a .eh_frame / .debug_frame section in the running executable.
|
||||
/// Otherwise, it should be the relative offset to translate addresses from
|
||||
/// where the section is currently stored in memory, to where it *would* be
|
||||
/// stored at runtime: section base addr - backing data base ptr.
|
||||
///
|
||||
/// Similarly, `is_runtime` specifies this function is being called on a runtime
|
||||
/// section, and so indirect pointers can be followed.
|
||||
pub fn parse(
|
||||
fde_bytes: []const u8,
|
||||
pc_rel_offset: i64,
|
||||
is_runtime: bool,
|
||||
cie: CommonInformationEntry,
|
||||
addr_size_bytes: u8,
|
||||
endian: std.builtin.Endian,
|
||||
) !FrameDescriptionEntry {
|
||||
if (addr_size_bytes > 8) return error.InvalidAddrSize;
|
||||
|
||||
var stream = io.fixedBufferStream(fde_bytes);
|
||||
const reader = stream.reader();
|
||||
|
||||
const pc_begin = try readEhPointer(
|
||||
reader,
|
||||
cie.fde_pointer_enc,
|
||||
addr_size_bytes,
|
||||
.{
|
||||
.pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[stream.pos]), pc_rel_offset),
|
||||
.follow_indirect = is_runtime,
|
||||
},
|
||||
endian,
|
||||
) orelse return badDwarf();
|
||||
|
||||
const pc_range = try readEhPointer(
|
||||
reader,
|
||||
cie.fde_pointer_enc,
|
||||
addr_size_bytes,
|
||||
.{
|
||||
.pc_rel_base = 0,
|
||||
.follow_indirect = false,
|
||||
},
|
||||
endian,
|
||||
) orelse return badDwarf();
|
||||
|
||||
var aug_data: []const u8 = &[_]u8{};
|
||||
const lsda_pointer = if (cie.aug_str.len > 0) blk: {
|
||||
const aug_data_len = try leb.readULEB128(usize, reader);
|
||||
const aug_data_start = stream.pos;
|
||||
aug_data = fde_bytes[aug_data_start..][0..aug_data_len];
|
||||
|
||||
const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit)
|
||||
try readEhPointer(
|
||||
reader,
|
||||
cie.lsda_pointer_enc,
|
||||
addr_size_bytes,
|
||||
.{
|
||||
.pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[stream.pos]), pc_rel_offset),
|
||||
.follow_indirect = is_runtime,
|
||||
},
|
||||
endian,
|
||||
)
|
||||
else
|
||||
null;
|
||||
|
||||
try stream.seekTo(aug_data_start + aug_data_len);
|
||||
break :blk lsda_pointer;
|
||||
} else null;
|
||||
|
||||
const instructions = fde_bytes[stream.pos..];
|
||||
return .{
|
||||
.cie_length_offset = cie.length_offset,
|
||||
.pc_begin = pc_begin,
|
||||
.pc_range = pc_range,
|
||||
.lsda_pointer = lsda_pointer,
|
||||
.aug_data = aug_data,
|
||||
.instructions = instructions,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {
|
||||
if (pc_rel_offset < 0) {
|
||||
return math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset)));
|
||||
} else {
|
||||
return math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset)));
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
pub const PE = struct {
|
||||
pub const absptr = 0x00;
|
||||
|
||||
pub const size_mask = 0x7;
|
||||
pub const sign_mask = 0x8;
|
||||
pub const type_mask = size_mask | sign_mask;
|
||||
|
||||
pub const uleb128 = 0x01;
|
||||
pub const udata2 = 0x02;
|
||||
pub const udata4 = 0x03;
|
||||
pub const udata8 = 0x04;
|
||||
pub const sleb128 = 0x09;
|
||||
pub const sdata2 = 0x0A;
|
||||
pub const sdata4 = 0x0B;
|
||||
pub const sdata8 = 0x0C;
|
||||
|
||||
pub const rel_mask = 0x70;
|
||||
pub const pcrel = 0x10;
|
||||
pub const textrel = 0x20;
|
||||
pub const datarel = 0x30;
|
||||
pub const funcrel = 0x40;
|
||||
pub const aligned = 0x50;
|
||||
|
||||
pub const indirect = 0x80;
|
||||
|
||||
pub const omit = 0xff;
|
||||
};
|
||||
@@ -0,0 +1,387 @@
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("../std.zig");
|
||||
const os = std.os;
|
||||
const mem = std.mem;
|
||||
|
||||
pub fn isSupportedArch(arch: std.Target.Cpu.Arch) bool {
|
||||
return switch (arch) {
|
||||
.x86,
|
||||
.x86_64,
|
||||
.arm,
|
||||
.aarch64,
|
||||
=> true,
|
||||
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 historicaly 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 (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 => 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 (builtin.os.tag) {
|
||||
.linux, .netbsd, .solaris => switch (reg_number) {
|
||||
0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EAX]),
|
||||
1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ECX]),
|
||||
2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDX]),
|
||||
3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.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[os.REG.EBP])
|
||||
else
|
||||
mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]);
|
||||
} else {
|
||||
break :bytes if (r.eh_frame and r.is_macho)
|
||||
mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP])
|
||||
else
|
||||
mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]);
|
||||
}
|
||||
} else error.RegisterContextRequired,
|
||||
6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESI]),
|
||||
7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDI]),
|
||||
8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EIP]),
|
||||
9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EFL]),
|
||||
10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.CS]),
|
||||
11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.SS]),
|
||||
12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.DS]),
|
||||
13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ES]),
|
||||
14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.FS]),
|
||||
15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.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, .netbsd, .solaris => switch (reg_number) {
|
||||
0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RAX]),
|
||||
1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDX]),
|
||||
2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RCX]),
|
||||
3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBX]),
|
||||
4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSI]),
|
||||
5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDI]),
|
||||
6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBP]),
|
||||
7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSP]),
|
||||
8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R8]),
|
||||
9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R9]),
|
||||
10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R10]),
|
||||
11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R11]),
|
||||
12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R12]),
|
||||
13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R13]),
|
||||
14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R14]),
|
||||
15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R15]),
|
||||
16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RIP]),
|
||||
17...32 => |i| 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 => 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 (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 => switch (builtin.os.tag) {
|
||||
.macos => 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,
|
||||
},
|
||||
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.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);
|
||||
}
|
||||
@@ -0,0 +1,610 @@
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("../std.zig");
|
||||
const mem = std.mem;
|
||||
const debug = std.debug;
|
||||
const leb = std.leb;
|
||||
const dwarf = std.dwarf;
|
||||
const abi = dwarf.abi;
|
||||
const expressions = dwarf.expressions;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
const Operand = enum {
|
||||
opcode_delta,
|
||||
opcode_register,
|
||||
uleb128_register,
|
||||
uleb128_offset,
|
||||
sleb128_offset,
|
||||
address,
|
||||
u8_delta,
|
||||
u16_delta,
|
||||
u32_delta,
|
||||
block,
|
||||
|
||||
fn Storage(comptime self: Operand) type {
|
||||
return switch (self) {
|
||||
.opcode_delta, .opcode_register => u8,
|
||||
.uleb128_register => u8,
|
||||
.uleb128_offset => u64,
|
||||
.sleb128_offset => i64,
|
||||
.address => u64,
|
||||
.u8_delta => u8,
|
||||
.u16_delta => u16,
|
||||
.u32_delta => u32,
|
||||
.block => []const u8,
|
||||
};
|
||||
}
|
||||
|
||||
fn read(
|
||||
comptime self: Operand,
|
||||
stream: *std.io.FixedBufferStream([]const u8),
|
||||
opcode_value: ?u6,
|
||||
addr_size_bytes: u8,
|
||||
endian: std.builtin.Endian,
|
||||
) !Storage(self) {
|
||||
const reader = stream.reader();
|
||||
return switch (self) {
|
||||
.opcode_delta, .opcode_register => opcode_value orelse return error.InvalidOperand,
|
||||
.uleb128_register => try leb.readULEB128(u8, reader),
|
||||
.uleb128_offset => try leb.readULEB128(u64, reader),
|
||||
.sleb128_offset => try leb.readILEB128(i64, reader),
|
||||
.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,
|
||||
},
|
||||
.u8_delta => try reader.readByte(),
|
||||
.u16_delta => try reader.readInt(u16, endian),
|
||||
.u32_delta => try reader.readInt(u32, endian),
|
||||
.block => {
|
||||
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;
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn InstructionType(comptime definition: anytype) type {
|
||||
const definition_type = @typeInfo(@TypeOf(definition));
|
||||
assert(definition_type == .Struct);
|
||||
|
||||
const definition_len = definition_type.Struct.fields.len;
|
||||
comptime var fields: [definition_len]std.builtin.Type.StructField = undefined;
|
||||
inline for (definition_type.Struct.fields, &fields) |definition_field, *operands_field| {
|
||||
const opcode = std.enums.nameCast(Operand, @field(definition, definition_field.name));
|
||||
const storage_type = opcode.Storage();
|
||||
operands_field.* = .{
|
||||
.name = definition_field.name,
|
||||
.type = storage_type,
|
||||
.default_value = null,
|
||||
.is_comptime = false,
|
||||
.alignment = @alignOf(storage_type),
|
||||
};
|
||||
}
|
||||
|
||||
const InstructionOperands = @Type(.{
|
||||
.Struct = .{
|
||||
.layout = .Auto,
|
||||
.fields = &fields,
|
||||
.decls = &.{},
|
||||
.is_tuple = false,
|
||||
},
|
||||
});
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
operands: InstructionOperands,
|
||||
|
||||
pub fn read(
|
||||
stream: *std.io.FixedBufferStream([]const u8),
|
||||
opcode_value: ?u6,
|
||||
addr_size_bytes: u8,
|
||||
endian: std.builtin.Endian,
|
||||
) !Self {
|
||||
var operands: InstructionOperands = undefined;
|
||||
inline for (definition_type.Struct.fields) |definition_field| {
|
||||
const operand = comptime std.enums.nameCast(Operand, @field(definition, definition_field.name));
|
||||
@field(operands, definition_field.name) = try operand.read(stream, opcode_value, addr_size_bytes, endian);
|
||||
}
|
||||
|
||||
return .{ .operands = operands };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const Instruction = union(Opcode) {
|
||||
advance_loc: InstructionType(.{ .delta = .opcode_delta }),
|
||||
offset: InstructionType(.{ .register = .opcode_register, .offset = .uleb128_offset }),
|
||||
offset_extended: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }),
|
||||
restore: InstructionType(.{ .register = .opcode_register }),
|
||||
restore_extended: InstructionType(.{ .register = .uleb128_register }),
|
||||
nop: InstructionType(.{}),
|
||||
set_loc: InstructionType(.{ .address = .address }),
|
||||
advance_loc1: InstructionType(.{ .delta = .u8_delta }),
|
||||
advance_loc2: InstructionType(.{ .delta = .u16_delta }),
|
||||
advance_loc4: InstructionType(.{ .delta = .u32_delta }),
|
||||
undefined: InstructionType(.{ .register = .uleb128_register }),
|
||||
same_value: InstructionType(.{ .register = .uleb128_register }),
|
||||
register: InstructionType(.{ .register = .uleb128_register, .target_register = .uleb128_register }),
|
||||
remember_state: InstructionType(.{}),
|
||||
restore_state: InstructionType(.{}),
|
||||
def_cfa: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }),
|
||||
def_cfa_register: InstructionType(.{ .register = .uleb128_register }),
|
||||
def_cfa_offset: InstructionType(.{ .offset = .uleb128_offset }),
|
||||
def_cfa_expression: InstructionType(.{ .block = .block }),
|
||||
expression: InstructionType(.{ .register = .uleb128_register, .block = .block }),
|
||||
offset_extended_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
|
||||
def_cfa_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
|
||||
def_cfa_offset_sf: InstructionType(.{ .offset = .sleb128_offset }),
|
||||
val_offset: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }),
|
||||
val_offset_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }),
|
||||
val_expression: InstructionType(.{ .register = .uleb128_register, .block = .block }),
|
||||
|
||||
fn readOperands(
|
||||
self: *Instruction,
|
||||
stream: *std.io.FixedBufferStream([]const u8),
|
||||
opcode_value: ?u6,
|
||||
addr_size_bytes: u8,
|
||||
endian: std.builtin.Endian,
|
||||
) !void {
|
||||
switch (self.*) {
|
||||
inline else => |*inst| inst.* = try @TypeOf(inst.*).read(stream, opcode_value, addr_size_bytes, endian),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
stream: *std.io.FixedBufferStream([]const u8),
|
||||
addr_size_bytes: u8,
|
||||
endian: std.builtin.Endian,
|
||||
) !Instruction {
|
||||
return switch (try stream.reader().readByte()) {
|
||||
inline Opcode.lo_inline...Opcode.hi_inline => |opcode| blk: {
|
||||
const e: Opcode = @enumFromInt(opcode & 0b11000000);
|
||||
var result = @unionInit(Instruction, @tagName(e), undefined);
|
||||
try result.readOperands(stream, @as(u6, @intCast(opcode & 0b111111)), addr_size_bytes, endian);
|
||||
break :blk result;
|
||||
},
|
||||
inline Opcode.lo_reserved...Opcode.hi_reserved => |opcode| blk: {
|
||||
const e: Opcode = @enumFromInt(opcode);
|
||||
var result = @unionInit(Instruction, @tagName(e), undefined);
|
||||
try result.readOperands(stream, null, addr_size_bytes, endian);
|
||||
break :blk result;
|
||||
},
|
||||
Opcode.lo_user...Opcode.hi_user => error.UnimplementedUserOpcode,
|
||||
else => 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: *dwarf.UnwindContext,
|
||||
expression_context: dwarf.expressions.ExpressionContext,
|
||||
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 (expression_context.isValidMemory) |isValidMemory| if (!isValidMemory(addr)) return error.InvalidAddress;
|
||||
const ptr: *const usize = @ptrFromInt(addr);
|
||||
mem.writeIntSliceNative(usize, out, ptr.*);
|
||||
} else return error.InvalidCFA;
|
||||
},
|
||||
.val_offset => |offset| {
|
||||
if (context.cfa) |cfa| {
|
||||
mem.writeIntSliceNative(usize, out, try applyOffset(cfa, offset));
|
||||
} 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 (!context.isValidMemory(addr)) return error.InvalidExpressionAddress;
|
||||
const ptr: *usize = @ptrFromInt(addr);
|
||||
mem.writeIntSliceNative(usize, out, ptr.*);
|
||||
},
|
||||
.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.writeIntSliceNative(usize, out, v.generic);
|
||||
} 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 {
|
||||
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: dwarf.CommonInformationEntry,
|
||||
fde: 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 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: dwarf.CommonInformationEntry,
|
||||
fde: 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: 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.operands.address <= self.current_row.offset) return error.InvalidOperation;
|
||||
// TODO: Check cie.segment_selector_size != 0 for DWARFV4
|
||||
self.current_row.offset = i.operands.address;
|
||||
},
|
||||
inline .advance_loc,
|
||||
.advance_loc1,
|
||||
.advance_loc2,
|
||||
.advance_loc4,
|
||||
=> |i| {
|
||||
self.current_row.offset += i.operands.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.operands.register);
|
||||
column.rule = .{ .offset = @as(i64, @intCast(i.operands.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.operands.register);
|
||||
column.rule = for (self.rowColumns(cie_row)) |cie_column| {
|
||||
if (cie_column.register == i.operands.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.operands.register);
|
||||
column.rule = .{ .undefined = {} };
|
||||
},
|
||||
.same_value => |i| {
|
||||
try self.resolveCopyOnWrite(allocator);
|
||||
const column = try self.getOrAddColumn(allocator, i.operands.register);
|
||||
column.rule = .{ .same_value = {} };
|
||||
},
|
||||
.register => |i| {
|
||||
try self.resolveCopyOnWrite(allocator);
|
||||
const column = try self.getOrAddColumn(allocator, i.operands.register);
|
||||
column.rule = .{ .register = i.operands.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.operands.register,
|
||||
.rule = .{ .val_offset = @intCast(i.operands.offset) },
|
||||
};
|
||||
},
|
||||
.def_cfa_sf => |i| {
|
||||
try self.resolveCopyOnWrite(allocator);
|
||||
self.current_row.cfa = .{
|
||||
.register = i.operands.register,
|
||||
.rule = .{ .val_offset = i.operands.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.operands.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.operands.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.operands.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.operands.block,
|
||||
};
|
||||
},
|
||||
.expression => |i| {
|
||||
try self.resolveCopyOnWrite(allocator);
|
||||
const column = try self.getOrAddColumn(allocator, i.operands.register);
|
||||
column.rule = .{
|
||||
.expression = i.operands.block,
|
||||
};
|
||||
},
|
||||
.val_offset => |i| {
|
||||
try self.resolveCopyOnWrite(allocator);
|
||||
const column = try self.getOrAddColumn(allocator, i.operands.register);
|
||||
column.rule = .{
|
||||
.val_offset = @as(i64, @intCast(i.operands.offset)) * cie.data_alignment_factor,
|
||||
};
|
||||
},
|
||||
.val_offset_sf => |i| {
|
||||
try self.resolveCopyOnWrite(allocator);
|
||||
const column = try self.getOrAddColumn(allocator, i.operands.register);
|
||||
column.rule = .{
|
||||
.val_offset = i.operands.offset * cie.data_alignment_factor,
|
||||
};
|
||||
},
|
||||
.val_expression => |i| {
|
||||
try self.resolveCopyOnWrite(allocator);
|
||||
const column = try self.getOrAddColumn(allocator, i.operands.register);
|
||||
column.rule = .{
|
||||
.val_expression = i.operands.block,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
return prev_row;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,1639 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const OP = @import("OP.zig");
|
||||
const leb = std.leb;
|
||||
const dwarf = std.dwarf;
|
||||
const abi = dwarf.abi;
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
/// 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.
|
||||
pub const ExpressionContext = struct {
|
||||
/// This expression is from a DWARF64 section
|
||||
is_64: bool = false,
|
||||
|
||||
/// If specified, any addresses will pass through this function before being acccessed
|
||||
isValidMemory: ?*const fn (address: usize) bool = null,
|
||||
|
||||
/// The compilation unit this expression relates to, if any
|
||||
compile_unit: ?*const dwarf.CompileUnit = null,
|
||||
|
||||
/// When evaluating a user-presented expression, this is the address of the object being evaluated
|
||||
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,
|
||||
|
||||
/// Call frame address, if in a CFI context
|
||||
cfa: ?usize = null,
|
||||
|
||||
/// This expression is a sub-expression from an OP.entry_value instruction
|
||||
entry_value_context: bool = false,
|
||||
};
|
||||
|
||||
pub const ExpressionOptions = struct {
|
||||
/// The address size of the target architecture
|
||||
addr_size: u8 = @sizeOf(usize),
|
||||
|
||||
/// Endianess of the target architecture
|
||||
endian: std.builtin.Endian = builtin.target.cpu.arch.endian(),
|
||||
|
||||
/// Restrict the stack machine to a subset of opcodes used in call frame instructions
|
||||
call_frame_context: bool = false,
|
||||
};
|
||||
|
||||
// Explcitly defined to support executing sub-expressions
|
||||
pub const ExpressionError = error{
|
||||
UnimplementedExpressionCall,
|
||||
UnimplementedOpcode,
|
||||
UnimplementedUserOpcode,
|
||||
UnimplementedTypedComparison,
|
||||
UnimplementedTypeConversion,
|
||||
|
||||
UnknownExpressionOpcode,
|
||||
|
||||
IncompleteExpressionContext,
|
||||
|
||||
InvalidCFAOpcode,
|
||||
InvalidExpression,
|
||||
InvalidFrameBase,
|
||||
InvalidIntegralTypeSize,
|
||||
InvalidRegister,
|
||||
InvalidSubExpression,
|
||||
InvalidTypeLength,
|
||||
|
||||
TruncatedIntegralType,
|
||||
} || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero };
|
||||
|
||||
/// A stack machine that can decode and run DWARF expressions.
|
||||
/// Expressions can be decoded for non-native address size and endianness,
|
||||
/// but can only be executed if the current target matches the configuration.
|
||||
pub fn StackMachine(comptime options: ExpressionOptions) type {
|
||||
const addr_type = switch (options.addr_size) {
|
||||
2 => u16,
|
||||
4 => u32,
|
||||
8 => u64,
|
||||
else => @compileError("Unsupported address size of " ++ options.addr_size),
|
||||
};
|
||||
|
||||
const addr_type_signed = switch (options.addr_size) {
|
||||
2 => i16,
|
||||
4 => i32,
|
||||
8 => i64,
|
||||
else => @compileError("Unsupported address size of " ++ options.addr_size),
|
||||
};
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
const Operand = union(enum) {
|
||||
generic: addr_type,
|
||||
register: u8,
|
||||
type_size: u8,
|
||||
branch_offset: i16,
|
||||
base_register: struct {
|
||||
base_register: u8,
|
||||
offset: i64,
|
||||
},
|
||||
composite_location: struct {
|
||||
size: u64,
|
||||
offset: i64,
|
||||
},
|
||||
block: []const u8,
|
||||
register_type: struct {
|
||||
register: u8,
|
||||
type_offset: addr_type,
|
||||
},
|
||||
const_type: struct {
|
||||
type_offset: addr_type,
|
||||
value_bytes: []const u8,
|
||||
},
|
||||
deref_type: struct {
|
||||
size: u8,
|
||||
type_offset: addr_type,
|
||||
},
|
||||
};
|
||||
|
||||
const Value = union(enum) {
|
||||
generic: addr_type,
|
||||
|
||||
// Typed value with a maximum size of a register
|
||||
regval_type: struct {
|
||||
// Offset of DW_TAG_base_type DIE
|
||||
type_offset: addr_type,
|
||||
type_size: u8,
|
||||
value: addr_type,
|
||||
},
|
||||
|
||||
// Typed value specified directly in the instruction stream
|
||||
const_type: struct {
|
||||
// Offset of DW_TAG_base_type DIE
|
||||
type_offset: addr_type,
|
||||
// Backed by the instruction stream
|
||||
value_bytes: []const u8,
|
||||
},
|
||||
|
||||
pub fn asIntegral(self: Value) !addr_type {
|
||||
return switch (self) {
|
||||
.generic => |v| v,
|
||||
|
||||
// TODO: For these two prongs, look up the type and assert it's integral?
|
||||
.regval_type => |regval_type| regval_type.value,
|
||||
.const_type => |const_type| {
|
||||
const value: u64 = switch (const_type.value_bytes.len) {
|
||||
1 => mem.readIntSliceNative(u8, const_type.value_bytes),
|
||||
2 => mem.readIntSliceNative(u16, const_type.value_bytes),
|
||||
4 => mem.readIntSliceNative(u32, const_type.value_bytes),
|
||||
8 => mem.readIntSliceNative(u64, const_type.value_bytes),
|
||||
else => return error.InvalidIntegralTypeSize,
|
||||
};
|
||||
|
||||
return std.math.cast(addr_type, value) orelse error.TruncatedIntegralType;
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
stack: std.ArrayListUnmanaged(Value) = .{},
|
||||
|
||||
pub fn reset(self: *Self) void {
|
||||
self.stack.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
|
||||
self.stack.deinit(allocator);
|
||||
}
|
||||
|
||||
fn generic(value: anytype) Operand {
|
||||
const int_info = @typeInfo(@TypeOf(value)).Int;
|
||||
if (@sizeOf(@TypeOf(value)) > options.addr_size) {
|
||||
return .{ .generic = switch (int_info.signedness) {
|
||||
.signed => @bitCast(@as(addr_type_signed, @truncate(value))),
|
||||
.unsigned => @truncate(value),
|
||||
} };
|
||||
} else {
|
||||
return .{ .generic = switch (int_info.signedness) {
|
||||
.signed => @bitCast(@as(addr_type_signed, @intCast(value))),
|
||||
.unsigned => @intCast(value),
|
||||
} };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: ExpressionContext) !?Operand {
|
||||
const reader = stream.reader();
|
||||
return switch (opcode) {
|
||||
OP.addr => generic(try reader.readInt(addr_type, options.endian)),
|
||||
OP.call_ref => if (context.is_64)
|
||||
generic(try reader.readInt(u64, options.endian))
|
||||
else
|
||||
generic(try reader.readInt(u32, options.endian)),
|
||||
OP.const1u,
|
||||
OP.pick,
|
||||
=> generic(try reader.readByte()),
|
||||
OP.deref_size,
|
||||
OP.xderef_size,
|
||||
=> .{ .type_size = try reader.readByte() },
|
||||
OP.const1s => generic(try reader.readByteSigned()),
|
||||
OP.const2u,
|
||||
OP.call2,
|
||||
=> generic(try reader.readInt(u16, options.endian)),
|
||||
OP.call4 => generic(try reader.readInt(u32, options.endian)),
|
||||
OP.const2s => generic(try reader.readInt(i16, options.endian)),
|
||||
OP.bra,
|
||||
OP.skip,
|
||||
=> .{ .branch_offset = try reader.readInt(i16, options.endian) },
|
||||
OP.const4u => generic(try reader.readInt(u32, options.endian)),
|
||||
OP.const4s => generic(try reader.readInt(i32, options.endian)),
|
||||
OP.const8u => generic(try reader.readInt(u64, options.endian)),
|
||||
OP.const8s => generic(try reader.readInt(i64, options.endian)),
|
||||
OP.constu,
|
||||
OP.plus_uconst,
|
||||
OP.addrx,
|
||||
OP.constx,
|
||||
OP.convert,
|
||||
OP.reinterpret,
|
||||
=> generic(try leb.readULEB128(u64, reader)),
|
||||
OP.consts,
|
||||
OP.fbreg,
|
||||
=> generic(try leb.readILEB128(i64, reader)),
|
||||
OP.lit0...OP.lit31 => |n| generic(n - OP.lit0),
|
||||
OP.reg0...OP.reg31 => |n| .{ .register = n - OP.reg0 },
|
||||
OP.breg0...OP.breg31 => |n| .{ .base_register = .{
|
||||
.base_register = n - OP.breg0,
|
||||
.offset = try leb.readILEB128(i64, reader),
|
||||
} },
|
||||
OP.regx => .{ .register = try leb.readULEB128(u8, reader) },
|
||||
OP.bregx => blk: {
|
||||
const base_register = try leb.readULEB128(u8, reader);
|
||||
const offset = try leb.readILEB128(i64, reader);
|
||||
break :blk .{ .base_register = .{
|
||||
.base_register = base_register,
|
||||
.offset = offset,
|
||||
} };
|
||||
},
|
||||
OP.regval_type => blk: {
|
||||
const register = try leb.readULEB128(u8, reader);
|
||||
const type_offset = try leb.readULEB128(addr_type, reader);
|
||||
break :blk .{ .register_type = .{
|
||||
.register = register,
|
||||
.type_offset = type_offset,
|
||||
} };
|
||||
},
|
||||
OP.piece => .{
|
||||
.composite_location = .{
|
||||
.size = try leb.readULEB128(u8, reader),
|
||||
.offset = 0,
|
||||
},
|
||||
},
|
||||
OP.bit_piece => blk: {
|
||||
const size = try leb.readULEB128(u8, reader);
|
||||
const offset = try leb.readILEB128(i64, reader);
|
||||
break :blk .{ .composite_location = .{
|
||||
.size = size,
|
||||
.offset = offset,
|
||||
} };
|
||||
},
|
||||
OP.implicit_value, OP.entry_value => blk: {
|
||||
const size = try leb.readULEB128(u8, reader);
|
||||
if (stream.pos + size > stream.buffer.len) return error.InvalidExpression;
|
||||
const block = stream.buffer[stream.pos..][0..size];
|
||||
stream.pos += size;
|
||||
break :blk .{
|
||||
.block = block,
|
||||
};
|
||||
},
|
||||
OP.const_type => blk: {
|
||||
const type_offset = try leb.readULEB128(addr_type, reader);
|
||||
const size = try reader.readByte();
|
||||
if (stream.pos + size > stream.buffer.len) return error.InvalidExpression;
|
||||
const value_bytes = stream.buffer[stream.pos..][0..size];
|
||||
stream.pos += size;
|
||||
break :blk .{ .const_type = .{
|
||||
.type_offset = type_offset,
|
||||
.value_bytes = value_bytes,
|
||||
} };
|
||||
},
|
||||
OP.deref_type,
|
||||
OP.xderef_type,
|
||||
=> .{
|
||||
.deref_type = .{
|
||||
.size = try reader.readByte(),
|
||||
.type_offset = try leb.readULEB128(addr_type, reader),
|
||||
},
|
||||
},
|
||||
OP.lo_user...OP.hi_user => return error.UnimplementedUserOpcode,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
self: *Self,
|
||||
expression: []const u8,
|
||||
allocator: std.mem.Allocator,
|
||||
context: ExpressionContext,
|
||||
initial_value: ?usize,
|
||||
) ExpressionError!?Value {
|
||||
if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i });
|
||||
var stream = std.io.fixedBufferStream(expression);
|
||||
while (try self.step(&stream, allocator, context)) {}
|
||||
if (self.stack.items.len == 0) return null;
|
||||
return self.stack.items[self.stack.items.len - 1];
|
||||
}
|
||||
|
||||
/// Reads an opcode and its operands from `stream`, then executes it
|
||||
pub fn step(
|
||||
self: *Self,
|
||||
stream: *std.io.FixedBufferStream([]const u8),
|
||||
allocator: std.mem.Allocator,
|
||||
context: ExpressionContext,
|
||||
) ExpressionError!bool {
|
||||
if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian())
|
||||
@compileError("Execution of non-native address sizes / endianness is not supported");
|
||||
|
||||
const opcode = try stream.reader().readByte();
|
||||
if (options.call_frame_context and !isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode;
|
||||
switch (opcode) {
|
||||
|
||||
// 2.5.1.1: Literal Encodings
|
||||
OP.lit0...OP.lit31,
|
||||
OP.addr,
|
||||
OP.const1u,
|
||||
OP.const2u,
|
||||
OP.const4u,
|
||||
OP.const8u,
|
||||
OP.const1s,
|
||||
OP.const2s,
|
||||
OP.const4s,
|
||||
OP.const8s,
|
||||
OP.constu,
|
||||
OP.consts,
|
||||
=> try self.stack.append(allocator, .{ .generic = (try readOperand(stream, opcode, context)).?.generic }),
|
||||
|
||||
OP.const_type => {
|
||||
const const_type = (try readOperand(stream, opcode, context)).?.const_type;
|
||||
try self.stack.append(allocator, .{ .const_type = .{
|
||||
.type_offset = const_type.type_offset,
|
||||
.value_bytes = const_type.value_bytes,
|
||||
} });
|
||||
},
|
||||
|
||||
OP.addrx,
|
||||
OP.constx,
|
||||
=> {
|
||||
if (context.compile_unit == null) return error.IncompleteExpressionContext;
|
||||
if (context.debug_addr == null) return error.IncompleteExpressionContext;
|
||||
const debug_addr_index = (try readOperand(stream, opcode, context)).?.generic;
|
||||
const offset = context.compile_unit.?.addr_base + debug_addr_index;
|
||||
if (offset >= context.debug_addr.?.len) return error.InvalidExpression;
|
||||
const value = mem.readIntSliceNative(usize, context.debug_addr.?[offset..][0..@sizeOf(usize)]);
|
||||
try self.stack.append(allocator, .{ .generic = value });
|
||||
},
|
||||
|
||||
// 2.5.1.2: Register Values
|
||||
OP.fbreg => {
|
||||
if (context.compile_unit == null) return error.IncompleteExpressionContext;
|
||||
if (context.compile_unit.?.frame_base == null) return error.IncompleteExpressionContext;
|
||||
|
||||
const offset: i64 = @intCast((try readOperand(stream, opcode, context)).?.generic);
|
||||
_ = offset;
|
||||
|
||||
switch (context.compile_unit.?.frame_base.?.*) {
|
||||
.ExprLoc => {
|
||||
// TODO: Run this expression in a nested stack machine
|
||||
return error.UnimplementedOpcode;
|
||||
},
|
||||
.LocListOffset => {
|
||||
// TODO: Read value from .debug_loclists
|
||||
return error.UnimplementedOpcode;
|
||||
},
|
||||
.SecOffset => {
|
||||
// TODO: Read value from .debug_loclists
|
||||
return error.UnimplementedOpcode;
|
||||
},
|
||||
else => return error.InvalidFrameBase,
|
||||
}
|
||||
},
|
||||
OP.breg0...OP.breg31,
|
||||
OP.bregx,
|
||||
=> {
|
||||
if (context.thread_context == null) return error.IncompleteExpressionContext;
|
||||
|
||||
const base_register = (try readOperand(stream, opcode, context)).?.base_register;
|
||||
var value: i64 = @intCast(mem.readIntSliceNative(usize, try abi.regBytes(
|
||||
context.thread_context.?,
|
||||
base_register.base_register,
|
||||
context.reg_context,
|
||||
)));
|
||||
value += base_register.offset;
|
||||
try self.stack.append(allocator, .{ .generic = @intCast(value) });
|
||||
},
|
||||
OP.regval_type => {
|
||||
const register_type = (try readOperand(stream, opcode, context)).?.register_type;
|
||||
const value = mem.readIntSliceNative(usize, try abi.regBytes(
|
||||
context.thread_context.?,
|
||||
register_type.register,
|
||||
context.reg_context,
|
||||
));
|
||||
try self.stack.append(allocator, .{
|
||||
.regval_type = .{
|
||||
.type_offset = register_type.type_offset,
|
||||
.type_size = @sizeOf(addr_type),
|
||||
.value = value,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 2.5.1.3: Stack Operations
|
||||
OP.dup => {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1]);
|
||||
},
|
||||
OP.drop => {
|
||||
_ = self.stack.pop();
|
||||
},
|
||||
OP.pick, OP.over => {
|
||||
const stack_index = if (opcode == OP.over) 1 else (try readOperand(stream, opcode, context)).?.generic;
|
||||
if (stack_index >= self.stack.items.len) return error.InvalidExpression;
|
||||
try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1 - stack_index]);
|
||||
},
|
||||
OP.swap => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
mem.swap(Value, &self.stack.items[self.stack.items.len - 1], &self.stack.items[self.stack.items.len - 2]);
|
||||
},
|
||||
OP.rot => {
|
||||
if (self.stack.items.len < 3) return error.InvalidExpression;
|
||||
const first = self.stack.items[self.stack.items.len - 1];
|
||||
self.stack.items[self.stack.items.len - 1] = self.stack.items[self.stack.items.len - 2];
|
||||
self.stack.items[self.stack.items.len - 2] = self.stack.items[self.stack.items.len - 3];
|
||||
self.stack.items[self.stack.items.len - 3] = first;
|
||||
},
|
||||
OP.deref,
|
||||
OP.xderef,
|
||||
OP.deref_size,
|
||||
OP.xderef_size,
|
||||
OP.deref_type,
|
||||
OP.xderef_type,
|
||||
=> {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
var addr = try self.stack.items[self.stack.items.len - 1].asIntegral();
|
||||
const addr_space_identifier: ?usize = switch (opcode) {
|
||||
OP.xderef,
|
||||
OP.xderef_size,
|
||||
OP.xderef_type,
|
||||
=> blk: {
|
||||
_ = self.stack.pop();
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
break :blk try self.stack.items[self.stack.items.len - 1].asIntegral();
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
|
||||
// Usage of addr_space_identifier in the address calculation is implementation defined.
|
||||
// This code will need to be updated to handle any architectures that utilize this.
|
||||
_ = addr_space_identifier;
|
||||
|
||||
if (context.isValidMemory) |isValidMemory| if (!isValidMemory(addr)) return error.InvalidExpression;
|
||||
|
||||
const operand = try readOperand(stream, opcode, context);
|
||||
const size = switch (opcode) {
|
||||
OP.deref,
|
||||
OP.xderef,
|
||||
=> @sizeOf(addr_type),
|
||||
OP.deref_size,
|
||||
OP.xderef_size,
|
||||
=> operand.?.type_size,
|
||||
OP.deref_type,
|
||||
OP.xderef_type,
|
||||
=> operand.?.deref_type.size,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
const value: addr_type = std.math.cast(addr_type, @as(u64, switch (size) {
|
||||
1 => @as(*const u8, @ptrFromInt(addr)).*,
|
||||
2 => @as(*const u16, @ptrFromInt(addr)).*,
|
||||
4 => @as(*const u32, @ptrFromInt(addr)).*,
|
||||
8 => @as(*const u64, @ptrFromInt(addr)).*,
|
||||
else => return error.InvalidExpression,
|
||||
})) orelse return error.InvalidExpression;
|
||||
|
||||
switch (opcode) {
|
||||
OP.deref_type,
|
||||
OP.xderef_type,
|
||||
=> {
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.regval_type = .{
|
||||
.type_offset = operand.?.deref_type.type_offset,
|
||||
.type_size = operand.?.deref_type.size,
|
||||
.value = value,
|
||||
},
|
||||
};
|
||||
},
|
||||
else => {
|
||||
self.stack.items[self.stack.items.len - 1] = .{ .generic = value };
|
||||
},
|
||||
}
|
||||
},
|
||||
OP.push_object_address => {
|
||||
// In sub-expressions, `push_object_address` is not meaningful (as per the
|
||||
// spec), so treat it like a nop
|
||||
if (!context.entry_value_context) {
|
||||
if (context.object_address == null) return error.IncompleteExpressionContext;
|
||||
try self.stack.append(allocator, .{ .generic = @intFromPtr(context.object_address.?) });
|
||||
}
|
||||
},
|
||||
OP.form_tls_address => {
|
||||
return error.UnimplementedOpcode;
|
||||
},
|
||||
OP.call_frame_cfa => {
|
||||
if (context.cfa) |cfa| {
|
||||
try self.stack.append(allocator, .{ .generic = cfa });
|
||||
} else return error.IncompleteExpressionContext;
|
||||
},
|
||||
|
||||
// 2.5.1.4: Arithmetic and Logical Operations
|
||||
OP.abs => {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
const value: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = std.math.absCast(value),
|
||||
};
|
||||
},
|
||||
OP.@"and" => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a = try self.stack.pop().asIntegral();
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = a & try self.stack.items[self.stack.items.len - 1].asIntegral(),
|
||||
};
|
||||
},
|
||||
OP.div => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a: isize = @bitCast(try self.stack.pop().asIntegral());
|
||||
const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = @bitCast(try std.math.divTrunc(isize, b, a)),
|
||||
};
|
||||
},
|
||||
OP.minus => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const b = try self.stack.pop().asIntegral();
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = try std.math.sub(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b),
|
||||
};
|
||||
},
|
||||
OP.mod => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a: isize = @bitCast(try self.stack.pop().asIntegral());
|
||||
const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = @bitCast(@mod(b, a)),
|
||||
};
|
||||
},
|
||||
OP.mul => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a: isize = @bitCast(try self.stack.pop().asIntegral());
|
||||
const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = @bitCast(@mulWithOverflow(a, b)[0]),
|
||||
};
|
||||
},
|
||||
OP.neg => {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = @bitCast(
|
||||
try std.math.negate(
|
||||
@as(isize, @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral())),
|
||||
),
|
||||
),
|
||||
};
|
||||
},
|
||||
OP.not => {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = ~try self.stack.items[self.stack.items.len - 1].asIntegral(),
|
||||
};
|
||||
},
|
||||
OP.@"or" => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a = try self.stack.pop().asIntegral();
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = a | try self.stack.items[self.stack.items.len - 1].asIntegral(),
|
||||
};
|
||||
},
|
||||
OP.plus => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const b = try self.stack.pop().asIntegral();
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b),
|
||||
};
|
||||
},
|
||||
OP.plus_uconst => {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
const constant = (try readOperand(stream, opcode, context)).?.generic;
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), constant),
|
||||
};
|
||||
},
|
||||
OP.shl => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a = try self.stack.pop().asIntegral();
|
||||
const b = try self.stack.items[self.stack.items.len - 1].asIntegral();
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = std.math.shl(usize, b, a),
|
||||
};
|
||||
},
|
||||
OP.shr => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a = try self.stack.pop().asIntegral();
|
||||
const b = try self.stack.items[self.stack.items.len - 1].asIntegral();
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = std.math.shr(usize, b, a),
|
||||
};
|
||||
},
|
||||
OP.shra => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a = try self.stack.pop().asIntegral();
|
||||
const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = @bitCast(std.math.shr(isize, b, a)),
|
||||
};
|
||||
},
|
||||
OP.xor => {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a = try self.stack.pop().asIntegral();
|
||||
self.stack.items[self.stack.items.len - 1] = .{
|
||||
.generic = a ^ try self.stack.items[self.stack.items.len - 1].asIntegral(),
|
||||
};
|
||||
},
|
||||
|
||||
// 2.5.1.5: Control Flow Operations
|
||||
OP.le,
|
||||
OP.ge,
|
||||
OP.eq,
|
||||
OP.lt,
|
||||
OP.gt,
|
||||
OP.ne,
|
||||
=> {
|
||||
if (self.stack.items.len < 2) return error.InvalidExpression;
|
||||
const a = self.stack.pop();
|
||||
const b = self.stack.items[self.stack.items.len - 1];
|
||||
|
||||
if (a == .generic and b == .generic) {
|
||||
const a_int: isize = @bitCast(a.asIntegral() catch unreachable);
|
||||
const b_int: isize = @bitCast(b.asIntegral() catch unreachable);
|
||||
const result = @intFromBool(switch (opcode) {
|
||||
OP.le => b_int <= a_int,
|
||||
OP.ge => b_int >= a_int,
|
||||
OP.eq => b_int == a_int,
|
||||
OP.lt => b_int < a_int,
|
||||
OP.gt => b_int > a_int,
|
||||
OP.ne => b_int != a_int,
|
||||
else => unreachable,
|
||||
});
|
||||
|
||||
self.stack.items[self.stack.items.len - 1] = .{ .generic = result };
|
||||
} else {
|
||||
// TODO: Load the types referenced by these values, find their comparison operator, and run it
|
||||
return error.UnimplementedTypedComparison;
|
||||
}
|
||||
},
|
||||
OP.skip, OP.bra => {
|
||||
const branch_offset = (try readOperand(stream, opcode, context)).?.branch_offset;
|
||||
const condition = if (opcode == OP.bra) blk: {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
break :blk try self.stack.pop().asIntegral() != 0;
|
||||
} else true;
|
||||
|
||||
if (condition) {
|
||||
const new_pos = std.math.cast(
|
||||
usize,
|
||||
try std.math.add(isize, @as(isize, @intCast(stream.pos)), branch_offset),
|
||||
) orelse return error.InvalidExpression;
|
||||
|
||||
if (new_pos < 0 or new_pos > stream.buffer.len) return error.InvalidExpression;
|
||||
stream.pos = new_pos;
|
||||
}
|
||||
},
|
||||
OP.call2,
|
||||
OP.call4,
|
||||
OP.call_ref,
|
||||
=> {
|
||||
const debug_info_offset = (try readOperand(stream, opcode, context)).?.generic;
|
||||
_ = debug_info_offset;
|
||||
|
||||
// TODO: Load a DIE entry at debug_info_offset in a .debug_info section (the spec says that it
|
||||
// can be in a separate exe / shared object from the one containing this expression).
|
||||
// Transfer control to the DW_AT_location attribute, with the current stack as input.
|
||||
|
||||
return error.UnimplementedExpressionCall;
|
||||
},
|
||||
|
||||
// 2.5.1.6: Type Conversions
|
||||
OP.convert => {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
const type_offset = (try readOperand(stream, opcode, context)).?.generic;
|
||||
|
||||
// TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size
|
||||
const value = self.stack.items[self.stack.items.len - 1];
|
||||
if (type_offset == 0) {
|
||||
self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() };
|
||||
} else {
|
||||
// TODO: Load the DW_TAG_base_type entry in context.compile_unit, find a conversion operator
|
||||
// from the old type to the new type, run it.
|
||||
return error.UnimplementedTypeConversion;
|
||||
}
|
||||
},
|
||||
OP.reinterpret => {
|
||||
if (self.stack.items.len == 0) return error.InvalidExpression;
|
||||
const type_offset = (try readOperand(stream, opcode, context)).?.generic;
|
||||
|
||||
// TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size
|
||||
const value = self.stack.items[self.stack.items.len - 1];
|
||||
if (type_offset == 0) {
|
||||
self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() };
|
||||
} else {
|
||||
self.stack.items[self.stack.items.len - 1] = switch (value) {
|
||||
.generic => |v| .{
|
||||
.regval_type = .{
|
||||
.type_offset = type_offset,
|
||||
.type_size = @sizeOf(addr_type),
|
||||
.value = v,
|
||||
},
|
||||
},
|
||||
.regval_type => |r| .{
|
||||
.regval_type = .{
|
||||
.type_offset = type_offset,
|
||||
.type_size = r.type_size,
|
||||
.value = r.value,
|
||||
},
|
||||
},
|
||||
.const_type => |c| .{
|
||||
.const_type = .{
|
||||
.type_offset = type_offset,
|
||||
.value_bytes = c.value_bytes,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 2.5.1.7: Special Operations
|
||||
OP.nop => {},
|
||||
OP.entry_value => {
|
||||
const block = (try readOperand(stream, opcode, context)).?.block;
|
||||
if (block.len == 0) return error.InvalidSubExpression;
|
||||
|
||||
// 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.
|
||||
|
||||
if (isOpcodeRegisterLocation(block[0])) {
|
||||
if (context.thread_context == null) return error.IncompleteExpressionContext;
|
||||
|
||||
var block_stream = std.io.fixedBufferStream(block);
|
||||
const register = (try readOperand(&block_stream, block[0], context)).?.register;
|
||||
const value = mem.readIntSliceNative(usize, try abi.regBytes(context.thread_context.?, register, context.reg_context));
|
||||
try self.stack.append(allocator, .{ .generic = value });
|
||||
} else {
|
||||
var stack_machine: Self = .{};
|
||||
defer stack_machine.deinit(allocator);
|
||||
|
||||
var sub_context = context;
|
||||
sub_context.entry_value_context = true;
|
||||
const result = try stack_machine.run(block, allocator, sub_context, null);
|
||||
try self.stack.append(allocator, result orelse return error.InvalidSubExpression);
|
||||
}
|
||||
},
|
||||
|
||||
// These have already been handled by readOperand
|
||||
OP.lo_user...OP.hi_user => unreachable,
|
||||
else => {
|
||||
//std.debug.print("Unknown DWARF expression opcode: {x}\n", .{opcode});
|
||||
return error.UnknownExpressionOpcode;
|
||||
},
|
||||
}
|
||||
|
||||
return stream.pos < stream.buffer.len;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Builder(comptime options: ExpressionOptions) type {
|
||||
const addr_type = switch (options.addr_size) {
|
||||
2 => u16,
|
||||
4 => u32,
|
||||
8 => u64,
|
||||
else => @compileError("Unsupported address size of " ++ options.addr_size),
|
||||
};
|
||||
|
||||
return struct {
|
||||
/// Zero-operand instructions
|
||||
pub fn writeOpcode(writer: anytype, comptime opcode: u8) !void {
|
||||
if (options.call_frame_context and !comptime isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode;
|
||||
switch (opcode) {
|
||||
OP.dup,
|
||||
OP.drop,
|
||||
OP.over,
|
||||
OP.swap,
|
||||
OP.rot,
|
||||
OP.deref,
|
||||
OP.xderef,
|
||||
OP.push_object_address,
|
||||
OP.form_tls_address,
|
||||
OP.call_frame_cfa,
|
||||
OP.abs,
|
||||
OP.@"and",
|
||||
OP.div,
|
||||
OP.minus,
|
||||
OP.mod,
|
||||
OP.mul,
|
||||
OP.neg,
|
||||
OP.not,
|
||||
OP.@"or",
|
||||
OP.plus,
|
||||
OP.shl,
|
||||
OP.shr,
|
||||
OP.shra,
|
||||
OP.xor,
|
||||
OP.le,
|
||||
OP.ge,
|
||||
OP.eq,
|
||||
OP.lt,
|
||||
OP.gt,
|
||||
OP.ne,
|
||||
OP.nop,
|
||||
OP.stack_value,
|
||||
=> try writer.writeByte(opcode),
|
||||
else => @compileError("This opcode requires operands, use `write<Opcode>()` instead"),
|
||||
}
|
||||
}
|
||||
|
||||
// 2.5.1.1: Literal Encodings
|
||||
pub fn writeLiteral(writer: anytype, literal: u8) !void {
|
||||
switch (literal) {
|
||||
0...31 => |n| try writer.writeByte(n + OP.lit0),
|
||||
else => return error.InvalidLiteral,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeConst(writer: anytype, comptime T: type, value: T) !void {
|
||||
if (@typeInfo(T) != .Int) @compileError("Constants must be integers");
|
||||
|
||||
switch (T) {
|
||||
u8, i8, u16, i16, u32, i32, u64, i64 => {
|
||||
try writer.writeByte(switch (T) {
|
||||
u8 => OP.const1u,
|
||||
i8 => OP.const1s,
|
||||
u16 => OP.const2u,
|
||||
i16 => OP.const2s,
|
||||
u32 => OP.const4u,
|
||||
i32 => OP.const4s,
|
||||
u64 => OP.const8u,
|
||||
i64 => OP.const8s,
|
||||
else => unreachable,
|
||||
});
|
||||
|
||||
try writer.writeInt(T, value, options.endian);
|
||||
},
|
||||
else => switch (@typeInfo(T).Int.signedness) {
|
||||
.unsigned => {
|
||||
try writer.writeByte(OP.constu);
|
||||
try leb.writeULEB128(writer, value);
|
||||
},
|
||||
.signed => {
|
||||
try writer.writeByte(OP.consts);
|
||||
try leb.writeILEB128(writer, value);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeConstx(writer: anytype, debug_addr_offset: anytype) !void {
|
||||
try writer.writeByte(OP.constx);
|
||||
try leb.writeULEB128(writer, debug_addr_offset);
|
||||
}
|
||||
|
||||
pub fn writeConstType(writer: anytype, die_offset: anytype, value_bytes: []const u8) !void {
|
||||
if (options.call_frame_context) return error.InvalidCFAOpcode;
|
||||
if (value_bytes.len > 0xff) return error.InvalidTypeLength;
|
||||
try writer.writeByte(OP.const_type);
|
||||
try leb.writeULEB128(writer, die_offset);
|
||||
try writer.writeByte(@intCast(value_bytes.len));
|
||||
try writer.writeAll(value_bytes);
|
||||
}
|
||||
|
||||
pub fn writeAddr(writer: anytype, value: addr_type) !void {
|
||||
try writer.writeByte(OP.addr);
|
||||
try writer.writeInt(addr_type, value, options.endian);
|
||||
}
|
||||
|
||||
pub fn writeAddrx(writer: anytype, debug_addr_offset: anytype) !void {
|
||||
if (options.call_frame_context) return error.InvalidCFAOpcode;
|
||||
try writer.writeByte(OP.addrx);
|
||||
try leb.writeULEB128(writer, debug_addr_offset);
|
||||
}
|
||||
|
||||
// 2.5.1.2: Register Values
|
||||
pub fn writeFbreg(writer: anytype, offset: anytype) !void {
|
||||
try writer.writeByte(OP.fbreg);
|
||||
try leb.writeILEB128(writer, offset);
|
||||
}
|
||||
|
||||
pub fn writeBreg(writer: anytype, register: u8, offset: anytype) !void {
|
||||
if (register > 31) return error.InvalidRegister;
|
||||
try writer.writeByte(OP.breg0 + register);
|
||||
try leb.writeILEB128(writer, offset);
|
||||
}
|
||||
|
||||
pub fn writeBregx(writer: anytype, register: anytype, offset: anytype) !void {
|
||||
try writer.writeByte(OP.bregx);
|
||||
try leb.writeULEB128(writer, register);
|
||||
try leb.writeILEB128(writer, offset);
|
||||
}
|
||||
|
||||
pub fn writeRegvalType(writer: anytype, register: anytype, offset: anytype) !void {
|
||||
if (options.call_frame_context) return error.InvalidCFAOpcode;
|
||||
try writer.writeByte(OP.regval_type);
|
||||
try leb.writeULEB128(writer, register);
|
||||
try leb.writeULEB128(writer, offset);
|
||||
}
|
||||
|
||||
// 2.5.1.3: Stack Operations
|
||||
pub fn writePick(writer: anytype, index: u8) !void {
|
||||
try writer.writeByte(OP.pick);
|
||||
try writer.writeByte(index);
|
||||
}
|
||||
|
||||
pub fn writeDerefSize(writer: anytype, size: u8) !void {
|
||||
try writer.writeByte(OP.deref_size);
|
||||
try writer.writeByte(size);
|
||||
}
|
||||
|
||||
pub fn writeXDerefSize(writer: anytype, size: u8) !void {
|
||||
try writer.writeByte(OP.xderef_size);
|
||||
try writer.writeByte(size);
|
||||
}
|
||||
|
||||
pub fn writeDerefType(writer: anytype, size: u8, die_offset: anytype) !void {
|
||||
if (options.call_frame_context) return error.InvalidCFAOpcode;
|
||||
try writer.writeByte(OP.deref_type);
|
||||
try writer.writeByte(size);
|
||||
try leb.writeULEB128(writer, die_offset);
|
||||
}
|
||||
|
||||
pub fn writeXDerefType(writer: anytype, size: u8, die_offset: anytype) !void {
|
||||
try writer.writeByte(OP.xderef_type);
|
||||
try writer.writeByte(size);
|
||||
try leb.writeULEB128(writer, die_offset);
|
||||
}
|
||||
|
||||
// 2.5.1.4: Arithmetic and Logical Operations
|
||||
|
||||
pub fn writePlusUconst(writer: anytype, uint_value: anytype) !void {
|
||||
try writer.writeByte(OP.plus_uconst);
|
||||
try leb.writeULEB128(writer, uint_value);
|
||||
}
|
||||
|
||||
// 2.5.1.5: Control Flow Operations
|
||||
|
||||
pub fn writeSkip(writer: anytype, offset: i16) !void {
|
||||
try writer.writeByte(OP.skip);
|
||||
try writer.writeInt(i16, offset, options.endian);
|
||||
}
|
||||
|
||||
pub fn writeBra(writer: anytype, offset: i16) !void {
|
||||
try writer.writeByte(OP.bra);
|
||||
try writer.writeInt(i16, offset, options.endian);
|
||||
}
|
||||
|
||||
pub fn writeCall(writer: anytype, comptime T: type, offset: T) !void {
|
||||
if (options.call_frame_context) return error.InvalidCFAOpcode;
|
||||
switch (T) {
|
||||
u16 => try writer.writeByte(OP.call2),
|
||||
u32 => try writer.writeByte(OP.call4),
|
||||
else => @compileError("Call operand must be a 2 or 4 byte offset"),
|
||||
}
|
||||
|
||||
try writer.writeInt(T, offset, options.endian);
|
||||
}
|
||||
|
||||
pub fn writeCallRef(writer: anytype, comptime is_64: bool, value: if (is_64) u64 else u32) !void {
|
||||
if (options.call_frame_context) return error.InvalidCFAOpcode;
|
||||
try writer.writeByte(OP.call_ref);
|
||||
try writer.writeInt(if (is_64) u64 else u32, value, options.endian);
|
||||
}
|
||||
|
||||
pub fn writeConvert(writer: anytype, die_offset: anytype) !void {
|
||||
if (options.call_frame_context) return error.InvalidCFAOpcode;
|
||||
try writer.writeByte(OP.convert);
|
||||
try leb.writeULEB128(writer, die_offset);
|
||||
}
|
||||
|
||||
pub fn writeReinterpret(writer: anytype, die_offset: anytype) !void {
|
||||
if (options.call_frame_context) return error.InvalidCFAOpcode;
|
||||
try writer.writeByte(OP.reinterpret);
|
||||
try leb.writeULEB128(writer, die_offset);
|
||||
}
|
||||
|
||||
// 2.5.1.7: Special Operations
|
||||
|
||||
pub fn writeEntryValue(writer: anytype, expression: []const u8) !void {
|
||||
try writer.writeByte(OP.entry_value);
|
||||
try leb.writeULEB128(writer, expression.len);
|
||||
try writer.writeAll(expression);
|
||||
}
|
||||
|
||||
// 2.6: Location Descriptions
|
||||
pub fn writeReg(writer: anytype, register: u8) !void {
|
||||
try writer.writeByte(OP.reg0 + register);
|
||||
}
|
||||
|
||||
pub fn writeRegx(writer: anytype, register: anytype) !void {
|
||||
try writer.writeByte(OP.regx);
|
||||
try leb.writeULEB128(writer, register);
|
||||
}
|
||||
|
||||
pub fn writeImplicitValue(writer: anytype, value_bytes: []const u8) !void {
|
||||
try writer.writeByte(OP.implicit_value);
|
||||
try leb.writeULEB128(writer, value_bytes.len);
|
||||
try writer.writeAll(value_bytes);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Certain opcodes are not allowed in a CFA context, see 6.4.2
|
||||
fn isOpcodeValidInCFA(opcode: u8) bool {
|
||||
return switch (opcode) {
|
||||
OP.addrx,
|
||||
OP.call2,
|
||||
OP.call4,
|
||||
OP.call_ref,
|
||||
OP.const_type,
|
||||
OP.constx,
|
||||
OP.convert,
|
||||
OP.deref_type,
|
||||
OP.regval_type,
|
||||
OP.reinterpret,
|
||||
OP.push_object_address,
|
||||
OP.call_frame_cfa,
|
||||
=> false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
fn isOpcodeRegisterLocation(opcode: u8) bool {
|
||||
return switch (opcode) {
|
||||
OP.reg0...OP.reg31, OP.regx => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
const testing = std.testing;
|
||||
test "DWARF expressions" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
const options = ExpressionOptions{};
|
||||
var stack_machine = StackMachine(options){};
|
||||
defer stack_machine.deinit(allocator);
|
||||
|
||||
const b = Builder(options);
|
||||
|
||||
var program = std.ArrayList(u8).init(allocator);
|
||||
defer program.deinit();
|
||||
|
||||
const writer = program.writer();
|
||||
|
||||
// Literals
|
||||
{
|
||||
const context = ExpressionContext{};
|
||||
for (0..32) |i| {
|
||||
try b.writeLiteral(writer, @intCast(i));
|
||||
}
|
||||
|
||||
_ = try stack_machine.run(program.items, allocator, context, 0);
|
||||
|
||||
for (0..32) |i| {
|
||||
const expected = 31 - i;
|
||||
try testing.expectEqual(expected, stack_machine.stack.popOrNull().?.generic);
|
||||
}
|
||||
}
|
||||
|
||||
// Constants
|
||||
{
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
|
||||
const input = [_]comptime_int{
|
||||
1,
|
||||
-1,
|
||||
@as(usize, @truncate(0x0fff)),
|
||||
@as(isize, @truncate(-0x0fff)),
|
||||
@as(usize, @truncate(0x0fffffff)),
|
||||
@as(isize, @truncate(-0x0fffffff)),
|
||||
@as(usize, @truncate(0x0fffffffffffffff)),
|
||||
@as(isize, @truncate(-0x0fffffffffffffff)),
|
||||
@as(usize, @truncate(0x8000000)),
|
||||
@as(isize, @truncate(-0x8000000)),
|
||||
@as(usize, @truncate(0x12345678_12345678)),
|
||||
@as(usize, @truncate(0xffffffff_ffffffff)),
|
||||
@as(usize, @truncate(0xeeeeeeee_eeeeeeee)),
|
||||
};
|
||||
|
||||
try b.writeConst(writer, u8, input[0]);
|
||||
try b.writeConst(writer, i8, input[1]);
|
||||
try b.writeConst(writer, u16, input[2]);
|
||||
try b.writeConst(writer, i16, input[3]);
|
||||
try b.writeConst(writer, u32, input[4]);
|
||||
try b.writeConst(writer, i32, input[5]);
|
||||
try b.writeConst(writer, u64, input[6]);
|
||||
try b.writeConst(writer, i64, input[7]);
|
||||
try b.writeConst(writer, u28, input[8]);
|
||||
try b.writeConst(writer, i28, input[9]);
|
||||
try b.writeAddr(writer, input[10]);
|
||||
|
||||
var mock_compile_unit: dwarf.CompileUnit = undefined;
|
||||
mock_compile_unit.addr_base = 1;
|
||||
|
||||
var mock_debug_addr = std.ArrayList(u8).init(allocator);
|
||||
defer mock_debug_addr.deinit();
|
||||
|
||||
try mock_debug_addr.writer().writeIntNative(u16, 0);
|
||||
try mock_debug_addr.writer().writeIntNative(usize, input[11]);
|
||||
try mock_debug_addr.writer().writeIntNative(usize, input[12]);
|
||||
|
||||
const context = ExpressionContext{
|
||||
.compile_unit = &mock_compile_unit,
|
||||
.debug_addr = mock_debug_addr.items,
|
||||
};
|
||||
|
||||
try b.writeConstx(writer, @as(usize, 1));
|
||||
try b.writeAddrx(writer, @as(usize, 1 + @sizeOf(usize)));
|
||||
|
||||
const die_offset: usize = @truncate(0xaabbccdd);
|
||||
const type_bytes: []const u8 = &.{ 1, 2, 3, 4 };
|
||||
try b.writeConstType(writer, die_offset, type_bytes);
|
||||
|
||||
_ = try stack_machine.run(program.items, allocator, context, 0);
|
||||
|
||||
const const_type = stack_machine.stack.popOrNull().?.const_type;
|
||||
try testing.expectEqual(die_offset, const_type.type_offset);
|
||||
try testing.expectEqualSlices(u8, type_bytes, const_type.value_bytes);
|
||||
|
||||
const expected = .{
|
||||
.{ usize, input[12], usize },
|
||||
.{ usize, input[11], usize },
|
||||
.{ usize, input[10], usize },
|
||||
.{ isize, input[9], isize },
|
||||
.{ usize, input[8], usize },
|
||||
.{ isize, input[7], isize },
|
||||
.{ usize, input[6], usize },
|
||||
.{ isize, input[5], isize },
|
||||
.{ usize, input[4], usize },
|
||||
.{ isize, input[3], isize },
|
||||
.{ usize, input[2], usize },
|
||||
.{ isize, input[1], isize },
|
||||
.{ usize, input[0], usize },
|
||||
};
|
||||
|
||||
inline for (expected) |e| {
|
||||
try testing.expectEqual(@as(e[0], e[1]), @as(e[2], @bitCast(stack_machine.stack.popOrNull().?.generic)));
|
||||
}
|
||||
}
|
||||
|
||||
// Register values
|
||||
if (@sizeOf(std.debug.ThreadContext) != 0) {
|
||||
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);
|
||||
const context = ExpressionContext{
|
||||
.thread_context = &thread_context,
|
||||
.reg_context = reg_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.writeIntSliceNative(usize, reg_bytes, 0xee);
|
||||
(try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1;
|
||||
(try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2;
|
||||
(try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3;
|
||||
|
||||
try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100));
|
||||
try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200));
|
||||
try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300));
|
||||
try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400));
|
||||
|
||||
_ = try stack_machine.run(program.items, allocator, context, 0);
|
||||
|
||||
const regval_type = stack_machine.stack.popOrNull().?.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.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, 202), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, 101), stack_machine.stack.popOrNull().?.generic);
|
||||
} else |err| {
|
||||
switch (err) {
|
||||
error.UnimplementedArch,
|
||||
error.UnimplementedOs,
|
||||
error.ThreadContextNotSupported,
|
||||
=> {},
|
||||
else => return err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stack operations
|
||||
{
|
||||
var context = ExpressionContext{};
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u8, 1);
|
||||
try b.writeOpcode(writer, OP.dup);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u8, 1);
|
||||
try b.writeOpcode(writer, OP.drop);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expect(stack_machine.stack.popOrNull() == null);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u8, 4);
|
||||
try b.writeConst(writer, u8, 5);
|
||||
try b.writeConst(writer, u8, 6);
|
||||
try b.writePick(writer, 2);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u8, 4);
|
||||
try b.writeConst(writer, u8, 5);
|
||||
try b.writeConst(writer, u8, 6);
|
||||
try b.writeOpcode(writer, OP.over);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u8, 5);
|
||||
try b.writeConst(writer, u8, 6);
|
||||
try b.writeOpcode(writer, OP.swap);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u8, 4);
|
||||
try b.writeConst(writer, u8, 5);
|
||||
try b.writeConst(writer, u8, 6);
|
||||
try b.writeOpcode(writer, OP.rot);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
const deref_target: usize = @truncate(0xffeeffee_ffeeffee);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeAddr(writer, @intFromPtr(&deref_target));
|
||||
try b.writeOpcode(writer, OP.deref);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeLiteral(writer, 0);
|
||||
try b.writeAddr(writer, @intFromPtr(&deref_target));
|
||||
try b.writeOpcode(writer, OP.xderef);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeAddr(writer, @intFromPtr(&deref_target));
|
||||
try b.writeDerefSize(writer, 1);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeLiteral(writer, 0);
|
||||
try b.writeAddr(writer, @intFromPtr(&deref_target));
|
||||
try b.writeXDerefSize(writer, 1);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
const type_offset: usize = @truncate(0xaabbaabb_aabbaabb);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeAddr(writer, @intFromPtr(&deref_target));
|
||||
try b.writeDerefType(writer, 1, type_offset);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
const deref_type = stack_machine.stack.popOrNull().?.regval_type;
|
||||
try testing.expectEqual(type_offset, deref_type.type_offset);
|
||||
try testing.expectEqual(@as(u8, 1), deref_type.type_size);
|
||||
try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), deref_type.value);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeLiteral(writer, 0);
|
||||
try b.writeAddr(writer, @intFromPtr(&deref_target));
|
||||
try b.writeXDerefType(writer, 1, type_offset);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
const xderef_type = stack_machine.stack.popOrNull().?.regval_type;
|
||||
try testing.expectEqual(type_offset, xderef_type.type_offset);
|
||||
try testing.expectEqual(@as(u8, 1), xderef_type.type_size);
|
||||
try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), xderef_type.value);
|
||||
|
||||
context.object_address = &deref_target;
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeOpcode(writer, OP.push_object_address);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, @intFromPtr(context.object_address.?)), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
// TODO: Test OP.form_tls_address
|
||||
|
||||
context.cfa = @truncate(0xccddccdd_ccddccdd);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeOpcode(writer, OP.call_frame_cfa);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(context.cfa.?, stack_machine.stack.popOrNull().?.generic);
|
||||
}
|
||||
|
||||
// Arithmetic and Logical Operations
|
||||
{
|
||||
var context = ExpressionContext{};
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, i16, -4096);
|
||||
try b.writeOpcode(writer, OP.abs);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 4096), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 0xff0f);
|
||||
try b.writeConst(writer, u16, 0xf0ff);
|
||||
try b.writeOpcode(writer, OP.@"and");
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 0xf00f), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, i16, -404);
|
||||
try b.writeConst(writer, i16, 100);
|
||||
try b.writeOpcode(writer, OP.div);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(isize, -404 / 100), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic)));
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 200);
|
||||
try b.writeConst(writer, u16, 50);
|
||||
try b.writeOpcode(writer, OP.minus);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 150), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 123);
|
||||
try b.writeConst(writer, u16, 100);
|
||||
try b.writeOpcode(writer, OP.mod);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 23), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 0xff);
|
||||
try b.writeConst(writer, u16, 0xee);
|
||||
try b.writeOpcode(writer, OP.mul);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 0xed12), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 5);
|
||||
try b.writeOpcode(writer, OP.neg);
|
||||
try b.writeConst(writer, i16, -6);
|
||||
try b.writeOpcode(writer, OP.neg);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(isize, -5), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic)));
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 0xff0f);
|
||||
try b.writeOpcode(writer, OP.not);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(~@as(usize, 0xff0f), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 0xff0f);
|
||||
try b.writeConst(writer, u16, 0xf0ff);
|
||||
try b.writeOpcode(writer, OP.@"or");
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 0xffff), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, i16, 402);
|
||||
try b.writeConst(writer, i16, 100);
|
||||
try b.writeOpcode(writer, OP.plus);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 502), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 4096);
|
||||
try b.writePlusUconst(writer, @as(usize, 8192));
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 4096 + 8192), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 0xfff);
|
||||
try b.writeConst(writer, u16, 1);
|
||||
try b.writeOpcode(writer, OP.shl);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 0xfff << 1), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 0xfff);
|
||||
try b.writeConst(writer, u16, 1);
|
||||
try b.writeOpcode(writer, OP.shr);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 0xfff >> 1), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 0xfff);
|
||||
try b.writeConst(writer, u16, 1);
|
||||
try b.writeOpcode(writer, OP.shr);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, @bitCast(@as(isize, 0xfff) >> 1)), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConst(writer, u16, 0xf0ff);
|
||||
try b.writeConst(writer, u16, 0xff0f);
|
||||
try b.writeOpcode(writer, OP.xor);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 0x0ff0), stack_machine.stack.popOrNull().?.generic);
|
||||
}
|
||||
|
||||
// Control Flow Operations
|
||||
{
|
||||
var context = ExpressionContext{};
|
||||
const expected = .{
|
||||
.{ OP.le, 1, 1, 0 },
|
||||
.{ OP.ge, 1, 0, 1 },
|
||||
.{ OP.eq, 1, 0, 0 },
|
||||
.{ OP.lt, 0, 1, 0 },
|
||||
.{ OP.gt, 0, 0, 1 },
|
||||
.{ OP.ne, 0, 1, 1 },
|
||||
};
|
||||
|
||||
inline for (expected) |e| {
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
|
||||
try b.writeConst(writer, u16, 0);
|
||||
try b.writeConst(writer, u16, 0);
|
||||
try b.writeOpcode(writer, e[0]);
|
||||
try b.writeConst(writer, u16, 0);
|
||||
try b.writeConst(writer, u16, 1);
|
||||
try b.writeOpcode(writer, e[0]);
|
||||
try b.writeConst(writer, u16, 1);
|
||||
try b.writeConst(writer, u16, 0);
|
||||
try b.writeOpcode(writer, e[0]);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, e[3]), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, e[2]), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, e[1]), stack_machine.stack.popOrNull().?.generic);
|
||||
}
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeLiteral(writer, 2);
|
||||
try b.writeSkip(writer, 1);
|
||||
try b.writeLiteral(writer, 3);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 2), stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeLiteral(writer, 2);
|
||||
try b.writeBra(writer, 1);
|
||||
try b.writeLiteral(writer, 3);
|
||||
try b.writeLiteral(writer, 0);
|
||||
try b.writeBra(writer, 1);
|
||||
try b.writeLiteral(writer, 4);
|
||||
try b.writeLiteral(writer, 5);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic);
|
||||
try testing.expect(stack_machine.stack.popOrNull() == null);
|
||||
|
||||
// TODO: Test call2, call4, call_ref once implemented
|
||||
|
||||
}
|
||||
|
||||
// Type conversions
|
||||
{
|
||||
var context = ExpressionContext{};
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
|
||||
// TODO: Test typed OP.convert once implemented
|
||||
|
||||
const value: usize = @truncate(0xffeeffee_ffeeffee);
|
||||
var value_bytes: [options.addr_size]u8 = undefined;
|
||||
mem.writeIntSliceNative(usize, &value_bytes, value);
|
||||
|
||||
// Convert to generic type
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConstType(writer, @as(usize, 0), &value_bytes);
|
||||
try b.writeConvert(writer, @as(usize, 0));
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
// Reinterpret to generic type
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConstType(writer, @as(usize, 0), &value_bytes);
|
||||
try b.writeReinterpret(writer, @as(usize, 0));
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic);
|
||||
|
||||
// Reinterpret to new type
|
||||
const die_offset: usize = 0xffee;
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeConstType(writer, @as(usize, 0), &value_bytes);
|
||||
try b.writeReinterpret(writer, die_offset);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
const const_type = stack_machine.stack.popOrNull().?.const_type;
|
||||
try testing.expectEqual(die_offset, const_type.type_offset);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeLiteral(writer, 0);
|
||||
try b.writeReinterpret(writer, die_offset);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
const regval_type = stack_machine.stack.popOrNull().?.regval_type;
|
||||
try testing.expectEqual(die_offset, regval_type.type_offset);
|
||||
}
|
||||
|
||||
// Special operations
|
||||
{
|
||||
var context = ExpressionContext{};
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeOpcode(writer, OP.nop);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expect(stack_machine.stack.popOrNull() == null);
|
||||
|
||||
// Sub-expression
|
||||
{
|
||||
var sub_program = std.ArrayList(u8).init(allocator);
|
||||
defer sub_program.deinit();
|
||||
const sub_writer = sub_program.writer();
|
||||
try b.writeLiteral(sub_writer, 3);
|
||||
|
||||
stack_machine.reset();
|
||||
program.clearRetainingCapacity();
|
||||
try b.writeEntryValue(writer, sub_program.items);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 3), stack_machine.stack.popOrNull().?.generic);
|
||||
}
|
||||
|
||||
// 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);
|
||||
context = ExpressionContext{
|
||||
.thread_context = &thread_context,
|
||||
.reg_context = reg_context,
|
||||
};
|
||||
|
||||
if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| {
|
||||
mem.writeIntSliceNative(usize, reg_bytes, 0xee);
|
||||
|
||||
var sub_program = std.ArrayList(u8).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.items);
|
||||
_ = try stack_machine.run(program.items, allocator, context, null);
|
||||
try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.popOrNull().?.generic);
|
||||
} else |err| {
|
||||
switch (err) {
|
||||
error.UnimplementedArch,
|
||||
error.UnimplementedOs,
|
||||
error.ThreadContextNotSupported,
|
||||
=> {},
|
||||
else => return err,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,6 +371,9 @@ pub const SHT_LOUSER = 0x80000000;
|
||||
/// End of application-specific
|
||||
pub const SHT_HIUSER = 0xffffffff;
|
||||
|
||||
// Note type for .note.gnu.build_id
|
||||
pub const NT_GNU_BUILD_ID = 3;
|
||||
|
||||
/// Local symbol
|
||||
pub const STB_LOCAL = 0;
|
||||
/// Global symbol
|
||||
@@ -1055,6 +1058,11 @@ pub const Shdr = switch (@sizeOf(usize)) {
|
||||
8 => Elf64_Shdr,
|
||||
else => @compileError("expected pointer size of 32 or 64"),
|
||||
};
|
||||
pub const Chdr = switch (@sizeOf(usize)) {
|
||||
4 => Elf32_Chdr,
|
||||
8 => Elf64_Chdr,
|
||||
else => @compileError("expected pointer size of 32 or 64"),
|
||||
};
|
||||
pub const Sym = switch (@sizeOf(usize)) {
|
||||
4 => Elf32_Sym,
|
||||
8 => Elf64_Sym,
|
||||
|
||||
@@ -2064,3 +2064,64 @@ pub const UNWIND_ARM64_FRAME_D14_D15_PAIR: u32 = 0x00000800;
|
||||
|
||||
pub const UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK: u32 = 0x00FFF000;
|
||||
pub const UNWIND_ARM64_DWARF_SECTION_OFFSET: u32 = 0x00FFFFFF;
|
||||
|
||||
pub const CompactUnwindEncoding = packed struct(u32) {
|
||||
value: packed union {
|
||||
x86_64: packed union {
|
||||
frame: packed struct(u24) {
|
||||
reg4: u3,
|
||||
reg3: u3,
|
||||
reg2: u3,
|
||||
reg1: u3,
|
||||
reg0: u3,
|
||||
unused: u1 = 0,
|
||||
frame_offset: u8,
|
||||
},
|
||||
frameless: packed struct(u24) {
|
||||
stack_reg_permutation: u10,
|
||||
stack_reg_count: u3,
|
||||
stack: packed union {
|
||||
direct: packed struct(u11) {
|
||||
_: u3,
|
||||
stack_size: u8,
|
||||
},
|
||||
indirect: packed struct(u11) {
|
||||
stack_adjust: u3,
|
||||
sub_offset: u8,
|
||||
},
|
||||
},
|
||||
},
|
||||
dwarf: u24,
|
||||
},
|
||||
arm64: packed union {
|
||||
frame: packed struct(u24) {
|
||||
x_reg_pairs: packed struct(u5) {
|
||||
x19_x20: u1,
|
||||
x21_x22: u1,
|
||||
x23_x24: u1,
|
||||
x25_x26: u1,
|
||||
x27_x28: u1,
|
||||
},
|
||||
d_reg_pairs: packed struct(u4) {
|
||||
d8_d9: u1,
|
||||
d10_d11: u1,
|
||||
d12_d13: u1,
|
||||
d14_d15: u1,
|
||||
},
|
||||
_: u15,
|
||||
},
|
||||
frameless: packed struct(u24) {
|
||||
_: u12 = 0,
|
||||
stack_size: u12,
|
||||
},
|
||||
dwarf: u24,
|
||||
},
|
||||
},
|
||||
mode: packed union {
|
||||
x86_64: UNWIND_X86_64_MODE,
|
||||
arm64: UNWIND_ARM64_MODE,
|
||||
},
|
||||
personality_index: u2,
|
||||
has_lsda: u1,
|
||||
start: u1,
|
||||
};
|
||||
|
||||
@@ -86,6 +86,7 @@ pub const timeval = arch_bits.timeval;
|
||||
pub const timezone = arch_bits.timezone;
|
||||
pub const ucontext_t = arch_bits.ucontext_t;
|
||||
pub const user_desc = arch_bits.user_desc;
|
||||
pub const getcontext = arch_bits.getcontext;
|
||||
|
||||
pub const tls = @import("linux/tls.zig");
|
||||
pub const pie = @import("linux/start_pie.zig");
|
||||
|
||||
@@ -389,3 +389,86 @@ pub const SC = struct {
|
||||
pub const recvmmsg = 19;
|
||||
pub const sendmmsg = 20;
|
||||
};
|
||||
|
||||
fn gpRegisterOffset(comptime reg_index: comptime_int) usize {
|
||||
return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index;
|
||||
}
|
||||
|
||||
noinline fn getContextReturnAddress() usize {
|
||||
return @returnAddress();
|
||||
}
|
||||
|
||||
pub fn getContextInternal() callconv(.Naked) void {
|
||||
asm volatile (
|
||||
\\ movl $0, (%[flags_offset])(%%edx)
|
||||
\\ movl $0, (%[link_offset])(%%edx)
|
||||
\\ movl %%edi, (%[edi_offset])(%%edx)
|
||||
\\ movl %%esi, (%[esi_offset])(%%edx)
|
||||
\\ movl %%ebp, (%[ebp_offset])(%%edx)
|
||||
\\ movl %%ebx, (%[ebx_offset])(%%edx)
|
||||
\\ movl %%edx, (%[edx_offset])(%%edx)
|
||||
\\ movl %%ecx, (%[ecx_offset])(%%edx)
|
||||
\\ movl %%eax, (%[eax_offset])(%%edx)
|
||||
\\ movl (%%esp), %%ecx
|
||||
\\ movl %%ecx, (%[eip_offset])(%%edx)
|
||||
\\ leal 4(%%esp), %%ecx
|
||||
\\ movl %%ecx, (%[esp_offset])(%%edx)
|
||||
\\ xorl %%ecx, %%ecx
|
||||
\\ movw %%fs, %%cx
|
||||
\\ movl %%ecx, (%[fs_offset])(%%edx)
|
||||
\\ leal (%[regspace_offset])(%%edx), %%ecx
|
||||
\\ movl %%ecx, (%[fpregs_offset])(%%edx)
|
||||
\\ fnstenv (%%ecx)
|
||||
\\ fldenv (%%ecx)
|
||||
\\ pushl %%ebx
|
||||
\\ pushl %%esi
|
||||
\\ xorl %%ebx, %%ebx
|
||||
\\ movl %[sigaltstack], %%eax
|
||||
\\ leal (%[stack_offset])(%%edx), %%ecx
|
||||
\\ int $0x80
|
||||
\\ cmpl $0, %%eax
|
||||
\\ jne return
|
||||
\\ movl %[sigprocmask], %%eax
|
||||
\\ xorl %%ecx, %%ecx
|
||||
\\ leal (%[sigmask_offset])(%%edx), %%edx
|
||||
\\ movl %[sigset_size], %%esi
|
||||
\\ int $0x80
|
||||
\\ return:
|
||||
\\ popl %%esi
|
||||
\\ popl %%ebx
|
||||
:
|
||||
: [flags_offset] "p" (@offsetOf(ucontext_t, "flags")),
|
||||
[link_offset] "p" (@offsetOf(ucontext_t, "link")),
|
||||
[edi_offset] "p" (comptime gpRegisterOffset(REG.EDI)),
|
||||
[esi_offset] "p" (comptime gpRegisterOffset(REG.ESI)),
|
||||
[ebp_offset] "p" (comptime gpRegisterOffset(REG.EBP)),
|
||||
[esp_offset] "p" (comptime gpRegisterOffset(REG.ESP)),
|
||||
[ebx_offset] "p" (comptime gpRegisterOffset(REG.EBX)),
|
||||
[edx_offset] "p" (comptime gpRegisterOffset(REG.EDX)),
|
||||
[ecx_offset] "p" (comptime gpRegisterOffset(REG.ECX)),
|
||||
[eax_offset] "p" (comptime gpRegisterOffset(REG.EAX)),
|
||||
[eip_offset] "p" (comptime gpRegisterOffset(REG.EIP)),
|
||||
[fs_offset] "p" (comptime gpRegisterOffset(REG.FS)),
|
||||
[fpregs_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")),
|
||||
[regspace_offset] "p" (@offsetOf(ucontext_t, "regspace")),
|
||||
[sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)),
|
||||
[stack_offset] "p" (@offsetOf(ucontext_t, "stack")),
|
||||
[sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)),
|
||||
[sigmask_offset] "p" (@offsetOf(ucontext_t, "sigmask")),
|
||||
[sigset_size] "i" (linux.NSIG / 8),
|
||||
: "memory", "eax", "ecx", "edx"
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn getcontext(context: *ucontext_t) usize {
|
||||
// This method is used so that getContextInternal can control
|
||||
// its prologue in order to read ESP from a constant offset.
|
||||
// The unused &getContextInternal input is required so the function is included in the binary.
|
||||
return asm volatile (
|
||||
\\ call os.linux.x86.getContextInternal
|
||||
: [ret] "={eax}" (-> usize),
|
||||
: [context] "{edx}" (context),
|
||||
[getContextInternal] "X" (&getContextInternal),
|
||||
: "memory", "ecx"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -395,3 +395,97 @@ pub const ucontext_t = extern struct {
|
||||
sigmask: sigset_t,
|
||||
fpregs_mem: [64]usize,
|
||||
};
|
||||
|
||||
fn gpRegisterOffset(comptime reg_index: comptime_int) usize {
|
||||
return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index;
|
||||
}
|
||||
|
||||
fn getContextInternal() callconv(.Naked) void {
|
||||
// TODO: Read GS/FS registers?
|
||||
asm volatile (
|
||||
\\ movq $0, (%[flags_offset])(%%rdi)
|
||||
\\ movq $0, (%[link_offset])(%%rdi)
|
||||
\\ movq %%r8, (%[r8_offset])(%%rdi)
|
||||
\\ movq %%r9, (%[r9_offset])(%%rdi)
|
||||
\\ movq %%r10, (%[r10_offset])(%%rdi)
|
||||
\\ movq %%r11, (%[r11_offset])(%%rdi)
|
||||
\\ movq %%r12, (%[r12_offset])(%%rdi)
|
||||
\\ movq %%r13, (%[r13_offset])(%%rdi)
|
||||
\\ movq %%r14, (%[r14_offset])(%%rdi)
|
||||
\\ movq %%r15, (%[r15_offset])(%%rdi)
|
||||
\\ movq %%rdi, (%[rdi_offset])(%%rdi)
|
||||
\\ movq %%rsi, (%[rsi_offset])(%%rdi)
|
||||
\\ movq %%rbp, (%[rbp_offset])(%%rdi)
|
||||
\\ movq %%rbx, (%[rbx_offset])(%%rdi)
|
||||
\\ movq %%rdx, (%[rdx_offset])(%%rdi)
|
||||
\\ movq %%rax, (%[rax_offset])(%%rdi)
|
||||
\\ movq %%rcx, (%[rcx_offset])(%%rdi)
|
||||
\\ movq (%%rsp), %%rcx
|
||||
\\ movq %%rcx, (%[rip_offset])(%%rdi)
|
||||
\\ leaq 8(%%rsp), %%rcx
|
||||
\\ movq %%rcx, (%[rsp_offset])(%%rdi)
|
||||
\\ pushfq
|
||||
\\ popq (%[efl_offset])(%%rdi)
|
||||
\\ leaq (%[fpmem_offset])(%%rdi), %%rcx
|
||||
\\ movq %%rcx, (%[fpstate_offset])(%%rdi)
|
||||
\\ fnstenv (%%rcx)
|
||||
\\ fldenv (%%rcx)
|
||||
\\ stmxcsr (%[mxcsr_offset])(%%rdi)
|
||||
\\ leaq (%[stack_offset])(%%rdi), %%rsi
|
||||
\\ movq %%rdi, %%r8
|
||||
\\ xorq %%rdi, %%rdi
|
||||
\\ movq %[sigaltstack], %%rax
|
||||
\\ syscall
|
||||
\\ cmpq $0, %%rax
|
||||
\\ jne return
|
||||
\\ movq %[sigprocmask], %%rax
|
||||
\\ xorq %%rsi, %%rsi
|
||||
\\ leaq (%[sigmask_offset])(%%r8), %%rdx
|
||||
\\ movq %[sigset_size], %%r10
|
||||
\\ syscall
|
||||
\\ return:
|
||||
:
|
||||
: [flags_offset] "p" (@offsetOf(ucontext_t, "flags")),
|
||||
[link_offset] "p" (@offsetOf(ucontext_t, "link")),
|
||||
[r8_offset] "p" (comptime gpRegisterOffset(REG.R8)),
|
||||
[r9_offset] "p" (comptime gpRegisterOffset(REG.R9)),
|
||||
[r10_offset] "p" (comptime gpRegisterOffset(REG.R10)),
|
||||
[r11_offset] "p" (comptime gpRegisterOffset(REG.R11)),
|
||||
[r12_offset] "p" (comptime gpRegisterOffset(REG.R12)),
|
||||
[r13_offset] "p" (comptime gpRegisterOffset(REG.R13)),
|
||||
[r14_offset] "p" (comptime gpRegisterOffset(REG.R14)),
|
||||
[r15_offset] "p" (comptime gpRegisterOffset(REG.R15)),
|
||||
[rdi_offset] "p" (comptime gpRegisterOffset(REG.RDI)),
|
||||
[rsi_offset] "p" (comptime gpRegisterOffset(REG.RSI)),
|
||||
[rbp_offset] "p" (comptime gpRegisterOffset(REG.RBP)),
|
||||
[rbx_offset] "p" (comptime gpRegisterOffset(REG.RBX)),
|
||||
[rdx_offset] "p" (comptime gpRegisterOffset(REG.RDX)),
|
||||
[rax_offset] "p" (comptime gpRegisterOffset(REG.RAX)),
|
||||
[rcx_offset] "p" (comptime gpRegisterOffset(REG.RCX)),
|
||||
[rsp_offset] "p" (comptime gpRegisterOffset(REG.RSP)),
|
||||
[rip_offset] "p" (comptime gpRegisterOffset(REG.RIP)),
|
||||
[efl_offset] "p" (comptime gpRegisterOffset(REG.EFL)),
|
||||
[fpstate_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")),
|
||||
[fpmem_offset] "p" (@offsetOf(ucontext_t, "fpregs_mem")),
|
||||
[mxcsr_offset] "p" (@offsetOf(ucontext_t, "fpregs_mem") + @offsetOf(fpstate, "mxcsr")),
|
||||
[sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)),
|
||||
[stack_offset] "p" (@offsetOf(ucontext_t, "stack")),
|
||||
[sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)),
|
||||
[sigmask_offset] "p" (@offsetOf(ucontext_t, "sigmask")),
|
||||
[sigset_size] "i" (linux.NSIG / 8),
|
||||
: "memory", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11"
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn getcontext(context: *ucontext_t) usize {
|
||||
// This method is used so that getContextInternal can control
|
||||
// its prologue in order to read RSP from a constant offset
|
||||
// The unused &getContextInternal input is required so the function is included in the binary.
|
||||
return asm volatile (
|
||||
\\ call os.linux.x86_64.getContextInternal
|
||||
: [ret] "={rax}" (-> usize),
|
||||
: [context] "{rdi}" (context),
|
||||
[getContextInternal] "X" (&getContextInternal),
|
||||
: "memory", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3301,6 +3301,35 @@ pub const REGSAM = ACCESS_MASK;
|
||||
pub const ACCESS_MASK = DWORD;
|
||||
pub const LSTATUS = LONG;
|
||||
|
||||
pub const SECTION_INHERIT = enum(c_int) {
|
||||
ViewShare = 0,
|
||||
ViewUnmap = 1,
|
||||
};
|
||||
|
||||
pub const SECTION_QUERY = 0x0001;
|
||||
pub const SECTION_MAP_WRITE = 0x0002;
|
||||
pub const SECTION_MAP_READ = 0x0004;
|
||||
pub const SECTION_MAP_EXECUTE = 0x0008;
|
||||
pub const SECTION_EXTEND_SIZE = 0x0010;
|
||||
pub const SECTION_ALL_ACCESS =
|
||||
STANDARD_RIGHTS_REQUIRED |
|
||||
SECTION_QUERY |
|
||||
SECTION_MAP_WRITE |
|
||||
SECTION_MAP_READ |
|
||||
SECTION_MAP_EXECUTE |
|
||||
SECTION_EXTEND_SIZE;
|
||||
|
||||
pub const SEC_64K_PAGES = 0x80000;
|
||||
pub const SEC_FILE = 0x800000;
|
||||
pub const SEC_IMAGE = 0x1000000;
|
||||
pub const SEC_PROTECTED_IMAGE = 0x2000000;
|
||||
pub const SEC_RESERVE = 0x4000000;
|
||||
pub const SEC_COMMIT = 0x8000000;
|
||||
pub const SEC_IMAGE_NO_EXECUTE = SEC_IMAGE | SEC_NOCACHE;
|
||||
pub const SEC_NOCACHE = 0x10000000;
|
||||
pub const SEC_WRITECOMBINE = 0x40000000;
|
||||
pub const SEC_LARGE_PAGES = 0x80000000;
|
||||
|
||||
pub const HKEY = *opaque {};
|
||||
|
||||
pub const HKEY_LOCAL_MACHINE: HKEY = @as(HKEY, @ptrFromInt(0x80000002));
|
||||
|
||||
@@ -36,6 +36,7 @@ const THREADINFOCLASS = windows.THREADINFOCLASS;
|
||||
const PROCESSINFOCLASS = windows.PROCESSINFOCLASS;
|
||||
const LPVOID = windows.LPVOID;
|
||||
const LPCVOID = windows.LPCVOID;
|
||||
const SECTION_INHERIT = windows.SECTION_INHERIT;
|
||||
|
||||
pub extern "ntdll" fn NtQueryInformationProcess(
|
||||
ProcessHandle: HANDLE,
|
||||
@@ -125,6 +126,31 @@ pub extern "ntdll" fn NtCreateFile(
|
||||
EaBuffer: ?*anyopaque,
|
||||
EaLength: ULONG,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
pub extern "ntdll" fn NtCreateSection(
|
||||
SectionHandle: *HANDLE,
|
||||
DesiredAccess: ACCESS_MASK,
|
||||
ObjectAttributes: ?*OBJECT_ATTRIBUTES,
|
||||
MaximumSize: ?*LARGE_INTEGER,
|
||||
SectionPageProtection: ULONG,
|
||||
AllocationAttributes: ULONG,
|
||||
FileHandle: ?HANDLE,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
pub extern "ntdll" fn NtMapViewOfSection(
|
||||
SectionHandle: HANDLE,
|
||||
ProcessHandle: HANDLE,
|
||||
BaseAddress: *PVOID,
|
||||
ZeroBits: ?*ULONG,
|
||||
CommitSize: SIZE_T,
|
||||
SectionOffset: ?*LARGE_INTEGER,
|
||||
ViewSize: *SIZE_T,
|
||||
InheritDispostion: SECTION_INHERIT,
|
||||
AllocationType: ULONG,
|
||||
Win32Protect: ULONG,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
pub extern "ntdll" fn NtUnmapViewOfSection(
|
||||
ProcessHandle: HANDLE,
|
||||
BaseAddress: PVOID,
|
||||
) callconv(WINAPI) NTSTATUS;
|
||||
pub extern "ntdll" fn NtDeviceIoControlFile(
|
||||
FileHandle: HANDLE,
|
||||
Event: ?HANDLE,
|
||||
|
||||
@@ -5288,6 +5288,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
|
||||
\\pub const position_independent_executable = {};
|
||||
\\pub const strip_debug_info = {};
|
||||
\\pub const code_model = std.builtin.CodeModel.{};
|
||||
\\pub const omit_frame_pointer = {};
|
||||
\\
|
||||
, .{
|
||||
std.zig.fmtId(@tagName(target.ofmt)),
|
||||
@@ -5301,6 +5302,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
|
||||
comp.bin_file.options.pie,
|
||||
comp.bin_file.options.strip,
|
||||
std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)),
|
||||
comp.bin_file.options.omit_frame_pointer,
|
||||
});
|
||||
|
||||
if (target.os.tag == .wasi) {
|
||||
|
||||
+12
-58
@@ -203,53 +203,11 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any
|
||||
};
|
||||
|
||||
const stack_ctx: StackContext = switch (builtin.cpu.arch) {
|
||||
.x86 => ctx: {
|
||||
const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||
const ip = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EIP]));
|
||||
const bp = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EBP]));
|
||||
break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
|
||||
},
|
||||
.x86_64 => ctx: {
|
||||
const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||
const ip = switch (builtin.os.tag) {
|
||||
.linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RIP])),
|
||||
.freebsd => @as(usize, @intCast(ctx.mcontext.rip)),
|
||||
.openbsd => @as(usize, @intCast(ctx.sc_rip)),
|
||||
.macos => @as(usize, @intCast(ctx.mcontext.ss.rip)),
|
||||
else => unreachable,
|
||||
};
|
||||
const bp = switch (builtin.os.tag) {
|
||||
.linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RBP])),
|
||||
.openbsd => @as(usize, @intCast(ctx.sc_rbp)),
|
||||
.freebsd => @as(usize, @intCast(ctx.mcontext.rbp)),
|
||||
.macos => @as(usize, @intCast(ctx.mcontext.ss.rbp)),
|
||||
else => unreachable,
|
||||
};
|
||||
break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
|
||||
},
|
||||
.arm => ctx: {
|
||||
const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||
const ip = @as(usize, @intCast(ctx.mcontext.arm_pc));
|
||||
const bp = @as(usize, @intCast(ctx.mcontext.arm_fp));
|
||||
break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
|
||||
},
|
||||
.aarch64 => ctx: {
|
||||
const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr));
|
||||
const ip = switch (native_os) {
|
||||
.macos => @as(usize, @intCast(ctx.mcontext.ss.pc)),
|
||||
.netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.PC])),
|
||||
.freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.elr)),
|
||||
else => @as(usize, @intCast(ctx.mcontext.pc)),
|
||||
};
|
||||
// x29 is the ABI-designated frame pointer
|
||||
const bp = switch (native_os) {
|
||||
.macos => @as(usize, @intCast(ctx.mcontext.ss.fp)),
|
||||
.netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.FP])),
|
||||
.freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.x[os.REG.FP])),
|
||||
else => @as(usize, @intCast(ctx.mcontext.regs[29])),
|
||||
};
|
||||
break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } };
|
||||
},
|
||||
.x86,
|
||||
.x86_64,
|
||||
.arm,
|
||||
.aarch64,
|
||||
=> StackContext{ .exception = @ptrCast(@alignCast(ctx_ptr)) },
|
||||
else => .not_supported,
|
||||
};
|
||||
|
||||
@@ -275,10 +233,9 @@ fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windo
|
||||
fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn {
|
||||
PanicSwitch.preDispatch();
|
||||
|
||||
const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) ctx: {
|
||||
const regs = info.ContextRecord.getRegs();
|
||||
break :ctx StackContext{ .exception = .{ .bp = regs.bp, .ip = regs.ip } };
|
||||
} else ctx: {
|
||||
const stack_ctx = if (@hasDecl(os.windows, "CONTEXT"))
|
||||
StackContext{ .exception = info.ContextRecord }
|
||||
else ctx: {
|
||||
const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress);
|
||||
break :ctx StackContext{ .current = .{ .ret_addr = addr } };
|
||||
};
|
||||
@@ -293,7 +250,7 @@ fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg
|
||||
},
|
||||
.illegal_instruction => {
|
||||
const ip: ?usize = switch (stack_ctx) {
|
||||
.exception => |ex| ex.ip,
|
||||
.exception => |ex| ex.getRegs().ip,
|
||||
.current => |cur| cur.ret_addr,
|
||||
.not_supported => null,
|
||||
};
|
||||
@@ -314,10 +271,7 @@ const StackContext = union(enum) {
|
||||
current: struct {
|
||||
ret_addr: ?usize,
|
||||
},
|
||||
exception: struct {
|
||||
bp: usize,
|
||||
ip: usize,
|
||||
},
|
||||
exception: *const debug.ThreadContext,
|
||||
not_supported: void,
|
||||
|
||||
pub fn dumpStackTrace(ctx: @This()) void {
|
||||
@@ -325,8 +279,8 @@ const StackContext = union(enum) {
|
||||
.current => |ct| {
|
||||
debug.dumpCurrentStackTrace(ct.ret_addr);
|
||||
},
|
||||
.exception => |ex| {
|
||||
debug.dumpStackTraceFromBase(ex.bp, ex.ip);
|
||||
.exception => |context| {
|
||||
debug.dumpStackTraceFromBase(context);
|
||||
},
|
||||
.not_supported => {
|
||||
const stderr = io.getStdErr().writer();
|
||||
|
||||
+1
-1
@@ -510,7 +510,7 @@ pub fn clangAssemblerSupportsMcpuArg(target: std.Target) bool {
|
||||
}
|
||||
|
||||
pub fn needUnwindTables(target: std.Target) bool {
|
||||
return target.os.tag == .windows;
|
||||
return target.os.tag == .windows or target.isDarwin();
|
||||
}
|
||||
|
||||
pub fn defaultAddressSpace(
|
||||
|
||||
@@ -230,6 +230,14 @@ pub const build_cases = [_]BuildCase{
|
||||
.build_root = "test/standalone/zerolength_check",
|
||||
.import = @import("standalone/zerolength_check/build.zig"),
|
||||
},
|
||||
.{
|
||||
.build_root = "test/standalone/stack_iterator",
|
||||
.import = @import("standalone/stack_iterator/build.zig"),
|
||||
},
|
||||
.{
|
||||
.build_root = "test/standalone/coff_dwarf",
|
||||
.import = @import("standalone/coff_dwarf/build.zig"),
|
||||
},
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
/// This tests the path where DWARF information is embedded in a COFF binary
|
||||
pub fn build(b: *std.Build) void {
|
||||
const test_step = b.step("test", "Test it");
|
||||
b.default_step = test_step;
|
||||
|
||||
const optimize: std.builtin.OptimizeMode = .Debug;
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
if (builtin.os.tag != .windows) return;
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "main",
|
||||
.root_source_file = .{ .path = "main.zig" },
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
});
|
||||
|
||||
const lib = b.addSharedLibrary(.{
|
||||
.name = "shared_lib",
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
});
|
||||
lib.addCSourceFile("shared_lib.c", &.{"-gdwarf"});
|
||||
lib.linkLibC();
|
||||
exe.linkLibrary(lib);
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
run.expectExitCode(0);
|
||||
run.skip_foreign_checks = true;
|
||||
|
||||
test_step.dependOn(&run.step);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
|
||||
extern fn add(a: u32, b: u32, addr: *usize) u32;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer assert(gpa.deinit() == .ok);
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var debug_info = try std.debug.openSelfDebugInfo(allocator);
|
||||
defer debug_info.deinit();
|
||||
|
||||
var add_addr: usize = undefined;
|
||||
_ = add(1, 2, &add_addr);
|
||||
|
||||
const module = try debug_info.getModuleForAddress(add_addr);
|
||||
const symbol = try module.getSymbolAtAddress(allocator, add_addr);
|
||||
defer symbol.deinit(allocator);
|
||||
|
||||
try testing.expectEqualStrings("add", symbol.symbol_name);
|
||||
try testing.expect(symbol.line_info != null);
|
||||
try testing.expectEqualStrings("shared_lib.c", std.fs.path.basename(symbol.line_info.?.file_name));
|
||||
try testing.expectEqual(@as(u64, 3), symbol.line_info.?.line);
|
||||
try testing.expectEqual(@as(u64, 0), symbol.line_info.?.column);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdint.h>
|
||||
|
||||
__declspec(dllexport) uint32_t add(uint32_t a, uint32_t b, uintptr_t* addr) {
|
||||
*addr = (uintptr_t)&add;
|
||||
return a + b;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const test_step = b.step("test", "Test it");
|
||||
b.default_step = test_step;
|
||||
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
// Unwinding with a frame pointer
|
||||
//
|
||||
// getcontext version: zig std
|
||||
//
|
||||
// Unwind info type:
|
||||
// - ELF: DWARF .debug_frame
|
||||
// - MachO: __unwind_info encodings:
|
||||
// - x86_64: RBP_FRAME
|
||||
// - aarch64: FRAME, DWARF
|
||||
{
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "unwind_fp",
|
||||
.root_source_file = .{ .path = "unwind.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
if (target.isDarwin()) exe.unwind_tables = true;
|
||||
exe.omit_frame_pointer = false;
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
test_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
|
||||
// Unwinding without a frame pointer
|
||||
//
|
||||
// getcontext version: zig std
|
||||
//
|
||||
// Unwind info type:
|
||||
// - ELF: DWARF .eh_frame_hdr + .eh_frame
|
||||
// - MachO: __unwind_info encodings:
|
||||
// - x86_64: STACK_IMMD, STACK_IND
|
||||
// - aarch64: FRAMELESS, DWARF
|
||||
{
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "unwind_nofp",
|
||||
.root_source_file = .{ .path = "unwind.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
exe.omit_frame_pointer = true;
|
||||
exe.unwind_tables = true;
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
test_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
|
||||
// Unwinding through a C shared library without a frame pointer (libc)
|
||||
//
|
||||
// getcontext version: libc
|
||||
//
|
||||
// Unwind info type:
|
||||
// - ELF: DWARF .eh_frame + .debug_frame
|
||||
// - MachO: __unwind_info encodings:
|
||||
// - x86_64: STACK_IMMD, STACK_IND
|
||||
// - aarch64: FRAMELESS, DWARF
|
||||
{
|
||||
const c_shared_lib = b.addSharedLibrary(.{
|
||||
.name = "c_shared_lib",
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)");
|
||||
|
||||
c_shared_lib.strip = false;
|
||||
c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"});
|
||||
c_shared_lib.linkLibC();
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "shared_lib_unwind",
|
||||
.root_source_file = .{ .path = "shared_lib_unwind.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
if (target.isDarwin()) exe.unwind_tables = true;
|
||||
exe.omit_frame_pointer = true;
|
||||
exe.linkLibrary(c_shared_lib);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
test_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef LIB_API
|
||||
#define LIB_API
|
||||
#endif
|
||||
|
||||
__attribute__((noinline)) void frame1(
|
||||
void** expected,
|
||||
void** unwound,
|
||||
void (*frame2)(void** expected, void** unwound)) {
|
||||
expected[3] = __builtin_extract_return_addr(__builtin_return_address(0));
|
||||
frame2(expected, unwound);
|
||||
}
|
||||
|
||||
LIB_API void frame0(
|
||||
void** expected,
|
||||
void** unwound,
|
||||
void (*frame2)(void** expected, void** unwound)) {
|
||||
expected[4] = __builtin_extract_return_addr(__builtin_return_address(0));
|
||||
frame1(expected, unwound, frame2);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const debug = std.debug;
|
||||
const testing = std.testing;
|
||||
|
||||
noinline fn frame4(expected: *[5]usize, unwound: *[5]usize) void {
|
||||
expected[0] = @returnAddress();
|
||||
|
||||
var context: debug.ThreadContext = undefined;
|
||||
testing.expect(debug.getContext(&context)) catch @panic("failed to getContext");
|
||||
|
||||
var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo");
|
||||
var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext");
|
||||
defer it.deinit();
|
||||
|
||||
for (unwound) |*addr| {
|
||||
if (it.next()) |return_address| addr.* = return_address;
|
||||
}
|
||||
}
|
||||
|
||||
noinline fn frame3(expected: *[5]usize, unwound: *[5]usize) void {
|
||||
expected[1] = @returnAddress();
|
||||
frame4(expected, unwound);
|
||||
}
|
||||
|
||||
fn frame2(expected: *[5]usize, unwound: *[5]usize) callconv(.C) void {
|
||||
expected[2] = @returnAddress();
|
||||
frame3(expected, unwound);
|
||||
}
|
||||
|
||||
extern fn frame0(
|
||||
expected: *[5]usize,
|
||||
unwound: *[5]usize,
|
||||
frame_2: *const fn (expected: *[5]usize, unwound: *[5]usize) callconv(.C) void,
|
||||
) void;
|
||||
|
||||
pub fn main() !void {
|
||||
// Disabled until the DWARF unwinder bugs on .aarch64 are solved
|
||||
if (builtin.omit_frame_pointer and comptime builtin.target.isDarwin() and builtin.cpu.arch == .aarch64) return;
|
||||
|
||||
if (!std.debug.have_ucontext or !std.debug.have_getcontext) return;
|
||||
|
||||
var expected: [5]usize = undefined;
|
||||
var unwound: [5]usize = undefined;
|
||||
frame0(&expected, &unwound, &frame2);
|
||||
try testing.expectEqual(expected, unwound);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const debug = std.debug;
|
||||
const testing = std.testing;
|
||||
|
||||
noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void {
|
||||
expected[0] = @returnAddress();
|
||||
|
||||
var context: debug.ThreadContext = undefined;
|
||||
testing.expect(debug.getContext(&context)) catch @panic("failed to getContext");
|
||||
|
||||
var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo");
|
||||
var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext");
|
||||
defer it.deinit();
|
||||
|
||||
for (unwound) |*addr| {
|
||||
if (it.next()) |return_address| addr.* = return_address;
|
||||
}
|
||||
}
|
||||
|
||||
noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void {
|
||||
// Excercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored
|
||||
if (builtin.target.ofmt != .c) {
|
||||
switch (builtin.cpu.arch) {
|
||||
.x86 => {
|
||||
if (builtin.omit_frame_pointer) {
|
||||
asm volatile (
|
||||
\\movl $3, %%ebx
|
||||
\\movl $1, %%ecx
|
||||
\\movl $2, %%edx
|
||||
\\movl $7, %%edi
|
||||
\\movl $6, %%esi
|
||||
\\movl $5, %%ebp
|
||||
::: "ebx", "ecx", "edx", "edi", "esi", "ebp");
|
||||
} else {
|
||||
asm volatile (
|
||||
\\movl $3, %%ebx
|
||||
\\movl $1, %%ecx
|
||||
\\movl $2, %%edx
|
||||
\\movl $7, %%edi
|
||||
\\movl $6, %%esi
|
||||
::: "ebx", "ecx", "edx", "edi", "esi");
|
||||
}
|
||||
},
|
||||
.x86_64 => {
|
||||
if (builtin.omit_frame_pointer) {
|
||||
asm volatile (
|
||||
\\movq $3, %%rbx
|
||||
\\movq $12, %%r12
|
||||
\\movq $13, %%r13
|
||||
\\movq $14, %%r14
|
||||
\\movq $15, %%r15
|
||||
\\movq $6, %%rbp
|
||||
::: "rbx", "r12", "r13", "r14", "r15", "rbp");
|
||||
} else {
|
||||
asm volatile (
|
||||
\\movq $3, %%rbx
|
||||
\\movq $12, %%r12
|
||||
\\movq $13, %%r13
|
||||
\\movq $14, %%r14
|
||||
\\movq $15, %%r15
|
||||
::: "rbx", "r12", "r13", "r14", "r15");
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
expected[1] = @returnAddress();
|
||||
frame3(expected, unwound);
|
||||
}
|
||||
|
||||
noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void {
|
||||
expected[2] = @returnAddress();
|
||||
|
||||
// Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
|
||||
// to exercise the stack-indirect encoding path
|
||||
var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
|
||||
_ = pad;
|
||||
|
||||
frame2(expected, unwound);
|
||||
}
|
||||
|
||||
noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void {
|
||||
expected[3] = @returnAddress();
|
||||
frame1(expected, unwound);
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
// Disabled until the DWARF unwinder bugs on .aarch64 are solved
|
||||
if (builtin.omit_frame_pointer and comptime builtin.target.isDarwin() and builtin.cpu.arch == .aarch64) return;
|
||||
|
||||
if (!std.debug.have_ucontext or !std.debug.have_getcontext) return;
|
||||
|
||||
var expected: [4]usize = undefined;
|
||||
var unwound: [4]usize = undefined;
|
||||
frame0(&expected, &unwound);
|
||||
try testing.expectEqual(expected, unwound);
|
||||
}
|
||||
Reference in New Issue
Block a user