Commit 9859440d83

mlugg <mlugg@mlugg.co.uk>
2025-09-05 22:28:18
add freestanding support IN THEORY
untested because this branch has errors rn
1 parent c2ada49
lib/std/debug/SelfInfo/DarwinModule.zig
@@ -251,6 +251,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
         ) catch null,
     };
 }
+pub const supports_unwinding: bool = true;
 /// 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.
lib/std/debug/SelfInfo/ElfModule.zig
@@ -205,6 +205,26 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con
     }
     return error.MissingDebugInfo;
 }
+pub const supports_unwinding: bool = s: {
+    const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
+        .linux => &.{ .x86, .x86_64, .arm, .armeb, .thumb, .thumbeb, .aarch64, .aarch64_be },
+        .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
+        .freebsd => &.{ .x86_64, .aarch64, .aarch64_be },
+        .openbsd => &.{.x86_64},
+        .solaris => &.{ .x86, .x86_64 },
+        .illumos => &.{ .x86, .x86_64 },
+        else => unreachable,
+    };
+    for (archs) |a| {
+        if (builtin.target.cpu.arch == a) break :s true;
+    }
+    break :s false;
+};
+comptime {
+    if (supports_unwinding) {
+        std.debug.assert(Dwarf.abi.supportsUnwinding(&builtin.target));
+    }
+}
 
 const ElfModule = @This();
 
lib/std/debug/SelfInfo/WindowsModule.zig
@@ -246,6 +246,7 @@ pub const DebugInfo = struct {
         };
     }
 };
+pub const supports_unwinding: bool = false;
 
 const WindowsModule = @This();
 
lib/std/debug/SelfInfo.zig
@@ -14,6 +14,8 @@ const Dwarf = std.debug.Dwarf;
 const regBytes = Dwarf.abi.regBytes;
 const regValueNative = Dwarf.abi.regValueNative;
 
+const root = @import("root");
+
 const SelfInfo = @This();
 
 modules: std.AutoArrayHashMapUnmanaged(usize, Module.DebugInfo),
@@ -33,49 +35,12 @@ pub const Error = error{
 };
 
 /// Indicates whether the `SelfInfo` implementation has support for this target.
-pub const target_supported: bool = switch (native_os) {
-    .linux,
-    .freebsd,
-    .netbsd,
-    .dragonfly,
-    .openbsd,
-    .macos,
-    .solaris,
-    .illumos,
-    .windows,
-    => true,
-    else => false,
-};
+pub const target_supported: bool = Module != void;
 
-/// Indicates whether unwinding for the host is *implemented* here in the Zig
-/// standard library.
+/// Indicates whether the `SelfInfo` implementation has support for unwinding on this target.
 ///
-/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports
-/// unwinding on a target *in theory*.
-pub const supports_unwinding: bool = switch (builtin.target.cpu.arch) {
-    .x86 => switch (builtin.target.os.tag) {
-        .linux, .netbsd, .solaris, .illumos => true,
-        else => false,
-    },
-    .x86_64 => switch (builtin.target.os.tag) {
-        .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
-        else => false,
-    },
-    .arm, .armeb, .thumb, .thumbeb => switch (builtin.target.os.tag) {
-        .linux => true,
-        else => false,
-    },
-    .aarch64, .aarch64_be => switch (builtin.target.os.tag) {
-        .linux, .netbsd, .freebsd, .macos, .ios => true,
-        else => false,
-    },
-    // Unwinding is possible on other targets but this implementation does
-    // not support them...yet!
-    else => false,
-};
-comptime {
-    if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target));
-}
+/// For whether DWARF unwinding is *theoretically* possible, see `Dwarf.abi.supportsUnwinding`.
+pub const supports_unwinding: bool = Module.supports_unwinding;
 
 pub const init: SelfInfo = .{
     .modules = .empty,
@@ -114,48 +79,61 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)
     return module.name;
 }
 
-/// This type contains the target-specific implementation. It must expose the following declarations:
+/// `void` indicates that `SelfInfo` is not supported for this target.
 ///
-/// * `LookupCache: type`, with the following declarations unless `LookupCache == void`:
-///   * `init: LookupCache`
-///   * `deinit: fn (*LookupCache, Allocator) void`
-/// * `lookup: fn (*LookupCache, Allocator, address: usize) !Module`
-/// * `key: fn (*const Module) usize`
-/// * `DebugInfo: type`, with the following declarations:
-///   * `DebugInfo.init: DebugInfo`
-/// * `getSymbolAtAddress: fn (*const Module, Allocator, *DebugInfo, address: usize) !std.debug.Symbol`
+/// This type contains the target-specific implementation. Logically, a `Module` represents a subset
+/// of the executable with its own debug information. This typically corresponds to what ELF calls a
+/// module, i.e. a shared library or executable image, but could be anything. For instance, it would
+/// be valid to consider the entire application one module, or on the other hand to consider each
+/// object file a module.
 ///
-/// If unwinding is supported on this target, it must additionally expose the following declarations:
+/// This type must must expose the following declarations:
 ///
