add freestanding support IN THEORY

untested because this branch has errors rn
This commit is contained in:
mlugg 2025-09-05 21:28:18 +01:00
parent c2ada49354
commit 9859440d83
No known key found for this signature in database
GPG Key ID: 3F5B7DCCBF4AF02E
5 changed files with 93 additions and 90 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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.

View File

@ -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();

View File

@ -246,6 +246,7 @@ pub const DebugInfo = struct {
};
}
};
pub const supports_unwinding: bool = false;
const WindowsModule = @This();