Commit c895aa7a35

mlugg <mlugg@mlugg.co.uk>
2025-09-03 16:42:33
std.debug.SelfInfo: concrete error sets
The downside of this commit is that more precise errors are no longer propagated up. However, these errors were pretty useless in isolation due to them having no context; and regardless, we intentionally swallow most of them in `std.debug` anyway. Therefore, this is better in practice, because it allows `std.debug` to give slightly more useful warnings when handling errors. This commit does that for unwind errors, for instance, which differentiate between the unwind info being corrupt vs missing vs inaccessible vs unsupported. A better solution would be to also include more detailed information via the diagnostics pattern, but this commit is an incremental improvement.
1 parent dd9cb1b
lib/std/debug/SelfInfo/DarwinModule.zig
@@ -7,7 +7,9 @@ pub fn key(m: *const DarwinModule) usize {
     return m.text_base;
 }
 
-pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !DarwinModule {
+/// No cache needed, because `_dyld_get_image_header` etc are already fast.
+pub const LookupCache = void;
+pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!DarwinModule {
     _ = cache;
     _ = gpa;
     const image_count = std.c._dyld_image_count();
@@ -186,8 +188,11 @@ fn loadFullInfo(module: *const DarwinModule, gpa: Allocator) !DebugInfo.Full {
         .ofiles = .empty,
     };
 }
-pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
-    if (di.full == null) di.full = try module.loadFullInfo(gpa);
+pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
+    if (di.full == null) di.full = module.loadFullInfo(gpa) catch |err| switch (err) {
+        error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| return e,
+        else => return error.ReadFailed,
+    };
     const full = &di.full.?;
 
     const vaddr = address - module.load_offset;
@@ -215,14 +220,9 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
         const gop = try full.ofiles.getOrPut(gpa, symbol.ofile);
         if (!gop.found_existing) {
             const o_file_path = mem.sliceTo(full.strings[symbol.ofile..], 0);
-            gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch |err| {
-                defer _ = full.ofiles.pop().?;
-                switch (err) {
-                    error.MissingDebugInfo,
-                    error.InvalidDebugInfo,
-                    => return sym_only_result,
-                    else => |e| return e,
-                }
+            gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch {
+                _ = full.ofiles.pop().?;
+                return sym_only_result;
             };
         }
         break :of gop.value_ptr;
@@ -234,10 +234,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
     ) orelse return sym_only_result;
     const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value;
 
-    const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch |err| switch (err) {
-        error.MissingDebugInfo, error.InvalidDebugInfo => return sym_only_result,
-        else => |e| return e,
-    };
+    const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result;
 
     return .{
         .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr) orelse stab_symbol,
@@ -255,28 +252,44 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
             native_endian,
             compile_unit,
             symbol_ofile_vaddr + address_symbol_offset,
-        ) catch |err| switch (err) {
-            error.MissingDebugInfo, error.InvalidDebugInfo => null,
-            else => return err,
-        },
+        ) catch null,
     };
 }
 /// Unwind a frame using MachO compact unwind info (from __unwind_info).
 /// If the compact encoding can't encode a way to unwind a frame, it will
 /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
-pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
+pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
+    return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {
+        error.InvalidDebugInfo,
+        error.MissingDebugInfo,
+        error.UnsupportedDebugInfo,
+        error.ReadFailed,
+        error.OutOfMemory,
+        error.Unexpected,
+        => |e| return e,
+        error.UnimplementedArch,
+        error.UnimplementedOs,
+        error.ThreadContextNotSupported,
+        => return error.UnsupportedDebugInfo,
+        error.InvalidRegister,
+        error.RegisterContextRequired,
+        error.IncompatibleRegisterSize,
+        => return error.InvalidDebugInfo,
+    };
+}
+fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
     _ = gpa;
     if (di.unwind == null) di.unwind = module.loadUnwindInfo();
     const unwind = &di.unwind.?;
 
