mirror of
https://github.com/ziglang/zig.git
synced 2025-12-22 14:13:08 +00:00
1579 lines
70 KiB
Zig
1579 lines
70 KiB
Zig
//! Cross-platform abstraction for this binary's own debug information, with a
|
|
//! goal of minimal code bloat and compilation speed penalty.
|
|
|
|
const builtin = @import("builtin");
|
|
const native_os = builtin.os.tag;
|
|
const native_endian = native_arch.endian();
|
|
const native_arch = builtin.cpu.arch;
|
|
|
|
const std = @import("../std.zig");
|
|
const mem = std.mem;
|
|
const Allocator = std.mem.Allocator;
|
|
const windows = std.os.windows;
|
|
const macho = std.macho;
|
|
const fs = std.fs;
|
|
const coff = std.coff;
|
|
const assert = std.debug.assert;
|
|
const posix = std.posix;
|
|
const elf = std.elf;
|
|
const Dwarf = std.debug.Dwarf;
|
|
const Pdb = std.debug.Pdb;
|
|
const File = std.fs.File;
|
|
const math = std.math;
|
|
const testing = std.testing;
|
|
const regBytes = Dwarf.abi.regBytes;
|
|
const regValueNative = Dwarf.abi.regValueNative;
|
|
|
|
const SelfInfo = @This();
|
|
|
|
modules: std.AutoHashMapUnmanaged(usize, struct {
|
|
di: Module.DebugInfo,
|
|
loaded_debug: bool,
|
|
loaded_unwind: bool,
|
|
const init: @This() = .{ .di = .init, .loaded_debug = false, .loaded_unwind = false };
|
|
}),
|
|
lookup_cache: Module.LookupCache,
|
|
|
|
pub const target_supported: bool = switch (native_os) {
|
|
.linux,
|
|
.freebsd,
|
|
.netbsd,
|
|
.dragonfly,
|
|
.openbsd,
|
|
.macos,
|
|
.solaris,
|
|
.illumos,
|
|
.windows,
|
|
=> true,
|
|
else => false,
|
|
};
|
|
|
|
pub const init: SelfInfo = .{
|
|
.modules = .empty,
|
|
.lookup_cache = if (Module.LookupCache != void) .init,
|
|
};
|
|
|
|
pub fn deinit(self: *SelfInfo) void {
|
|
// MLUGG TODO: that's amusing, this function is straight-up unused. i... wonder if it even should be used anywhere? perhaps not... so perhaps it should not even exist...????
|
|
var it = self.modules.iterator();
|
|
while (it.next()) |entry| {
|
|
const mdi = entry.value_ptr.*;
|
|
mdi.deinit(self.allocator);
|
|
self.allocator.destroy(mdi);
|
|
}
|
|
self.modules.deinit(self.allocator);
|
|
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);
|
|
}
|
|
}
|
|
|
|
pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
|
|
comptime assert(target_supported);
|
|
const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); // MLUGG TODO: don't take gpa
|
|
const gop = try self.modules.getOrPut(gpa, module.load_offset);
|
|
if (!gop.found_existing) gop.value_ptr.* = .init;
|
|
if (!gop.value_ptr.loaded_unwind) {
|
|
try module.loadUnwindInfo(gpa, &gop.value_ptr.di);
|
|
gop.value_ptr.loaded_unwind = true;
|
|
}
|
|
// MLUGG TODO: the stuff below is impl!
|
|
if (native_os.isDarwin()) {
|
|
// __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 (gop.value_ptr.di.unwind_info) |unwind_info| {
|
|
if (unwindFrameMachO(
|
|
module.text_base,
|
|
module.load_offset,
|
|
context,
|
|
unwind_info,
|
|
gop.value_ptr.di.eh_frame,
|
|
)) |return_address| {
|
|
return return_address;
|
|
} else |err| {
|
|
if (err != error.RequiresDWARFUnwind) return err;
|
|
}
|
|
}
|
|
return error.MissingUnwindInfo;
|
|
}
|
|
if (try gop.value_ptr.di.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| {
|
|
return unwindFrameDwarf(unwind, module.load_offset, context, null);
|
|
}
|
|
return error.MissingDebugInfo;
|
|
}
|
|
|
|
pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
|
|
comptime assert(target_supported);
|
|
const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa
|
|
const gop = try self.modules.getOrPut(gpa, module.key());
|
|
if (!gop.found_existing) gop.value_ptr.* = .init;
|
|
if (!gop.value_ptr.loaded_debug) {
|
|
// MLUGG TODO: this overloads the name 'debug info' with including vs excluding unwind info
|
|
// figure out a better name for one or the other (i think the inner one is maybe 'symbol info' or something idk)
|
|
try module.loadDebugInfo(gpa, &gop.value_ptr.di);
|
|
}
|
|
return module.getSymbolAtAddress(gpa, &gop.value_ptr.di, address);
|
|
}
|
|
|
|
/// 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: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
|
|
comptime assert(target_supported);
|
|
const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa
|
|
return module.name;
|
|
}
|
|
|
|
const Module = switch (native_os) {
|
|
else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
|
|
.macos, .ios, .watchos, .tvos, .visionos => struct {
|
|
/// The runtime address where __TEXT is loaded.
|
|
text_base: usize,
|
|
load_offset: usize,
|
|
name: []const u8,
|
|
unwind_info: ?[]const u8,
|
|
eh_frame: ?[]const u8,
|
|
fn key(m: *const Module) usize {
|
|
return m.text_base;
|
|
}
|
|
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
|
|
_ = cache;
|
|
_ = gpa;
|
|
const image_count = std.c._dyld_image_count();
|
|
for (0..image_count) |image_idx| {
|
|
const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
|
|
const text_base = @intFromPtr(header);
|
|
if (address < text_base) continue;
|
|
const load_offset = std.c._dyld_get_image_vmaddr_slide(@intCast(image_idx));
|
|
|
|
// Find the __TEXT segment
|
|
var it: macho.LoadCommandIterator = .{
|
|
.ncmds = header.ncmds,
|
|
.buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
|
|
};
|
|
const text_segment_cmd, const text_sections = while (it.next()) |load_cmd| {
|
|
if (load_cmd.cmd() != .SEGMENT_64) continue;
|
|
const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
|
|
if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
|
|
break .{ segment_cmd, load_cmd.getSections() };
|
|
} else continue;
|
|
|
|
const seg_start = load_offset + text_segment_cmd.vmaddr;
|
|
assert(seg_start == text_base);
|
|
const seg_end = seg_start + text_segment_cmd.vmsize;
|
|
if (address < seg_start or address >= seg_end) continue;
|
|
|
|
// We've found the matching __TEXT segment. This is the image we need, but we must look
|
|
// for unwind info in it before returning.
|
|
|
|
var result: Module = .{
|
|
.text_base = text_base,
|
|
.load_offset = load_offset,
|
|
.name = mem.span(std.c._dyld_get_image_name(@intCast(image_idx))),
|
|
.unwind_info = null,
|
|
.eh_frame = null,
|
|
};
|
|
for (text_sections) |sect| {
|
|
if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
|
|
const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr)));
|
|
result.unwind_info = sect_ptr[0..@intCast(sect.size)];
|
|
} else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
|
|
const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(load_offset + sect.addr)));
|
|
result.eh_frame = sect_ptr[0..@intCast(sect.size)];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
return error.MissingDebugInfo;
|
|
}
|
|
fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
|
|
return loadMachODebugInfo(gpa, module, di);
|
|
}
|
|
fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
|
|
// MLUGG TODO HACKHACK
|
|
try loadMachODebugInfo(gpa, module, di);
|
|
}
|
|
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
|
const vaddr = address - module.load_offset;
|
|
const symbol = MachoSymbol.find(di.symbols, vaddr) orelse return .{}; // MLUGG TODO null?
|
|
|
|
// offset of `address` from start of `symbol`
|
|
const address_symbol_offset = vaddr - symbol.addr;
|
|
|
|
// 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(di.strings[symbol.strx..], 0);
|
|
const o_file_path = mem.sliceTo(di.strings[symbol.ofile..], 0);
|
|
|
|
const o_file: *DebugInfo.OFile = of: {
|
|
const gop = try di.ofiles.getOrPut(gpa, o_file_path);
|
|
if (!gop.found_existing) {
|
|
gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
|
|
defer _ = di.ofiles.pop().?;
|
|
switch (err) {
|
|
error.FileNotFound,
|
|
error.MissingDebugInfo,
|
|
error.InvalidDebugInfo,
|
|
=> return .{ .name = stab_symbol },
|
|
else => |e| return e,
|
|
}
|
|
};
|
|
}
|
|
break :of gop.value_ptr;
|
|
};
|
|
|
|
const symbol_ofile_vaddr = o_file.addr_table.get(stab_symbol) orelse return .{ .name = stab_symbol };
|
|
|
|
const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
|
|
error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = stab_symbol },
|
|
else => |e| return e,
|
|
};
|
|
|
|
return .{
|
|
.name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) orelse stab_symbol,
|
|
.compile_unit_name = compile_unit.die.getAttrString(
|
|
&o_file.dwarf,
|
|
native_endian,
|
|
std.dwarf.AT.name,
|
|
o_file.dwarf.section(.debug_str),
|
|
compile_unit,
|
|
) catch |err| switch (err) {
|
|
error.MissingDebugInfo, error.InvalidDebugInfo => "???",
|
|
},
|
|
.source_location = o_file.dwarf.getLineNumberInfo(
|
|
gpa,
|
|
native_endian,
|
|
compile_unit,
|
|
symbol_ofile_vaddr + address_symbol_offset,
|
|
) catch |err| switch (err) {
|
|
error.MissingDebugInfo, error.InvalidDebugInfo => null,
|
|
else => return err,
|
|
},
|
|
};
|
|
}
|
|
const LookupCache = void;
|
|
const DebugInfo = struct {
|
|
mapped_memory: []align(std.heap.page_size_min) const u8,
|
|
symbols: []const MachoSymbol,
|
|
strings: [:0]const u8,
|
|
// MLUGG TODO: this could use an adapter to just index straight into `strings`!
|
|
ofiles: std.StringArrayHashMapUnmanaged(OFile),
|
|
|
|
// Backed by the in-memory sections mapped by the loader
|
|
// MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them!
|
|
unwind_info: ?[]const u8,
|
|
eh_frame: ?[]const u8,
|
|
|
|
// MLUGG TODO HACKHACK: this is awful
|
|
const init: DebugInfo = undefined;
|
|
|
|
const OFile = struct {
|
|
dwarf: Dwarf,
|
|
// MLUGG TODO: this could use an adapter to just index straight into the strtab!
|
|
addr_table: std.StringArrayHashMapUnmanaged(u64),
|
|
};
|
|
|
|
fn deinit(di: *DebugInfo, gpa: Allocator) void {
|
|
for (di.ofiles.values()) |*ofile| {
|
|
ofile.dwarf.deinit(gpa);
|
|
ofile.addr_table.deinit(gpa);
|
|
}
|
|
di.ofiles.deinit();
|
|
gpa.free(di.symbols);
|
|
posix.munmap(di.mapped_memory);
|
|
}
|
|
|
|
fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
|
|
const mapped_mem = try mapFileOrSelfExe(o_file_path);
|
|
errdefer posix.munmap(mapped_mem);
|
|
|
|
if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
|
|
const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
|
|
if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
|
|
|
|
const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
|
|
var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
|
|
var symtab_cmd: ?macho.symtab_command = null;
|
|
var it: macho.LoadCommandIterator = .{
|
|
.ncmds = hdr.ncmds,
|
|
.buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
|
|
};
|
|
while (it.next()) |cmd| switch (cmd.cmd()) {
|
|
.SEGMENT_64 => seg_cmd = cmd,
|
|
.SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
|
|
else => {},
|
|
};
|
|
break :cmds .{
|
|
seg_cmd orelse return error.MissingDebugInfo,
|
|
symtab_cmd orelse return error.MissingDebugInfo,
|
|
};
|
|
};
|
|
|
|
if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
|
|
if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
|
|
const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
|
|
|
|
const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
|
|
if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
|
|
const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
|
|
|
|
// TODO handle tentative (common) symbols
|
|
// MLUGG TODO: does initCapacity actually make sense?
|
|
var addr_table: std.StringArrayHashMapUnmanaged(u64) = .empty;
|
|
defer addr_table.deinit(gpa);
|
|
try addr_table.ensureUnusedCapacity(gpa, @intCast(symtab.len));
|
|
for (symtab) |sym| {
|
|
if (sym.n_strx == 0) continue;
|
|
switch (sym.n_type.bits.type) {
|
|
.undf => continue, // includes tentative symbols
|
|
.abs => continue,
|
|
else => {},
|
|
}
|
|
const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
|
|
const gop = addr_table.getOrPutAssumeCapacity(sym_name);
|
|
if (gop.found_existing) return error.InvalidDebugInfo;
|
|
gop.value_ptr.* = sym.n_value;
|
|
}
|
|
|
|
var sections: Dwarf.SectionArray = @splat(null);
|
|
for (seg_cmd.getSections()) |sect| {
|
|
if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
|
|
|
|
const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
|
|
if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
|
|
} else continue;
|
|
|
|
const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);
|
|
sections[section_index] = .{
|
|
.data = section_bytes,
|
|
.virtual_address = @intCast(sect.addr),
|
|
.owned = false,
|
|
};
|
|
}
|
|
|
|
const missing_debug_info =
|
|
sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
|
|
sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
|
|
sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
|
|
sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
|
|
if (missing_debug_info) return error.MissingDebugInfo;
|
|
|
|
var dwarf: Dwarf = .{ .sections = sections };
|
|
errdefer dwarf.deinit(gpa);
|
|
try dwarf.open(gpa, native_endian);
|
|
|
|
return .{
|
|
.dwarf = dwarf,
|
|
.addr_table = addr_table.move(),
|
|
};
|
|
}
|
|
};
|
|
},
|
|
.wasi, .emscripten => struct {
|
|
const LookupCache = void;
|
|
const DebugInfo = struct {
|
|
const init: DebugInfo = .{};
|
|
};
|
|
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
|
|
_ = cache;
|
|
_ = gpa;
|
|
_ = address;
|
|
@panic("TODO implement lookup module for Wasm");
|
|
}
|
|
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
|
_ = module;
|
|
_ = gpa;
|
|
_ = di;
|
|
_ = address;
|
|
unreachable;
|
|
}
|
|
fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
|
|
_ = module;
|
|
_ = gpa;
|
|
_ = di;
|
|
unreachable;
|
|
}
|
|
fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
|
|
_ = module;
|
|
_ = gpa;
|
|
_ = di;
|
|
unreachable;
|
|
}
|
|
},
|
|
.linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
|
|
load_offset: usize,
|
|
name: []const u8,
|
|
build_id: ?[]const u8,
|
|
gnu_eh_frame: ?[]const u8,
|
|
const LookupCache = void;
|
|
const DebugInfo = Dwarf.ElfModule;
|
|
fn key(m: Module) usize {
|
|
return m.load_offset; // MLUGG TODO: is this technically valid? idk
|
|
}
|
|
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
|
|
_ = cache;
|
|
_ = gpa;
|
|
if (native_os == .haiku) @panic("TODO implement lookup module for Haiku");
|
|
const DlIterContext = struct {
|
|
/// input
|
|
address: usize,
|
|
/// output
|
|
module: Module,
|
|
|
|
fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
|
|
_ = size;
|
|
// The base address is too high
|
|
if (context.address < info.addr)
|
|
return;
|
|
|
|
const phdrs = info.phdr[0..info.phnum];
|
|
for (phdrs) |*phdr| {
|
|
if (phdr.p_type != elf.PT_LOAD) continue;
|
|
|
|
// Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
|
|
const seg_start = info.addr +% phdr.p_vaddr;
|
|
const seg_end = seg_start + phdr.p_memsz;
|
|
if (context.address >= seg_start and context.address < seg_end) {
|
|
context.module = .{
|
|
.load_offset = info.addr,
|
|
// Android libc uses NULL instead of "" to mark the main program
|
|
.name = mem.sliceTo(info.name, 0) orelse "",
|
|
.build_id = null,
|
|
.gnu_eh_frame = null,
|
|
};
|
|
break;
|
|
}
|
|
} else return;
|
|
|
|
for (info.phdr[0..info.phnum]) |phdr| {
|
|
switch (phdr.p_type) {
|
|
elf.PT_NOTE => {
|
|
// Look for .note.gnu.build-id
|
|
const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
|
|
var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
|
|
const name_size = r.takeInt(u32, native_endian) catch continue;
|
|
const desc_size = r.takeInt(u32, native_endian) catch continue;
|
|
const note_type = r.takeInt(u32, native_endian) catch continue;
|
|
const name = r.take(name_size) catch continue;
|
|
if (note_type != elf.NT_GNU_BUILD_ID) continue;
|
|
if (!mem.eql(u8, name, "GNU\x00")) continue;
|
|
const desc = r.take(desc_size) catch continue;
|
|
context.module.build_id = desc;
|
|
},
|
|
elf.PT_GNU_EH_FRAME => {
|
|
const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
|
|
context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
// Stop the iteration
|
|
return error.Found;
|
|
}
|
|
};
|
|
var ctx: DlIterContext = .{
|
|
.address = address,
|
|
.module = undefined,
|
|
};
|
|
posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
|
|
error.Found => return ctx.module,
|
|
};
|
|
return error.MissingDebugInfo;
|
|
}
|
|
fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
|
|
const filename: ?[]const u8 = if (module.name.len > 0) module.name else null;
|
|
const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) {
|
|
error.FileNotFound => return error.MissingDebugInfo,
|
|
error.FileTooBig => return error.InvalidDebugInfo,
|
|
else => |e| return e,
|
|
};
|
|
errdefer posix.munmap(mapped_mem);
|
|
try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename);
|
|
assert(di.mapped_memory != null);
|
|
}
|
|
fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
|
|
const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
|
|
const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
|
|
const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
|
|
try di.unwind.loadFromEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
|
|
try di.unwind.prepareLookup(gpa, @sizeOf(usize), native_endian);
|
|
}
|
|
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
|
return di.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);
|
|
}
|
|
},
|
|
.uefi, .windows => struct {
|
|
base_address: usize,
|
|
size: usize,
|
|
name: []const u8,
|
|
handle: windows.HMODULE,
|
|
fn key(m: Module) usize {
|
|
return m.base_address;
|
|
}
|
|
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
|
|
if (lookupInCache(cache, address)) |m| return m;
|
|
{
|
|
// Check a new module hasn't been loaded
|
|
cache.modules.clearRetainingCapacity();
|
|
|
|
const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
|
|
if (handle == windows.INVALID_HANDLE_VALUE) {
|
|
return windows.unexpectedError(windows.GetLastError());
|
|
}
|
|
defer windows.CloseHandle(handle);
|
|
|
|
var entry: windows.MODULEENTRY32 = undefined;
|
|
entry.dwSize = @sizeOf(windows.MODULEENTRY32);
|
|
if (windows.kernel32.Module32First(handle, &entry) != 0) {
|
|
try cache.modules.append(gpa, entry);
|
|
while (windows.kernel32.Module32Next(handle, &entry) != 0) {
|
|
try cache.modules.append(gpa, entry);
|
|
}
|
|
}
|
|
}
|
|
if (lookupInCache(cache, address)) |m| return m;
|
|
return error.MissingDebugInfo;
|
|
}
|
|
fn lookupInCache(cache: *const LookupCache, address: usize) ?Module {
|
|
for (cache.modules.items) |*entry| {
|
|
const base_address = @intFromPtr(entry.modBaseAddr);
|
|
if (address >= base_address and address < base_address + entry.modBaseSize) {
|
|
return .{
|
|
.base_address = base_address,
|
|
.size = entry.modBaseSize,
|
|
.name = std.mem.sliceTo(&entry.szModule, 0),
|
|
.handle = entry.hModule,
|
|
};
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
|
|
const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
|
|
const mapped = mapped_ptr[0..module.size];
|
|
var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
|
|
// 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;
|
|
name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
|
|
const process_handle = windows.GetCurrentProcess();
|
|
const len = windows.kernel32.GetModuleFileNameExW(
|
|
process_handle,
|
|
module.handle,
|
|
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 => |e| return e,
|
|
};
|
|
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 section_view_ptr: [*]const u8 = undefined;
|
|
const map_section_rc = windows.ntdll.NtMapViewOfSection(
|
|
section_handle,
|
|
process_handle,
|
|
@ptrCast(§ion_view_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, @constCast(section_view_ptr)) == .SUCCESS);
|
|
const section_view = section_view_ptr[0..coff_len];
|
|
coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
|
|
di.mapped_file = .{
|
|
.file = coff_file,
|
|
.section_handle = section_handle,
|
|
.section_view = section_view,
|
|
};
|
|
}
|
|
di.coff_image_base = coff_obj.getImageBase();
|
|
|
|
if (coff_obj.getSectionByName(".debug_info")) |_| {
|
|
di.dwarf = .{};
|
|
|
|
inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
|
|
di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
|
|
break :blk .{
|
|
.data = try coff_obj.getSectionDataAlloc(section_header, gpa),
|
|
.virtual_address = section_header.virtual_address,
|
|
.owned = true,
|
|
};
|
|
} else null;
|
|
}
|
|
|
|
try di.dwarf.?.open(gpa, native_endian);
|
|
}
|
|
|
|
if (try coff_obj.getPdbPath()) |raw_path| pdb: {
|
|
const path = blk: {
|
|
if (fs.path.isAbsolute(raw_path)) {
|
|
break :blk raw_path;
|
|
} else {
|
|
const self_dir = try fs.selfExeDirPathAlloc(gpa);
|
|
defer gpa.free(self_dir);
|
|
break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
|
|
}
|
|
};
|
|
defer if (path.ptr != raw_path.ptr) gpa.free(path);
|
|
|
|
di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
|
|
error.FileNotFound, error.IsDir => break :pdb,
|
|
else => return err,
|
|
};
|
|
try di.pdb.?.parseInfoStream();
|
|
try di.pdb.?.parseDbiStream();
|
|
|
|
if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
|
|
return error.InvalidDebugInfo;
|
|
|
|
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
|
|
}
|
|
}
|
|
const LookupCache = struct {
|
|
modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
|
|
const init: LookupCache = .{ .modules = .empty };
|
|
};
|
|
const DebugInfo = struct {
|
|
coff_image_base: u64,
|
|
mapped_file: ?struct {
|
|
file: File,
|
|
section_handle: windows.HANDLE,
|
|
section_view: []const u8,
|
|
fn deinit(mapped: @This()) void {
|
|
const process_handle = windows.GetCurrentProcess();
|
|
assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS);
|
|
windows.CloseHandle(mapped.section_handle);
|
|
mapped.file.close();
|
|
}
|
|
},
|
|
|
|
dwarf: ?Dwarf,
|
|
|
|
pdb: ?Pdb,
|
|
/// Populated iff `pdb != null`; otherwise `&.{}`.
|
|
coff_section_headers: []coff.SectionHeader,
|
|
|
|
const init: DebugInfo = .{
|
|
.coff_image_base = undefined,
|
|
.mapped_file = null,
|
|
.dwarf = null,
|
|
.pdb = null,
|
|
.coff_section_headers = &.{},
|
|
};
|
|
|
|
fn deinit(di: *DebugInfo, gpa: Allocator) void {
|
|
if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
|
|
if (di.pdb) |*pdb| pdb.deinit();
|
|
gpa.free(di.coff_section_headers);
|
|
if (di.mapped_file) |mapped| mapped.deinit();
|
|
}
|
|
|
|
fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol {
|
|
var coff_section: *align(1) const coff.SectionHeader = undefined;
|
|
const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| {
|
|
if (sect_contrib.section > di.coff_section_headers.len) continue;
|
|
// Remember that SectionContribEntry.Section is 1-based.
|
|
coff_section = &di.coff_section_headers[sect_contrib.section - 1];
|
|
|
|
const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
|
|
const vaddr_end = vaddr_start + sect_contrib.size;
|
|
if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
|
|
break sect_contrib.module_index;
|
|
}
|
|
} else {
|
|
// we have no information to add to the address
|
|
return null;
|
|
};
|
|
|
|
const module = (try di.pdb.?.getModule(mod_index)) orelse
|
|
return error.InvalidDebugInfo;
|
|
const obj_basename = fs.path.basename(module.obj_file_name);
|
|
|
|
const symbol_name = di.pdb.?.getSymbolName(
|
|
module,
|
|
relocated_address - coff_section.virtual_address,
|
|
) orelse "???";
|
|
const opt_line_info = try di.pdb.?.getLineNumberInfo(
|
|
module,
|
|
relocated_address - coff_section.virtual_address,
|
|
);
|
|
|
|
return .{
|
|
.name = symbol_name,
|
|
.compile_unit_name = obj_basename,
|
|
.source_location = opt_line_info,
|
|
};
|
|
}
|
|
};
|
|
|
|
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
|
// Translate the runtime address into a virtual address into the module
|
|
const vaddr = address - module.base_address;
|
|
|
|
if (di.pdb != null) {
|
|
if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
|
|
}
|
|
|
|
if (di.dwarf) |*dwarf| {
|
|
const dwarf_address = vaddr + di.coff_image_base;
|
|
return dwarf.getSymbol(gpa, native_endian, dwarf_address);
|
|
}
|
|
|
|
return error.MissingDebugInfo;
|
|
}
|
|
},
|
|
};
|
|
|
|
fn loadMachODebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
|
|
const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) {
|
|
error.FileNotFound => return error.MissingDebugInfo,
|
|
error.FileTooBig => return error.InvalidDebugInfo,
|
|
else => |e| return e,
|
|
};
|
|
errdefer posix.munmap(mapped_mem);
|
|
|
|
const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
|
|
if (hdr.magic != macho.MH_MAGIC_64)
|
|
return error.InvalidDebugInfo;
|
|
|
|
const symtab: macho.symtab_command = symtab: {
|
|
var it: macho.LoadCommandIterator = .{
|
|
.ncmds = hdr.ncmds,
|
|
.buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
|
|
};
|
|
while (it.next()) |cmd| switch (cmd.cmd()) {
|
|
.SYMTAB => break :symtab cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
|
|
else => {},
|
|
};
|
|
return error.MissingDebugInfo;
|
|
};
|
|
|
|
const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab.symoff..]);
|
|
const syms = syms_ptr[0..symtab.nsyms];
|
|
const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
|
|
|
|
// MLUGG TODO: does it really make sense to initCapacity here? how many of syms are omitted?
|
|
var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
|
|
defer symbols.deinit(gpa);
|
|
|
|
var ofile: u32 = undefined;
|
|
var last_sym: MachoSymbol = undefined;
|
|
var state: enum {
|
|
init,
|
|
oso_open,
|
|
oso_close,
|
|
bnsym,
|
|
fun_strx,
|
|
fun_size,
|
|
ensym,
|
|
} = .init;
|
|
|
|
for (syms) |*sym| {
|
|
if (sym.n_type.bits.is_stab == 0) continue;
|
|
|
|
// TODO handle globals N_GSYM, and statics N_STSYM
|
|
switch (sym.n_type.stab) {
|
|
.oso => switch (state) {
|
|
.init, .oso_close => {
|
|
state = .oso_open;
|
|
ofile = sym.n_strx;
|
|
},
|
|
else => return error.InvalidDebugInfo,
|
|
},
|
|
.bnsym => switch (state) {
|
|
.oso_open, .ensym => {
|
|
state = .bnsym;
|
|
last_sym = .{
|
|
.strx = 0,
|
|
.addr = sym.n_value,
|
|
.size = 0,
|
|
.ofile = ofile,
|
|
};
|
|
},
|
|
else => return error.InvalidDebugInfo,
|
|
},
|
|
.fun => switch (state) {
|
|
.bnsym => {
|
|
state = .fun_strx;
|
|
last_sym.strx = sym.n_strx;
|
|
},
|
|
.fun_strx => {
|
|
state = .fun_size;
|
|
last_sym.size = @intCast(sym.n_value);
|
|
},
|
|
else => return error.InvalidDebugInfo,
|
|
},
|
|
.ensym => switch (state) {
|
|
.fun_size => {
|
|
state = .ensym;
|
|
symbols.appendAssumeCapacity(last_sym);
|
|
},
|
|
else => return error.InvalidDebugInfo,
|
|
},
|
|
.so => switch (state) {
|
|
.init, .oso_close => {},
|
|
.oso_open, .ensym => {
|
|
state = .oso_close;
|
|
},
|
|
else => return error.InvalidDebugInfo,
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
switch (state) {
|
|
.init => return error.MissingDebugInfo,
|
|
.oso_close => {},
|
|
else => return error.InvalidDebugInfo,
|
|
}
|
|
|
|
const symbols_slice = try symbols.toOwnedSlice(gpa);
|
|
errdefer gpa.free(symbols_slice);
|
|
|
|
// Even though lld emits symbols in ascending order, this debug code
|
|
// should work for programs linked in any valid way.
|
|
// This sort is so that we can binary search later.
|
|
mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
|
|
|
|
di.* = .{
|
|
.unwind_info = module.unwind_info,
|
|
.eh_frame = module.eh_frame,
|
|
.mapped_memory = mapped_mem,
|
|
.symbols = symbols_slice,
|
|
.strings = strings,
|
|
.ofiles = .empty,
|
|
};
|
|
}
|
|
|
|
const MachoSymbol = struct {
|
|
strx: u32,
|
|
addr: u64,
|
|
size: u32,
|
|
ofile: u32,
|
|
fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
|
|
_ = context;
|
|
return lhs.addr < rhs.addr;
|
|
}
|
|
/// Assumes that `symbols` is sorted in order of ascending `addr`.
|
|
fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
|
|
if (symbols.len == 0) return null; // no potential match
|
|
if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
|
|
var left: usize = 0;
|
|
var len: usize = symbols.len;
|
|
while (len > 1) {
|
|
const mid = left + len / 2;
|
|
if (address < symbols[mid].addr) {
|
|
len /= 2;
|
|
} else {
|
|
left = mid;
|
|
len -= len / 2;
|
|
}
|
|
}
|
|
return &symbols[left];
|
|
}
|
|
|
|
test find {
|
|
const symbols: []const MachoSymbol = &.{
|
|
.{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
|
|
.{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
|
|
.{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
|
|
};
|
|
|
|
try testing.expectEqual(null, find(symbols, 0));
|
|
try testing.expectEqual(null, find(symbols, 99));
|
|
try testing.expectEqual(&symbols[0], find(symbols, 100).?);
|
|
try testing.expectEqual(&symbols[0], find(symbols, 150).?);
|
|
try testing.expectEqual(&symbols[0], find(symbols, 199).?);
|
|
|
|
try testing.expectEqual(&symbols[1], find(symbols, 200).?);
|
|
try testing.expectEqual(&symbols[1], find(symbols, 250).?);
|
|
try testing.expectEqual(&symbols[1], find(symbols, 299).?);
|
|
|
|
try testing.expectEqual(&symbols[2], find(symbols, 300).?);
|
|
try testing.expectEqual(&symbols[2], find(symbols, 301).?);
|
|
try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
|
|
}
|
|
};
|
|
test {
|
|
_ = MachoSymbol;
|
|
}
|
|
|
|
pub const UnwindContext = struct {
|
|
gpa: Allocator,
|
|
cfa: ?usize,
|
|
pc: usize,
|
|
thread_context: *std.debug.ThreadContext,
|
|
reg_context: Dwarf.abi.RegisterContext,
|
|
vm: Dwarf.Unwind.VirtualMachine,
|
|
stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
|
|
|
|
pub fn init(gpa: Allocator, thread_context: *std.debug.ThreadContext) !UnwindContext {
|
|
comptime assert(supports_unwinding);
|
|
|
|
const pc = stripInstructionPtrAuthCode(
|
|
(try regValueNative(thread_context, ip_reg_num, null)).*,
|
|
);
|
|
|
|
const context_copy = try gpa.create(std.debug.ThreadContext);
|
|
std.debug.copyContext(thread_context, context_copy);
|
|
|
|
return .{
|
|
.gpa = gpa,
|
|
.cfa = null,
|
|
.pc = pc,
|
|
.thread_context = context_copy,
|
|
.reg_context = undefined,
|
|
.vm = .{},
|
|
.stack_machine = .{},
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *UnwindContext) void {
|
|
self.vm.deinit(self.gpa);
|
|
self.stack_machine.deinit(self.gpa);
|
|
self.gpa.destroy(self.thread_context);
|
|
self.* = undefined;
|
|
}
|
|
|
|
pub fn getFp(self: *const UnwindContext) !usize {
|
|
return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*;
|
|
}
|
|
|
|
/// Resolves the register rule and places the result into `out` (see regBytes)
|
|
pub fn resolveRegisterRule(
|
|
context: *UnwindContext,
|
|
col: Dwarf.Unwind.VirtualMachine.Column,
|
|
expression_context: std.debug.Dwarf.expression.Context,
|
|
out: []u8,
|
|
) !void {
|
|
switch (col.rule) {
|
|
.default => {
|
|
const register = col.register orelse return error.InvalidRegister;
|
|
// The default type is usually undefined, but can be overriden by ABI authors.
|
|
// See the doc comment on `Dwarf.Unwind.VirtualMachine.RegisterRule.default`.
|
|
if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 18) {
|
|
// Callee-saved registers are initialized as if they had the .same_value rule
|
|
const src = try regBytes(context.thread_context, register, context.reg_context);
|
|
if (src.len != out.len) return error.RegisterSizeMismatch;
|
|
@memcpy(out, src);
|
|
return;
|
|
}
|
|
@memset(out, undefined);
|
|
},
|
|
.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 = col.register orelse return error.InvalidRegister;
|
|
const src = try 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);
|
|
const ptr: *const usize = @ptrFromInt(addr);
|
|
mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
|
|
} else return error.InvalidCFA;
|
|
},
|
|
.val_offset => |offset| {
|
|
if (context.cfa) |cfa| {
|
|
mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
|
|
} else return error.InvalidCFA;
|
|
},
|
|
.register => |register| {
|
|
const src = try regBytes(context.thread_context, register, context.reg_context);
|
|
if (src.len != out.len) return error.RegisterSizeMismatch;
|
|
@memcpy(out, try regBytes(context.thread_context, register, context.reg_context));
|
|
},
|
|
.expression => |expression| {
|
|
context.stack_machine.reset();
|
|
const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?);
|
|
const addr = if (value) |v| blk: {
|
|
if (v != .generic) return error.InvalidExpressionValue;
|
|
break :blk v.generic;
|
|
} else return error.NoExpressionValue;
|
|
|
|
const ptr: *usize = @ptrFromInt(addr);
|
|
mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
|
|
},
|
|
.val_expression => |expression| {
|
|
context.stack_machine.reset();
|
|
const value = try context.stack_machine.run(expression, context.gpa, expression_context, context.cfa.?);
|
|
if (value) |v| {
|
|
if (v != .generic) return error.InvalidExpressionValue;
|
|
mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian);
|
|
} else return error.NoExpressionValue;
|
|
},
|
|
.architectural => return error.UnimplementedRegisterRule,
|
|
}
|
|
}
|
|
};
|
|
|
|
/// 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 (native_arch.isAARCH64()) {
|
|
// `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 = true });
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/// Unwind a stack frame using DWARF unwinding info, updating the register context.
|
|
///
|
|
/// If `.eh_frame_hdr` is available and complete, 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. The latter
|
|
/// may require lazily loading the data in those sections.
|
|
///
|
|
/// `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.
|
|
fn unwindFrameDwarf(
|
|
unwind: *const Dwarf.Unwind,
|
|
load_offset: usize,
|
|
context: *UnwindContext,
|
|
explicit_fde_offset: ?usize,
|
|
) !usize {
|
|
if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
|
|
if (context.pc == 0) return 0;
|
|
|
|
const pc_vaddr = context.pc - load_offset;
|
|
|
|
const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
|
|
pc_vaddr,
|
|
@sizeOf(usize),
|
|
native_endian,
|
|
) orelse return error.MissingDebugInfo;
|
|
const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian);
|
|
|
|
// Check if this FDE *actually* includes the address.
|
|
if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
|
|
|
|
// Do not set `compile_unit` because the spec states that CFIs
|
|
// may not reference other debug sections anyway.
|
|
var expression_context: Dwarf.expression.Context = .{
|
|
.format = format,
|
|
.thread_context = context.thread_context,
|
|
.reg_context = context.reg_context,
|
|
.cfa = context.cfa,
|
|
};
|
|
|
|
context.vm.reset();
|
|
context.reg_context.eh_frame = cie.version != 4;
|
|
context.reg_context.is_macho = native_os.isDarwin();
|
|
|
|
const row = try context.vm.runTo(context.gpa, context.pc - load_offset, cie, fde, @sizeOf(usize), native_endian);
|
|
context.cfa = switch (row.cfa.rule) {
|
|
.val_offset => |offset| blk: {
|
|
const register = row.cfa.register orelse return error.InvalidCFARule;
|
|
const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian);
|
|
break :blk try applyOffset(value, offset);
|
|
},
|
|
.expression => |expr| blk: {
|
|
context.stack_machine.reset();
|
|
const value = try context.stack_machine.run(
|
|
expr,
|
|
context.gpa,
|
|
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,
|
|
};
|
|
|
|
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.gpa);
|
|
defer arena.deinit();
|
|
const update_arena = arena.allocator();
|
|
|
|
const RegisterUpdate = struct {
|
|
// Backed by thread_context
|
|
dest: []u8,
|
|
// Backed by arena
|
|
src: []const u8,
|
|
prev: ?*@This(),
|
|
};
|
|
|
|
var update_tail: ?*RegisterUpdate = null;
|
|
var has_return_address = true;
|
|
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 regBytes(context.thread_context, register, context.reg_context);
|
|
const src = try update_arena.alloc(u8, dest.len);
|
|
try context.resolveRegisterRule(column, expression_context, src);
|
|
|
|
const new_update = try update_arena.create(RegisterUpdate);
|
|
new_update.* = .{
|
|
.dest = dest,
|
|
.src = src,
|
|
.prev = update_tail,
|
|
};
|
|
update_tail = new_update;
|
|
}
|
|
}
|
|
|
|
// On all implemented architectures, the CFA is defined as being the previous frame's SP
|
|
(try regValueNative(context.thread_context, 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 = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes(
|
|
context.thread_context,
|
|
cie.return_address_register,
|
|
context.reg_context,
|
|
))[0..@sizeOf(usize)], native_endian));
|
|
} else {
|
|
context.pc = 0;
|
|
}
|
|
|
|
(try regValueNative(context.thread_context, ip_reg_num, 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.is_signal_frame) context.pc -= 1;
|
|
|
|
return return_address;
|
|
}
|
|
|
|
fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
|
|
return Dwarf.abi.fpRegNum(native_arch, reg_context);
|
|
}
|
|
|
|
fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 {
|
|
return Dwarf.abi.spRegNum(native_arch, reg_context);
|
|
}
|
|
|
|
const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
|
|
|
|
/// Tells whether unwinding for the host is implemented.
|
|
pub const supports_unwinding = supportsUnwinding(&builtin.target);
|
|
|
|
comptime {
|
|
if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target));
|
|
}
|
|
|
|
/// Tells whether unwinding for this target is *implemented* here in the Zig
|
|
/// standard library.
|
|
///
|
|
/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports
|
|
/// unwinding on that target *in theory*.
|
|
pub fn supportsUnwinding(target: *const std.Target) bool {
|
|
return switch (target.cpu.arch) {
|
|
.x86 => switch (target.os.tag) {
|
|
.linux, .netbsd, .solaris, .illumos => true,
|
|
else => false,
|
|
},
|
|
.x86_64 => switch (target.os.tag) {
|
|
.linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
|
|
else => false,
|
|
},
|
|
.arm, .armeb, .thumb, .thumbeb => switch (target.os.tag) {
|
|
.linux => true,
|
|
else => false,
|
|
},
|
|
.aarch64, .aarch64_be => switch (target.os.tag) {
|
|
.linux, .netbsd, .freebsd, .macos, .ios => true,
|
|
else => false,
|
|
},
|
|
// Unwinding is possible on other targets but this implementation does
|
|
// not support them...yet!
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// 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.
|
|
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)));
|
|
}
|
|
|
|
/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory.
|
|
fn mapFileOrSelfExe(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
|
|
const file = if (opt_path) |path|
|
|
try fs.cwd().openFile(path, .{})
|
|
else
|
|
try fs.openSelfExe(.{});
|
|
defer file.close();
|
|
|
|
const file_len = math.cast(usize, try file.getEndPos()) orelse return error.FileTooBig;
|
|
|
|
return posix.mmap(
|
|
null,
|
|
file_len,
|
|
posix.PROT.READ,
|
|
.{ .TYPE = .SHARED },
|
|
file.handle,
|
|
0,
|
|
);
|
|
}
|
|
|
|
/// 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.
|
|
fn unwindFrameMachO(
|
|
text_base: usize,
|
|
load_offset: usize,
|
|
context: *UnwindContext,
|
|
unwind_info: []const u8,
|
|
opt_eh_frame: ?[]const u8,
|
|
) !usize {
|
|
if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
|
|
const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
|
|
|
|
const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
|
|
if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidUnwindInfo;
|
|
const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
|
|
if (indices.len == 0) return error.MissingUnwindInfo;
|
|
|
|
// offset of the PC into the `__TEXT` segment
|
|
const pc_text_offset = context.pc - text_base;
|
|
|
|
const start_offset: u32, const first_level_offset: u32 = index: {
|
|
var left: usize = 0;
|
|
var len: usize = indices.len;
|
|
while (len > 1) {
|
|
const mid = left + len / 2;
|
|
if (pc_text_offset < indices[mid].functionOffset) {
|
|
len /= 2;
|
|
} else {
|
|
left = mid;
|
|
len -= len / 2;
|
|
}
|
|
}
|
|
break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
|
|
};
|
|
// An offset of 0 is a sentinel indicating a range does not have unwind info.
|
|
if (start_offset == 0) return error.MissingUnwindInfo;
|
|
|
|
const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
|
|
if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidUnwindInfo;
|
|
const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
|
|
unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
|
|
);
|
|
|
|
if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidUnwindInfo;
|
|
const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
|
|
|
|
const entry: struct {
|
|
function_offset: usize,
|
|
raw_encoding: u32,
|
|
} = switch (kind.*) {
|
|
.REGULAR => entry: {
|
|
if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidUnwindInfo;
|
|
const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
|
|
|
|
const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
|
|
if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
|
|
const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
|
|
unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
|
|
);
|
|
if (entries.len == 0) return error.InvalidUnwindInfo;
|
|
|
|
var left: usize = 0;
|
|
var len: usize = entries.len;
|
|
while (len > 1) {
|
|
const mid = left + len / 2;
|
|
if (pc_text_offset < entries[mid].functionOffset) {
|
|
len /= 2;
|
|
} else {
|
|
left = mid;
|
|
len -= len / 2;
|
|
}
|
|
}
|
|
break :entry .{
|
|
.function_offset = entries[left].functionOffset,
|
|
.raw_encoding = entries[left].encoding,
|
|
};
|
|
},
|
|
.COMPRESSED => entry: {
|
|
if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidUnwindInfo;
|
|
const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
|
|
|
|
const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
|
|
if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
|
|
const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
|
|
unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
|
|
);
|
|
if (entries.len == 0) return error.InvalidUnwindInfo;
|
|
|
|
var left: usize = 0;
|
|
var len: usize = entries.len;
|
|
while (len > 1) {
|
|
const mid = left + len / 2;
|
|
if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
|
|
len /= 2;
|
|
} else {
|
|
left = mid;
|
|
len -= len / 2;
|
|
}
|
|
}
|
|
const entry = entries[left];
|
|
|
|
const function_offset = first_level_offset + entry.funcOffset;
|
|
if (entry.encodingIndex < common_encodings.len) {
|
|
break :entry .{
|
|
.function_offset = function_offset,
|
|
.raw_encoding = common_encodings[entry.encodingIndex],
|
|
};
|
|
}
|
|
|
|
const local_index = entry.encodingIndex - common_encodings.len;
|
|
const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
|
|
if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidUnwindInfo;
|
|
const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
|
|
unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
|
|
);
|
|
if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
|
|
break :entry .{
|
|
.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: Dwarf.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 => ip: {
|
|
const frame = encoding.value.x86_64.frame;
|
|
|
|
const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
|
|
const new_sp = fp + 2 * @sizeOf(usize);
|
|
|
|
const ip_ptr = fp + @sizeOf(usize);
|
|
const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
|
|
const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
|
|
|
|
(try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
|
|
(try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
|
|
(try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
|
|
|
|
const regs: [5]u3 = .{
|
|
frame.reg0,
|
|
frame.reg1,
|
|
frame.reg2,
|
|
frame.reg3,
|
|
frame.reg4,
|
|
};
|
|
for (regs, 0..) |reg, i| {
|
|
if (reg == 0) continue;
|
|
const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
|
|
const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
|
|
(try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*;
|
|
}
|
|
|
|
break :ip new_ip;
|
|
},
|
|
.STACK_IMMD,
|
|
.STACK_IND,
|
|
=> ip: {
|
|
const frameless = encoding.value.x86_64.frameless;
|
|
|
|
const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
|
|
const stack_size: usize = stack_size: {
|
|
if (encoding.mode.x86_64 == .STACK_IMMD) {
|
|
break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
|
|
}
|
|
// In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
|
|
const sub_offset_addr =
|
|
text_base +
|
|
entry.function_offset +
|
|
frameless.stack.indirect.sub_offset;
|
|
// `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, 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 = frameless.stack_reg_count;
|
|
const ip_ptr = ip_ptr: {
|
|
var digits: [6]u3 = undefined;
|
|
var accumulator: usize = 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;
|
|
}
|
|
|
|
var registers: [6]u3 = undefined;
|
|
var used_indices: [6]bool = @splat(false);
|
|
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] = @intCast(unused_index + 1);
|
|
used_indices[unused_index] = true;
|
|
}
|
|
|
|
var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
|
|
for (0..reg_count) |i| {
|
|
const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
|
|
(try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
|
|
reg_addr += @sizeOf(usize);
|
|
}
|
|
|
|
break :ip_ptr reg_addr;
|
|
};
|
|
|
|
const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
|
|
const new_sp = ip_ptr + @sizeOf(usize);
|
|
|
|
(try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
|
|
(try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
|
|
|
|
break :ip new_ip;
|
|
},
|
|
.DWARF => {
|
|
const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
|
|
const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
|
|
var dwarf_unwind: Dwarf.Unwind = .init;
|
|
dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);
|
|
return unwindFrameDwarf(&dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));
|
|
},
|
|
},
|
|
.aarch64, .aarch64_be => switch (encoding.mode.arm64) {
|
|
.OLD => return error.UnimplementedUnwindEncoding,
|
|
.FRAMELESS => ip: {
|
|
const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
|
|
const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
|
|
const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*;
|
|
(try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp;
|
|
break :ip new_ip;
|
|
},
|
|
.DWARF => {
|
|
const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
|
|
const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
|
|
var dwarf_unwind: Dwarf.Unwind = .init;
|
|
dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);
|
|
return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));
|
|
},
|
|
.FRAME => ip: {
|
|
const frame = encoding.value.arm64.frame;
|
|
|
|
const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*;
|
|
const ip_ptr = fp + @sizeOf(usize);
|
|
|
|
var reg_addr = fp - @sizeOf(usize);
|
|
inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
|
|
if (@field(frame.x_reg_pairs, field.name) != 0) {
|
|
(try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
|
|
reg_addr += @sizeOf(usize);
|
|
(try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
|
|
reg_addr += @sizeOf(usize);
|
|
}
|
|
}
|
|
|
|
inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
|
|
if (@field(frame.d_reg_pairs, field.name) != 0) {
|
|
// Only the lower half of the 128-bit V registers are restored during unwinding
|
|
{
|
|
const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 8 + i, context.reg_context));
|
|
dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
|
|
}
|
|
reg_addr += @sizeOf(usize);
|
|
{
|
|
const dest: *align(1) usize = @ptrCast(try regBytes(context.thread_context, 64 + 9 + i, context.reg_context));
|
|
dest.* = @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 regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp;
|
|
(try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip;
|
|
|
|
break :ip new_ip;
|
|
},
|
|
},
|
|
else => comptime unreachable, // unimplemented
|
|
};
|
|
|
|
context.pc = stripInstructionPtrAuthCode(new_ip);
|
|
if (context.pc > 0) context.pc -= 1;
|
|
return new_ip;
|
|
}
|