authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-02 16:14:54+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:49+01:00
log8fdcdb8c69712ebbfbd06e0c18d373eee462fe31
treee3de995453f29a8fe7b1293eec772386758f3eb0
parent84b65860cfa4b7e61cb98347778331d67137d7e8
signaturelock-open Commit is signed but in an unrecognized format.

the world if Dwarf.ElfModule was like REALLY good:


3 files changed, 339 insertions(+), 328 deletions(-)

lib/std/debug/Dwarf.zig+2-321
......@@ -8,11 +8,9 @@
88//! For unopinionated types and bits, see `std.dwarf`.
99
1010const builtin = @import("builtin");
11const native_endian = builtin.cpu.arch.endian();
1211
1312const std = @import("../std.zig");
1413const Allocator = std.mem.Allocator;
15const elf = std.elf;
1614const mem = std.mem;
1715const DW = std.dwarf;
1816const AT = DW.AT;
......@@ -23,7 +21,6 @@ const UT = DW.UT;
2321const assert = std.debug.assert;
2422const cast = std.math.cast;
2523const maxInt = std.math.maxInt;
26const Path = std.Build.Cache.Path;
2724const ArrayList = std.ArrayList;
2825const Endian = std.builtin.Endian;
2926const Reader = std.Io.Reader;
......@@ -34,6 +31,7 @@ pub const expression = @import("Dwarf/expression.zig");
3431pub const abi = @import("Dwarf/abi.zig");
3532pub const call_frame = @import("Dwarf/call_frame.zig");
3633pub const Unwind = @import("Dwarf/Unwind.zig");
34pub const ElfModule = @import("Dwarf/ElfModule.zig");
3735
3836/// Useful to temporarily enable while working on this file.
3937const debug_debug_mode = false;
......@@ -1431,7 +1429,7 @@ pub fn bad() error{InvalidDebugInfo} {
14311429 return error.InvalidDebugInfo;
14321430}
14331431
1434fn invalidDebugInfoDetected() void {
1432pub fn invalidDebugInfoDetected() void {
14351433 if (debug_debug_mode) @panic("bad dwarf");
14361434}
14371435
......@@ -1449,317 +1447,6 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 {
14491447 return str[casted_offset..last :0];
14501448}
14511449
1452// MLUGG TODO: i am dubious of this whole thing being here atp. look closely and see if it depends on being the self process
1453pub const ElfModule = struct {
1454 dwarf: Dwarf,
1455 mapped_memory: []align(std.heap.page_size_min) const u8,
1456 external_mapped_memory: ?[]align(std.heap.page_size_min) const u8,
1457
1458 pub fn deinit(self: *@This(), allocator: Allocator) void {
1459 self.dwarf.deinit(allocator);
1460 std.posix.munmap(self.mapped_memory);
1461 if (self.external_mapped_memory) |m| std.posix.munmap(m);
1462 }
1463
1464 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, endian: Endian, load_offset: usize, address: usize) !std.debug.Symbol {
1465 // Translate the runtime address into a virtual address into the module
1466 // MLUGG TODO: this clearly tells us that the logic should live near SelfInfo...
1467 const vaddr = address - load_offset;
1468 return self.dwarf.getSymbol(allocator, endian, vaddr);
1469 }
1470
1471 pub const LoadError = error{
1472 InvalidDebugInfo,
1473 MissingDebugInfo,
1474 InvalidElfMagic,
1475 InvalidElfVersion,
1476 InvalidElfEndian,
1477 /// TODO: implement this and then remove this error code
1478 UnimplementedDwarfForeignEndian,
1479 /// The debug info may be valid but this implementation uses memory
1480 /// mapping which limits things to usize. If the target debug info is
1481 /// 64-bit and host is 32-bit, there may be debug info that is not
1482 /// supportable using this method.
1483 Overflow,
1484
1485 PermissionDenied,
1486 LockedMemoryLimitExceeded,
1487 MemoryMappingNotSupported,
1488 } || Allocator.Error || std.fs.File.OpenError || OpenError;
1489
1490 /// Reads debug info from an ELF file given its path.
1491 ///
1492 /// If the required sections aren't present but a reference to external debug
1493 /// info is, then this this function will recurse to attempt to load the debug
1494 /// sections from an external file.
1495 pub fn load(
1496 gpa: Allocator,
1497 elf_file_path: Path,
1498 build_id: ?[]const u8,
1499 expected_crc: ?u32,
1500 parent_sections: ?*Dwarf.SectionArray,
1501 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
1502 ) 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
1526 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
1527
1528 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
1529 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
1530 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
1531
1532 const endian: Endian = switch (hdr.e_ident[elf.EI_DATA]) {
1533 elf.ELFDATA2LSB => .little,
1534 elf.ELFDATA2MSB => .big,
1535 else => return error.InvalidElfEndian,
1536 };
1537 if (endian != native_endian) return error.UnimplementedDwarfForeignEndian;
1538
1539 const shoff = hdr.e_shoff;
1540 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
1541 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[cast(usize, str_section_off) orelse return error.Overflow]));
1542 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
1543 const shdrs = @as(
1544 [*]const elf.Shdr,
1545 @ptrCast(@alignCast(&mapped_mem[shoff])),
1546 )[0..hdr.e_shnum];
1547
1548 var sections: Dwarf.SectionArray = @splat(null);
1549
1550 // Combine section list. This takes ownership over any owned sections from the parent scope.
1551 if (parent_sections) |ps| {
1552 for (ps, &sections) |*parent, *section_elem| {
1553 if (parent.*) |*p| {
1554 section_elem.* = p.*;
1555 p.owned = false;
1556 }
1557 }
1558 }
1559 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
1560
1561 var separate_debug_filename: ?[]const u8 = null;
1562 var separate_debug_crc: ?u32 = null;
1563
1564 for (shdrs) |*shdr| {
1565 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
1566 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
1567
1568 if (mem.eql(u8, name, ".gnu_debuglink")) {
1569 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1570 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1571 const crc_offset = mem.alignForward(usize, debug_filename.len + 1, 4);
1572 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
1573 separate_debug_crc = mem.readInt(u32, crc_bytes, endian);
1574 separate_debug_filename = debug_filename;
1575 continue;
1576 }
1577
1578 var section_index: ?usize = null;
1579 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |sect, i| {
1580 if (mem.eql(u8, "." ++ sect.name, name)) section_index = i;
1581 }
1582 if (section_index == null) continue;
1583 if (sections[section_index.?] != null) continue;
1584
1585 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1586 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1587 var section_reader: Reader = .fixed(section_bytes);
1588 const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;
1589 if (chdr.ch_type != .ZLIB) continue;
1590
1591 var decompress: std.compress.flate.Decompress = .init(&section_reader, .zlib, &.{});
1592 var decompressed_section: ArrayList(u8) = .empty;
1593 defer decompressed_section.deinit(gpa);
1594 decompress.reader.appendRemainingUnlimited(gpa, &decompressed_section) catch {
1595 invalidDebugInfoDetected();
1596 continue;
1597 };
1598 if (chdr.ch_size != decompressed_section.items.len) {
1599 invalidDebugInfoDetected();
1600 continue;
1601 }
1602 break :blk .{
1603 .data = try decompressed_section.toOwnedSlice(gpa),
1604 .virtual_address = shdr.sh_addr,
1605 .owned = true,
1606 };
1607 } else .{
1608 .data = section_bytes,
1609 .virtual_address = shdr.sh_addr,
1610 .owned = false,
1611 };
1612 }
1613
1614 const missing_debug_info =
1615 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
1616 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
1617 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
1618 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
1619
1620 // Attempt to load debug info from an external file
1621 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1622 if (missing_debug_info) {
1623 // Only allow one level of debug info nesting
1624 if (parent_mapped_mem) |_| {
1625 return error.MissingDebugInfo;
1626 }
1627
1628 // $XDG_CACHE_HOME/debuginfod_client/<buildid>/debuginfo
1629 // This only opportunisticly tries to load from the debuginfod cache, but doesn't try to populate it.
1630 // One can manually run `debuginfod-find debuginfo PATH` to download the symbols
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 });
1644 }
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;
1650 };
1651 defer gpa.free(id_dir_path);
1652 if (!std.fs.path.isAbsolute(id_dir_path)) break :debuginfod;
1653
1654 var id_dir = std.fs.openDirAbsolute(id_dir_path, .{}) catch break :debuginfod;
1655 defer id_dir.close();
1656
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;
1661 }
1662
1663 const global_debug_directories = [_][]const u8{
1664 "/usr/lib/debug",
1665 };
1666
1667 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
1668 if (build_id) |id| blk: {
1669 if (id.len < 3) break :blk;
1670
1671 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
1672 const extension = ".debug";
1673 var id_prefix_buf: [2]u8 = undefined;
1674 var filename_buf: [38 + extension.len]u8 = undefined;
1675
1676 _ = std.fmt.bufPrint(&id_prefix_buf, "{x}", .{id[0..1]}) catch unreachable;
1677 const filename = std.fmt.bufPrint(&filename_buf, "{x}" ++ extension, .{id[1..]}) catch break :blk;
1678
1679 for (global_debug_directories) |global_directory| {
1680 const path: Path = .{
1681 .root_dir = .cwd(),
1682 .sub_path = try std.fs.path.join(gpa, &.{
1683 global_directory, ".build-id", &id_prefix_buf, filename,
1684 }),
1685 };
1686 defer gpa.free(path.sub_path);
1687
1688 return load(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1689 }
1690 }
1691
1692 // use the path from .gnu_debuglink, in the same search order as gdb
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))
1696 return error.MissingDebugInfo;
1697
1698 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);
1704 var exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch break :exe_dir;
1705 defer exe_dir.close();
1706
1707 // <exe_dir>/<gnu_debuglink>
1708 if (load(
1709 gpa,
1710 .{
1711 .root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
1712 .sub_path = separate_filename,
1713 },
1714 null,
1715 separate_debug_crc,
1716 &sections,
1717 mapped_mem,
1718 )) |em| {
1719 return em;
1720 } else |_| {}
1721
1722 // <exe_dir>/.debug/<gnu_debuglink>
1723 const path: Path = .{
1724 .root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
1725 .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }),
1726 };
1727 defer gpa.free(path.sub_path);
1728
1729 if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
1730 return em;
1731 } else |_| {}
1732 }
1733
1734 var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
1735 const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :separate;
1736
1737 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
1738 for (global_debug_directories) |global_directory| {
1739 const path: Path = .{
1740 .root_dir = .cwd(),
1741 .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }),
1742 };
1743 defer gpa.free(path.sub_path);
1744 if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
1745 return em;
1746 } else |_| {}
1747 }
1748 }
1749
1750 return error.MissingDebugInfo;
1751 }
1752
1753 var dwarf: Dwarf = .{ .sections = sections };
1754 try dwarf.open(gpa, endian);
1755 return .{
1756 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1757 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
1758 .dwarf = dwarf,
1759 };
1760 }
1761};
1762
17631450pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64) !std.debug.Symbol {
17641451 const compile_unit = di.findCompileUnit(endian, address) catch |err| switch (err) {
17651452 error.MissingDebugInfo, error.InvalidDebugInfo => return .{ .name = null, .compile_unit_name = null, .source_location = null },
......@@ -1777,12 +1464,6 @@ pub fn getSymbol(di: *Dwarf, allocator: Allocator, endian: Endian, address: u64)
17771464 };
17781465}
17791466
1780pub fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
1781 const start = cast(usize, offset) orelse return error.Overflow;
1782 const end = start + (cast(usize, size) orelse return error.Overflow);
1783 return ptr[start..end];
1784}
1785
17861467fn readAddress(r: *Reader, format: std.dwarf.Format, endian: Endian) !u64 {
17871468 // MLUGG TODO FIX BEFORE MERGE: this function is slightly bogus. addresses have a byte width which is independent of the `dwarf.Format`!
17881469 return switch (format) {
lib/std/debug/Dwarf/ElfModule.zig created+328
......@@ -0,0 +1,328 @@
1//! A thin wrapper around `Dwarf` which handles loading debug information from an ELF file. Load the
2//! info with `load`, then directly access the `dwarf` field before finally `deinit`ing.
3
4dwarf: Dwarf,
5
6/// The memory-mapped ELF file, which is referenced by `dwarf`. This field is here only so that
7/// this memory can be unmapped by `ElfModule.deinit`.
8mapped_file: []align(std.heap.page_size_min) const u8,
9/// Sometimes, debug info is stored separately to the main ELF file. In that case, `mapped_file`
10/// is the mapped ELF binary, and `mapped_debug_file` is the mapped debug info file. Both must
11/// be unmapped by `ElfModule.deinit`.
12mapped_debug_file: ?[]align(std.heap.page_size_min) const u8,
13
14pub fn deinit(em: *ElfModule, allocator: Allocator) void {
15 em.dwarf.deinit(allocator);
16 std.posix.munmap(em.mapped_file);
17 if (em.mapped_debug_file) |m| std.posix.munmap(m);
18}
19
20pub const LoadError = error{
21 InvalidDebugInfo,
22 MissingDebugInfo,
23 InvalidElfMagic,
24 InvalidElfVersion,
25 InvalidElfEndian,
26 /// TODO: implement this and then remove this error code
27 UnimplementedDwarfForeignEndian,
28 /// The debug info may be valid but this implementation uses memory
29 /// mapping which limits things to usize. If the target debug info is
30 /// 64-bit and host is 32-bit, there may be debug info that is not
31 /// supportable using this method.
32 Overflow,
33
34 PermissionDenied,
35 LockedMemoryLimitExceeded,
36 MemoryMappingNotSupported,
37} || Allocator.Error || std.fs.File.OpenError || Dwarf.OpenError;
38
39/// Reads debug info from an ELF file given its path.
40///
41/// If the required sections aren't present but a reference to external debug
42/// info is, then this this function will recurse to attempt to load the debug
43/// sections from an external file.
44pub fn load(
45 gpa: Allocator,
46 elf_file_path: Path,
47 build_id: ?[]const u8,
48 expected_crc: ?u32,
49 parent_sections: ?*Dwarf.SectionArray,
50 parent_mapped_mem: ?[]align(std.heap.page_size_min) const u8,
51) LoadError!ElfModule {
52 const mapped_mem: []align(std.heap.page_size_min) const u8 = mapped: {
53 const elf_file = try elf_file_path.root_dir.handle.openFile(elf_file_path.sub_path, .{});
54 defer elf_file.close();
55
56 const file_len = std.math.cast(
57 usize,
58 elf_file.getEndPos() catch return Dwarf.bad(),
59 ) orelse return error.Overflow;
60
61 break :mapped std.posix.mmap(
62 null,
63 file_len,
64 std.posix.PROT.READ,
65 .{ .TYPE = .SHARED },
66 elf_file.handle,
67 0,
68 ) catch |err| switch (err) {
69 error.MappingAlreadyExists => unreachable,
70 else => |e| return e,
71 };
72 };
73 errdefer std.posix.munmap(mapped_mem);
74
75 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
76
77 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
78 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
79 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
80
81 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
82 elf.ELFDATA2LSB => .little,
83 elf.ELFDATA2MSB => .big,
84 else => return error.InvalidElfEndian,
85 };
86 if (endian != native_endian) return error.UnimplementedDwarfForeignEndian;
87
88 const shoff = hdr.e_shoff;
89 const str_section_off = std.math.cast(
90 usize,
91 shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx),
92 ) orelse return error.Overflow;
93 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(mapped_mem[str_section_off..]));
94 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
95 const shdrs = @as(
96 [*]const elf.Shdr,
97 @ptrCast(@alignCast(&mapped_mem[shoff])),
98 )[0..hdr.e_shnum];
99
100 var sections: Dwarf.SectionArray = @splat(null);
101
102 // Combine section list. This takes ownership over any owned sections from the parent scope.
103 if (parent_sections) |ps| {
104 for (ps, &sections) |*parent, *section_elem| {
105 if (parent.*) |*p| {
106 section_elem.* = p.*;
107 p.owned = false;
108 }
109 }
110 }
111 errdefer for (sections) |opt_section| if (opt_section) |s| if (s.owned) gpa.free(s.data);
112
113 var separate_debug_filename: ?[]const u8 = null;
114 var separate_debug_crc: ?u32 = null;
115
116 for (shdrs) |*shdr| {
117 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
118 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
119
120 if (mem.eql(u8, name, ".gnu_debuglink")) {
121 if (mapped_mem.len < shdr.sh_offset + shdr.sh_size) return error.InvalidDebugInfo;
122 const gnu_debuglink = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)];
123 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
124 const crc_offset = mem.alignForward(usize, debug_filename.len + 1, 4);
125 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
126 separate_debug_crc = mem.readInt(u32, crc_bytes, endian);
127 separate_debug_filename = debug_filename;
128 continue;
129 }
130
131 var section_index: ?usize = null;
132 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |sect, i| {
133 if (mem.eql(u8, "." ++ sect.name, name)) section_index = i;
134 }
135 if (section_index == null) continue;
136 if (sections[section_index.?] != null) continue;
137
138 if (mapped_mem.len < shdr.sh_offset + shdr.sh_size) return error.InvalidDebugInfo;
139 const section_bytes = mapped_mem[@intCast(shdr.sh_offset)..][0..@intCast(shdr.sh_size)];
140 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
141 var section_reader: Reader = .fixed(section_bytes);
142 const chdr = section_reader.takeStruct(elf.Chdr, endian) catch continue;
143 if (chdr.ch_type != .ZLIB) continue;
144
145 var decompress: std.compress.flate.Decompress = .init(&section_reader, .zlib, &.{});
146 var decompressed_section: ArrayList(u8) = .empty;
147 defer decompressed_section.deinit(gpa);
148 decompress.reader.appendRemainingUnlimited(gpa, &decompressed_section) catch {
149 Dwarf.invalidDebugInfoDetected();
150 continue;
151 };
152 if (chdr.ch_size != decompressed_section.items.len) {
153 Dwarf.invalidDebugInfoDetected();
154 continue;
155 }
156 break :blk .{
157 .data = try decompressed_section.toOwnedSlice(gpa),
158 .virtual_address = shdr.sh_addr,
159 .owned = true,
160 };
161 } else .{
162 .data = section_bytes,
163 .virtual_address = shdr.sh_addr,
164 .owned = false,
165 };
166 }
167
168 const missing_debug_info =
169 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
170 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
171 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
172 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
173
174 // Attempt to load debug info from an external file
175 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
176 if (missing_debug_info) {
177 // Only allow one level of debug info nesting
178 if (parent_mapped_mem) |_| {
179 return error.MissingDebugInfo;
180 }
181
182 // $XDG_CACHE_HOME/debuginfod_client/<buildid>/debuginfo
183 // This only opportunisticly tries to load from the debuginfod cache, but doesn't try to populate it.
184 // One can manually run `debuginfod-find debuginfo PATH` to download the symbols
185 debuginfod: {
186 const id = build_id orelse break :debuginfod;
187 switch (builtin.os.tag) {
188 .wasi, .windows => break :debuginfod,
189 else => {},
190 }
191 const id_dir_path: []u8 = p: {
192 if (std.posix.getenv("DEBUGINFOD_CACHE_PATH")) |path| {
193 break :p try std.fmt.allocPrint(gpa, "{s}/{x}", .{ path, id });
194 }
195 if (std.posix.getenv("XDG_CACHE_HOME")) |cache_path| {
196 if (cache_path.len > 0) {
197 break :p try std.fmt.allocPrint(gpa, "{s}/debuginfod_client/{x}", .{ cache_path, id });
198 }
199 }
200 if (std.posix.getenv("HOME")) |home_path| {
201 break :p try std.fmt.allocPrint(gpa, "{s}/.cache/debuginfod_client/{x}", .{ home_path, id });
202 }
203 break :debuginfod;
204 };
205 defer gpa.free(id_dir_path);
206 if (!std.fs.path.isAbsolute(id_dir_path)) break :debuginfod;
207
208 var id_dir = std.fs.openDirAbsolute(id_dir_path, .{}) catch break :debuginfod;
209 defer id_dir.close();
210
211 return load(gpa, .{
212 .root_dir = .{ .path = id_dir_path, .handle = id_dir },
213 .sub_path = "debuginfo",
214 }, null, separate_debug_crc, &sections, mapped_mem) catch break :debuginfod;
215 }
216
217 const global_debug_directories = [_][]const u8{
218 "/usr/lib/debug",
219 };
220
221 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
222 if (build_id) |id| blk: {
223 if (id.len < 3) break :blk;
224
225 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
226 const extension = ".debug";
227 var id_prefix_buf: [2]u8 = undefined;
228 var filename_buf: [38 + extension.len]u8 = undefined;
229
230 _ = std.fmt.bufPrint(&id_prefix_buf, "{x}", .{id[0..1]}) catch unreachable;
231 const filename = std.fmt.bufPrint(&filename_buf, "{x}" ++ extension, .{id[1..]}) catch break :blk;
232
233 for (global_debug_directories) |global_directory| {
234 const path: Path = .{
235 .root_dir = .cwd(),
236 .sub_path = try std.fs.path.join(gpa, &.{
237 global_directory, ".build-id", &id_prefix_buf, filename,
238 }),
239 };
240 defer gpa.free(path.sub_path);
241
242 return load(gpa, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
243 }
244 }
245
246 // use the path from .gnu_debuglink, in the same search order as gdb
247 separate: {
248 const separate_filename = separate_debug_filename orelse break :separate;
249 if (mem.eql(u8, std.fs.path.basename(elf_file_path.sub_path), separate_filename))
250 return error.MissingDebugInfo;
251
252 exe_dir: {
253 const exe_dir_path = try std.fs.path.resolve(gpa, &.{
254 elf_file_path.root_dir.path orelse ".",
255 std.fs.path.dirname(elf_file_path.sub_path) orelse ".",
256 });
257 defer gpa.free(exe_dir_path);
258 var exe_dir = std.fs.openDirAbsolute(exe_dir_path, .{}) catch break :exe_dir;
259 defer exe_dir.close();
260
261 // <exe_dir>/<gnu_debuglink>
262 if (load(
263 gpa,
264 .{
265 .root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
266 .sub_path = separate_filename,
267 },
268 null,
269 separate_debug_crc,
270 &sections,
271 mapped_mem,
272 )) |em| {
273 return em;
274 } else |_| {}
275
276 // <exe_dir>/.debug/<gnu_debuglink>
277 const path: Path = .{
278 .root_dir = .{ .path = exe_dir_path, .handle = exe_dir },
279 .sub_path = try std.fs.path.join(gpa, &.{ ".debug", separate_filename }),
280 };
281 defer gpa.free(path.sub_path);
282
283 if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
284 return em;
285 } else |_| {}
286 }
287
288 var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
289 const cwd_path = std.posix.realpath(".", &cwd_buf) catch break :separate;
290
291 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
292 for (global_debug_directories) |global_directory| {
293 const path: Path = .{
294 .root_dir = .cwd(),
295 .sub_path = try std.fs.path.join(gpa, &.{ global_directory, cwd_path, separate_filename }),
296 };
297 defer gpa.free(path.sub_path);
298 if (load(gpa, path, null, separate_debug_crc, &sections, mapped_mem)) |em| {
299 return em;
300 } else |_| {}
301 }
302 }
303
304 return error.MissingDebugInfo;
305 }
306
307 var dwarf: Dwarf = .{ .sections = sections };
308 try dwarf.open(gpa, endian);
309 return .{
310 .mapped_file = parent_mapped_mem orelse mapped_mem,
311 .mapped_debug_file = if (parent_mapped_mem != null) mapped_mem else null,
312 .dwarf = dwarf,
313 };
314}
315
316const std = @import("../../std.zig");
317const Allocator = std.mem.Allocator;
318const ArrayList = std.ArrayList;
319const Dwarf = std.debug.Dwarf;
320const Path = std.Build.Cache.Path;
321const Reader = std.Io.Reader;
322const mem = std.mem;
323const elf = std.elf;
324
325const builtin = @import("builtin");
326const native_endian = builtin.cpu.arch.endian();
327
328const ElfModule = @This();
lib/std/debug/SelfInfo.zig+9-7
......@@ -456,7 +456,8 @@ const Module = switch (native_os) {
456456 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
457457 } else continue;
458458
459 const section_bytes = try Dwarf.chopSlice(mapped_mem, sect.offset, sect.size);
459 if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo;
460 const section_bytes = mapped_mem[sect.offset..][0..sect.size];
460461 sections[section_index] = .{
461462 .data = section_bytes,
462463 .virtual_address = @intCast(sect.addr),
......@@ -508,10 +509,10 @@ const Module = switch (native_os) {
508509 gnu_eh_frame: ?[]const u8,
509510 const LookupCache = void;
510511 const DebugInfo = struct {
511 em: ?Dwarf.ElfModule, // MLUGG TODO: bad field name (and, frankly, type)
512 loaded_elf: ?Dwarf.ElfModule, // MLUGG TODO: bad field name
512513 unwind: ?Dwarf.Unwind,
513514 const init: DebugInfo = .{
514 .em = null,
515 .loaded_elf = null,
515516 .unwind = null,
516517 };
517518 };
......@@ -591,7 +592,7 @@ const Module = switch (native_os) {
591592 }
592593 fn loadLocationInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
593594 if (module.name.len > 0) {
594 di.em = Dwarf.ElfModule.load(gpa, .{
595 di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
595596 .root_dir = .cwd(),
596597 .sub_path = module.name,
597598 }, module.build_id, null, null, null) catch |err| switch (err) {
......@@ -602,7 +603,7 @@ const Module = switch (native_os) {
602603 } else {
603604 const path = try std.fs.selfExePathAlloc(gpa);
604605 defer gpa.free(path);
605 di.em = Dwarf.ElfModule.load(gpa, .{
606 di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
606607 .root_dir = .cwd(),
607608 .sub_path = path,
608609 }, module.build_id, null, null, null) catch |err| switch (err) {
......@@ -613,8 +614,9 @@ const Module = switch (native_os) {
613614 }
614615 }
615616 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
616 if (di.em == null) try module.loadLocationInfo(gpa, di);
617 return di.em.?.getSymbolAtAddress(gpa, native_endian, module.load_offset, address);
617 if (di.loaded_elf == null) try module.loadLocationInfo(gpa, di);
618 const vaddr = address - module.load_offset;
619 return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr);
618620 }
619621 fn loadUnwindInfo(module: *const Module, gpa: Allocator, di: *Module.DebugInfo) !void {
620622 const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file