the world if ElfModule didn't suck:

This commit is contained in:
mlugg 2025-09-02 15:54:36 +01:00
parent 55a7affea4
commit 84b65860cf
No known key found for this signature in database
GPG key ID: 3F5B7DCCBF4AF02E
5 changed files with 126 additions and 137 deletions

View file

@ -153,10 +153,9 @@ pub const SourceLocation = struct {
};
pub const Symbol = struct {
// MLUGG TODO: remove the defaults and audit everywhere. also grep for '???' across std
name: []const u8 = "???",
compile_unit_name: []const u8 = "???",
source_location: ?SourceLocation = null,
name: ?[]const u8,
compile_unit_name: ?[]const u8,
source_location: ?SourceLocation,
};
/// Deprecated because it returns the optimization mode of the standard
@ -1040,10 +1039,11 @@ fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writ
fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: UnwindError, tty_config: tty.Config) !void {
const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
error.Unexpected, error.OutOfMemory => |e| return e,
error.MissingDebugInfo => "???",
error.Unexpected, error.OutOfMemory => |e| return e,
};
try tty_config.setColor(writer, .dim);
// MLUGG TODO this makes no sense given that MissingUnwindInfo exists?
if (unwind_err == error.MissingDebugInfo) {
try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
} else {
@ -1054,35 +1054,27 @@ fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwi
pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
const gpa = getDebugInfoAllocator();
if (debug_info.getSymbolAtAddress(gpa, address)) |symbol_info| {
defer if (symbol_info.source_location) |sl| gpa.free(sl.file_name);
return printLineInfo(
writer,
symbol_info.source_location,
address,
symbol_info.name,
symbol_info.compile_unit_name,
tty_config,
);
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {},
const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => .{
.name = null,
.compile_unit_name = null,
.source_location = null,
},
else => |e| return e,
}
// Unknown source location, but perhaps we can at least get a module name
const compile_unit_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
error.MissingDebugInfo => "???",
error.Unexpected, error.OutOfMemory => |e| return e,
};
defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
return printLineInfo(
writer,
null,
symbol.source_location,
address,
"???",
compile_unit_name,
symbol.name orelse "???",
symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch |err| switch (err) {
error.MissingDebugInfo => "???",
error.Unexpected, error.OutOfMemory => |e| return e,
},
tty_config,
);
}
fn printLineInfo(
writer: *Writer,
source_location: ?SourceLocation,

View file

@ -1487,20 +1487,42 @@ pub const ElfModule = struct {
MemoryMappingNotSupported,
} || Allocator.Error || std.fs.File.OpenError || OpenError;
/// Reads debug info from an already mapped ELF file.
/// Reads debug info from an ELF file given its path.
///
/// If the required sections aren't present but a reference to external debug
/// info is, then this this function will recurse to attempt to load the debug
/// sections from an external file.
pub fn load(
gpa: Allocator,
mapped_mem: []align(std.heap.page_size_min) const u8,
elf_file_path: Path,
build_id: ?[]const u8,
expected_crc: ?u32,
parent_sections: ?*Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
elf_filename: ?[]const u8,
) LoadError!ElfModule {
const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
const elf_file = try elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{});
defer elf_file.close();
const file_len = cast(
usize,
elf_file.getEndPos() catch return bad(),
) orelse return error.Overflow;
break :mapped std.posix.mmap(
null,
file_len,
std.posix.PROT.READ,
.{ .TYPE = .SHARED },
elf_file.handle,
0,
) catch |err| switch (err) {
error.MappingAlreadyExists => unreachable,
else => |e| return e,
};
};
errdefer std.posix.munmap(mapped_mem);
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]);
@ -1606,39 +1628,36 @@ pub const ElfModule = struct {
// $XDG_CACHE_HOME/debuginfod_client/<buildid>/debuginfo
// This only opportunisticly tries to load from the debuginfod cache, but doesn't try to populate it.
// One can manually run `debuginfod-find debuginfo PATH` to download the symbols
if (build_id) |id| blk: {
var debuginfod_dir: std.fs.Dir = switch (builtin.os.tag) {
.wasi, .windows => break :blk,
else => dir: {
if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| {
break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
debuginfod: {
const id = build_id orelse break :debuginfod;
switch (builtin.os.tag) {
.wasi, .windows => break :debuginfod,
else => {},
}
const id_dir_path: []u8 = p: {
if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| {
break :p try std.fmt.allocPrint(gpa, "{s}/{x}", .{ path, id });
}
if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {
if (cache_path.len > 0) {
break :p try std.fmt.allocPrint(gpa, "{s}/debuginfod_client/{x}", .{ cache_path, id });
}
if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {
if (cache_path.len > 0) {
const path = std.fs.path.join(gpa, &[_][]const u8{ cache_path, "debuginfod_client" }) catch break :blk;
defer gpa.free(path);
break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
}
}
if (std.posix.getenv("HOME")) |home_path| {
const path = std.fs.path.join(gpa, &[_][]const u8{ home_path, ".cache", "debuginfod_client" }) catch break :blk;
defer gpa.free(path);
break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
}
break :blk;
},
}
if (std.posix.getenv("HOME")) |home_path| {
break :p try std.fmt.allocPrint(gpa, "{s}/.cache/debuginfod_client/{x}", .{ home_path, id });
}
break :debuginfod;
};
defer debuginfod_dir.close();
defer gpa.free(id_dir_path);
if (!std.fs.path.isAbsolute(id_dir_path)) break :debuginfod;
const filename = std.fmt.allocPrint(gpa, "{x}/debuginfo", .{id}) catch break :blk;
defer gpa.free(filename);
var id_dir = std.fs.openDirAbsolute(id_dir_path, .{}) catch break :debuginfod;
defer id_dir.close();
const path: Path = .{
.root_dir = .{ .path = null, .handle = debuginfod_dir },
.sub_path = filename,
};
return loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch break :blk;
return load(gpa, .{
.root_dir = .{ .path = id_dir_path, .handle = id_dir },
.sub_path = "debuginfo",
}, null, separate_debug_crc, &sections, mapped_mem) catch break :debuginfod;
}
const global_debug_directories = [_][]const u8{
@ -1659,33 +1678,37 @@ pub const ElfModule = struct {
for (global_debug_directories) |global_directory| {
const path: Path = .{
.root_dir = std.Build.Cache.Directory.cwd(),
.root_dir = .cwd(),
.sub_path = try std.fs.path.join(gpa, &.{
global_directory, ".build-id", &id_prefix_buf, filename,
}),
};
defer gpa.free(path.sub_path);
return loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
return load(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
}
}
// use the path from .gnu_debuglink, in the same search order as gdb
if (separate_debug_filename) |separate_filename| blk: {
if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename))
separate: {
const separate_filename = separate_debug_filename orelse break :separate;
if (mem.eql(u8, std.fs.path.basename(elf_file_path.sub_path), separate_filename))
return error.MissingDebugInfo;
exe_dir: {
var exe_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
const exe_dir_path = std.fs.selfExeDirPath(&exe_dir_buf) catch break :exe_dir;
const exe_dir_path = try std.fs.path.resolve(gpa, &.{
elf_file_path.root_dir.path orelse ".",
std.fs.path.dirname(elf_file_path.sub_path) orelse ".",
});
defer gpa.free(exe_dir_path);
var exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch break :exe_dir;
defer exe_dir.close();
// <exe_dir>/<gnu_debuglink>
if (loadPath(
if (load(
gpa,
.{
.root_dir = .{ .path = null, .handle = exe_dir },
.root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
.sub_path = separate_filename,
},
null,
@ -1698,27 +1721,27 @@ pub const ElfModule = struct {
// <exe_dir>/.debug/<gnu_debuglink>
const path: Path = .{
.root_dir = .{ .path = null, .handle = exe_dir },
.root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
.sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }),
};
defer gpa.free(path.sub_path);
if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
return em;
} else |_| {}
}
var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :blk;
const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :separate;
// <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
for (global_debug_directories) |global_directory| {
const path: Path = .{
.root_dir = std.Build.Cache.Directory.cwd(),
.root_dir = .cwd(),
.sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }),
};
defer gpa.free(path.sub_path);
if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
return em;
} else |_| {}
}
@ -1735,47 +1758,6 @@ pub const ElfModule = struct {
.dwarf = dwarf,
};
}
pub fn loadPath(
gpa: Allocator,
elf_file_path: Path,
build_id: ?[]const u8,
expected_crc: ?u32,
parent_sections: *Dwarf.SectionArray,
parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
) LoadError!ElfModule {
const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) {
error.FileNotFound => return missing(),
else => return err,
};
defer elf_file.close();
const end_pos = elf_file.getEndPos() catch return bad();
const file_len = cast(usize, end_pos) orelse return error.Overflow;
const mapped_mem = std.posix.mmap(
null,
file_len,
std.posix.PROT.READ,
.{ .TYPE = .SHARED },
elf_file.handle,
0,
) catch |err| switch (err) {
error.MappingAlreadyExists => unreachable,
else => |e| return e,
};
errdefer std.posix.munmap(mapped_mem);
return load(
gpa,
mapped_mem,
build_id,
expected_crc,
parent_sections,
parent_mapped_mem,
elf_file_path.sub_path,
);
}
};
pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64) !std.debug.Symbol {

View file

@ -41,8 +41,6 @@ const SortedFdeEntry = struct {
const Section = enum { debug_frame, eh_frame };
// MLUGG TODO deinit?
/// Initialize with unwind information from the contents of a `.debug_frame` or `.eh_frame` section.
///
/// If the `.eh_frame_hdr` section is available, consider instead using `initEhFrameHdr`. This
@ -78,6 +76,13 @@ pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_p
};
}
pub fn deinit(unwind: *Unwind, gpa: Allocator) void {
if (unwind.lookup) |lookup| switch (lookup) {
.eh_frame_hdr => {},
.sorted_fdes => |fdes| gpa.free(fdes),
};
}
/// This represents the decoded .eh_frame_hdr header
pub const EhFrameHeader = struct {
eh_frame_vaddr: u64,
@ -205,8 +210,6 @@ pub const EntryHeader = union(enum) {
const unit_header = try Dwarf.readUnitHeader(r, endian);
if (unit_header.unit_length == 0) return .terminator;
// TODO MLUGG: seriously, just... check the formats of everything in BOTH LSB Core and DWARF. this is a fucking *mess*. maybe add spec references.
// Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this
// value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`.
const cie_ptr_or_id_size: u8 = switch (section) {

View file

@ -25,7 +25,7 @@ pub const LoadError = Dwarf.ElfModule.LoadError;
pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {
var sections: Dwarf.SectionArray = Dwarf.null_section_array;
var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);
var elf_module = try Dwarf.ElfModule.load(gpa, path, null, null, &sections, null);
try elf_module.dwarf.populateRanges(gpa);
var info: Info = .{
.address_map = .{},

View file

@ -156,11 +156,7 @@ const Module = switch (native_os) {
return error.MissingDebugInfo;
}
fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) {
error.FileNotFound => return error.MissingDebugInfo,
error.FileTooBig => return error.InvalidDebugInfo,
else => |e| return e,
};
const mapped_mem = try mapDebugInfoFile(module.name);
errdefer posix.munmap(mapped_mem);
const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
@ -311,7 +307,6 @@ const Module = switch (native_os) {
gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
defer _ = di.full.?.ofiles.pop().?;
switch (err) {
error.FileNotFound,
error.MissingDebugInfo,
error.InvalidDebugInfo,
=> return sym_only_result,
@ -402,7 +397,7 @@ const Module = switch (native_os) {
}
fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
const mapped_mem = try mapFileOrSelfExe(o_file_path);
const mapped_mem = try mapDebugInfoFile(o_file_path);
errdefer posix.munmap(mapped_mem);
if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
@ -595,14 +590,27 @@ const Module = switch (native_os) {
return error.MissingDebugInfo;
}
fn loadLocationInfo(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);
di.em = try .load(gpa, mapped_mem, module.build_id, null, null, null, filename);
if (module.name.len > 0) {
di.em = Dwarf.ElfModule.load(gpa, .{
.root_dir = .cwd(),
.sub_path = module.name,
}, module.build_id, null, null, null) catch |err| switch (err) {
error.FileNotFound => return error.MissingDebugInfo,
error.Overflow => return error.InvalidDebugInfo,
else => |e| return e,
};
} else {
const path = try std.fs.selfExePathAlloc(gpa);
defer gpa.free(path);
di.em = Dwarf.ElfModule.load(gpa, .{
.root_dir = .cwd(),
.sub_path = path,
}, module.build_id, null, null, null) catch |err| switch (err) {
error.FileNotFound => return error.MissingDebugInfo,
error.Overflow => return error.InvalidDebugInfo,
else => |e| return e,
};
}
}
fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
if (di.em == null) try module.loadLocationInfo(gpa, di);
@ -1247,14 +1255,18 @@ fn applyOffset(base: usize, offset: i64) !usize {
}
/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory.
fn mapFileOrSelfExe(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
const file = if (opt_path) |path|
try fs.cwd().openFile(path, .{})
fn mapDebugInfoFile(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
const open_result = if (opt_path) |path|
fs.cwd().openFile(path, .{})
else
try fs.openSelfExe(.{});
fs.openSelfExe(.{});
const file = open_result catch |err| switch (err) {
error.FileNotFound => return error.MissingDebugInfo,
else => |e| return e,
};
defer file.close();
const file_len = math.cast(usize, try file.getEndPos()) orelse return error.FileTooBig;
const file_len = math.cast(usize, try file.getEndPos()) orelse return error.InvalidDebugInfo;
return posix.mmap(
null,