debug: refactor stack frame capturing

This commit is contained in:
Jacob Young 2025-08-27 15:36:17 -04:00 committed by mlugg
parent 7adb15892e
commit b706949736
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
4 changed files with 1158 additions and 1126 deletions

View File

@ -498,10 +498,17 @@ 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 SelfInfo 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, @frameAddress());
if (builtin.cpu.arch == .powerpc64) {
// https://github.com/ziglang/zig/issues/24970
stack_trace.index = 0;
return;
}
var context: ThreadContext = undefined;
const has_context = getContext(&context);
var it = (if (has_context) blk: {
break :blk StackIterator.initWithContext(first_address, getSelfDebugInfo() catch break :blk null, &context) catch null;
} else null) orelse StackIterator.init(first_address, null);
defer it.deinit();
for (stack_trace.instruction_addresses, 0..) |*addr, i| {
addr.* = it.next() orelse {
@ -764,7 +771,7 @@ pub fn writeStackTrace(
}
pub const UnwindError = if (have_ucontext)
@typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).@"fn".return_type.?).error_union.error_set
@typeInfo(@typeInfo(@TypeOf(SelfInfo.unwindFrame)).@"fn".return_type.?).error_union.error_set
else
void;
@ -865,11 +872,11 @@ pub const StackIterator = struct {
@sizeOf(usize);
pub fn next(it: *StackIterator) ?usize {
var address = it.next_internal() orelse return null;
var address = it.nextInternal() orelse return null;
if (it.first_address) |first_address| {
while (address != first_address) {
address = it.next_internal() orelse return null;
address = it.nextInternal() orelse return null;
}
it.first_address = null;
}
@ -877,48 +884,13 @@ pub const StackIterator = struct {
return address;
}
fn next_unwind(it: *StackIterator) !usize {
const unwind_state = &it.unwind_state.?;
const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc);
switch (native_os) {
.macos, .ios, .watchos, .tvos, .visionos => {
// __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 (SelfInfo.unwindFrameMachO(
unwind_state.debug_info.allocator,
module.base_address,
&unwind_state.dwarf_context,
unwind_info,
module.eh_frame,
)) |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 SelfInfo.unwindFrameDwarf(
unwind_state.debug_info.allocator,
di,
module.base_address,
&unwind_state.dwarf_context,
null,
);
} else return error.MissingDebugInfo;
}
fn next_internal(it: *StackIterator) ?usize {
fn nextInternal(it: *StackIterator) ?usize {
if (have_ucontext) {
if (it.unwind_state) |*unwind_state| {
if (!unwind_state.failed) {
if (unwind_state.dwarf_context.pc == 0) return null;
defer it.fp = unwind_state.dwarf_context.getFp() catch 0;
if (it.next_unwind()) |return_address| {
if (unwind_state.debug_info.unwindFrame(&unwind_state.dwarf_context)) |return_address| {
return return_address;
} else |err| {
unwind_state.last_error = err;
@ -948,7 +920,7 @@ pub const StackIterator = struct {
// Sanity check: the stack grows down thus all the parent frames must be
// be at addresses that are greater (or equal) than the previous one.
// A zero frame pointer often signals this is the last frame, that case
// is gracefully handled by the next call to next_internal.
// is gracefully handled by the next call to nextInternal.
if (new_fp != 0 and new_fp < it.fp) return null;
const new_pc = @as(*usize, @ptrFromInt(math.add(usize, fp, pc_offset) catch return null)).*;
@ -1099,12 +1071,7 @@ fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, err:
}
pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
else => return err,
};
const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch |err| switch (err) {
const symbol_info = debug_info.getSymbolAtAddress(address) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, writer, address, tty_config),
else => return err,
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,645 @@
sections: SectionArray = @splat(null),
/// Starts out non-`null` if the `.eh_frame_hdr` section is present. May become `null` later if we
/// find that `.eh_frame_hdr` is incomplete.
eh_frame_hdr: ?ExceptionFrameHeader = null,
/// These lookup tables are only used if `eh_frame_hdr` is null
cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .empty,
/// Sorted by start_pc
fde_list: std.ArrayList(FrameDescriptionEntry) = .empty,
pub const Section = struct {
data: []const u8,
pub const Id = enum {
debug_frame,
eh_frame,
eh_frame_hdr,
};
};
const num_sections = std.enums.directEnumArrayLen(Section.Id, 0);
pub const SectionArray = [num_sections]?Section;
pub fn section(unwind: Unwind, dwarf_section: Section.Id) ?[]const u8 {
return if (unwind.sections[@intFromEnum(dwarf_section)]) |s| s.data else null;
}
/// 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 bad(),
};
}
pub fn findEntry(
self: ExceptionFrameHeader,
eh_frame_len: usize,
eh_frame_hdr_ptr: usize,
pc: usize,
cie: *CommonInformationEntry,
fde: *FrameDescriptionEntry,
endian: Endian,
) !void {
const entry_size = try entrySize(self.table_enc);
var left: usize = 0;
var len: usize = self.fde_count;
var fbr: Reader = .fixed(self.entries);
while (len > 1) {
const mid = left + len / 2;
fbr.seek = mid * entry_size;
const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
.pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
.follow_indirect = true,
.data_rel_base = eh_frame_hdr_ptr,
}, endian) orelse return bad();
if (pc < pc_begin) {
len /= 2;
} else {
left = mid;
if (pc == pc_begin) break;
len -= len / 2;
}
}
if (len == 0) return missing();
fbr.seek = left * entry_size;
// Read past the pc_begin field of the entry
_ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
.pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
.follow_indirect = true,
.data_rel_base = eh_frame_hdr_ptr,
}, endian) orelse return bad();
const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{
.pc_rel_base = @intFromPtr(&self.entries[fbr.seek]),
.follow_indirect = true,
.data_rel_base = eh_frame_hdr_ptr,
}, endian) orelse return bad()) orelse return bad();
if (fde_ptr < self.eh_frame_ptr) return bad();
const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0..eh_frame_len];
const fde_offset = fde_ptr - self.eh_frame_ptr;
var eh_frame_fbr: Reader = .fixed(eh_frame);
eh_frame_fbr.seek = fde_offset;
const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian);
if (fde_entry_header.type != .fde) return bad();
// 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;
eh_frame_fbr.seek = @intCast(cie_offset);
const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, .eh_frame, endian);
if (cie_entry_header.type != .cie) return bad();
cie.* = try CommonInformationEntry.parse(
cie_entry_header.entry_bytes,
0,
true,
cie_entry_header.format,
.eh_frame,
cie_entry_header.length_offset,
@sizeOf(usize),
endian,
);
fde.* = try FrameDescriptionEntry.parse(
fde_entry_header.entry_bytes,
0,
true,
cie.*,
@sizeOf(usize),
endian,
);
if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return missing();
}
};
pub const EntryHeader = struct {
/// Offset of the length field in the backing buffer
length_offset: usize,
format: Format,
type: union(enum) {
cie,
/// Value is the offset of the corresponding CIE
fde: u64,
terminator,
},
/// The entry's contents, not including the ID field
entry_bytes: []const u8,
/// 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.format == .@"64") 8 else 4);
}
/// Reads a header for either an FDE or a CIE, then advances the fbr to the
/// position after the trailing structure.
///
/// `fbr` must be backed by either the .eh_frame or .debug_frame sections.
///
/// TODO that's a bad API, don't do that. this function should neither require
/// a fixed reader nor depend on seeking.
pub fn read(fbr: *Reader, dwarf_section: Section.Id, endian: Endian) !EntryHeader {
assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame);
const length_offset = fbr.seek;
const unit_header = try Dwarf.readUnitHeader(fbr, endian);
const unit_length = cast(usize, unit_header.unit_length) orelse return bad();
if (unit_length == 0) return .{
.length_offset = length_offset,
.format = unit_header.format,
.type = .terminator,
.entry_bytes = &.{},
};
const start_offset = fbr.seek;
const end_offset = start_offset + unit_length;
defer fbr.seek = end_offset;
const id = try Dwarf.readAddress(fbr, unit_header.format, endian);
const entry_bytes = fbr.buffer[fbr.seek..end_offset];
const cie_id: u64 = switch (dwarf_section) {
.eh_frame => CommonInformationEntry.eh_id,
.debug_frame => switch (unit_header.format) {
.@"32" => CommonInformationEntry.dwarf32_id,
.@"64" => CommonInformationEntry.dwarf64_id,
},
else => unreachable,
};
return .{
.length_offset = length_offset,
.format = unit_header.format,
.type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) {
.eh_frame => try std.math.sub(u64, start_offset, id),
.debug_frame => id,
else => unreachable,
} },
.entry_bytes = entry_bytes,
};
}
};
pub const CommonInformationEntry = struct {
// Used in .eh_frame
pub const eh_id = 0;
// Used in .debug_frame (DWARF32)
pub const dwarf32_id = maxInt(u32);
// Used in .debug_frame (DWARF64)
pub const dwarf64_id = 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,
format: Format,
// 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,
format: Format,
dwarf_section: Section.Id,
length_offset: u64,
addr_size_bytes: u8,
endian: Endian,
) !CommonInformationEntry {
if (addr_size_bytes > 8) return error.UnsupportedAddrSize;
var fbr: Reader = .fixed(cie_bytes);
const version = try fbr.takeByte();
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;
const aug_str_start = fbr.seek;
var aug_byte = try fbr.takeByte();
while (aug_byte != 0) : (aug_byte = try fbr.takeByte()) {
switch (aug_byte) {
'z' => {
if (aug_str_len != 0) return bad();
has_aug_data = true;
},
'e' => {
if (has_aug_data or aug_str_len != 0) return bad();
if (try fbr.takeByte() != 'h') return bad();
has_eh_data = true;
},
else => if (has_eh_data) return bad(),
}
aug_str_len += 1;
}
if (has_eh_data) {
// legacy data created by older versions of gcc - unsupported here
for (0..addr_size_bytes) |_| _ = try fbr.takeByte();
}
const address_size = if (version == 4) try fbr.takeByte() else addr_size_bytes;
const segment_selector_size = if (version == 4) try fbr.takeByte() else null;
const code_alignment_factor = try fbr.takeLeb128(u32);
const data_alignment_factor = try fbr.takeLeb128(i32);
const return_address_register = if (version == 1) try fbr.takeByte() else try fbr.takeLeb128(u8);
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 fbr.takeLeb128(usize);
const aug_data_start = fbr.seek;
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 fbr.takeByte();
},
'P' => {
personality_enc = try fbr.takeByte();
personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{
.pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.seek]), pc_rel_offset),
.follow_indirect = is_runtime,
}, endian);
},
'R' => {
fde_pointer_enc = try fbr.takeByte();
},
'S', 'B', 'G' => {},
else => return bad(),
}
}
// aug_data_len can include padding so the CIE ends on an address boundary
fbr.seek = aug_data_start + aug_data_len;
break :blk aug_str;
} else &[_]u8{};
const initial_instructions = cie_bytes[fbr.seek..];
return .{
.length_offset = length_offset,
.version = version,
.address_size = address_size,
.format = format,
.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: Endian,
) !FrameDescriptionEntry {
if (addr_size_bytes > 8) return error.InvalidAddrSize;
var fbr: Reader = .fixed(fde_bytes);
const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
.pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),
.follow_indirect = is_runtime,
}, endian) orelse return bad();
const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{
.pc_rel_base = 0,
.follow_indirect = false,
}, endian) orelse return bad();
var aug_data: []const u8 = &[_]u8{};
const lsda_pointer = if (cie.aug_str.len > 0) blk: {
const aug_data_len = try fbr.takeLeb128(usize);
const aug_data_start = fbr.seek;
aug_data = fde_bytes[aug_data_start..][0..aug_data_len];
const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit)
try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{
.pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.seek]), pc_rel_offset),
.follow_indirect = is_runtime,
}, endian)
else
null;
fbr.seek = aug_data_start + aug_data_len;
break :blk lsda_pointer;
} else null;
const instructions = fde_bytes[fbr.seek..];
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,
};
}
};
/// 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. Even if `.eh_frame_hdr` is used, we may find during unwinding that it's incomplete,
/// in which case we build the sorted list of FDEs at that point.
///
/// See also `scanCieFdeInfo`.
pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void {
const endian = di.endian;
if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
var fbr: Reader = .fixed(eh_frame_hdr);
const version = try fbr.takeByte();
if (version != 1) break :blk;
const eh_frame_ptr_enc = try fbr.takeByte();
if (eh_frame_ptr_enc == EH.PE.omit) break :blk;
const fde_count_enc = try fbr.takeByte();
if (fde_count_enc == EH.PE.omit) break :blk;
const table_enc = try fbr.takeByte();
if (table_enc == EH.PE.omit) break :blk;
const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{
.pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),
.follow_indirect = true,
}, endian) orelse return bad()) orelse return bad();
const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{
.pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.seek]),
.follow_indirect = true,
}, endian) orelse return bad()) orelse return bad();
const entry_size = try ExceptionFrameHeader.entrySize(table_enc);
const entries_len = fde_count * entry_size;
if (entries_len > eh_frame_hdr.len - fbr.seek) return bad();
di.eh_frame_hdr = .{
.eh_frame_ptr = eh_frame_ptr,
.table_enc = table_enc,
.fde_count = fde_count,
.entries = eh_frame_hdr[fbr.seek..][0..entries_len],
};
// No need to scan .eh_frame, we have a binary search table already
return;
}
try di.scanCieFdeInfo(allocator, base_address);
}
/// Scan `.eh_frame` and `.debug_frame` and build a sorted list of FDEs for binary searching during
/// unwinding.
pub fn scanCieFdeInfo(unwind: *Unwind, allocator: Allocator, endian: Endian, base_address: usize) !void {
const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame };
for (frame_sections) |frame_section| {
if (unwind.section(frame_section)) |section_data| {
var fbr: Reader = .fixed(section_data);
while (fbr.seek < fbr.buffer.len) {
const entry_header = try EntryHeader.read(&fbr, frame_section, endian);
switch (entry_header.type) {
.cie => {
const cie = try CommonInformationEntry.parse(
entry_header.entry_bytes,
unwind.sectionVirtualOffset(frame_section, base_address).?,
true,
entry_header.format,
frame_section,
entry_header.length_offset,
@sizeOf(usize),
endian,
);
try unwind.cie_map.put(allocator, entry_header.length_offset, cie);
},
.fde => |cie_offset| {
const cie = unwind.cie_map.get(cie_offset) orelse return bad();
const fde = try FrameDescriptionEntry.parse(
entry_header.entry_bytes,
unwind.sectionVirtualOffset(frame_section, base_address).?,
true,
cie,
@sizeOf(usize),
endian,
);
try unwind.fde_list.append(allocator, fde);
},
.terminator => break,
}
}
std.mem.sortUnstable(FrameDescriptionEntry, unwind.fde_list.items, {}, struct {
fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool {
_ = ctx;
return a.pc_begin < b.pc_begin;
}
}.lessThan);
}
}
}
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(fbr: *Reader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: 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 fbr.takeInt(u16, endian),
4 => try fbr.takeInt(u32, endian),
8 => try fbr.takeInt(u64, endian),
else => return error.InvalidAddrSize,
},
},
EH.PE.uleb128 => .{ .unsigned = try fbr.takeLeb128(u64) },
EH.PE.udata2 => .{ .unsigned = try fbr.takeInt(u16, endian) },
EH.PE.udata4 => .{ .unsigned = try fbr.takeInt(u32, endian) },
EH.PE.udata8 => .{ .unsigned = try fbr.takeInt(u64, endian) },
EH.PE.sleb128 => .{ .signed = try fbr.takeLeb128(i64) },
EH.PE.sdata2 => .{ .signed = try fbr.takeInt(i16, endian) },
EH.PE.sdata4 => .{ .signed = try fbr.takeInt(i32, endian) },
EH.PE.sdata8 => .{ .signed = try fbr.takeInt(i64, endian) },
else => return bad(),
};
const 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 std.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 = 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;
}
}
fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {
if (pc_rel_offset < 0) {
return std.math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset)));
} else {
return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset)));
}
}
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const bad = Dwarf.bad;
const cast = std.math.cast;
const DW = std.dwarf;
const Dwarf = std.debug.Dwarf;
const EH = DW.EH;
const Endian = std.builtin.Endian;
const Format = DW.Format;
const maxInt = std.math.maxInt;
const missing = Dwarf.missing;
const Reader = std.Io.Reader;
const std = @import("std");
const Unwind = @This();

