authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-05 21:28:18+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:51+01:00
log9859440d83e5ef17d353be39f32f2dc0b9ce0e02
treec94b29a9be0a120d6df33509d50e46915f488a82
parentc2ada49354897f89a2e2030ad1e083a8ee9b7c3b
signaturelock-open Commit is signed but in an unrecognized format.

add freestanding support IN THEORY

untested because this branch has errors rn

5 files changed, 93 insertions(+), 90 deletions(-)

lib/std/debug.zig+14-11
......@@ -14,6 +14,8 @@ const builtin = @import("builtin");
1414const native_arch = builtin.cpu.arch;
1515const native_os = builtin.os.tag;
1616
17const root = @import("root");
18
1719pub const Dwarf = @import("debug/Dwarf.zig");
1820pub const Pdb = @import("debug/Pdb.zig");
1921pub const SelfInfo = @import("debug/SelfInfo.zig");
......@@ -942,19 +944,15 @@ fn printLineInfo(
942944 tty_config.setColor(writer, .reset) catch {};
943945 }
944946 try writer.writeAll("\n");
945 } else |err| switch (err) {
946 error.WriteFailed => |e| return e,
947 else => {
948 // Ignore everything else. Seeing some lines in the trace without the associated
949 // source line printed is a far better user experience than interleaving the
950 // trace with a load of filesystem error crap. The user can always just open the
951 // source file themselves to see the line.
952 },
953947 }
954948 }
955949 }
956950}
957951fn printLineFromFile(writer: *Writer, source_location: SourceLocation) !void {
952 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "printLineFromFile")) {
953 return root.debug.printLineFromFile(writer, source_location);
954 }
955
958956 // Need this to always block even in async I/O mode, because this could potentially
959957 // be called from e.g. the event loop code crashing.
960958 var f = try fs.cwd().openFile(source_location.file_name, .{});
......@@ -1139,11 +1137,16 @@ test printLineFromFile {
11391137
11401138/// TODO multithreaded awareness
11411139var debug_info_arena: ?std.heap.ArenaAllocator = null;
1140var debug_info_fba: std.heap.FixedBufferAllocator = .init(&debug_info_fba_buf);
1141var debug_info_fba_buf: [1024 * 1024 * 4]u8 = undefined;
11421142fn getDebugInfoAllocator() mem.Allocator {
1143 if (debug_info_arena == null) {
1144 debug_info_arena = .init(std.heap.page_allocator);
1143 if (false) {
1144 if (debug_info_arena == null) {
1145 debug_info_arena = .init(std.heap.page_allocator);
1146 }
1147 return debug_info_arena.?.allocator();
11451148 }
1146 return debug_info_arena.?.allocator();
1149 return debug_info_fba.allocator();
11471150}
11481151
11491152/// Whether or not the current target can print useful debug information when a segfault occurs.
lib/std/debug/SelfInfo.zig+57-79
......@@ -14,6 +14,8 @@ const Dwarf = std.debug.Dwarf;
1414const regBytes = Dwarf.abi.regBytes;
1515const regValueNative = Dwarf.abi.regValueNative;
1616
17const root = @import("root");
18
1719const SelfInfo = @This();
1820
1921modules: std.AutoArrayHashMapUnmanaged(usize, Module.DebugInfo),
......@@ -33,49 +35,12 @@ pub const Error = error{
3335};
3436
3537/// Indicates whether the `SelfInfo` implementation has support for this target.
36pub const target_supported: bool = switch (native_os) {
37 .linux,
38 .freebsd,
39 .netbsd,
40 .dragonfly,
41 .openbsd,
42 .macos,
43 .solaris,
44 .illumos,
45 .windows,
46 => true,
47 else => false,
48};
38pub const target_supported: bool = Module != void;
4939
50/// Indicates whether unwinding for the host is *implemented* here in the Zig
51/// standard library.
40/// Indicates whether the `SelfInfo` implementation has support for unwinding on this target.
5241///
53/// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports
54/// unwinding on a target *in theory*.
55pub const supports_unwinding: bool = switch (builtin.target.cpu.arch) {
56 .x86 => switch (builtin.target.os.tag) {
57 .linux, .netbsd, .solaris, .illumos => true,
58 else => false,
59 },
60 .x86_64 => switch (builtin.target.os.tag) {
61 .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true,
62 else => false,
63 },
64 .arm, .armeb, .thumb, .thumbeb => switch (builtin.target.os.tag) {
65 .linux => true,
66 else => false,
67 },
68 .aarch64, .aarch64_be => switch (builtin.target.os.tag) {
69 .linux, .netbsd, .freebsd, .macos, .ios => true,
70 else => false,
71 },
72 // Unwinding is possible on other targets but this implementation does
73 // not support them...yet!
74 else => false,
75};
76comptime {
77 if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(&builtin.target));
78}
42/// For whether DWARF unwinding is *theoretically* possible, see `Dwarf.abi.supportsUnwinding`.
43pub const supports_unwinding: bool = Module.supports_unwinding;
7944
8045pub const init: SelfInfo = .{
8146 .modules = .empty,
......@@ -114,48 +79,61 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)
11479 return module.name;
11580}
11681
117/// This type contains the target-specific implementation. It must expose the following declarations:
82/// `void` indicates that `SelfInfo` is not supported for this target.
11883///
119/// * `LookupCache: type`, with the following declarations unless `LookupCache == void`:
120/// * `init: LookupCache`
121/// * `deinit: fn (*LookupCache, Allocator) void`
122/// * `lookup: fn (*LookupCache, Allocator, address: usize) !Module`
123/// * `key: fn (*const Module) usize`
124/// * `DebugInfo: type`, with the following declarations:
125/// * `DebugInfo.init: DebugInfo`
126/// * `getSymbolAtAddress: fn (*const Module, Allocator, *DebugInfo, address: usize) !std.debug.Symbol`
84/// This type contains the target-specific implementation. Logically, a `Module` represents a subset
85/// of the executable with its own debug information. This typically corresponds to what ELF calls a
86/// module, i.e. a shared library or executable image, but could be anything. For instance, it would
87/// be valid to consider the entire application one module, or on the other hand to consider each
88/// object file a module.
12789///
128/// If unwinding is supported on this target, it must additionally expose the following declarations:
90/// This type must must expose the following declarations:
12991///
130/// * `unwindFrame: fn (*const Module, Allocator, *DebugInfo, *UnwindContext) !usize`
131const Module = switch (native_os) {
132 else => {}, // Dwarf, // TODO MLUGG: it's this on master but that's definitely broken atm...
133 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => @import("SelfInfo/ElfModule.zig"),
134 .macos, .ios, .watchos, .tvos, .visionos => @import("SelfInfo/DarwinModule.zig"),
135 .uefi, .windows => @import("SelfInfo/WindowsModule.zig"),
136 .wasi, .emscripten => struct {
137 const LookupCache = void;
138 fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) !Module {
139 _ = cache;
140 _ = gpa;
141 _ = address;
142 @panic("TODO implement lookup module for Wasm");
143 }
144 const DebugInfo = struct {
145 const init: DebugInfo = .{};
146 };
147 fn getSymbolAtAddress(module: *const Module, gpa: Allocator, di: *DebugInfo, address: usize) !std.debug.Symbol {
148 _ = module;
149 _ = gpa;
150 _ = di;
151 _ = address;
152 unreachable;
153 }
154 },
92/// ```
93/// /// Holds state cached by the implementation between calls to `lookup`.
94/// /// This may be `void`, in which case the inner declarations can be omitted.
95/// pub const LookupCache = struct {
96/// pub const init: LookupCache;
97/// pub fn deinit(lc: *LookupCache, gpa: Allocator) void;
98/// };
99/// /// Holds debug information associated with a particular `Module`.
100/// pub const DebugInfo = struct {
101/// pub const init: DebugInfo;
102/// };
103/// /// Finds the `Module` corresponding to `address`.
104/// pub fn lookup(lc: *LookupCache, gpa: Allocator, address: usize) SelfInfo.Error!Module;
105/// /// Returns a unique identifier for this `Module`, such as a load address.
106/// pub fn key(mod: *const Module) usize;
107/// /// Locates and loads location information for the symbol corresponding to `address`.
108/// pub fn getSymbolAtAddress(
109/// mod: *const Module,
110/// gpa: Allocator,
111/// di: *DebugInfo,
112/// address: usize,
113/// ) SelfInfo.Error!std.debug.Symbol;
114/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.
115/// pub const supports_unwinding: bool;
116/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame and returns
117/// /// the next return address (which may be 0 indicating end of stack). This is currently
118/// /// specialized to DWARF unwinding.
119/// pub fn unwindFrame(
120/// mod: *const Module,
121/// gpa: Allocator,
122/// di: *DebugInfo,
123/// ctx: *SelfInfo.UnwindContext,
124/// ) SelfInfo.Error!usize;
125/// ```
126const Module: type = Module: {
127 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "Module")) {
128 break :Module root.debug.Module;
129 }
130 break :Module switch (native_os) {
131 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => @import("SelfInfo/ElfModule.zig"),
132 .macos, .ios, .watchos, .tvos, .visionos => @import("SelfInfo/DarwinModule.zig"),
133 .uefi, .windows => @import("SelfInfo/WindowsModule.zig"),
134 else => void,
135 };
155136};
156test {
157 _ = Module;
158}
159137
160138pub const UnwindContext = struct {
161139 gpa: Allocator, // MLUGG TODO: make unmanaged (also maybe rename this type, DwarfUnwindContext or smth idk)
lib/std/debug/SelfInfo/DarwinModule.zig+1
......@@ -251,6 +251,7 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
251251 ) catch null,
252252 };
253253}
254pub const supports_unwinding: bool = true;
254255/// Unwind a frame using MachO compact unwind info (from __unwind_info).
255256/// If the compact encoding can't encode a way to unwind a frame, it will
256257/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
lib/std/debug/SelfInfo/ElfModule.zig+20
......@@ -205,6 +205,26 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con
205205 }
206206 return error.MissingDebugInfo;
207207}
208pub const supports_unwinding: bool = s: {
209 const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
210 .linux => &.{ .x86, .x86_64, .arm, .armeb, .thumb, .thumbeb, .aarch64, .aarch64_be },
211 .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
212 .freebsd => &.{ .x86_64, .aarch64, .aarch64_be },
213 .openbsd => &.{.x86_64},
214 .solaris => &.{ .x86, .x86_64 },
215 .illumos => &.{ .x86, .x86_64 },
216 else => unreachable,
217 };
218 for (archs) |a| {
219 if (builtin.target.cpu.arch == a) break :s true;
220 }
221 break :s false;
222};
223comptime {
224 if (supports_unwinding) {
225 std.debug.assert(Dwarf.abi.supportsUnwinding(&builtin.target));
226 }
227}
208228
209229const ElfModule = @This();
210230
lib/std/debug/SelfInfo/WindowsModule.zig+1
......@@ -246,6 +246,7 @@ pub const DebugInfo = struct {
246246 };
247247 }
248248};
249pub const supports_unwinding: bool = false;
249250
250251const WindowsModule = @This();
251252