-/// * `unwindFrame: fn (*const Module, Allocator, *DebugInfo, *UnwindContext) !usize`
-const Module = switch (native_os) {
-    else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
-    .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => @import("SelfInfo/ElfModule.zig"),
-    .macos, .ios, .watchos, .tvos, .visionos => @import("SelfInfo/DarwinModule.zig"),
-    .uefi, .windows => @import("SelfInfo/WindowsModule.zig"),
-    .wasi, .emscripten => struct {
-        const LookupCache = void;
-        fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
-            _ = cache;
-            _ = gpa;
-            _ = address;
-            @panic("TODO implement lookup module for Wasm");
-        }
-        const DebugInfo = struct {
-            const init: DebugInfo = .{};
-        };
-        fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
-            _ = module;
-            _ = gpa;
-            _ = di;
-            _ = address;
-            unreachable;
-        }
-    },
+/// ```
+/// /// Holds state cached by the implementation between calls to `lookup`.
+/// /// This may be `void`, in which case the inner declarations can be omitted.
+/// pub const LookupCache = struct {
+///     pub const init: LookupCache;
+///     pub fn deinit(lc: *LookupCache, gpa: Allocator) void;
+/// };
+/// /// Holds debug information associated with a particular `Module`.
+/// pub const DebugInfo = struct {
+///     pub const init: DebugInfo;
+/// };
+/// /// Finds the `Module` corresponding to `address`.
+/// pub fn lookup(lc: *LookupCache, gpa: Allocator, address: usize) SelfInfo.Error!Module;
+/// /// Returns a unique identifier for this `Module`, such as a load address.
+/// pub fn key(mod: *const Module) usize;
+/// /// Locates and loads location information for the symbol corresponding to `address`.
+/// pub fn getSymbolAtAddress(
+///     mod: *const Module,
+///     gpa: Allocator,
+///     di: *DebugInfo,
+///     address: usize,
+/// ) SelfInfo.Error!std.debug.Symbol;
+/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.
+/// pub const supports_unwinding: bool;
+/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame and returns
+/// /// the next return address (which may be 0 indicating end of stack). This is currently
+/// /// specialized to DWARF unwinding.
+/// pub fn unwindFrame(
+///     mod: *const Module,
+///     gpa: Allocator,
+///     di: *DebugInfo,
+///     ctx: *SelfInfo.UnwindContext,
+/// ) SelfInfo.Error!usize;
+/// ```
+const Module: type = Module: {
+    if (@hasDecl(root, "debug") and @hasDecl(root.debug, "Module")) {
+        break :Module root.debug.Module;
+    }
+    break :Module switch (native_os) {
+        .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => @import("SelfInfo/ElfModule.zig"),
+        .macos, .ios, .watchos, .tvos, .visionos => @import("SelfInfo/DarwinModule.zig"),
+        .uefi, .windows => @import("SelfInfo/WindowsModule.zig"),
+        else => void,
+    };
 };
-test {
-    _ = Module;
-}
 
 pub const UnwindContext = struct {
     gpa: Allocator, // MLUGG TODO: make unmanaged (also maybe rename this type, DwarfUnwindContext or smth idk)
lib/std/debug.zig
@@ -14,6 +14,8 @@ const builtin = @import("builtin");
 const native_arch = builtin.cpu.arch;
 const native_os = builtin.os.tag;
 
+const root = @import("root");
+
 pub const Dwarf = @import("debug/Dwarf.zig");
 pub const Pdb = @import("debug/Pdb.zig");
 pub const SelfInfo = @import("debug/SelfInfo.zig");
@@ -942,19 +944,15 @@ fn printLineInfo(
                     tty_config.setColor(writer, .reset) catch {};
                 }
                 try writer.writeAll("\n");
-            } else |err| switch (err) {
-                error.WriteFailed => |e| return e,
-                else => {
-                    // Ignore everything else. Seeing some lines in the trace without the associated
-                    // source line printed is a far better user experience than interleaving the
-                    // trace with a load of filesystem error crap. The user can always just open the
-                    // source file themselves to see the line.
-                },
             }
         }
     }
 }
 fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
+    if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) {
+        return root.debug.printLineFromFile(writer, source_location);
+    }
+
     // Need this to always block even in async I/O mode, because this could potentially
     // be called from e.g. the event loop code crashing.
     var f = try fs.cwd().openFile(source_location.file_name, .{});
@@ -1139,11 +1137,16 @@ test printLineFromFile {
 
 /// TODO multithreaded awareness
 var debug_info_arena: ?std.heap.ArenaAllocator = null;
+var debug_info_fba: std.heap.FixedBufferAllocator = .init(&debug_info_fba_buf);
+var debug_info_fba_buf: [1024 * 1024 * 4]u8 = undefined;
 fn getDebugInfoAllocator() mem.Allocator {
-    if (debug_info_arena == null) {
-        debug_info_arena = .init(std.heap.page_allocator);
+    if (false) {
+        if (debug_info_arena == null) {
+            debug_info_arena = .init(std.heap.page_allocator);
+        }
+        return debug_info_arena.?.allocator();
     }
-    return debug_info_arena.?.allocator();
+    return debug_info_fba.allocator();
 }
 
 /// Whether or not the current target can print useful debug information when a segfault occurs.