-    const unwind_info = unwind.unwind_info orelse return error.MissingUnwindInfo;
-    if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidUnwindInfo;
+    const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;
+    if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;
     const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
 
     const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
-    if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidUnwindInfo;
+    if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo;
     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.MissingDebugInfo;
 
     // offset of the PC into the `__TEXT` segment
     const pc_text_offset = context.pc - module.text_base;
@@ -296,15 +309,15 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
         break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
     };
     // An offset of 0 is a sentinel indicating a range does not have unwind info.
-    if (start_offset == 0) return error.MissingUnwindInfo;
+    if (start_offset == 0) return error.MissingDebugInfo;
 
     const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
-    if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidUnwindInfo;
+    if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo;
     const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
         unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
     );
 
-    if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidUnwindInfo;
+    if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo;
     const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
 
     const entry: struct {
@@ -312,15 +325,15 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
         raw_encoding: u32,
     } = switch (kind.*) {
         .REGULAR => entry: {
-            if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidUnwindInfo;
+            if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo;
             const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
 
             const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
-            if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
+            if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
             const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
                 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
             );
-            if (entries.len == 0) return error.InvalidUnwindInfo;
+            if (entries.len == 0) return error.InvalidDebugInfo;
 
             var left: usize = 0;
             var len: usize = entries.len;
@@ -339,15 +352,15 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
             };
         },
         .COMPRESSED => entry: {
-            if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidUnwindInfo;
+            if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo;
             const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
 
             const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
-            if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidUnwindInfo;
+            if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
             const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
                 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
             );
-            if (entries.len == 0) return error.InvalidUnwindInfo;
+            if (entries.len == 0) return error.InvalidDebugInfo;
 
             var left: usize = 0;
             var len: usize = entries.len;
@@ -372,26 +385,26 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
 
             const local_index = entry.encodingIndex - common_encodings.len;
             const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
-            if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidUnwindInfo;
+            if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo;
             const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
                 unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
             );
-            if (local_index >= local_encodings.len) return error.InvalidUnwindInfo;
+            if (local_index >= local_encodings.len) return error.InvalidDebugInfo;
             break :entry .{
                 .function_offset = function_offset,
                 .raw_encoding = local_encodings[local_index],
             };
         },
-        else => return error.InvalidUnwindInfo,
+        else => return error.InvalidDebugInfo,
     };
 
