mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 14:25:16 +00:00
debug: refactor stack frame capturing
This commit is contained in:
parent
7adb15892e
commit
b706949736
@ -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
645
lib/std/debug/Dwarf/Unwind.zig
Normal file
645
lib/std/debug/Dwarf/Unwind.zig
Normal 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();
|
||||
@ -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, §ions);
|
||||
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, §ions, 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, §ions);
|
||||
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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user