Commit 84b65860cf

mlugg <mlugg@mlugg.co.uk>
2025-09-02 16:54:36
the world if ElfModule didn't suck:
1 parent 55a7aff
Changed files (5)
lib/std/debug/Dwarf/Unwind.zig
@@ -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) {
lib/std/debug/Dwarf.zig
@@ -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;
-                        }
-                        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;
+            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 });
                         }
-                        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();
-
-                const filename = std.fmt.allocPrint(gpa, "{x}/debuginfo", .{id}) catch break :blk;
-                defer gpa.free(filename);
+                defer gpa.free(id_dir_path);
+                if (!std.fs.path.isAbsolute(id_dir_path)) break :debuginfod;
 
-                const path: Path = .{
-                    .root_dir = .{ .path = null, .handle = debuginfod_dir },
-                    .sub_path = filename,
-                };
+                var id_dir = std.fs.openDirAbsolute(id_dir_path, .{}) catch break :debuginfod;
+                defer id_dir.close();
 
-                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 {
lib/std/debug/Info.zig
@@ -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 = .{},
lib/std/debug/SelfInfo.zig
@@ -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,
lib/std/debug.zig
@@ -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,