View File

@ -31,7 +31,7 @@ const SelfInfo = @This();
const root = @import("root");
allocator: Allocator,
address_map: std.AutoHashMap(usize, *Module),
address_map: std.AutoHashMapUnmanaged(usize, Module),
modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void,
pub const OpenError = error{
@ -40,29 +40,27 @@ pub const OpenError = error{
} || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).@"fn".return_type.?).error_union.error_set;
pub fn open(allocator: Allocator) OpenError!SelfInfo {
nosuspend {
if (builtin.strip_debug_info)
return error.MissingDebugInfo;
switch (native_os) {
.linux,
.freebsd,
.netbsd,
.dragonfly,
.openbsd,
.macos,
.solaris,
.illumos,
.windows,
=> return try SelfInfo.init(allocator),
else => return error.UnsupportedOperatingSystem,
}
if (builtin.strip_debug_info)
return error.MissingDebugInfo;
switch (native_os) {
.linux,
.freebsd,
.netbsd,
.dragonfly,
.openbsd,
.macos,
.solaris,
.illumos,
.windows,
=> return try SelfInfo.init(allocator),
else => return error.UnsupportedOperatingSystem,
}
}
pub fn init(allocator: Allocator) !SelfInfo {
var debug_info: SelfInfo = .{
.allocator = allocator,
.address_map = std.AutoHashMap(usize, *Module).init(allocator),
.address_map = .empty,
.modules = if (native_os == .windows) .{} else {},
};
@ -110,7 +108,7 @@ pub fn deinit(self: *SelfInfo) void {
mdi.deinit(self.allocator);
self.allocator.destroy(mdi);
}
self.address_map.deinit();
self.address_map.deinit(self.allocator);
if (native_os == .windows) {
for (self.modules.items) |module| {
self.allocator.free(module.name);
@ -120,7 +118,7 @@ pub fn deinit(self: *SelfInfo) void {
}
}
pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module {
fn lookupModuleForAddress(self: *SelfInfo, address: usize) !Module.Lookup {
if (builtin.target.os.tag.isDarwin()) {
return self.lookupModuleDyld(address);
} else if (native_os == .windows) {
@ -134,23 +132,67 @@ pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module {
}
}
// 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, address: usize) ?[]const u8 {
fn loadModuleDebugInfo(self: *SelfInfo, lookup: *const Module.Lookup, module: *Module) !void {
if (builtin.target.os.tag.isDarwin()) {
return self.lookupModuleNameDyld(address);
@compileError("TODO");
} else if (native_os == .windows) {
return self.lookupModuleNameWin32(address);
@compileError("TODO");
} else if (native_os == .haiku) {
return null;
@compileError("TODO");
} else if (builtin.target.cpu.arch.isWasm()) {
return null;
@compileError("TODO");
} else {
return self.lookupModuleNameDl(address);
if (module.mapped_memory == null) {
var sections: Dwarf.SectionArray = @splat(null);
try readElfDebugInfo(module, self.allocator, if (lookup.name.len > 0) lookup.name else null, lookup.build_id, &sections);
assert(module.mapped_memory != null);
}
}
}
pub fn unwindFrame(self: *SelfInfo, context: *UnwindContext) !usize {
const lookup = try self.lookupModuleForAddress(context.pc);
const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address);
if (!gop.found_existing) gop.value_ptr.* = .init(&lookup);
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.unwind_info) |unwind_info| {
if (unwindFrameMachO(
self.allocator,
lookup.base_address,
context,
unwind_info,
gop.value_ptr.eh_frame,
)) |return_address| {
return return_address;
} else |err| {
if (err != error.RequiresDWARFUnwind) return err;
}
} else return error.MissingUnwindInfo;
}
if (try gop.value_ptr.getDwarfUnwindForAddress(self.allocator, context.pc)) |unwind| {
return unwindFrameDwarf(self.allocator, unwind, lookup.base_address, context, null);
} else return error.MissingDebugInfo;
}
pub fn getSymbolAtAddress(self: *SelfInfo, address: usize) !std.debug.Symbol {
const lookup = try self.lookupModuleForAddress(address);
const gop = try self.address_map.getOrPut(self.allocator, lookup.base_address);
if (!gop.found_existing) gop.value_ptr.* = .init(&lookup);
try self.loadModuleDebugInfo(&lookup, gop.value_ptr);
return gop.value_ptr.getSymbolAtAddress(self.allocator, native_endian, lookup.base_address, 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, address: usize) ?[]const u8 {
return if (self.lookupModuleForAddress(address)) |lookup| lookup.name else |err| switch (err) {
error.MissingDebugInfo => null,
};
}
fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module {
const image_count = std.c._dyld_image_count();
@ -394,19 +436,24 @@ fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 {
return null;
}
fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
fn lookupModuleDl(self: *SelfInfo, address: usize) !Module.Lookup {
var ctx: struct {
// Input
address: usize,
// Output
base_address: usize = undefined,
name: []const u8 = undefined,
build_id: ?[]const u8 = null,
gnu_eh_frame: ?[]const u8 = null,
} = .{ .address = address };
lookup: Module.Lookup,
} = .{
.address = address,
.lookup = .{
.base_address = undefined,
.name = undefined,
.build_id = null,
.gnu_eh_frame = null,
},
};
const CtxTy = @TypeOf(ctx);
if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
posix.dl_iterate_phdr(&ctx, error{Found}, struct {
fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
_ = size;
// The base address is too high
@ -423,8 +470,8 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
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.name, 0) orelse "";
context.base_address = info.addr;
context.lookup.name = mem.sliceTo(info.name, 0) orelse "";
context.lookup.base_address = info.addr;
break;
}
} else return;
@ -440,10 +487,10 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
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];
context.lookup.build_id = note_bytes[16..][0..desc_size];
},
elf.PT_GNU_EH_FRAME => {
context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
context.lookup.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
},
else => {},
}
@ -452,38 +499,36 @@ fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module {
// Stop the iteration
return error.Found;
}
}.callback)) {
return error.MissingDebugInfo;
} else |err| switch (err) {
error.Found => {},
}
}.callback) catch |err| switch (err) {
error.Found => return ctx.lookup,
};
if (true) return error.MissingDebugInfo;
if (self.address_map.get(ctx.base_address)) |obj_di| {
if (self.address_map.get(ctx.lookup.base_address)) |obj_di| {
return obj_di;
}
const obj_di = try self.allocator.create(Module);
errdefer self.allocator.destroy(obj_di);
var sections: Dwarf.SectionArray = Dwarf.null_section_array;
if (ctx.gnu_eh_frame) |eh_frame_hdr| {
var sections: Dwarf.SectionArray = @splat(null);
if (ctx.lookup.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(Dwarf.Section.Id.eh_frame_hdr)] = .{
sections[@intFromEnum(Dwarf.Unwind.Section.Id.eh_frame_hdr)] = .{
.data = eh_frame_hdr,
.owned = false,
};
}
obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
obj_di.base_address = ctx.base_address;
const obj_di = try self.allocator.create(Module);
errdefer self.allocator.destroy(obj_di);
obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.lookup.name.len > 0) ctx.lookup.name else null, ctx.lookup.build_id, &sections);
obj_di.base_address = ctx.lookup.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 {};
obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.lookup.base_address) catch {};
try self.address_map.putNoClobber(ctx.base_address, obj_di);
try self.address_map.putNoClobber(self.allocator, ctx.lookup.base_address, obj_di);
return obj_di;
}
@ -625,49 +670,47 @@ pub const Module = switch (native_os) {
}
pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !std.debug.Symbol {
nosuspend {
const result = try self.getOFileInfoForAddress(allocator, address);
if (result.symbol == null) return .{};
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[result.symbol.?.strx..], 0);
if (result.o_file_info == null) return .{ .name = stab_symbol };
// 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[result.symbol.?.strx..], 0);
if (result.o_file_info == null) return .{ .name = stab_symbol };
// Translate again the address, this time into an address inside the
// .o file
const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
.name = "???",
};
// Translate again the address, this time into an address inside the
// .o file
const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
.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 .{
.name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
.compile_unit_name = compile_unit.die.getAttrString(
o_file_di,
std.dwarf.AT.name,
o_file_di.section(.debug_str),
compile_unit.*,
) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => "???",
},
.source_location = o_file_di.getLineNumberInfo(
allocator,
compile_unit,
relocated_address_o + addr_off,
) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => null,
else => return err,
},
};
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {
return .{ .name = stab_symbol };
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 .{
.name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
.compile_unit_name = compile_unit.die.getAttrString(
o_file_di,
std.dwarf.AT.name,
o_file_di.section(.debug_str),
compile_unit.*,
) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => "???",
},
else => return err,
}
.source_location = o_file_di.getLineNumberInfo(
allocator,
compile_unit,
relocated_address_o + addr_off,
) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => null,
else => return err,
},
};
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {
return .{ .name = stab_symbol };
},
else => return err,
}
}
@ -676,35 +719,33 @@ pub const Module = switch (native_os) {
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;
// 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,
};
// 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);
const 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,
});
// Check if its debug infos are already in the cache
const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
const 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,
};
}
return .{
.relocated_address = relocated_address,
.symbol = symbol,
.o_file_info = o_file_info,
};
}
pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*Dwarf {
@ -974,83 +1015,68 @@ fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module {
};
}
fn readCoffDebugInfo(gpa: Allocator, coff_obj: *coff.Coff) !Module {
nosuspend {
var di: Module = .{
.base_address = undefined,
.coff_image_base = coff_obj.getImageBase(),
.coff_section_headers = undefined,
.pdb = null,
.dwarf = null,
};
fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
var di: Module = .{
.base_address = undefined,
.coff_image_base = coff_obj.getImageBase(),
.coff_section_headers = undefined,
};
if (coff_obj.getSectionByName(".debug_info")) |_| {
// This coff file has embedded DWARF debug info
var sections: Dwarf.SectionArray = Dwarf.null_section_array;
errdefer for (sections) |section| if (section) |s| if (s.owned) gpa.free(s.data);
if (coff_obj.getSectionByName(".debug_info")) |_| {
// This coff file has embedded DWARF debug info
var sections: Dwarf.SectionArray = Dwarf.null_section_array;
errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
inline for (@typeInfo(Dwarf.Section.Id).@"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, gpa),
.virtual_address = section_header.virtual_address,
.owned = true,
};
} else null;
}
var dwarf: Dwarf = .{
.endian = native_endian,
.sections = sections,
.is_macho = false,
};
try Dwarf.open(&dwarf, gpa);
di.dwarf = dwarf;
inline for (@typeInfo(Dwarf.Section.Id).@"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;
}
const raw_path = try coff_obj.getPdbPath() orelse return di;
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 });
}
var dwarf: Dwarf = .{
.endian = native_endian,
.sections = sections,
.is_macho = false,
};
defer if (path.ptr != raw_path.ptr) gpa.free(path);
const pdb_file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
error.FileNotFound, error.IsDir => {
if (di.dwarf == null) return error.MissingDebugInfo;
return di;
},
else => |e| return e,
};
errdefer pdb_file.close();
const pdb_file_reader_buffer = try gpa.alloc(u8, 4096);
errdefer gpa.free(pdb_file_reader_buffer);
const pdb_file_reader = try gpa.create(File.Reader);
errdefer gpa.destroy(pdb_file_reader);
pdb_file_reader.* = pdb_file.reader(pdb_file_reader_buffer);
di.pdb = try Pdb.init(gpa, pdb_file_reader);
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;
// Only used by the pdb path
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
errdefer gpa.free(di.coff_section_headers);
return di;
try Dwarf.open(&dwarf, allocator);
di.dwarf = dwarf;
}
const raw_path = try coff_obj.getPdbPath() orelse return di;
const path = blk: {
if (fs.path.isAbsolute(raw_path)) {
break :blk raw_path;
} else {
const self_dir = try fs.selfExeDirPathAlloc(allocator);
defer allocator.free(self_dir);
break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
}
};
defer if (path.ptr != raw_path.ptr) allocator.free(path);
di.pdb = Pdb.init(allocator, path) catch |err| switch (err) {
error.FileNotFound, error.IsDir => {
if (di.dwarf == null) return error.MissingDebugInfo;
return di;
},
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;
// Only used by the pdb path
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
errdefer allocator.free(di.coff_section_headers);
return di;
}
/// Reads debug info from an ELF file, or the current binary if none in specified.
@ -1058,32 +1084,29 @@ fn readCoffDebugInfo(gpa: Allocator, coff_obj: *coff.Coff) !Module {
/// then this this function will recurse to attempt to load the debug sections from
/// an external file.
pub fn readElfDebugInfo(
em: *Dwarf.ElfModule,
allocator: Allocator,
elf_filename: ?[]const u8,
build_id: ?[]const u8,
expected_crc: ?u32,
parent_sections: *Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
) !Dwarf.ElfModule {
nosuspend {
const elf_file = (if (elf_filename) |filename| blk: {
break :blk fs.cwd().openFile(filename, .{});
} else fs.openSelfExe(.{})) catch |err| switch (err) {
error.FileNotFound => return error.MissingDebugInfo,
else => return err,
};
) !void {
const elf_file = (if (elf_filename) |filename| blk: {
break :blk fs.cwd().openFile(filename, .{});
} else fs.openSelfExe(.{})) catch |err| switch (err) {
error.FileNotFound => return error.MissingDebugInfo,
else => return err,
};
const mapped_mem = try mapWholeFile(elf_file);
return Dwarf.ElfModule.load(
allocator,
mapped_mem,
build_id,
expected_crc,
parent_sections,
parent_mapped_mem,
elf_filename,
);
}
const mapped_mem = try mapWholeFile(elf_file);
return em.load(
allocator,
mapped_mem,
build_id,
null,
parent_sections,
null,
elf_filename,
);
}
const MachoSymbol = struct {
@ -1106,22 +1129,20 @@ const MachoSymbol = struct {
/// Takes ownership of file, even on error.
/// TODO it's weird to take ownership even on error, rework this code.
fn mapWholeFile(file: File) ![]align(std.heap.page_size_min) const u8 {
nosuspend {
defer file.close();
defer file.close();
const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
const mapped_mem = try posix.mmap(
null,
file_len,
posix.PROT.READ,
.{ .TYPE = .SHARED },
file.handle,
0,
);
errdefer posix.munmap(mapped_mem);
const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
const mapped_mem = try posix.mmap(
null,
file_len,
posix.PROT.READ,
.{ .TYPE = .SHARED },
file.handle,
0,
);
errdefer posix.munmap(mapped_mem);
return mapped_mem;
}
return mapped_mem;
}
fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
@ -1172,7 +1193,7 @@ test machoSearchSymbols {
/// 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(
fn unwindFrameMachO(
allocator: Allocator,
base_address: usize,
context: *UnwindContext,
@ -1562,9 +1583,9 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
///
/// `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 unwindFrameDwarf(
fn unwindFrameDwarf(
allocator: Allocator,
di: *Dwarf,
unwind: *Dwarf.Unwind,
base_address: usize,
context: *UnwindContext,
explicit_fde_offset: ?usize,
@ -1572,37 +1593,34 @@ pub fn unwindFrameDwarf(
if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
if (context.pc == 0) return 0;
const endian = di.endian;
// Find the FDE and CIE
const cie, const fde = if (explicit_fde_offset) |fde_offset| blk: {
const dwarf_section: Dwarf.Section.Id = .eh_frame;
const frame_section = di.section(dwarf_section) orelse return error.MissingFDE;
const frame_section = unwind.section(.eh_frame) orelse return error.MissingFDE;
if (fde_offset >= frame_section.len) return error.MissingFDE;
var fbr: std.Io.Reader = .fixed(frame_section);
fbr.seek = fde_offset;
const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian);
const fde_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian);
if (fde_entry_header.type != .fde) return error.MissingFDE;
const cie_offset = fde_entry_header.type.fde;
fbr.seek = @intCast(cie_offset);
const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, dwarf_section, endian);
const cie_entry_header = try Dwarf.Unwind.EntryHeader.read(&fbr, .eh_frame, native_endian);
if (cie_entry_header.type != .cie) return Dwarf.bad();
const cie = try Dwarf.CommonInformationEntry.parse(
const cie = try Dwarf.Unwind.CommonInformationEntry.parse(
cie_entry_header.entry_bytes,
0,
true,
cie_entry_header.format,
dwarf_section,
.eh_frame,
cie_entry_header.length_offset,
@sizeOf(usize),
native_endian,
);
const fde = try Dwarf.FrameDescriptionEntry.parse(
const fde = try Dwarf.Unwind.FrameDescriptionEntry.parse(
fde_entry_header.entry_bytes,
0,
true,
@ -1616,33 +1634,33 @@ pub fn unwindFrameDwarf(
// `.eh_frame_hdr` may be incomplete. We'll try it first, but if the lookup fails, we fall
// back to loading `.eh_frame`/`.debug_frame` and using those from that point on.
if (di.eh_frame_hdr) |header| hdr: {
const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else {
try di.scanCieFdeInfo(allocator, base_address);
di.eh_frame_hdr = null;
if (unwind.eh_frame_hdr) |header| hdr: {
const eh_frame_len = if (unwind.section(.eh_frame)) |eh_frame| eh_frame.len else {
try unwind.scanCieFdeInfo(allocator, native_endian, base_address);
unwind.eh_frame_hdr = null;
break :hdr;
};
var cie: Dwarf.CommonInformationEntry = undefined;
var fde: Dwarf.FrameDescriptionEntry = undefined;
var cie: Dwarf.Unwind.CommonInformationEntry = undefined;
var fde: Dwarf.Unwind.FrameDescriptionEntry = undefined;
header.findEntry(
eh_frame_len,
@intFromPtr(di.section(.eh_frame_hdr).?.ptr),
@intFromPtr(unwind.section(.eh_frame_hdr).?.ptr),
context.pc,
&cie,
&fde,
endian,
native_endian,
) catch |err| switch (err) {
error.MissingDebugInfo => {
// `.eh_frame_hdr` appears to be incomplete, so go ahead and populate `cie_map`
// and `fde_list`, and fall back to the binary search logic below.
try di.scanCieFdeInfo(allocator, base_address);
try unwind.scanCieFdeInfo(allocator, native_endian, base_address);
// Since `.eh_frame_hdr` is incomplete, we're very likely to get more lookup
// failures using it, and we've just built a complete, sorted list of FDEs
// anyway, so just stop using `.eh_frame_hdr` altogether.
di.eh_frame_hdr = null;
unwind.eh_frame_hdr = null;
break :hdr;
},
@ -1652,8 +1670,8 @@ pub fn unwindFrameDwarf(
break :blk .{ cie, fde };
}
const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, di.fde_list.items, context.pc, struct {
pub fn compareFn(pc: usize, item: Dwarf.FrameDescriptionEntry) std.math.Order {
const index = std.sort.binarySearch(Dwarf.Unwind.FrameDescriptionEntry, unwind.fde_list.items, context.pc, struct {
pub fn compareFn(pc: usize, item: Dwarf.Unwind.FrameDescriptionEntry) std.math.Order {
if (pc < item.pc_begin) return .lt;
const range_end = item.pc_begin + item.pc_range;
@ -1663,15 +1681,16 @@ pub fn unwindFrameDwarf(
}
}.compareFn);
const fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;
const cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
const fde = if (index) |i| unwind.fde_list.items[i] else return error.MissingFDE;
const cie = unwind.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
break :blk .{ cie, fde };
};
// 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 = cie.format,
.compile_unit = di.findCompileUnit(fde.pc_begin) catch null,
.thread_context = context.thread_context,
.reg_context = context.reg_context,
.cfa = context.cfa,
@ -1679,7 +1698,7 @@ pub fn unwindFrameDwarf(
context.vm.reset();
context.reg_context.eh_frame = cie.version != 4;
context.reg_context.is_macho = di.is_macho;
context.reg_context.is_macho = native_os.isDarwin();
const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde);
context.cfa = switch (row.cfa.rule) {
@ -2007,8 +2026,8 @@ pub const VirtualMachine = struct {
self: *VirtualMachine,
allocator: std.mem.Allocator,
pc: u64,
cie: std.debug.Dwarf.CommonInformationEntry,
fde: std.debug.Dwarf.FrameDescriptionEntry,
cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,
addr_size_bytes: u8,
endian: std.builtin.Endian,
) !Row {
@ -2036,8 +2055,8 @@ pub const VirtualMachine = struct {
self: *VirtualMachine,
allocator: std.mem.Allocator,
pc: u64,
cie: std.debug.Dwarf.CommonInformationEntry,
fde: std.debug.Dwarf.FrameDescriptionEntry,
cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
fde: std.debug.Dwarf.Unwind.FrameDescriptionEntry,
) !Row {
return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian);
}
@ -2059,7 +2078,7 @@ pub const VirtualMachine = struct {
pub fn step(
self: *VirtualMachine,
allocator: std.mem.Allocator,
cie: std.debug.Dwarf.CommonInformationEntry,
cie: std.debug.Dwarf.Unwind.CommonInformationEntry,
is_initial: bool,
instruction: Dwarf.call_frame.Instruction,
) !Row {