-    if (entry.raw_encoding == 0) return error.NoUnwindInfo;
+    if (entry.raw_encoding == 0) return error.MissingDebugInfo;
     const reg_context: Dwarf.abi.RegisterContext = .{ .eh_frame = false, .is_macho = true };
 
     const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
     const new_ip = switch (builtin.cpu.arch) {
         .x86_64 => switch (encoding.mode.x86_64) {
-            .OLD => return error.UnimplementedUnwindEncoding,
+            .OLD => return error.UnsupportedDebugInfo,
             .RBP_FRAME => ip: {
                 const frame = encoding.value.x86_64.frame;
 
@@ -493,7 +506,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
                 break :ip new_ip;
             },
             .DWARF => {
-                const eh_frame = unwind.eh_frame orelse return error.MissingEhFrame;
+                const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;
                 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - module.load_offset;
                 return context.unwindFrameDwarf(
                     &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
@@ -503,7 +516,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
             },
         },
         .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
-            .OLD => return error.UnimplementedUnwindEncoding,
+            .OLD => return error.UnsupportedDebugInfo,
             .FRAMELESS => ip: {
                 const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*;
                 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
@@ -512,7 +525,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
                 break :ip new_ip;
             },
             .DWARF => {
-                const eh_frame = unwind.eh_frame orelse return error.MissingEhFrame;
+                const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;
                 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - module.load_offset;
                 return context.unwindFrameDwarf(
                     &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
@@ -568,8 +581,6 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
     if (context.pc > 0) context.pc -= 1;
     return new_ip;
 }
-/// No cache needed, because `_dyld_get_image_header` etc are already fast.
-pub const LookupCache = void;
 pub const DebugInfo = struct {
     unwind: ?Unwind,
     // MLUGG TODO: awful field name
@@ -785,7 +796,7 @@ const ip_reg_num = Dwarf.abi.ipRegNum(builtin.target.cpu.arch).?;
 fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
     const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
         error.FileNotFound => return error.MissingDebugInfo,
-        else => |e| return e,
+        else => return error.ReadFailed,
     };
     defer file.close();
 
@@ -812,6 +823,7 @@ const mem = std.mem;
 const posix = std.posix;
 const testing = std.testing;
 const UnwindContext = std.debug.SelfInfo.UnwindContext;
+const Error = std.debug.SelfInfo.Error;
 const regBytes = Dwarf.abi.regBytes;
 const regValueNative = Dwarf.abi.regValueNative;
 
lib/std/debug/SelfInfo/ElfModule.zig
@@ -21,7 +21,7 @@ pub const DebugInfo = struct {
 pub fn key(m: ElfModule) usize {
     return m.load_offset;
 }
-pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !ElfModule {
+pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModule {
     _ = cache;
     _ = gpa;
     if (builtin.target.os.tag == .haiku) @panic("TODO implement lookup module for Haiku");
@@ -92,42 +92,79 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !ElfModule {
     };
     return error.MissingDebugInfo;
 }
-fn loadDwarf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
-    if (module.name.len > 0) {
-        di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
+fn loadDwarf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
+    const load_result = if (module.name.len > 0) res: {
+        break :res 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,
+        }, module.build_id, null, null, null);
+    } else res: {
+        const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) {
+            error.OutOfMemory => |e| return e,
+            else => return error.ReadFailed,
         };
-    } else {
-        const path = try std.fs.selfExePathAlloc(gpa);
         defer gpa.free(path);
-        di.loaded_elf = Dwarf.ElfModule.load(gpa, .{
+        break :res 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,
-        };
-    }
+        }, module.build_id, null, null, null);
+    };
+    di.loaded_elf = load_result catch |err| switch (err) {
+        error.FileNotFound => return error.MissingDebugInfo,
+
+        error.OutOfMemory,
+        error.InvalidDebugInfo,
+        error.MissingDebugInfo,
+        error.Unexpected,
+        => |e| return e,
+
+        error.InvalidElfEndian,
+        error.InvalidElfMagic,
+        error.InvalidElfVersion,
+        error.InvalidUtf8,
+        error.InvalidWtf8,
+        error.EndOfStream,
+        error.Overflow,
+        error.UnimplementedDwarfForeignEndian, // this should be impossible as we're looking at the debug info for this process
+        => return error.InvalidDebugInfo,
+
+        else => return error.ReadFailed,
+    };
 }
-pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
+pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
     if (di.loaded_elf == null) try module.loadDwarf(gpa, di);
     const vaddr = address - module.load_offset;
-    return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr);
+    return di.loaded_elf.?.dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {
+        error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
+        error.ReadFailed,
+        error.EndOfStream,
+        error.Overflow,
+        error.StreamTooLong,
+        => return error.InvalidDebugInfo,
+    };
 }
-fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) !void {
-    const section_bytes = module.gnu_eh_frame orelse return error.MissingUnwindInfo; // MLUGG TODO: load from file
+fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
+    const section_bytes = module.gnu_eh_frame orelse return error.MissingDebugInfo; // 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);
-    di.unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
-    try di.unwind.?.prepareLookup(gpa, @sizeOf(usize), native_endian);
+    const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) {
+        error.ReadFailed => unreachable, // it's all fixed buffers
+        error.InvalidDebugInfo => |e| return e,
+        error.EndOfStream, error.Overflow => return error.InvalidDebugInfo,
+        error.UnsupportedAddrSize => return error.UnsupportedDebugInfo,
+    };
+
+    var unwind: Dwarf.Unwind = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(module.load_offset + header.eh_frame_vaddr));
+    unwind.prepareLookup(gpa, @sizeOf(usize), native_endian) catch |err| switch (err) {
+        error.ReadFailed => unreachable, // it's all fixed buffers
+        error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
+        error.EndOfStream, error.Overflow, error.StreamTooLong => return error.InvalidDebugInfo,
+        error.UnsupportedAddrSize, error.UnsupportedDwarfVersion => return error.UnsupportedDebugInfo,
+    };
+
+    di.unwind = unwind;
 }
-pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
+pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
     if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
     return context.unwindFrameDwarf(&di.unwind.?, module.load_offset, null);
 }
@@ -140,6 +177,7 @@ const Dwarf = std.debug.Dwarf;
 const elf = std.elf;
 const mem = std.mem;
 const UnwindContext = std.debug.SelfInfo.UnwindContext;
+const Error = std.debug.SelfInfo.Error;
 
 const builtin = @import("builtin");
 const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo/WindowsModule.zig
@@ -5,7 +5,7 @@ handle: windows.HMODULE,
 pub fn key(m: WindowsModule) usize {
     return m.base_address;
 }
-pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !WindowsModule {
+pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) std.debug.SelfInfo.Error!WindowsModule {
     if (lookupInCache(cache, address)) |m| return m;
     {
         // Check a new module hasn't been loaded
@@ -29,18 +29,23 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !WindowsModul
     if (lookupInCache(cache, address)) |m| return m;
     return error.MissingDebugInfo;
 }
-pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
-    if (!di.loaded) try module.loadLocationInfo(gpa, di);
+pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) std.debug.SelfInfo.Error!std.debug.Symbol {
+    if (!di.loaded) module.loadDebugInfo(gpa, di) catch |err| switch (err) {
+        error.OutOfMemory, error.InvalidDebugInfo, error.MissingDebugInfo, error.Unexpected => |e| return e,
+        error.FileNotFound => return error.MissingDebugInfo,
+        error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
+        else => return error.ReadFailed,
+    };
     // Translate the runtime address into a virtual address into the module
     const vaddr = address - module.base_address;
 
     if (di.pdb != null) {
-        if (try di.getSymbolFromPdb(vaddr)) |symbol| return symbol;
+        if (di.getSymbolFromPdb(vaddr) catch return error.InvalidDebugInfo) |symbol| return symbol;
     }
 
     if (di.dwarf) |*dwarf| {
         const dwarf_address = vaddr + di.coff_image_base;
-        return dwarf.getSymbol(gpa, native_endian, dwarf_address);
+        return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch return error.InvalidDebugInfo;
     }
 
     return error.MissingDebugInfo;
@@ -59,7 +64,7 @@ fn lookupInCache(cache: *const LookupCache, address: usize) ?WindowsModule {
     }
     return null;
 }
