From 9859440d83e5ef17d353be39f32f2dc0b9ce0e02 Mon Sep 17 00:00:00 2001 From: mlugg Date: Fri, 5 Sep 2025 21:28:18 +0100 Subject: [PATCH] add freestanding support IN THEORY untested because this branch has errors rn --- lib/std/debug.zig | 25 +++-- lib/std/debug/SelfInfo.zig | 136 ++++++++++------------- lib/std/debug/SelfInfo/DarwinModule.zig | 1 + lib/std/debug/SelfInfo/ElfModule.zig | 20 ++++ lib/std/debug/SelfInfo/WindowsModule.zig | 1 + 5 files changed, 93 insertions(+), 90 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index bc2610fb7c..f2d15e3fe8 100644 --- a/lib/std/debug.zig +++ b/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. diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index a7dea036c4..93fa7a6045 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/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) diff --git a/lib/std/debug/SelfInfo/DarwinModule.zig b/lib/std/debug/SelfInfo/DarwinModule.zig index 1de7c12015..4f94798c63 100644 --- a/lib/std/debug/SelfInfo/DarwinModule.zig +++ b/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. diff --git a/lib/std/debug/SelfInfo/ElfModule.zig b/lib/std/debug/SelfInfo/ElfModule.zig index e69600be14..b58125031e 100644 --- a/lib/std/debug/SelfInfo/ElfModule.zig +++ b/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(); diff --git a/lib/std/debug/SelfInfo/WindowsModule.zig b/lib/std/debug/SelfInfo/WindowsModule.zig index 674c6adae5..ccede7efb2 100644 --- a/lib/std/debug/SelfInfo/WindowsModule.zig +++ b/lib/std/debug/SelfInfo/WindowsModule.zig @@ -246,6 +246,7 @@ pub const DebugInfo = struct { }; } }; +pub const supports_unwinding: bool = false; const WindowsModule = @This();