authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-02 15:54:36+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:49+01:00
log84b65860cfa4b7e61cb98347778331d67137d7e8
tree164290d3b2e86f06e74e02f2ca9d315edaed4e5b
parent55a7affea41a4a1f4e117d7ee55c1c0e8b869203
signaturelock-open Commit is signed but in an unrecognized format.

the world if ElfModule didn't suck:


5 files changed, 126 insertions(+), 137 deletions(-)

lib/std/debug.zig+18-26
......@@ -153,10 +153,9 @@ pub const SourceLocation = struct {
153153};
154154
155155pub const Symbol = struct {
156 // MLUGG TODO: remove the defaults and audit everywhere. also grep for '???' across std
157 name: []const u8 = "???",
158 compile_unit_name: []const u8 = "???",
159 source_location: ?SourceLocation = null,
156 name: ?[]const u8,
157 compile_unit_name: ?[]const u8,
158 source_location: ?SourceLocation,
160159};
161160
162161/// Deprecated because it returns the optimization mode of the standard
......@@ -1040,10 +1039,11 @@ fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, writer: *Writ
10401039
10411040fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: UnwindError, tty_config: tty.Config) !void {
10421041 const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
1043 error.Unexpected, error.OutOfMemory => |e| return e,
10441042 error.MissingDebugInfo => "???",
1043 error.Unexpected, error.OutOfMemory => |e| return e,
10451044 };
10461045 try tty_config.setColor(writer, .dim);
1046 // MLUGG TODO this makes no sense given that MissingUnwindInfo exists?
10471047 if (unwind_err == error.MissingDebugInfo) {
10481048 try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
10491049 } else {
......@@ -1054,35 +1054,27 @@ fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwi
10541054
10551055pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) !void {
10561056 const gpa = getDebugInfoAllocator();
1057 if (debug_info.getSymbolAtAddress(gpa, address)) |symbol_info| {
1058 defer if (symbol_info.source_location) |sl| gpa.free(sl.file_name);
1059 return printLineInfo(
1060 writer,
1061 symbol_info.source_location,
1062 address,
1063 symbol_info.name,
1064 symbol_info.compile_unit_name,
1065 tty_config,
1066 );
1067 } else |err| switch (err) {
1068 error.MissingDebugInfo, error.InvalidDebugInfo => {},
1057 const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) {
1058 error.MissingDebugInfo, error.InvalidDebugInfo => .{
1059 .name = null,
1060 .compile_unit_name = null,
1061 .source_location = null,
1062 },
10691063 else => |e| return e,
1070 }
1071 // Unknown source location, but perhaps we can at least get a module name
1072 const compile_unit_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
1073 error.MissingDebugInfo => "???",
1074 error.Unexpected, error.OutOfMemory => |e| return e,
10751064 };
1065 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
10761066 return printLineInfo(
10771067 writer,
1078 null,
1068 symbol.source_location,
10791069 address,
1080 "???",
1081 compile_unit_name,
1070 symbol.name orelse "???",
1071 symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch |err| switch (err) {
1072 error.MissingDebugInfo => "???",
1073 error.Unexpected, error.OutOfMemory => |e| return e,
1074 },
10821075 tty_config,
10831076 );
10841077}
1085
10861078fn printLineInfo(
10871079 writer: *Writer,
10881080 source_location: ?SourceLocation,
lib/std/debug/Dwarf.zig+68-86
......@@ -1487,20 +1487,42 @@ pub const ElfModule = struct {
14871487 MemoryMappingNotSupported,
14881488 } || Allocator.Error || std.fs.File.OpenError || OpenError;
14891489
1490 /// Reads debug info from an already mapped ELF file.
1490 /// Reads debug info from an ELF file given its path.
14911491 ///
14921492 /// If the required sections aren't present but a reference to external debug
14931493 /// info is, then this this function will recurse to attempt to load the debug
14941494 /// sections from an external file.
14951495 pub fn load(
14961496 gpa: Allocator,
1497 mapped_mem: []align(std.heap.page_size_min) const u8,
1497 elf_file_path: Path,
14981498 build_id: ?[]const u8,
14991499 expected_crc: ?u32,
15001500 parent_sections: ?*Dwarf.SectionArray,
15011501 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
1502 elf_filename: ?[]const u8,
15031502 ) LoadError!ElfModule {
1503 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
1504 const elf_file = try elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{});
1505 defer elf_file.close();
1506
1507 const file_len = cast(
1508 usize,
1509 elf_file.getEndPos() catch return bad(),
1510 ) orelse return error.Overflow;
1511
1512 break :mapped std.posix.mmap(
1513 null,
1514 file_len,
1515 std.posix.PROT.READ,
1516 .{ .TYPE = .SHARED },
1517 elf_file.handle,
1518 0,
1519 ) catch |err| switch (err) {
1520 error.MappingAlreadyExists => unreachable,
1521 else => |e| return e,
1522 };
1523 };
1524 errdefer std.posix.munmap(mapped_mem);
1525
15041526 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
15051527
15061528 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
......@@ -1606,39 +1628,36 @@ pub const ElfModule = struct {
16061628 // $XDG_CACHE_HOME/debuginfod_client/<buildid>/debuginfo
16071629 // This only opportunisticly tries to load from the debuginfod cache, but doesn't try to populate it.
16081630 // One can manually run `debuginfod-find debuginfo PATH` to download the symbols
1609 if (build_id) |id| blk: {
1610 var debuginfod_dir: std.fs.Dir = switch (builtin.os.tag) {
1611 .wasi, .windows => break :blk,
1612 else => dir: {
1613 if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| {
1614 break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
1615 }
1616 if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {
1617 if (cache_path.len > 0) {
1618 const path = std.fs.path.join(gpa, &[_][]const u8{ cache_path, "debuginfod_client" }) catch break :blk;
1619 defer gpa.free(path);
1620 break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
1621 }
1622 }
1623 if (std.posix.getenv("HOME")) |home_path| {
1624 const path = std.fs.path.join(gpa, &[_][]const u8{ home_path, ".cache", "debuginfod_client" }) catch break :blk;
1625 defer gpa.free(path);
1626 break :dir std.fs.openDirAbsolute(path, .{}) catch break :blk;
1631 debuginfod: {
1632 const id = build_id orelse break :debuginfod;
1633 switch (builtin.os.tag) {
1634 .wasi, .windows => break :debuginfod,
1635 else => {},
1636 }
1637 const id_dir_path: []u8 = p: {
1638 if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| {
1639 break :p try std.fmt.allocPrint(gpa, "{s}/{x}", .{ path, id });
1640 }
1641 if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {
1642 if (cache_path.len > 0) {
1643 break :p try std.fmt.allocPrint(gpa, "{s}/debuginfod_client/{x}", .{ cache_path, id });
16271644 }
1628 break :blk;
1629 },
1645 }
1646 if (std.posix.getenv("HOME")) |home_path| {
1647 break :p try std.fmt.allocPrint(gpa, "{s}/.cache/debuginfod_client/{x}", .{ home_path, id });
1648 }
1649 break :debuginfod;
16301650 };
1631 defer debuginfod_dir.close();
1632
1633 const filename = std.fmt.allocPrint(gpa, "{x}/debuginfo", .{id}) catch break :blk;
1634 defer gpa.free(filename);
1651 defer gpa.free(id_dir_path);
1652 if (!std.fs.path.isAbsolute(id_dir_path)) break :debuginfod;
16351653
1636 const path: Path = .{
1637 .root_dir = .{ .path = null, .handle = debuginfod_dir },
1638 .sub_path = filename,
1639 };
1654 var id_dir = std.fs.openDirAbsolute(id_dir_path, .{}) catch break :debuginfod;
1655 defer id_dir.close();
16401656
1641 return loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch break :blk;
1657 return load(gpa, .{
1658 .root_dir = .{ .path = id_dir_path, .handle = id_dir },
1659 .sub_path = "debuginfo",
1660 }, null, separate_debug_crc, &sections, mapped_mem) catch break :debuginfod;
16421661 }
16431662
16441663 const global_debug_directories = [_][]const u8{
......@@ -1659,33 +1678,37 @@ pub const ElfModule = struct {
16591678
16601679 for (global_debug_directories) |global_directory| {
16611680 const path: Path = .{
1662 .root_dir = std.Build.Cache.Directory.cwd(),
1681 .root_dir = .cwd(),
16631682 .sub_path = try std.fs.path.join(gpa, &.{
16641683 global_directory, ".build-id", &id_prefix_buf, filename,
16651684 }),
16661685 };
16671686 defer gpa.free(path.sub_path);
16681687
1669 return loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1688 return load(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
16701689 }
16711690 }
16721691
16731692 // use the path from .gnu_debuglink, in the same search order as gdb
1674 if (separate_debug_filename) |separate_filename| blk: {
1675 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename))
1693 separate: {
1694 const separate_filename = separate_debug_filename orelse break :separate;
1695 if (mem.eql(u8, std.fs.path.basename(elf_file_path.sub_path), separate_filename))
16761696 return error.MissingDebugInfo;
16771697
16781698 exe_dir: {
1679 var exe_dir_buf: [std.fs.max_path_bytes]u8 = undefined;
1680 const exe_dir_path = std.fs.selfExeDirPath(&exe_dir_buf) catch break :exe_dir;
1699 const exe_dir_path = try std.fs.path.resolve(gpa, &.{
1700 elf_file_path.root_dir.path orelse ".",
1701 std.fs.path.dirname(elf_file_path.sub_path) orelse ".",
1702 });
1703 defer gpa.free(exe_dir_path);
16811704 var exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch break :exe_dir;
16821705 defer exe_dir.close();
16831706
16841707 // <exe_dir>/<gnu_debuglink>
1685 if (loadPath(
1708 if (load(
16861709 gpa,
16871710 .{
1688 .root_dir = .{ .path = null, .handle = exe_dir },
1711 .root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
16891712 .sub_path = separate_filename,
16901713 },
16911714 null,
......@@ -1698,27 +1721,27 @@ pub const ElfModule = struct {
16981721
16991722 // <exe_dir>/.debug/<gnu_debuglink>
17001723 const path: Path = .{
1701 .root_dir = .{ .path = null, .handle = exe_dir },
1724 .root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
17021725 .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }),
17031726 };
17041727 defer gpa.free(path.sub_path);
17051728
1706 if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
1729 if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
17071730 return em;
17081731 } else |_| {}
17091732 }
17101733
17111734 var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
1712 const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :blk;
1735 const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :separate;
17131736
17141737 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
17151738 for (global_debug_directories) |global_directory| {
17161739 const path: Path = .{
1717 .root_dir = std.Build.Cache.Directory.cwd(),
1740 .root_dir = .cwd(),
17181741 .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }),
17191742 };
17201743 defer gpa.free(path.sub_path);
1721 if (loadPath(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
1744 if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
17221745 return em;
17231746 } else |_| {}
17241747 }
......@@ -1735,47 +1758,6 @@ pub const ElfModule = struct {
17351758 .dwarf = dwarf,
17361759 };
17371760 }
1738
1739 pub fn loadPath(
1740 gpa: Allocator,
1741 elf_file_path: Path,
1742 build_id: ?[]const u8,
1743 expected_crc: ?u32,
1744 parent_sections: *Dwarf.SectionArray,
1745 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
1746 ) LoadError!ElfModule {
1747 const elf_file = elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{}) catch |err| switch (err) {
1748 error.FileNotFound => return missing(),
1749 else => return err,
1750 };
1751 defer elf_file.close();
1752
1753 const end_pos = elf_file.getEndPos() catch return bad();
1754 const file_len = cast(usize, end_pos) orelse return error.Overflow;
1755
1756 const mapped_mem = std.posix.mmap(
1757 null,
1758 file_len,
1759 std.posix.PROT.READ,
1760 .{ .TYPE = .SHARED },
1761 elf_file.handle,
1762 0,
1763 ) catch |err| switch (err) {
1764 error.MappingAlreadyExists => unreachable,
1765 else => |e| return e,
1766 };
1767 errdefer std.posix.munmap(mapped_mem);
1768
1769 return load(
1770 gpa,
1771 mapped_mem,
1772 build_id,
1773 expected_crc,
1774 parent_sections,
1775 parent_mapped_mem,
1776 elf_file_path.sub_path,
1777 );
1778 }
17791761};
17801762
17811763pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64) !std.debug.Symbol {
lib/std/debug/Dwarf/Unwind.zig+7-4
......@@ -41,8 +41,6 @@ const SortedFdeEntry = struct {
4141
4242const Section = enum { debug_frame, eh_frame };
4343
44// MLUGG TODO deinit?
45
4644/// Initialize with unwind information from the contents of a `.debug_frame` or `.eh_frame` section.
4745///
4846/// 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
7876 };
7977}
8078
79pub fn deinit(unwind: *Unwind, gpa: Allocator) void {
80 if (unwind.lookup) |lookup| switch (lookup) {
81 .eh_frame_hdr => {},
82 .sorted_fdes => |fdes| gpa.free(fdes),
83 };
84}
85
8186/// This represents the decoded .eh_frame_hdr header
8287pub const EhFrameHeader = struct {
8388 eh_frame_vaddr: u64,
......@@ -205,8 +210,6 @@ pub const EntryHeader = union(enum) {
205210 const unit_header = try Dwarf.readUnitHeader(r, endian);
206211 if (unit_header.unit_length == 0) return .terminator;
207212
208 // TODO MLUGG: seriously, just... check the formats of everything in BOTH LSB Core and DWARF. this is a fucking *mess*. maybe add spec references.
209
210213 // Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this
211214 // value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`.
212215 const cie_ptr_or_id_size: u8 = switch (section) {
lib/std/debug/Info.zig+1-1
......@@ -25,7 +25,7 @@ pub const LoadError = Dwarf.ElfModule.LoadError;
2525
2626pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {
2727 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
28 var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);
28 var elf_module = try Dwarf.ElfModule.load(gpa, path, null, null, &sections, null);
2929 try elf_module.dwarf.populateRanges(gpa);
3030 var info: Info = .{
3131 .address_map = .{},
lib/std/debug/SelfInfo.zig+32-20
......@@ -156,11 +156,7 @@ const Module = switch (native_os) {
156156 return error.MissingDebugInfo;
157157 }
158158 fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
159 const mapped_mem = mapFileOrSelfExe(module.name) catch |err| switch (err) {
160 error.FileNotFound => return error.MissingDebugInfo,
161 error.FileTooBig => return error.InvalidDebugInfo,
162 else => |e| return e,
163 };
159 const mapped_mem = try mapDebugInfoFile(module.name);
164160 errdefer posix.munmap(mapped_mem);
165161
166162 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
......@@ -311,7 +307,6 @@ const Module = switch (native_os) {
311307 gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
312308 defer _ = di.full.?.ofiles.pop().?;
313309 switch (err) {
314 error.FileNotFound,
315310 error.MissingDebugInfo,
316311 error.InvalidDebugInfo,
317312 => return sym_only_result,
......@@ -402,7 +397,7 @@ const Module = switch (native_os) {
402397 }
403398
404399 fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
405 const mapped_mem = try mapFileOrSelfExe(o_file_path);
400 const mapped_mem = try mapDebugInfoFile(o_file_path);
406401 errdefer posix.munmap(mapped_mem);
407402
408403 if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
......@@ -595,14 +590,27 @@ const Module = switch (native_os) {
595590 return error.MissingDebugInfo;
596591 }
597592 fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
598 const filename: ?[]const u8 = if (module.name.len > 0) module.name else null;
599 const mapped_mem = mapFileOrSelfExe(filename) catch |err| switch (err) {
600 error.FileNotFound => return error.MissingDebugInfo,
601 error.FileTooBig => return error.InvalidDebugInfo,
602 else => |e| return e,
603 };
604 errdefer posix.munmap(mapped_mem);
605 di.em = try .load(gpa, mapped_mem, module.build_id, null, null, null, filename);
593 if (module.name.len > 0) {
594 di.em = Dwarf.ElfModule.load(gpa, .{
595 .root_dir = .cwd(),
596 .sub_path = module.name,
597 }, module.build_id, null, null, null) catch |err| switch (err) {
598 error.FileNotFound => return error.MissingDebugInfo,
599 error.Overflow => return error.InvalidDebugInfo,
600 else => |e| return e,
601 };
602 } else {
603 const path = try std.fs.selfExePathAlloc(gpa);
604 defer gpa.free(path);
605 di.em = Dwarf.ElfModule.load(gpa, .{
606 .root_dir = .cwd(),
607 .sub_path = path,
608 }, module.build_id, null, null, null) catch |err| switch (err) {
609 error.FileNotFound => return error.MissingDebugInfo,
610 error.Overflow => return error.InvalidDebugInfo,
611 else => |e| return e,
612 };
613 }
606614 }
607615 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
608616 if (di.em == null) try module.loadLocationInfo(gpa, di);
......@@ -1247,14 +1255,18 @@ fn applyOffset(base: usize, offset: i64) !usize {
12471255}
12481256
12491257/// Uses `mmap` to map the file at `opt_path` (or, if `null`, the self executable image) into memory.
1250fn mapFileOrSelfExe(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
1251 const file = if (opt_path) |path|
1252 try fs.cwd().openFile(path, .{})
1258fn mapDebugInfoFile(opt_path: ?[]const u8) ![]align(std.heap.page_size_min) const u8 {
1259 const open_result = if (opt_path) |path|
1260 fs.cwd().openFile(path, .{})
12531261 else
1254 try fs.openSelfExe(.{});
1262 fs.openSelfExe(.{});
1263 const file = open_result catch |err| switch (err) {
1264 error.FileNotFound => return error.MissingDebugInfo,
1265 else => |e| return e,
1266 };
12551267 defer file.close();
12561268
1257 const file_len = math.cast(usize, try file.getEndPos()) orelse return error.FileTooBig;
1269 const file_len = math.cast(usize, try file.getEndPos()) orelse return error.InvalidDebugInfo;
12581270
12591271 return posix.mmap(
12601272 null,