-fn loadLocationInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !void {
+fn loadDebugInfo(module: *const WindowsModule, 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;
@@ -151,7 +156,7 @@ fn loadLocationInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo
 
         di.pdb = Pdb.init(gpa, path) catch |err| switch (err) {
             error.FileNotFound, error.IsDir => break :pdb,
-            else => return err,
+            else => |e| return e,
         };
         try di.pdb.?.parseInfoStream();
         try di.pdb.?.parseDbiStream();
lib/std/debug/Dwarf.zig
@@ -1418,7 +1418,7 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 {
         4 => 14, // R14
         5 => 15, // R15
         6 => 6, // RBP
-        else => error.InvalidUnwindRegisterNumber,
+        else => error.InvalidRegister,
     };
 }
 
lib/std/debug/SelfInfo.zig
@@ -1,8 +1,6 @@
 //! Cross-platform abstraction for this binary's own debug information, with a
 //! goal of minimal code bloat and compilation speed penalty.
 
-// MLUGG TODO: audit use of errors in this file. ideally, introduce some concrete error sets
-
 const builtin = @import("builtin");
 const native_os = builtin.os.tag;
 const native_endian = native_arch.endian();
@@ -21,6 +19,19 @@ const SelfInfo = @This();
 modules: std.AutoArrayHashMapUnmanaged(usize, Module.DebugInfo),
 lookup_cache: Module.LookupCache,
 
+pub const Error = error{
+    /// The required debug info is invalid or corrupted.
+    InvalidDebugInfo,
+    /// The required debug info could not be found.
+    MissingDebugInfo,
+    /// The required debug info was found, and may be valid, but is not supported by this implementation.
+    UnsupportedDebugInfo,
+    /// The required debug info could not be read from disk due to some IO error.
+    ReadFailed,
+    OutOfMemory,
+    Unexpected,
+};
+
 /// Indicates whether the `SelfInfo` implementation has support for this target.
 pub const target_supported: bool = switch (native_os) {
     .linux,
@@ -82,7 +93,7 @@ test {
     _ = &deinit;
 }
 
-pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
+pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
     comptime assert(supports_unwinding);
     const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
     const gop = try self.modules.getOrPut(gpa, module.key());
@@ -92,7 +103,7 @@ pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) !us
     return module.unwindFrame(gpa, gop.value_ptr, context);
 }
 
-pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.debug.Symbol {
+pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
     comptime assert(target_supported);
     const module: Module = try .lookup(&self.lookup_cache, gpa, address);
     const gop = try self.modules.getOrPut(gpa, module.key());
@@ -102,7 +113,7 @@ pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) !std.
     return module.getSymbolAtAddress(gpa, gop.value_ptr, address);
 }
 
