mirror of
https://github.com/ziglang/zig.git
synced 2025-12-16 19:23:08 +00:00
more stuff
This commit is contained in:
parent
b750e7cf9e
commit
ed6ed62c42
@ -1449,6 +1449,7 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
|
|||||||
return str[casted_offset..last :0];
|
return str[casted_offset..last :0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MLUGG TODO: i am dubious of this whole thing being here atp. look closely and see if it depends on being the self process
|
||||||
pub const ElfModule = struct {
|
pub const ElfModule = struct {
|
||||||
unwind: Dwarf.Unwind,
|
unwind: Dwarf.Unwind,
|
||||||
dwarf: Dwarf,
|
dwarf: Dwarf,
|
||||||
@ -1456,10 +1457,7 @@ pub const ElfModule = struct {
|
|||||||
external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
|
external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
|
||||||
|
|
||||||
pub const init: ElfModule = .{
|
pub const init: ElfModule = .{
|
||||||
.unwind = .{
|
.unwind = .init,
|
||||||
.debug_frame = null,
|
|
||||||
.eh_frame = null,
|
|
||||||
},
|
|
||||||
.dwarf = .{},
|
.dwarf = .{},
|
||||||
.mapped_memory = null,
|
.mapped_memory = null,
|
||||||
.external_mapped_memory = null,
|
.external_mapped_memory = null,
|
||||||
@ -1508,6 +1506,8 @@ pub const ElfModule = struct {
|
|||||||
/// If the required sections aren't present but a reference to external debug
|
/// If the required sections aren't present but a reference to external debug
|
||||||
/// info is, then this this function will recurse to attempt to load the debug
|
/// info is, then this this function will recurse to attempt to load the debug
|
||||||
/// sections from an external file.
|
/// sections from an external file.
|
||||||
|
///
|
||||||
|
/// MLUGG TODO: this should *return* a thing
|
||||||
pub fn load(
|
pub fn load(
|
||||||
em: *ElfModule,
|
em: *ElfModule,
|
||||||
gpa: Allocator,
|
gpa: Allocator,
|
||||||
@ -1518,6 +1518,8 @@ pub const ElfModule = struct {
|
|||||||
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
|
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
|
||||||
elf_filename: ?[]const u8,
|
elf_filename: ?[]const u8,
|
||||||
) LoadError!void {
|
) LoadError!void {
|
||||||
|
assert(em.mapped_memory == null);
|
||||||
|
|
||||||
if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
|
if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
|
||||||
|
|
||||||
const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
|
const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
|
||||||
@ -1709,8 +1711,9 @@ pub const ElfModule = struct {
|
|||||||
separate_debug_crc,
|
separate_debug_crc,
|
||||||
§ions,
|
§ions,
|
||||||
mapped_mem,
|
mapped_mem,
|
||||||
)) |debug_info| {
|
)) |v| {
|
||||||
return debug_info;
|
v;
|
||||||
|
return;
|
||||||
} else |_| {}
|
} else |_| {}
|
||||||
|
|
||||||
// <exe_dir>/.debug/<gnu_debuglink>
|
// <exe_dir>/.debug/<gnu_debuglink>
|
||||||
|
|||||||
@ -1,28 +1,35 @@
|
|||||||
|
//! MLUGG TODO DOCUMENT THIS
|
||||||
|
|
||||||
pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
|
pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
|
||||||
|
|
||||||
/// The contents of the `.debug_frame` section as specified by DWARF. This might be a more reliable
|
frame_section: ?struct {
|
||||||
/// stack unwind mechanism in some cases, or it may be present when `.eh_frame` is not, but fetching
|
id: Section,
|
||||||
/// the data requires loading the binary, so it is not a viable approach for fast stack trace
|
/// The virtual address of the start of the section. "Virtual address" refers to the address in
|
||||||
/// capturing within a process.
|
/// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated
|
||||||
debug_frame: ?struct {
|
/// in position-independent binaries.
|
||||||
data: []const u8,
|
vaddr: u64,
|
||||||
/// Offsets into `data` of FDEs, sorted by ascending `pc_begin`.
|
/// The full contents of the section. May have imprecise bounds depending on `section`.
|
||||||
sorted_fdes: []SortedFdeEntry,
|
///
|
||||||
|
/// For `.debug_frame`, the slice length is exactly equal to the section length. This is needed
|
||||||
|
/// to know the number of CIEs and FDEs.
|
||||||
|
///
|
||||||
|
/// For `.eh_frame`, the slice length may exceed the section length, i.e. the slice may refer to
|
||||||
|
/// more bytes than are in the second. This restriction exists because `.eh_frame_hdr` only
|
||||||
|
/// includes the address of the loaded `.eh_frame` data, not its length. It is not a problem
|
||||||
|
/// because unlike `.debug_frame`, the end of the CIE/FDE list is signaled through a sentinel
|
||||||
|
/// value. If this slice does have bounds, they will still be checked, preventing crashes when
|
||||||
|
/// reading potentially-invalid `.eh_frame` data from files.
|
||||||
|
bytes: []const u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Data associated with the `.eh_frame` and `.eh_frame_hdr` sections as defined by LSB Core. The
|
lookup: ?union(enum) {
|
||||||
/// format of `.eh_frame` is an extension of that of DWARF's `.debug_frame` -- in fact it is almost
|
eh_frame_hdr: struct {
|
||||||
/// identical, though subtly different in a few places.
|
/// Virtual address of the `.eh_frame_hdr` section.
|
||||||
eh_frame: ?struct {
|
vaddr: u64,
|
||||||
header: EhFrameHeader,
|
table: EhFrameHeader.SearchTable,
|
||||||
/// Though this is a slice, it may be longer than the `.eh_frame` section. When unwinding
|
},
|
||||||
/// through the runtime-loaded `.eh_frame_hdr` data, we are not told the size of the `.eh_frame`
|
/// Offsets into `frame_section` of FDEs, sorted by ascending `pc_begin`.
|
||||||
/// section, so construct a slice referring to all of the rest of memory. The end of the section
|
sorted_fdes: []SortedFdeEntry,
|
||||||
/// must be detected through `EntryHeader.terminator`.
|
|
||||||
eh_frame_data: []const u8,
|
|
||||||
/// Offsets into `eh_frame_data` of FDEs, sorted by ascending `pc_begin`.
|
|
||||||
/// Populated only if `header` does not already contain a lookup table.
|
|
||||||
sorted_fdes: ?[]SortedFdeEntry,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
const SortedFdeEntry = struct {
|
const SortedFdeEntry = struct {
|
||||||
@ -34,17 +41,61 @@ const SortedFdeEntry = struct {
|
|||||||
|
|
||||||
const Section = enum { debug_frame, eh_frame };
|
const Section = enum { debug_frame, eh_frame };
|
||||||
|
|
||||||
|
// MLUGG TODO deinit?
|
||||||
|
pub const init: Unwind = .{
|
||||||
|
.frame_section = null,
|
||||||
|
.lookup = null,
|
||||||
|
};
|
||||||
|
|
||||||
/// This represents the decoded .eh_frame_hdr header
|
/// This represents the decoded .eh_frame_hdr header
|
||||||
pub const EhFrameHeader = struct {
|
pub const EhFrameHeader = struct {
|
||||||
vaddr: u64,
|
|
||||||
eh_frame_vaddr: u64,
|
eh_frame_vaddr: u64,
|
||||||
search_table: ?struct {
|
search_table: ?SearchTable,
|
||||||
|
|
||||||
|
pub const SearchTable = struct {
|
||||||
/// The byte offset of the search table into the `.eh_frame_hdr` section.
|
/// The byte offset of the search table into the `.eh_frame_hdr` section.
|
||||||
offset: u8,
|
offset: u8,
|
||||||
encoding: EH.PE,
|
encoding: EH.PE,
|
||||||
fde_count: usize,
|
fde_count: usize,
|
||||||
entries: []const u8,
|
entries: []const u8,
|
||||||
},
|
|
||||||
|
/// Returns the vaddr of the FDE for `pc`, or `null` if no matching FDE was found.
|
||||||
|
fn findEntry(
|
||||||
|
table: *const SearchTable,
|
||||||
|
eh_frame_hdr_vaddr: u64,
|
||||||
|
pc: u64,
|
||||||
|
addr_size_bytes: u8,
|
||||||
|
endian: Endian,
|
||||||
|
) !?u64 {
|
||||||
|
const table_vaddr = eh_frame_hdr_vaddr + table.offset;
|
||||||
|
const entry_size = try EhFrameHeader.entrySize(table.encoding, addr_size_bytes);
|
||||||
|
var left: usize = 0;
|
||||||
|
var len: usize = table.fde_count;
|
||||||
|
while (len > 1) {
|
||||||
|
const mid = left + len / 2;
|
||||||
|
var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]);
|
||||||
|
const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
|
||||||
|
.pc_rel_base = table_vaddr + left * entry_size,
|
||||||
|
.data_rel_base = eh_frame_hdr_vaddr,
|
||||||
|
}, endian);
|
||||||
|
if (pc < pc_begin) {
|
||||||
|
len /= 2;
|
||||||
|
} else {
|
||||||
|
left = mid;
|
||||||
|
len -= len / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (len == 0) return null;
|
||||||
|
var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]);
|
||||||
|
// Skip past `pc_begin`; we're now interested in the fde offset
|
||||||
|
_ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian);
|
||||||
|
const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
|
||||||
|
.pc_rel_base = table_vaddr + left * entry_size,
|
||||||
|
.data_rel_base = eh_frame_hdr_vaddr,
|
||||||
|
}, endian);
|
||||||
|
return fde_ptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 {
|
pub fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 {
|
||||||
return switch (table_enc.type) {
|
return switch (table_enc.type) {
|
||||||
@ -76,10 +127,7 @@ pub const EhFrameHeader = struct {
|
|||||||
.pc_rel_base = eh_frame_hdr_vaddr + r.seek,
|
.pc_rel_base = eh_frame_hdr_vaddr + r.seek,
|
||||||
}, endian);
|
}, endian);
|
||||||
|
|
||||||
return .{
|
const table: ?SearchTable = table: {
|
||||||
.vaddr = eh_frame_hdr_vaddr,
|
|
||||||
.eh_frame_vaddr = eh_frame_ptr,
|
|
||||||
.search_table = table: {
|
|
||||||
if (fde_count_enc == EH.PE.omit) break :table null;
|
if (fde_count_enc == EH.PE.omit) break :table null;
|
||||||
if (table_enc == EH.PE.omit) break :table null;
|
if (table_enc == EH.PE.omit) break :table null;
|
||||||
const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{
|
const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{
|
||||||
@ -95,45 +143,12 @@ pub const EhFrameHeader = struct {
|
|||||||
.entries = bytes,
|
.entries = bytes,
|
||||||
.offset = @intCast(bytes_offset),
|
.offset = @intCast(bytes_offset),
|
||||||
};
|
};
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/// Asserts that `eh_frame_hdr.search_table != null`.
|
return .{
|
||||||
fn findEntry(
|
.eh_frame_vaddr = eh_frame_ptr,
|
||||||
eh_frame_hdr: *const EhFrameHeader,
|
.search_table = table,
|
||||||
pc: u64,
|
};
|
||||||
addr_size_bytes: u8,
|
|
||||||
endian: Endian,
|
|
||||||
) !?u64 {
|
|
||||||
const table = &eh_frame_hdr.search_table.?;
|
|
||||||
const table_vaddr = eh_frame_hdr.vaddr + table.offset;
|
|
||||||
const entry_size = try EhFrameHeader.entrySize(table.encoding, addr_size_bytes);
|
|
||||||
var left: usize = 0;
|
|
||||||
var len: usize = table.fde_count;
|
|
||||||
while (len > 1) {
|
|
||||||
const mid = left + len / 2;
|
|
||||||
var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]);
|
|
||||||
const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
|
|
||||||
.pc_rel_base = table_vaddr + left * entry_size,
|
|
||||||
.data_rel_base = eh_frame_hdr.vaddr,
|
|
||||||
}, endian);
|
|
||||||
if (pc < pc_begin) {
|
|
||||||
len /= 2;
|
|
||||||
} else {
|
|
||||||
left = mid;
|
|
||||||
len -= len / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (len == 0) return null;
|
|
||||||
var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]);
|
|
||||||
// Skip past `pc_begin`; we're now interested in the fde offset
|
|
||||||
_ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian);
|
|
||||||
const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
|
|
||||||
.pc_rel_base = table_vaddr + left * entry_size,
|
|
||||||
.data_rel_base = eh_frame_hdr.vaddr,
|
|
||||||
}, endian);
|
|
||||||
return std.math.sub(u64, fde_ptr, eh_frame_hdr.eh_frame_vaddr) catch bad(); // offset into .eh_frame
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -356,133 +371,84 @@ pub const FrameDescriptionEntry = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn scanDebugFrame(
|
/// Load unwind information from the contents of an `.eh_frame` or `.debug_frame` section.
|
||||||
unwind: *Unwind,
|
///
|
||||||
gpa: Allocator,
|
/// If the `.eh_frame_hdr` section is available, consider instead using `loadFromEhFrameHdr`. This
|
||||||
section_vaddr: u64,
|
/// allows the implementation to use a search table embedded in that section if it is available.
|
||||||
section_bytes: []const u8,
|
pub fn loadFromSection(unwind: *Unwind, section: Section, section_vaddr: u64, section_bytes: []const u8) void {
|
||||||
addr_size_bytes: u8,
|
assert(unwind.frame_section == null);
|
||||||
endian: Endian,
|
assert(unwind.lookup == null);
|
||||||
) void {
|
unwind.frame_section = .{
|
||||||
assert(unwind.debug_frame == null);
|
.id = section,
|
||||||
|
.bytes = section_bytes,
|
||||||
var fbr: Reader = .fixed(section_bytes);
|
.vaddr = section_vaddr,
|
||||||
var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
|
|
||||||
defer fde_list.deinit(gpa);
|
|
||||||
while (fbr.seek < fbr.buffer.len) {
|
|
||||||
const entry_offset = fbr.seek;
|
|
||||||
switch (try EntryHeader.read(&fbr, fbr.seek, .debug_frame, endian)) {
|
|
||||||
// Ignore CIEs; we only need them to parse the FDEs!
|
|
||||||
.cie => |info| {
|
|
||||||
try fbr.discardAll(info.bytes_len);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
.fde => |info| {
|
|
||||||
const cie: CommonInformationEntry = cie: {
|
|
||||||
var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);
|
|
||||||
const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .debug_frame, endian)) {
|
|
||||||
.cie => |cie_info| cie_info,
|
|
||||||
.fde, .terminator => return bad(), // This is meant to be a CIE
|
|
||||||
};
|
};
|
||||||
break :cie try .parse(try cie_reader.take(cie_info.bytes_len), .debug_frame, addr_size_bytes);
|
|
||||||
};
|
|
||||||
const fde: FrameDescriptionEntry = try .parse(
|
|
||||||
section_vaddr + fbr.seek,
|
|
||||||
try fbr.take(info.bytes_len),
|
|
||||||
cie,
|
|
||||||
endian,
|
|
||||||
);
|
|
||||||
try fde_list.append(.{
|
|
||||||
.pc_begin = fde.pc_begin,
|
|
||||||
.fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header
|
|
||||||
});
|
|
||||||
},
|
|
||||||
.terminator => return bad(), // DWARF `.debug_frame` isn't meant to have terminators
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const fde_slice = try fde_list.toOwnedSlice(gpa);
|
|
||||||
errdefer comptime unreachable;
|
|
||||||
std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
|
|
||||||
fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
|
|
||||||
ctx;
|
|
||||||
return a.pc_begin < b.pc_begin;
|
|
||||||
}
|
|
||||||
}.lessThan);
|
|
||||||
unwind.debug_frame = .{ .data = section_bytes, .sorted_fdes = fde_slice };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scanEhFrame(
|
/// Load unwind information from a header loaded from an `.eh_frame_hdr` section, and a pointer to
|
||||||
|
/// the contents of the `.eh_frame` section.
|
||||||
|
///
|
||||||
|
/// This differs from `loadFromSection` because `.eh_frame_hdr` may embed a binary search table, and
|
||||||
|
/// if it does, this function will use that for address lookups instead of constructing our own
|
||||||
|
/// search table.
|
||||||
|
pub fn loadFromEhFrameHdr(
|
||||||
unwind: *Unwind,
|
unwind: *Unwind,
|
||||||
gpa: Allocator,
|
|
||||||
header: EhFrameHeader,
|
header: EhFrameHeader,
|
||||||
|
section_vaddr: u64,
|
||||||
section_bytes_ptr: [*]const u8,
|
section_bytes_ptr: [*]const u8,
|
||||||
/// This is separate from `section_bytes_ptr` because it is unknown when `.eh_frame` is accessed
|
|
||||||
/// through the pointer in the `.eh_frame_hdr` section. If this is non-`null`, we avoid reading
|
|
||||||
/// past this number of bytes, but if `null`, we must assume that the `.eh_frame` data has a
|
|
||||||
/// valid terminator.
|
|
||||||
section_bytes_len: ?usize,
|
|
||||||
addr_size_bytes: u8,
|
|
||||||
endian: Endian,
|
|
||||||
) !void {
|
) !void {
|
||||||
assert(unwind.eh_frame == null);
|
assert(unwind.frame_section == null);
|
||||||
|
assert(unwind.lookup == null);
|
||||||
const section_bytes: []const u8 = bytes: {
|
unwind.frame_section = .{
|
||||||
// If the length is unknown, let the slice span from `section_bytes_ptr` to the end of memory.
|
.id = .eh_frame,
|
||||||
const len = section_bytes_len orelse (std.math.maxInt(usize) - @intFromPtr(section_bytes_ptr));
|
.bytes = maxSlice(section_bytes_ptr),
|
||||||
break :bytes section_bytes_ptr[0..len];
|
.vaddr = header.eh_frame_vaddr,
|
||||||
};
|
};
|
||||||
|
if (header.search_table) |table| {
|
||||||
if (header.search_table != null) {
|
unwind.lookup = .{ .eh_frame_hdr = .{
|
||||||
// No need to populate `sorted_fdes`, the header contains a search table.
|
.vaddr = section_vaddr,
|
||||||
unwind.eh_frame = .{
|
.table = table,
|
||||||
.header = header,
|
} };
|
||||||
.eh_frame_data = section_bytes,
|
|
||||||
.sorted_fdes = null,
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We aren't told the length of this section. Luckily, we don't need it, because there will be
|
pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endian: Endian) !void {
|
||||||
// an `EntryHeader.terminator` after the last CIE/FDE. Just make a `Reader` which will give us
|
const section = unwind.frame_section.?;
|
||||||
// alllll of the bytes!
|
if (unwind.lookup != null) return;
|
||||||
var fbr: Reader = .fixed(section_bytes);
|
|
||||||
|
|
||||||
|
var r: Reader = .fixed(section.bytes);
|
||||||
var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
|
var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
|
||||||
defer fde_list.deinit(gpa);
|
defer fde_list.deinit(gpa);
|
||||||
|
|
||||||
while (true) {
|
const saw_terminator = while (r.seek < r.buffer.len) {
|
||||||
const entry_offset = fbr.seek;
|
const entry_offset = r.seek;
|
||||||
switch (try EntryHeader.read(&fbr, fbr.seek, .eh_frame, endian)) {
|
switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) {
|
||||||
// Ignore CIEs; we only need them to parse the FDEs!
|
.cie => |cie_info| {
|
||||||
.cie => |info| {
|
// Ignore CIEs for now; we'll parse them when we read a corresponding FDE
|
||||||
try fbr.discardAll(info.bytes_len);
|
try r.discardAll(cie_info.bytes_len);
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
.fde => |info| {
|
.fde => |fde_info| {
|
||||||
const cie: CommonInformationEntry = cie: {
|
var cie_r: Reader = .fixed(section.bytes[fde_info.cie_offset..]);
|
||||||
var cie_reader: Reader = .fixed(section_bytes[info.cie_offset..]);
|
const cie_info = switch (try EntryHeader.read(&cie_r, fde_info.cie_offset, section.id, endian)) {
|
||||||
const cie_info = switch (try EntryHeader.read(&cie_reader, info.cie_offset, .eh_frame, endian)) {
|
|
||||||
.cie => |cie_info| cie_info,
|
.cie => |cie_info| cie_info,
|
||||||
.fde, .terminator => return bad(), // This is meant to be a CIE
|
.fde, .terminator => return bad(), // this is meant to be a CIE
|
||||||
};
|
};
|
||||||
break :cie try .parse(try cie_reader.take(cie_info.bytes_len), .eh_frame, addr_size_bytes);
|
const cie: CommonInformationEntry = try .parse(try cie_r.take(cie_info.bytes_len), section.id, addr_size_bytes);
|
||||||
};
|
const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(fde_info.bytes_len), cie, endian);
|
||||||
const fde: FrameDescriptionEntry = try .parse(
|
|
||||||
header.eh_frame_vaddr + fbr.seek,
|
|
||||||
try fbr.take(info.bytes_len),
|
|
||||||
cie,
|
|
||||||
endian,
|
|
||||||
);
|
|
||||||
try fde_list.append(gpa, .{
|
try fde_list.append(gpa, .{
|
||||||
.pc_begin = fde.pc_begin,
|
.pc_begin = fde.pc_begin,
|
||||||
.fde_offset = entry_offset, // *not* `fde_offset`, because we need to include the entry header
|
.fde_offset = entry_offset,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Unlike `.debug_frame`, the `.eh_frame` section does have a terminator CIE -- this is
|
.terminator => break true,
|
||||||
// necessary because `header` doesn't include the length of the `.eh_frame` section
|
|
||||||
.terminator => break,
|
|
||||||
}
|
}
|
||||||
|
} else false;
|
||||||
|
switch (section.id) {
|
||||||
|
.eh_frame => if (!saw_terminator) return bad(), // `.eh_frame` indicates the end of the CIE/FDE list with a sentinel entry
|
||||||
|
.debug_frame => if (saw_terminator) return bad(), // `.debug_frame` uses the section bounds and does not specify a sentinel entry
|
||||||
}
|
}
|
||||||
|
|
||||||
const fde_slice = try fde_list.toOwnedSlice(gpa);
|
const fde_slice = try fde_list.toOwnedSlice(gpa);
|
||||||
errdefer comptime unreachable;
|
errdefer comptime unreachable;
|
||||||
std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
|
std.mem.sortUnstable(SortedFdeEntry, fde_slice, {}, struct {
|
||||||
@ -491,26 +457,29 @@ pub fn scanEhFrame(
|
|||||||
return a.pc_begin < b.pc_begin;
|
return a.pc_begin < b.pc_begin;
|
||||||
}
|
}
|
||||||
}.lessThan);
|
}.lessThan);
|
||||||
unwind.eh_frame = .{
|
unwind.lookup = .{ .sorted_fdes = fde_slice };
|
||||||
.header = header,
|
|
||||||
.eh_frame_data = section_bytes,
|
|
||||||
.sorted_fdes = fde_slice,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no
|
||||||
|
/// matching FDE was found. The returned offset can be passed to `getFde` to load the data
|
||||||
|
/// associated with the FDE.
|
||||||
|
///
|
||||||
|
/// Before calling this function, `prepareLookup` must return successfully.
|
||||||
|
///
|
||||||
/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
|
/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
|
||||||
/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.
|
/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.
|
||||||
pub fn findFdeOffset(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 {
|
pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 {
|
||||||
// We'll break from this block only if we have a manually-constructed search table.
|
const sorted_fdes: []const SortedFdeEntry = switch (unwind.lookup.?) {
|
||||||
const sorted_fdes: []const SortedFdeEntry = fdes: {
|
.eh_frame_hdr => |eh_frame_hdr| {
|
||||||
if (unwind.debug_frame) |df| break :fdes df.sorted_fdes;
|
const fde_vaddr = try eh_frame_hdr.table.findEntry(
|
||||||
if (unwind.eh_frame) |eh_frame| {
|
eh_frame_hdr.vaddr,
|
||||||
if (eh_frame.sorted_fdes) |fdes| break :fdes fdes;
|
pc,
|
||||||
// Use the search table from the `.eh_frame_hdr` section rather than one of our own
|
addr_size_bytes,
|
||||||
return eh_frame.header.findEntry(pc, addr_size_bytes, endian);
|
endian,
|
||||||
}
|
) orelse return null;
|
||||||
// We have no available unwind info
|
return std.math.sub(u64, fde_vaddr, unwind.frame_section.?.vaddr) catch bad(); // convert vaddr to offset
|
||||||
return null;
|
},
|
||||||
|
.sorted_fdes => |sorted_fdes| sorted_fdes,
|
||||||
};
|
};
|
||||||
const first_bad_idx = std.sort.partitionPoint(SortedFdeEntry, sorted_fdes, pc, struct {
|
const first_bad_idx = std.sort.partitionPoint(SortedFdeEntry, sorted_fdes, pc, struct {
|
||||||
fn canIncludePc(target_pc: u64, entry: SortedFdeEntry) bool {
|
fn canIncludePc(target_pc: u64, entry: SortedFdeEntry) bool {
|
||||||
@ -523,33 +492,29 @@ pub fn findFdeOffset(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian
|
|||||||
return sorted_fdes[first_bad_idx - 1].fde_offset;
|
return sorted_fdes[first_bad_idx - 1].fde_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loadFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } {
|
pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } {
|
||||||
const section_bytes: []const u8, const section_vaddr: u64, const section: Section = s: {
|
const section = unwind.frame_section.?;
|
||||||
if (unwind.debug_frame) |df| break :s .{ df.data, if (true) @panic("MLUGG TODO"), .debug_frame };
|
|
||||||
if (unwind.eh_frame) |ef| break :s .{ ef.eh_frame_data, ef.header.eh_frame_vaddr, .eh_frame };
|
|
||||||
unreachable; // how did you get `fde_offset`?!
|
|
||||||
};
|
|
||||||
|
|
||||||
var fde_reader: Reader = .fixed(section_bytes[fde_offset..]);
|
var fde_reader: Reader = .fixed(section.bytes[fde_offset..]);
|
||||||
const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section, endian)) {
|
const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section.id, endian)) {
|
||||||
.fde => |info| info,
|
.fde => |info| info,
|
||||||
.cie, .terminator => return bad(), // This is meant to be an FDE
|
.cie, .terminator => return bad(), // This is meant to be an FDE
|
||||||
};
|
};
|
||||||
|
|
||||||
const cie_offset = fde_info.cie_offset;
|
const cie_offset = fde_info.cie_offset;
|
||||||
var cie_reader: Reader = .fixed(section_bytes[cie_offset..]);
|
var cie_reader: Reader = .fixed(section.bytes[cie_offset..]);
|
||||||
const cie_info = switch (try EntryHeader.read(&cie_reader, cie_offset, section, endian)) {
|
const cie_info = switch (try EntryHeader.read(&cie_reader, cie_offset, section.id, endian)) {
|
||||||
.cie => |info| info,
|
.cie => |info| info,
|
||||||
.fde, .terminator => return bad(), // This is meant to be a CIE
|
.fde, .terminator => return bad(), // This is meant to be a CIE
|
||||||
};
|
};
|
||||||
|
|
||||||
const cie: CommonInformationEntry = try .parse(
|
const cie: CommonInformationEntry = try .parse(
|
||||||
try cie_reader.take(cie_info.bytes_len),
|
try cie_reader.take(cie_info.bytes_len),
|
||||||
section,
|
section.id,
|
||||||
addr_size_bytes,
|
addr_size_bytes,
|
||||||
);
|
);
|
||||||
const fde: FrameDescriptionEntry = try .parse(
|
const fde: FrameDescriptionEntry = try .parse(
|
||||||
section_vaddr + fde_offset + fde_reader.seek,
|
section.vaddr + fde_offset + fde_reader.seek,
|
||||||
try fde_reader.take(fde_info.bytes_len),
|
try fde_reader.take(fde_info.bytes_len),
|
||||||
cie,
|
cie,
|
||||||
endian,
|
endian,
|
||||||
|
|||||||
@ -26,10 +26,13 @@ const regValueNative = Dwarf.abi.regValueNative;
|
|||||||
|
|
||||||
const SelfInfo = @This();
|
const SelfInfo = @This();
|
||||||
|
|
||||||
/// MLUGG TODO: what if this field had a less stupid name...
|
modules: std.AutoHashMapUnmanaged(usize, struct {
|
||||||
address_map: std.AutoHashMapUnmanaged(usize, Module.DebugInfo),
|
di: Module.DebugInfo,
|
||||||
|
loaded_debug: bool,
|
||||||
module_cache: if (native_os == .windows) std.ArrayListUnmanaged(windows.MODULEENTRY32) else void,
|
loaded_unwind: bool,
|
||||||
|
const init: @This() = .{ .di = .init, .loaded_debug = false, .loaded_unwind = false };
|
||||||
|
}),
|
||||||
|
lookup_cache: Module.LookupCache,
|
||||||
|
|
||||||
pub const target_supported: bool = switch (native_os) {
|
pub const target_supported: bool = switch (native_os) {
|
||||||
.linux,
|
.linux,
|
||||||
@ -46,19 +49,19 @@ pub const target_supported: bool = switch (native_os) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const init: SelfInfo = .{
|
pub const init: SelfInfo = .{
|
||||||
.address_map = .empty,
|
.modules = .empty,
|
||||||
.module_cache = if (native_os == .windows) .empty,
|
.lookup_cache = if (Module.LookupCache != void) .init,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn deinit(self: *SelfInfo) void {
|
pub fn deinit(self: *SelfInfo) void {
|
||||||
// MLUGG TODO: that's amusing, this function is straight-up unused. i... wonder if it even should be used anywhere? perhaps not... so perhaps it should not even exist...????
|
// MLUGG TODO: that's amusing, this function is straight-up unused. i... wonder if it even should be used anywhere? perhaps not... so perhaps it should not even exist...????
|
||||||
var it = self.address_map.iterator();
|
var it = self.modules.iterator();
|
||||||
while (it.next()) |entry| {
|
while (it.next()) |entry| {
|
||||||
const mdi = entry.value_ptr.*;
|
const mdi = entry.value_ptr.*;
|
||||||
mdi.deinit(self.allocator);
|
mdi.deinit(self.allocator);
|
||||||
self.allocator.destroy(mdi);
|
self.allocator.destroy(mdi);
|
||||||
}
|
}
|
||||||
self.address_map.deinit(self.allocator);
|
self.modules.deinit(self.allocator);
|
||||||
if (native_os == .windows) {
|
if (native_os == .windows) {
|
||||||
for (self.modules.items) |module| {
|
for (self.modules.items) |module| {
|
||||||
self.allocator.free(module.name);
|
self.allocator.free(module.name);
|
||||||
@ -68,94 +71,26 @@ pub fn deinit(self: *SelfInfo) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookupModuleForAddress(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
|
|
||||||
if (builtin.target.os.tag.isDarwin()) {
|
|
||||||
return self.lookupModuleDyld(address);
|
|
||||||
} else if (native_os == .windows) {
|
|
||||||
return self.lookupModuleWin32(gpa, address);
|
|
||||||
} else if (native_os == .haiku) {
|
|
||||||
@panic("TODO implement lookup module for Haiku");
|
|
||||||
} else if (builtin.target.cpu.arch.isWasm()) {
|
|
||||||
@panic("TODO implement lookup module for Wasm");
|
|
||||||
} else {
|
|
||||||
return self.lookupModuleDl(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn loadModuleDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
|
|
||||||
// MLUGG TODO: this should totally just go into the `Module` impl or something, right? lol
|
|
||||||
if (builtin.target.os.tag.isDarwin()) {
|
|
||||||
try loadMachODebugInfo(gpa, module, di);
|
|
||||||
} else if (native_os == .windows) {
|
|
||||||
// MLUGG TODO: deal with 'already loaded' properly
|
|
||||||
try readCoffDebugInfo(gpa, module, di);
|
|
||||||
} else if (native_os == .haiku) {
|
|
||||||
unreachable;
|
|
||||||
} else if (builtin.target.cpu.arch.isWasm()) {
|
|
||||||
unreachable;
|
|
||||||
} else {
|
|
||||||
if (di.mapped_memory != null) return; // already loaded
|
|
||||||
const filename: ?[]const u8 = if (module.name.len > 0) module.name else null;
|
|
||||||
const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) {
|
|
||||||
error.FileNotFound => return error.MissingDebugInfo,
|
|
||||||
error.FileTooBig => return error.InvalidDebugInfo,
|
|
||||||
else => |e| return e,
|
|
||||||
};
|
|
||||||
errdefer posix.munmap(mapped_mem);
|
|
||||||
try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename);
|
|
||||||
assert(di.mapped_memory != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn loadModuleUnwindInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
|
|
||||||
if (builtin.target.os.tag.isDarwin()) {
|
|
||||||
// MLUGG TODO HACKHACK
|
|
||||||
try loadMachODebugInfo(gpa, module, di);
|
|
||||||
} else if (native_os == .windows) {
|
|
||||||
comptime unreachable; // not supported
|
|
||||||
} else if (native_os == .haiku) {
|
|
||||||
comptime unreachable; // not supported
|
|
||||||
} else if (builtin.target.cpu.arch.isWasm()) {
|
|
||||||
comptime unreachable; // not supported
|
|
||||||
} else {
|
|
||||||
eh_frame: {
|
|
||||||
if (di.unwind.eh_frame != null) break :eh_frame; // already loaded
|
|
||||||
const eh_frame_hdr_bytes = module.gnu_eh_frame orelse break :eh_frame;
|
|
||||||
const eh_frame_hdr: Dwarf.Unwind.EhFrameHeader = try .parse(
|
|
||||||
@intFromPtr(eh_frame_hdr_bytes.ptr) - module.load_offset,
|
|
||||||
eh_frame_hdr_bytes,
|
|
||||||
@sizeOf(usize),
|
|
||||||
native_endian,
|
|
||||||
);
|
|
||||||
const eh_frame_addr = module.load_offset + @as(usize, @intCast(eh_frame_hdr.eh_frame_vaddr));
|
|
||||||
try di.unwind.scanEhFrame(
|
|
||||||
gpa,
|
|
||||||
eh_frame_hdr,
|
|
||||||
@ptrFromInt(eh_frame_addr),
|
|
||||||
null,
|
|
||||||
@sizeOf(usize),
|
|
||||||
native_endian,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
|
pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
|
||||||
comptime assert(target_supported);
|
comptime assert(target_supported);
|
||||||
const module = try self.lookupModuleForAddress(gpa, context.pc);
|
const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); // MLUGG TODO: don't take gpa
|
||||||
const gop = try self.address_map.getOrPut(gpa, module.load_offset);
|
const gop = try self.modules.getOrPut(gpa, module.load_offset);
|
||||||
if (!gop.found_existing) gop.value_ptr.* = .init;
|
if (!gop.found_existing) gop.value_ptr.* = .init;
|
||||||
try loadModuleUnwindInfo(gpa, &module, gop.value_ptr);
|
if (!gop.value_ptr.loaded_unwind) {
|
||||||
|
try module.loadUnwindInfo(gpa, &gop.value_ptr.di);
|
||||||
|
gop.value_ptr.loaded_unwind = true;
|
||||||
|
}
|
||||||
|
// MLUGG TODO: the stuff below is impl!
|
||||||
if (native_os.isDarwin()) {
|
if (native_os.isDarwin()) {
|
||||||
// __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding
|
// __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.
|
// via DWARF before attempting to use the compact unwind info will produce incorrect results.
|
||||||
if (gop.value_ptr.unwind_info) |unwind_info| {
|
if (gop.value_ptr.di.unwind_info) |unwind_info| {
|
||||||
if (unwindFrameMachO(
|
if (unwindFrameMachO(
|
||||||
module.text_base,
|
module.text_base,
|
||||||
module.load_offset,
|
module.load_offset,
|
||||||
context,
|
context,
|
||||||
unwind_info,
|
unwind_info,
|
||||||
gop.value_ptr.eh_frame,
|
gop.value_ptr.di.eh_frame,
|
||||||
)) |return_address| {
|
)) |return_address| {
|
||||||
return return_address;
|
return return_address;
|
||||||
} else |err| {
|
} else |err| {
|
||||||
@ -164,7 +99,7 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us
|
|||||||
}
|
}
|
||||||
return error.MissingUnwindInfo;
|
return error.MissingUnwindInfo;
|
||||||
}
|
}
|
||||||
if (try gop.value_ptr.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| {
|
if (try gop.value_ptr.di.getDwarfUnwindForAddress(gpa, context.pc)) |unwind| {
|
||||||
return unwindFrameDwarf(unwind, module.load_offset, context, null);
|
return unwindFrameDwarf(unwind, module.load_offset, context, null);
|
||||||
}
|
}
|
||||||
return error.MissingDebugInfo;
|
return error.MissingDebugInfo;
|
||||||
@ -172,11 +107,15 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us
|
|||||||
|
|
||||||
pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
|
pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
|
||||||
comptime assert(target_supported);
|
comptime assert(target_supported);
|
||||||
const module = try self.lookupModuleForAddress(gpa, address);
|
const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa
|
||||||
const gop = try self.address_map.getOrPut(gpa, module.key());
|
const gop = try self.modules.getOrPut(gpa, module.key());
|
||||||
if (!gop.found_existing) gop.value_ptr.* = .init;
|
if (!gop.found_existing) gop.value_ptr.* = .init;
|
||||||
try loadModuleDebugInfo(gpa, &module, gop.value_ptr);
|
if (!gop.value_ptr.loaded_debug) {
|
||||||
return module.getSymbolAtAddress(gpa, gop.value_ptr, address);
|
// MLUGG TODO: this overloads the name 'debug info' with including vs excluding unwind info
|
||||||
|
// figure out a better name for one or the other (i think the inner one is maybe 'symbol info' or something idk)
|
||||||
|
try module.loadDebugInfo(gpa, &gop.value_ptr.di);
|
||||||
|
}
|
||||||
|
return module.getSymbolAtAddress(gpa, &gop.value_ptr.di, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the module name for a given address.
|
/// Returns the module name for a given address.
|
||||||
@ -184,82 +123,25 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.
|
|||||||
/// a path that doesn't rely on any side-effects of a prior successful module lookup.
|
/// a path that doesn't rely on any side-effects of a prior successful module lookup.
|
||||||
pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
|
pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) error{ Unexpected, OutOfMemory, MissingDebugInfo }![]const u8 {
|
||||||
comptime assert(target_supported);
|
comptime assert(target_supported);
|
||||||
const module = try self.lookupModuleForAddress(gpa, address);
|
const module: Module = try .lookup(&self.lookup_cache, gpa, address); // MLUGG TODO: don't take gpa
|
||||||
return module.name;
|
return module.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lookupModuleDl(self: *SelfInfo, address: usize) !Module {
|
const Module = switch (native_os) {
|
||||||
_ = self; // MLUGG
|
else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
|
||||||
const DlIterContext = struct {
|
.macos, .ios, .watchos, .tvos, .visionos => struct {
|
||||||
/// input
|
/// The runtime address where __TEXT is loaded.
|
||||||
address: usize,
|
text_base: usize,
|
||||||
/// output
|
load_offset: usize,
|
||||||
module: Module,
|
name: []const u8,
|
||||||
|
unwind_info: ?[]const u8,
|
||||||
fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
|
eh_frame: ?[]const u8,
|
||||||
_ = size;
|
fn key(m: *const Module) usize {
|
||||||
// The base address is too high
|
return m.text_base;
|
||||||
if (context.address < info.addr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const phdrs = info.phdr[0..info.phnum];
|
|
||||||
for (phdrs) |*phdr| {
|
|
||||||
if (phdr.p_type != elf.PT_LOAD) continue;
|
|
||||||
|
|
||||||
// Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
|
|
||||||
const seg_start = info.addr +% phdr.p_vaddr;
|
|
||||||
const seg_end = seg_start + phdr.p_memsz;
|
|
||||||
if (context.address >= seg_start and context.address < seg_end) {
|
|
||||||
context.module = .{
|
|
||||||
.load_offset = info.addr,
|
|
||||||
// Android libc uses NULL instead of "" to mark the main program
|
|
||||||
.name = mem.sliceTo(info.name, 0) orelse "",
|
|
||||||
.build_id = null,
|
|
||||||
.gnu_eh_frame = null,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else return;
|
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
|
||||||
|
_ = cache;
|
||||||
for (info.phdr[0..info.phnum]) |phdr| {
|
_ = gpa;
|
||||||
switch (phdr.p_type) {
|
|
||||||
elf.PT_NOTE => {
|
|
||||||
// Look for .note.gnu.build-id
|
|
||||||
const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
|
|
||||||
var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
|
|
||||||
const name_size = r.takeInt(u32, native_endian) catch continue;
|
|
||||||
const desc_size = r.takeInt(u32, native_endian) catch continue;
|
|
||||||
const note_type = r.takeInt(u32, native_endian) catch continue;
|
|
||||||
const name = r.take(name_size) catch continue;
|
|
||||||
if (note_type != elf.NT_GNU_BUILD_ID) continue;
|
|
||||||
if (!mem.eql(u8, name, "GNU\x00")) continue;
|
|
||||||
const desc = r.take(desc_size) catch continue;
|
|
||||||
context.module.build_id = desc;
|
|
||||||
},
|
|
||||||
elf.PT_GNU_EH_FRAME => {
|
|
||||||
const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
|
|
||||||
context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the iteration
|
|
||||||
return error.Found;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var ctx: DlIterContext = .{
|
|
||||||
.address = address,
|
|
||||||
.module = undefined,
|
|
||||||
};
|
|
||||||
posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
|
|
||||||
error.Found => return ctx.module,
|
|
||||||
};
|
|
||||||
return error.MissingDebugInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookupModuleDyld(self: *SelfInfo, address: usize) !Module {
|
|
||||||
_ = self; // MLUGG
|
|
||||||
const image_count = std.c._dyld_image_count();
|
const image_count = std.c._dyld_image_count();
|
||||||
for (0..image_count) |image_idx| {
|
for (0..image_count) |image_idx| {
|
||||||
const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
|
const header = std.c._dyld_get_image_header(@intCast(image_idx)) orelse continue;
|
||||||
@ -306,165 +188,13 @@ fn lookupModuleDyld(self: *SelfInfo, address: usize) !Module {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return error.MissingDebugInfo;
|
return error.MissingDebugInfo;
|
||||||
}
|
|
||||||
|
|
||||||
fn lookupModuleWin32(self: *SelfInfo, gpa: Allocator, address: usize) !Module {
|
|
||||||
if (self.lookupModuleWin32Cache(address)) |m| return m;
|
|
||||||
|
|
||||||
{
|
|
||||||
// Check a new module hasn't been loaded
|
|
||||||
self.module_cache.clearRetainingCapacity();
|
|
||||||
|
|
||||||
const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
|
|
||||||
if (handle == windows.INVALID_HANDLE_VALUE) {
|
|
||||||
return windows.unexpectedError(windows.GetLastError());
|
|
||||||
}
|
}
|
||||||
defer windows.CloseHandle(handle);
|
fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
|
||||||
|
return loadMachODebugInfo(gpa, module, di);
|
||||||
var entry: windows.MODULEENTRY32 = undefined;
|
|
||||||
entry.dwSize = @sizeOf(windows.MODULEENTRY32);
|
|
||||||
if (windows.kernel32.Module32First(handle, &entry) != 0) {
|
|
||||||
try self.module_cache.append(gpa, entry);
|
|
||||||
while (windows.kernel32.Module32Next(handle, &entry) != 0) {
|
|
||||||
try self.module_cache.append(gpa, entry);
|
|
||||||
}
|
}
|
||||||
}
|
fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
|
||||||
}
|
// MLUGG TODO HACKHACK
|
||||||
|
try loadMachODebugInfo(gpa, module, di);
|
||||||
if (self.lookupModuleWin32Cache(address)) |m| return m;
|
|
||||||
return error.MissingDebugInfo;
|
|
||||||
}
|
|
||||||
fn lookupModuleWin32Cache(self: *SelfInfo, address: usize) ?Module {
|
|
||||||
for (self.module_cache.items) |*entry| {
|
|
||||||
const base_address = @intFromPtr(entry.modBaseAddr);
|
|
||||||
if (address >= base_address and address < base_address + entry.modBaseSize) {
|
|
||||||
return .{
|
|
||||||
.base_address = base_address,
|
|
||||||
.size = entry.modBaseSize,
|
|
||||||
.name = std.mem.sliceTo(&entry.szModule, 0),
|
|
||||||
.handle = entry.hModule,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readCoffDebugInfo(gpa: Allocator, module: *const Module, di: *Module.DebugInfo) !void {
|
|
||||||
const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
|
|
||||||
const mapped = mapped_ptr[0..module.size];
|
|
||||||
var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
|
|
||||||
// The string table is not mapped into memory by the loader, so if a section name is in the
|
|
||||||
// string table then we have to map the full image file from disk. This can happen when
|
|
||||||
// a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
|
|
||||||
if (coff_obj.strtabRequired()) {
|
|
||||||
var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
|
|
||||||
name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
|
|
||||||
const process_handle = windows.GetCurrentProcess();
|
|
||||||
const len = windows.kernel32.GetModuleFileNameExW(
|
|
||||||
process_handle,
|
|
||||||
module.handle,
|
|
||||||
name_buffer[4..],
|
|
||||||
windows.PATH_MAX_WIDE,
|
|
||||||
);
|
|
||||||
if (len == 0) return error.MissingDebugInfo;
|
|
||||||
const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
|
|
||||||
error.FileNotFound => return error.MissingDebugInfo,
|
|
||||||
else => |e| return e,
|
|
||||||
};
|
|
||||||
errdefer coff_file.close();
|
|
||||||
var section_handle: windows.HANDLE = undefined;
|
|
||||||
const create_section_rc = windows.ntdll.NtCreateSection(
|
|
||||||
§ion_handle,
|
|
||||||
windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
windows.PAGE_READONLY,
|
|
||||||
// The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
|
|
||||||
// In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
|
|
||||||
windows.SEC_COMMIT,
|
|
||||||
coff_file.handle,
|
|
||||||
);
|
|
||||||
if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
|
||||||
errdefer windows.CloseHandle(section_handle);
|
|
||||||
var coff_len: usize = 0;
|
|
||||||
var section_view_ptr: [*]const u8 = undefined;
|
|
||||||
const map_section_rc = windows.ntdll.NtMapViewOfSection(
|
|
||||||
section_handle,
|
|
||||||
process_handle,
|
|
||||||
@ptrCast(§ion_view_ptr),
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
&coff_len,
|
|
||||||
.ViewUnmap,
|
|
||||||
0,
|
|
||||||
windows.PAGE_READONLY,
|
|
||||||
);
|
|
||||||
if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
|
||||||
errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
|
|
||||||
const section_view = section_view_ptr[0..coff_len];
|
|
||||||
coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
|
|
||||||
di.mapped_file = .{
|
|
||||||
.file = coff_file,
|
|
||||||
.section_handle = section_handle,
|
|
||||||
.section_view = section_view,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
di.coff_image_base = coff_obj.getImageBase();
|
|
||||||
|
|
||||||
if (coff_obj.getSectionByName(".debug_info")) |_| {
|
|
||||||
di.dwarf = .{};
|
|
||||||
|
|
||||||
inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
|
|
||||||
di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
|
|
||||||
break :blk .{
|
|
||||||
.data = try coff_obj.getSectionDataAlloc(section_header, gpa),
|
|
||||||
.virtual_address = section_header.virtual_address,
|
|
||||||
.owned = true,
|
|
||||||
};
|
|
||||||
} else null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try di.dwarf.?.open(gpa, native_endian);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try coff_obj.getPdbPath()) |raw_path| pdb: {
|
|
||||||
const path = blk: {
|
|
||||||
if (fs.path.isAbsolute(raw_path)) {
|
|
||||||
break :blk raw_path;
|
|
||||||
} else {
|
|
||||||
const self_dir = try fs.selfExeDirPathAlloc(gpa);
|
|
||||||
defer gpa.free(self_dir);
|
|
||||||
break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
defer if (path.ptr != raw_path.ptr) gpa.free(path);
|
|
||||||
|
|
||||||
di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
|
|
||||||
error.FileNotFound, error.IsDir => break :pdb,
|
|
||||||
else => return err,
|
|
||||||
};
|
|
||||||
try di.pdb.?.parseInfoStream();
|
|
||||||
try di.pdb.?.parseDbiStream();
|
|
||||||
|
|
||||||
if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
|
|
||||||
return error.InvalidDebugInfo;
|
|
||||||
|
|
||||||
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Module = switch (native_os) {
|
|
||||||
else => "MLUGG TODO", // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
|
|
||||||
.macos, .ios, .watchos, .tvos, .visionos => struct {
|
|
||||||
/// The runtime address where __TEXT is loaded.
|
|
||||||
text_base: usize,
|
|
||||||
load_offset: usize,
|
|
||||||
name: []const u8,
|
|
||||||
unwind_info: ?[]const u8,
|
|
||||||
eh_frame: ?[]const u8,
|
|
||||||
fn key(m: *const Module) usize {
|
|
||||||
return m.text_base;
|
|
||||||
}
|
}
|
||||||
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
||||||
const vaddr = address - module.load_offset;
|
const vaddr = address - module.load_offset;
|
||||||
@ -524,8 +254,8 @@ const Module = switch (native_os) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const LookupCache = void;
|
||||||
const DebugInfo = struct {
|
const DebugInfo = struct {
|
||||||
// MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them!
|
|
||||||
mapped_memory: []align(std.heap.page_size_min) const u8,
|
mapped_memory: []align(std.heap.page_size_min) const u8,
|
||||||
symbols: []const MachoSymbol,
|
symbols: []const MachoSymbol,
|
||||||
strings: [:0]const u8,
|
strings: [:0]const u8,
|
||||||
@ -533,6 +263,7 @@ const Module = switch (native_os) {
|
|||||||
ofiles: std.StringArrayHashMapUnmanaged(OFile),
|
ofiles: std.StringArrayHashMapUnmanaged(OFile),
|
||||||
|
|
||||||
// Backed by the in-memory sections mapped by the loader
|
// Backed by the in-memory sections mapped by the loader
|
||||||
|
// MLUGG TODO: these are duplicated state. i actually reckon they should be removed from Module, and loadMachODebugInfo should be the one discovering them!
|
||||||
unwind_info: ?[]const u8,
|
unwind_info: ?[]const u8,
|
||||||
eh_frame: ?[]const u8,
|
eh_frame: ?[]const u8,
|
||||||
|
|
||||||
@ -642,28 +373,137 @@ const Module = switch (native_os) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
.wasi, .emscripten => struct {
|
.wasi, .emscripten => struct {
|
||||||
|
const LookupCache = void;
|
||||||
const DebugInfo = struct {
|
const DebugInfo = struct {
|
||||||
const init: DebugInfo = .{};
|
const init: DebugInfo = .{};
|
||||||
fn getSymbolAtAddress(di: *DebugInfo, gpa: Allocator, base_address: usize, address: usize) !std.debug.Symbol {
|
};
|
||||||
_ = di;
|
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
|
||||||
|
_ = cache;
|
||||||
_ = gpa;
|
_ = gpa;
|
||||||
_ = base_address;
|
_ = address;
|
||||||
|
@panic("TODO implement lookup module for Wasm");
|
||||||
|
}
|
||||||
|
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
||||||
|
_ = module;
|
||||||
|
_ = gpa;
|
||||||
|
_ = di;
|
||||||
_ = address;
|
_ = address;
|
||||||
unreachable;
|
unreachable;
|
||||||
}
|
}
|
||||||
};
|
fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
|
||||||
|
_ = module;
|
||||||
|
_ = gpa;
|
||||||
|
_ = di;
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
|
||||||
|
_ = module;
|
||||||
|
_ = gpa;
|
||||||
|
_ = di;
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
|
.linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
|
||||||
load_offset: usize,
|
load_offset: usize,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
build_id: ?[]const u8,
|
build_id: ?[]const u8,
|
||||||
gnu_eh_frame: ?[]const u8,
|
gnu_eh_frame: ?[]const u8,
|
||||||
|
const LookupCache = void;
|
||||||
|
const DebugInfo = Dwarf.ElfModule;
|
||||||
fn key(m: Module) usize {
|
fn key(m: Module) usize {
|
||||||
return m.load_offset; // MLUGG TODO: is this technically valid? idk
|
return m.load_offset; // MLUGG TODO: is this technically valid? idk
|
||||||
}
|
}
|
||||||
const DebugInfo = Dwarf.ElfModule;
|
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
|
||||||
fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
_ = cache;
|
||||||
return di.getSymbolAtAddress(gpa, native_endian, mod.load_offset, address);
|
_ = gpa;
|
||||||
|
if (native_os == .haiku) @panic("TODO implement lookup module for Haiku");
|
||||||
|
const DlIterContext = struct {
|
||||||
|
/// input
|
||||||
|
address: usize,
|
||||||
|
/// output
|
||||||
|
module: Module,
|
||||||
|
|
||||||
|
fn callback(info: *posix.dl_phdr_info, size: usize, context: *@This()) !void {
|
||||||
|
_ = size;
|
||||||
|
// The base address is too high
|
||||||
|
if (context.address < info.addr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const phdrs = info.phdr[0..info.phnum];
|
||||||
|
for (phdrs) |*phdr| {
|
||||||
|
if (phdr.p_type != elf.PT_LOAD) continue;
|
||||||
|
|
||||||
|
// Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
|
||||||
|
const seg_start = info.addr +% phdr.p_vaddr;
|
||||||
|
const seg_end = seg_start + phdr.p_memsz;
|
||||||
|
if (context.address >= seg_start and context.address < seg_end) {
|
||||||
|
context.module = .{
|
||||||
|
.load_offset = info.addr,
|
||||||
|
// Android libc uses NULL instead of "" to mark the main program
|
||||||
|
.name = mem.sliceTo(info.name, 0) orelse "",
|
||||||
|
.build_id = null,
|
||||||
|
.gnu_eh_frame = null,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else return;
|
||||||
|
|
||||||
|
for (info.phdr[0..info.phnum]) |phdr| {
|
||||||
|
switch (phdr.p_type) {
|
||||||
|
elf.PT_NOTE => {
|
||||||
|
// Look for .note.gnu.build-id
|
||||||
|
const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
|
||||||
|
var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
|
||||||
|
const name_size = r.takeInt(u32, native_endian) catch continue;
|
||||||
|
const desc_size = r.takeInt(u32, native_endian) catch continue;
|
||||||
|
const note_type = r.takeInt(u32, native_endian) catch continue;
|
||||||
|
const name = r.take(name_size) catch continue;
|
||||||
|
if (note_type != elf.NT_GNU_BUILD_ID) continue;
|
||||||
|
if (!mem.eql(u8, name, "GNU\x00")) continue;
|
||||||
|
const desc = r.take(desc_size) catch continue;
|
||||||
|
context.module.build_id = desc;
|
||||||
|
},
|
||||||
|
elf.PT_GNU_EH_FRAME => {
|
||||||
|
const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
|
||||||
|
context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the iteration
|
||||||
|
return error.Found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ctx: DlIterContext = .{
|
||||||
|
.address = address,
|
||||||
|
.module = undefined,
|
||||||
|
};
|
||||||
|
posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
|
||||||
|
error.Found => return ctx.module,
|
||||||
|
};
|
||||||
|
return error.MissingDebugInfo;
|
||||||
|
}
|
||||||
|
fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
|
||||||
|
const filename: ?[]const u8 = if (module.name.len > 0) module.name else null;
|
||||||
|
const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) {
|
||||||
|
error.FileNotFound => return error.MissingDebugInfo,
|
||||||
|
error.FileTooBig => return error.InvalidDebugInfo,
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
errdefer posix.munmap(mapped_mem);
|
||||||
|
try di.load(gpa, mapped_mem, module.build_id, null, null, null, filename);
|
||||||
|
assert(di.mapped_memory != null);
|
||||||
|
}
|
||||||
|
fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
|
||||||
|
const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
|
||||||
|
const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
|
||||||
|
const header: Dwarf.Unwind.EhFrameHeader = try .parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian);
|
||||||
|
try di.unwind.loadFromEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
|
||||||
|
try di.unwind.prepareLookup(gpa, @sizeOf(usize), native_endian);
|
||||||
|
}
|
||||||
|
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
||||||
|
return di.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.uefi, .windows => struct {
|
.uefi, .windows => struct {
|
||||||
@ -674,6 +514,152 @@ const Module = switch (native_os) {
|
|||||||
fn key(m: Module) usize {
|
fn key(m: Module) usize {
|
||||||
return m.base_address;
|
return m.base_address;
|
||||||
}
|
}
|
||||||
|
fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
|
||||||
|
if (lookupInCache(cache, address)) |m| return m;
|
||||||
|
{
|
||||||
|
// Check a new module hasn't been loaded
|
||||||
|
cache.modules.clearRetainingCapacity();
|
||||||
|
|
||||||
|
const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
|
||||||
|
if (handle == windows.INVALID_HANDLE_VALUE) {
|
||||||
|
return windows.unexpectedError(windows.GetLastError());
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(handle);
|
||||||
|
|
||||||
|
var entry: windows.MODULEENTRY32 = undefined;
|
||||||
|
entry.dwSize = @sizeOf(windows.MODULEENTRY32);
|
||||||
|
if (windows.kernel32.Module32First(handle, &entry) != 0) {
|
||||||
|
try cache.modules.append(gpa, entry);
|
||||||
|
while (windows.kernel32.Module32Next(handle, &entry) != 0) {
|
||||||
|
try cache.modules.append(gpa, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lookupInCache(cache, address)) |m| return m;
|
||||||
|
return error.MissingDebugInfo;
|
||||||
|
}
|
||||||
|
fn lookupInCache(cache: *const LookupCache, address: usize) ?Module {
|
||||||
|
for (cache.modules.items) |*entry| {
|
||||||
|
const base_address = @intFromPtr(entry.modBaseAddr);
|
||||||
|
if (address >= base_address and address < base_address + entry.modBaseSize) {
|
||||||
|
return .{
|
||||||
|
.base_address = base_address,
|
||||||
|
.size = entry.modBaseSize,
|
||||||
|
.name = std.mem.sliceTo(&entry.szModule, 0),
|
||||||
|
.handle = entry.hModule,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
fn loadDebugInfo(module: *const Module, gpa: Allocator, di: *DebugInfo) !void {
|
||||||
|
const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
|
||||||
|
const mapped = mapped_ptr[0..module.size];
|
||||||
|
var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
|
||||||
|
// The string table is not mapped into memory by the loader, so if a section name is in the
|
||||||
|
// string table then we have to map the full image file from disk. This can happen when
|
||||||
|
// a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
|
||||||
|
if (coff_obj.strtabRequired()) {
|
||||||
|
var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
|
||||||
|
name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
|
||||||
|
const process_handle = windows.GetCurrentProcess();
|
||||||
|
const len = windows.kernel32.GetModuleFileNameExW(
|
||||||
|
process_handle,
|
||||||
|
module.handle,
|
||||||
|
name_buffer[4..],
|
||||||
|
windows.PATH_MAX_WIDE,
|
||||||
|
);
|
||||||
|
if (len == 0) return error.MissingDebugInfo;
|
||||||
|
const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
|
||||||
|
error.FileNotFound => return error.MissingDebugInfo,
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
errdefer coff_file.close();
|
||||||
|
var section_handle: windows.HANDLE = undefined;
|
||||||
|
const create_section_rc = windows.ntdll.NtCreateSection(
|
||||||
|
§ion_handle,
|
||||||
|
windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
windows.PAGE_READONLY,
|
||||||
|
// The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
|
||||||
|
// In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
|
||||||
|
windows.SEC_COMMIT,
|
||||||
|
coff_file.handle,
|
||||||
|
);
|
||||||
|
if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
||||||
|
errdefer windows.CloseHandle(section_handle);
|
||||||
|
var coff_len: usize = 0;
|
||||||
|
var section_view_ptr: [*]const u8 = undefined;
|
||||||
|
const map_section_rc = windows.ntdll.NtMapViewOfSection(
|
||||||
|
section_handle,
|
||||||
|
process_handle,
|
||||||
|
@ptrCast(§ion_view_ptr),
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
&coff_len,
|
||||||
|
.ViewUnmap,
|
||||||
|
0,
|
||||||
|
windows.PAGE_READONLY,
|
||||||
|
);
|
||||||
|
if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
||||||
|
errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
|
||||||
|
const section_view = section_view_ptr[0..coff_len];
|
||||||
|
coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
|
||||||
|
di.mapped_file = .{
|
||||||
|
.file = coff_file,
|
||||||
|
.section_handle = section_handle,
|
||||||
|
.section_view = section_view,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
di.coff_image_base = coff_obj.getImageBase();
|
||||||
|
|
||||||
|
if (coff_obj.getSectionByName(".debug_info")) |_| {
|
||||||
|
di.dwarf = .{};
|
||||||
|
|
||||||
|
inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
|
||||||
|
di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
|
||||||
|
break :blk .{
|
||||||
|
.data = try coff_obj.getSectionDataAlloc(section_header, gpa),
|
||||||
|
.virtual_address = section_header.virtual_address,
|
||||||
|
.owned = true,
|
||||||
|
};
|
||||||
|
} else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try di.dwarf.?.open(gpa, native_endian);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try coff_obj.getPdbPath()) |raw_path| pdb: {
|
||||||
|
const path = blk: {
|
||||||
|
if (fs.path.isAbsolute(raw_path)) {
|
||||||
|
break :blk raw_path;
|
||||||
|
} else {
|
||||||
|
const self_dir = try fs.selfExeDirPathAlloc(gpa);
|
||||||
|
defer gpa.free(self_dir);
|
||||||
|
break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
defer if (path.ptr != raw_path.ptr) gpa.free(path);
|
||||||
|
|
||||||
|
di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
|
||||||
|
error.FileNotFound, error.IsDir => break :pdb,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
try di.pdb.?.parseInfoStream();
|
||||||
|
try di.pdb.?.parseDbiStream();
|
||||||
|
|
||||||
|
if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
|
||||||
|
return error.InvalidDebugInfo;
|
||||||
|
|
||||||
|
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const LookupCache = struct {
|
||||||
|
modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
|
||||||
|
const init: LookupCache = .{ .modules = .empty };
|
||||||
|
};
|
||||||
const DebugInfo = struct {
|
const DebugInfo = struct {
|
||||||
coff_image_base: u64,
|
coff_image_base: u64,
|
||||||
mapped_file: ?struct {
|
mapped_file: ?struct {
|
||||||
@ -747,9 +733,9 @@ const Module = switch (native_os) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn getSymbolAtAddress(mod: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
|
||||||
// Translate the runtime address into a virtual address into the module
|
// Translate the runtime address into a virtual address into the module
|
||||||
const vaddr = address - mod.base_address;
|
const vaddr = address - module.base_address;
|
||||||
|
|
||||||
if (di.pdb != null) {
|
if (di.pdb != null) {
|
||||||
if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
|
if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
|
||||||
@ -1091,12 +1077,12 @@ fn unwindFrameDwarf(
|
|||||||
|
|
||||||
const pc_vaddr = context.pc - load_offset;
|
const pc_vaddr = context.pc - load_offset;
|
||||||
|
|
||||||
const fde_offset = explicit_fde_offset orelse try unwind.findFdeOffset(
|
const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
|
||||||
pc_vaddr,
|
pc_vaddr,
|
||||||
@sizeOf(usize),
|
@sizeOf(usize),
|
||||||
native_endian,
|
native_endian,
|
||||||
) orelse return error.MissingDebugInfo;
|
) orelse return error.MissingDebugInfo;
|
||||||
const format, const cie, const fde = try unwind.loadFde(fde_offset, @sizeOf(usize), native_endian);
|
const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian);
|
||||||
|
|
||||||
// Check if this FDE *actually* includes the address.
|
// Check if this FDE *actually* includes the address.
|
||||||
if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
|
if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) return error.MissingDebugInfo;
|
||||||
@ -1294,7 +1280,7 @@ fn unwindFrameMachO(
|
|||||||
load_offset: usize,
|
load_offset: usize,
|
||||||
context: *UnwindContext,
|
context: *UnwindContext,
|
||||||
unwind_info: []const u8,
|
unwind_info: []const u8,
|
||||||
eh_frame: ?[]const u8,
|
opt_eh_frame: ?[]const u8,
|
||||||
) !usize {
|
) !usize {
|
||||||
if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
|
if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
|
||||||
const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
|
const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
|
||||||
@ -1304,20 +1290,6 @@ fn unwindFrameMachO(
|
|||||||
const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
|
const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
|
||||||
if (indices.len == 0) return error.MissingUnwindInfo;
|
if (indices.len == 0) return error.MissingUnwindInfo;
|
||||||
|
|
||||||
// MLUGG TODO HACKHACK -- Unwind needs a slight refactor to make this work well
|
|
||||||
const opt_dwarf_unwind: ?Dwarf.Unwind = if (eh_frame) |eh_frame_data| .{
|
|
||||||
.debug_frame = null,
|
|
||||||
.eh_frame = .{
|
|
||||||
.header = .{
|
|
||||||
.vaddr = undefined,
|
|
||||||
.eh_frame_vaddr = @intFromPtr(eh_frame_data.ptr) - load_offset,
|
|
||||||
.search_table = null,
|
|
||||||
},
|
|
||||||
.eh_frame_data = eh_frame_data,
|
|
||||||
.sorted_fdes = null,
|
|
||||||
},
|
|
||||||
} else null;
|
|
||||||
|
|
||||||
// offset of the PC into the `__TEXT` segment
|
// offset of the PC into the `__TEXT` segment
|
||||||
const pc_text_offset = context.pc - text_base;
|
const pc_text_offset = context.pc - text_base;
|
||||||
|
|
||||||
@ -1533,8 +1505,11 @@ fn unwindFrameMachO(
|
|||||||
break :ip new_ip;
|
break :ip new_ip;
|
||||||
},
|
},
|
||||||
.DWARF => {
|
.DWARF => {
|
||||||
const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);
|
const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
|
||||||
return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));
|
const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
|
||||||
|
var dwarf_unwind: Dwarf.Unwind = .init;
|
||||||
|
dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);
|
||||||
|
return unwindFrameDwarf(&dwarf_unwind, load_offset, context, @intCast(encoding.value.x86_64.dwarf));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.aarch64, .aarch64_be => switch (encoding.mode.arm64) {
|
.aarch64, .aarch64_be => switch (encoding.mode.arm64) {
|
||||||
@ -1547,7 +1522,10 @@ fn unwindFrameMachO(
|
|||||||
break :ip new_ip;
|
break :ip new_ip;
|
||||||
},
|
},
|
||||||
.DWARF => {
|
.DWARF => {
|
||||||
const dwarf_unwind = &(opt_dwarf_unwind orelse return error.MissingEhFrame);
|
const eh_frame = opt_eh_frame orelse return error.MissingEhFrame;
|
||||||
|
const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - load_offset;
|
||||||
|
var dwarf_unwind: Dwarf.Unwind = .init;
|
||||||
|
dwarf_unwind.loadFromSection(.eh_frame, eh_frame_vaddr, eh_frame);
|
||||||
return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));
|
return unwindFrameDwarf(dwarf_unwind, load_offset, context, @intCast(encoding.value.arm64.dwarf));
|
||||||
},
|
},
|
||||||
.FRAME => ip: {
|
.FRAME => ip: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user