-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![]const u8 {
     comptime assert(target_supported);
     const module: Module = try .lookup(&self.lookup_cache, gpa, address);
     return module.name;
@@ -271,12 +282,61 @@ pub const UnwindContext = struct {
     /// may require lazily loading the data in those sections.
     ///
     /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
-    /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section.
     pub fn unwindFrameDwarf(
         context: *UnwindContext,
         unwind: *const Dwarf.Unwind,
         load_offset: usize,
         explicit_fde_offset: ?usize,
+    ) Error!usize {
+        return unwindFrameDwarfInner(context, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {
+            error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
+
+            error.UnimplementedArch,
+            error.UnimplementedOs,
+            error.ThreadContextNotSupported,
+            error.UnimplementedRegisterRule,
+            error.UnsupportedAddrSize,
+            error.UnsupportedDwarfVersion,
+            error.UnimplementedUserOpcode,
+            error.UnimplementedExpressionCall,
+            error.UnimplementedOpcode,
+            error.UnimplementedTypedComparison,
+            error.UnimplementedTypeConversion,
+            error.UnknownExpressionOpcode,
+            => return error.UnsupportedDebugInfo,
+
+            error.InvalidRegister,
+            error.RegisterContextRequired,
+            error.ReadFailed,
+            error.EndOfStream,
+            error.IncompatibleRegisterSize,
+            error.Overflow,
+            error.StreamTooLong,
+            error.InvalidOperand,
+            error.InvalidOpcode,
+            error.InvalidOperation,
+            error.InvalidCFARule,
+            error.IncompleteExpressionContext,
+            error.InvalidCFAOpcode,
+            error.InvalidExpression,
+            error.InvalidFrameBase,
+            error.InvalidIntegralTypeSize,
+            error.InvalidSubExpression,
+            error.InvalidTypeLength,
+            error.TruncatedIntegralType,
+            error.DivisionByZero,
+            error.InvalidExpressionValue,
+            error.NoExpressionValue,
+            error.RegisterSizeMismatch,
+            error.InvalidCFA,
+            => return error.InvalidDebugInfo,
+        };
+    }
+    fn unwindFrameDwarfInner(
+        context: *UnwindContext,
+        unwind: *const Dwarf.Unwind,
+        load_offset: usize,
+        explicit_fde_offset: ?usize,
     ) !usize {
         if (!supports_unwinding) return error.UnsupportedCpuArchitecture;
         if (context.pc == 0) return 0;
lib/std/debug.zig
@@ -766,11 +766,6 @@ pub fn writeStackTrace(
     }
 }
 
-pub const UnwindError = if (have_ucontext)
-    @typeInfo(@typeInfo(@TypeOf(SelfInfo.unwindFrame)).@"fn".return_type.?).error_union.error_set
-else
-    void;
-
 pub const StackIterator = struct {
     // Skip every frame before this address is found.
     first_address: ?usize,
@@ -783,7 +778,7 @@ pub const StackIterator = struct {
     unwind_state: if (have_ucontext) ?struct {
         debug_info: *SelfInfo,
         dwarf_context: SelfInfo.UnwindContext,
-        last_error: ?UnwindError = null,
+        last_error: ?SelfInfo.Error = null,
         failed: bool = false,
     } else void = if (have_ucontext) null else {},
 
@@ -821,7 +816,7 @@ pub const StackIterator = struct {
     }
 
     pub fn getLastError(it: *StackIterator) ?struct {
-        err: UnwindError,
+        err: SelfInfo.Error,
         address: usize,
     } {
         if (!have_ucontext) return null;
@@ -1037,17 +1032,29 @@ 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 {
+fn printUnwindError(debug_info: *SelfInfo, writer: *Writer, address: usize, unwind_err: SelfInfo.Error, tty_config: tty.Config) !void {
     const module_name = debug_info.getModuleNameForAddress(getDebugInfoAllocator(), address) catch |err| switch (err) {
-        error.MissingDebugInfo => "???",
+        error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, error.ReadFailed => "???",
         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 {
-        try writer.print("Unwind error at address `{s}:0x{x}` ({}), trace may be incomplete\n\n", .{ module_name, address, unwind_err });
+    switch (unwind_err) {
+        error.Unexpected, error.OutOfMemory => |e| return e,
+        error.MissingDebugInfo => {
+            try writer.print("Unwind information for `{s}:0x{x}` was not available, trace may be incomplete\n\n", .{ module_name, address });
+        },
+        error.InvalidDebugInfo,
+        error.UnsupportedDebugInfo,
+        error.ReadFailed,
+        => {
+            const caption: []const u8 = switch (unwind_err) {
+                error.InvalidDebugInfo => "invalid unwind info",
+                error.UnsupportedDebugInfo => "unsupported unwind info",
+                error.ReadFailed => "filesystem error",
+                else => unreachable,
+            };
+            try writer.print("Unwind error at address `{s}:0x{x}` ({s}), trace may be incomplete\n\n", .{ module_name, address, caption });
+        },
     }
     try tty_config.setColor(writer, .reset);
 }
@@ -1055,12 +1062,17 @@ 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();
     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,
+        error.MissingDebugInfo,
+        error.UnsupportedDebugInfo,
+        error.InvalidDebugInfo,
+        => .{ .name = null, .compile_unit_name = null, .source_location = null },
+        error.ReadFailed => s: {
+            try tty_config.setColor(writer, .dim);
+            try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
+            try tty_config.setColor(writer, .reset);
+            break :s .{ .name = null, .compile_unit_name = null, .source_location = null };
         },
-        else => |e| return e,
+        error.OutOfMemory, error.Unexpected => |e| return e,
     };
     defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
     return printLineInfo(
@@ -1069,7 +1081,7 @@ pub fn printSourceAtAddress(debug_info: *SelfInfo, writer: *Writer, address: usi
         address,
         symbol.name orelse "???",
         symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch |err| switch (err) {
-            error.MissingDebugInfo => "???",
+            error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, error.ReadFailed => "???",
             error.Unexpected, error.OutOfMemory => |e| return e,
         },
         tty_config,