authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 11:06:21+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 14:18:26+01:00
log1120546f72405ac263dce7414eb71ca4e6c96fc8
tree4a6f90029d8feff983889a133326fbe2a4e3465d
parent12ceb896faebf25195d8b360e4972dd2bf23ede1
signaturelock-open Commit is signed but in an unrecognized format.

std.debug.SelfInfo: remove shared logic

There were only a few dozen lines of common logic, and they frankly introduced more complexity than they eliminated. Instead, let's accept that the implementations of `SelfInfo` are all pretty different and want to track different state. This probably fixes some synchronization and memory bugs by simplifying a bunch of stuff. It also improves the DWARF unwind cache, making it around twice as fast in a debug build with the self-hosted x86_64 backend, because we no longer have to redundantly go through the hashmap lookup logic to find the module. Unwinding on Windows will also see a slight performance boost from this change, because `RtlVirtualUnwind` does not need to know the module whatsoever, so the old `SelfInfo` implementation was doing redundant work. Lastly, this makes it even easier to implement `SelfInfo` on freestanding targets; there is no longer a need to emulate a real module system, since the user controls the whole implementation! There are various other small refactors here in the `SelfInfo` implementations as well as in the DWARF unwinding logic. This change turned out to make a lot of stuff simpler!

13 files changed, 2415 insertions(+), 2320 deletions(-)

lib/std/debug.zig+86-11
......@@ -19,11 +19,85 @@ const root = @import("root");
1919pub const Dwarf = @import("debug/Dwarf.zig");
2020pub const Pdb = @import("debug/Pdb.zig");
2121pub const ElfFile = @import("debug/ElfFile.zig");
22pub const SelfInfo = @import("debug/SelfInfo.zig");
2322pub const Info = @import("debug/Info.zig");
2423pub const Coverage = @import("debug/Coverage.zig");
2524pub const cpu_context = @import("debug/cpu_context.zig");
2625
26/// This type abstracts the target-specific implementation of accessing this process' own debug
27/// information behind a generic interface which supports looking up source locations associated
28/// with addresses, as well as unwinding the stack where a safe mechanism to do so exists.
29///
30/// The Zig Standard Library provides default implementations of `SelfInfo` for common targets, but
31/// the implementation can be overriden by exposing `root.debug.SelfInfo`. Setting `SelfInfo` to
32/// `void` indicates that the `SelfInfo` API is not supported.
33///
34/// This type must expose the following declarations:
35///
36/// ```
37/// pub const init: SelfInfo;
38/// pub fn deinit(si: *SelfInfo, gpa: Allocator) void;
39///
40/// /// Returns the symbol and source location of the instruction at `address`.
41/// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError!Symbol;
42/// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`.
43/// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8;
44///
45/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.
46/// pub const can_unwind: bool;
47/// /// Only required if `can_unwind == true`.
48/// pub const UnwindContext = struct {
49/// /// An address representing the instruction pointer in the last frame.
50/// pc: usize,
51///
52/// pub fn init(ctx: *cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext;
53/// pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void;
54/// /// Returns the frame pointer associated with the last unwound stack frame.
55/// /// If the frame pointer is unknown, 0 may be returned instead.
56/// pub fn getFp(uc: *UnwindContext) usize;
57/// };
58/// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's
59/// /// return address, or 0 if the end of the stack has been reached.
60/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) SelfInfoError!usize;
61/// ```
62pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))
63 root.debug.SelfInfo
64else switch (native_os) {
65 .linux,
66 .netbsd,
67 .freebsd,
68 .dragonfly,
69 .openbsd,
70 .solaris,
71 .illumos,
72 => @import("debug/SelfInfo/Elf.zig"),
73
74 .macos,
75 .ios,
76 .watchos,
77 .tvos,
78 .visionos,
79 => @import("debug/SelfInfo/Darwin.zig"),
80
81 .uefi,
82 .windows,
83 => @import("debug/SelfInfo/Windows.zig"),
84
85 else => void,
86};
87
88pub const SelfInfoError = error{
89 /// The required debug info is invalid or corrupted.
90 InvalidDebugInfo,
91 /// The required debug info could not be found.
92 MissingDebugInfo,
93 /// The required debug info was found, and may be valid, but is not supported by this implementation.
94 UnsupportedDebugInfo,
95 /// The required debug info could not be read from disk due to some IO error.
96 ReadFailed,
97 OutOfMemory,
98 Unexpected,
99};
100
27101pub const simple_panic = @import("debug/simple_panic.zig");
28102pub const no_panic = @import("debug/no_panic.zig");
29103
......@@ -240,7 +314,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
240314
241315/// Marked `inline` to propagate a comptime-known error to callers.
242316pub inline fn getSelfDebugInfo() !*SelfInfo {
243 if (!SelfInfo.target_supported) return error.UnsupportedTarget;
317 if (SelfInfo == void) return error.UnsupportedTarget;
244318 const S = struct {
245319 var self_info: SelfInfo = .init;
246320 };
......@@ -640,7 +714,7 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
640714 while (true) switch (it.next()) {
641715 .switch_to_fp => |unwind_error| {
642716 if (StackIterator.fp_unwind_is_safe) continue; // no need to even warn
643 const module_name = di.getModuleNameForAddress(di_gpa, unwind_error.address) catch "???";
717 const module_name = di.getModuleName(di_gpa, unwind_error.address) catch "???";
644718 const caption: []const u8 = switch (unwind_error.err) {
645719 error.MissingDebugInfo => "unwind info unavailable",
646720 error.InvalidDebugInfo => "unwind info invalid",
......@@ -753,9 +827,9 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void {
753827
754828const StackIterator = union(enum) {
755829 /// Unwinding using debug info (e.g. DWARF CFI).
756 di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn,
830 di: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn,
757831 /// We will first report the *current* PC of this `UnwindContext`, then we will switch to `di`.
758 di_first: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn,
832 di_first: if (SelfInfo != void and SelfInfo.can_unwind) SelfInfo.UnwindContext else noreturn,
759833 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
760834 fp: usize,
761835
......@@ -772,7 +846,7 @@ const StackIterator = union(enum) {
772846 }
773847 }
774848 if (opt_context_ptr) |context_ptr| {
775 if (!SelfInfo.supports_unwinding) return error.CannotUnwindFromContext;
849 if (SelfInfo == void or !SelfInfo.can_unwind) return error.CannotUnwindFromContext;
776850 // Use `di_first` here so we report the PC in the context before unwinding any further.
777851 return .{ .di_first = .init(context_ptr) };
778852 }
......@@ -780,7 +854,8 @@ const StackIterator = union(enum) {
780854 // call to `current`. This effectively constrains stack trace collection and dumping to FP
781855 // unwinding when building with CBE for MSVC.
782856 if (!(builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) and
783 SelfInfo.supports_unwinding and
857 SelfInfo != void and
858 SelfInfo.can_unwind and
784859 cpu_context.Native != noreturn)
785860 {
786861 // We don't need `di_first` here, because our PC is in `std.debug`; we're only interested
......@@ -820,7 +895,7 @@ const StackIterator = union(enum) {
820895 /// We were using `SelfInfo.UnwindInfo`, but are now switching to FP unwinding due to this error.
821896 switch_to_fp: struct {
822897 address: usize,
823 err: SelfInfo.Error,
898 err: SelfInfoError,
824899 },
825900 };
826901
......@@ -929,7 +1004,7 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize {
9291004}
9301005
9311006fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer, address: usize, tty_config: tty.Config) Writer.Error!void {
932 const symbol: Symbol = debug_info.getSymbolAtAddress(gpa, address) catch |err| switch (err) {
1007 const symbol: Symbol = debug_info.getSymbol(gpa, address) catch |err| switch (err) {
9331008 error.MissingDebugInfo,
9341009 error.UnsupportedDebugInfo,
9351010 error.InvalidDebugInfo,
......@@ -953,7 +1028,7 @@ fn printSourceAtAddress(gpa: Allocator, debug_info: *SelfInfo, writer: *Writer,
9531028 symbol.source_location,
9541029 address,
9551030 symbol.name orelse "???",
956 symbol.compile_unit_name orelse debug_info.getModuleNameForAddress(gpa, address) catch "???",
1031 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
9571032 tty_config,
9581033 );
9591034}
......@@ -1386,7 +1461,7 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void {
13861461}
13871462
13881463test "manage resources correctly" {
1389 if (!SelfInfo.target_supported) return error.SkipZigTest;
1464 if (SelfInfo == void) return error.SkipZigTest;
13901465 const S = struct {
13911466 noinline fn showMyTrace() usize {
13921467 return @returnAddress();
lib/std/debug/Dwarf.zig+3-2
......@@ -28,6 +28,7 @@ const Dwarf = @This();
2828
2929pub const expression = @import("Dwarf/expression.zig");
3030pub const Unwind = @import("Dwarf/Unwind.zig");
31pub const SelfUnwinder = @import("Dwarf/SelfUnwinder.zig");
3132
3233/// Useful to temporarily enable while working on this file.
3334const debug_debug_mode = false;
......@@ -1458,8 +1459,8 @@ pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
14581459
14591460/// Tells whether unwinding for this target is supported by the Dwarf standard.
14601461///
1461/// See also `std.debug.SelfInfo.supports_unwinding` which tells whether the Zig
1462/// standard library has a working implementation of unwinding for this target.
1462/// See also `std.debug.SelfInfo.can_unwind` which tells whether the Zig standard
1463/// library has a working implementation of unwinding for the current target.
14631464pub fn supportsUnwinding(target: *const std.Target) bool {
14641465 return switch (target.cpu.arch) {
14651466 .amdgcn,
lib/std/debug/Dwarf/SelfUnwinder.zig created+334
......@@ -0,0 +1,334 @@
1//! Implements stack unwinding based on `Dwarf.Unwind`. The caller is responsible for providing the
2//! initialized `Dwarf.Unwind` from the `.debug_frame` (or equivalent) section; this type handles
3//! computing and applying the CFI register rules to evolve a `std.debug.cpu_context.Native` through
4//! stack frames, hence performing the virtual unwind.
5//!
6//! Notably, this type is a valid implementation of `std.debug.SelfInfo.UnwindContext`.
7
8/// The state of the CPU in the current stack frame.
9cpu_state: std.debug.cpu_context.Native,
10/// The value of the Program Counter in this frame. This is almost the same as the value of the IP
11/// register in `cpu_state`, but may be off by one because the IP is typically a *return* address.
12pc: usize,
13
14cfi_vm: Dwarf.Unwind.VirtualMachine,
15expr_vm: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
16
17pub const CacheEntry = struct {
18 const max_regs = 32;
19
20 pc: usize,
21 cie: *const Dwarf.Unwind.CommonInformationEntry,
22 cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule,
23 num_rules: u8,
24 rules_regs: [max_regs]u16,
25 rules: [max_regs]Dwarf.Unwind.VirtualMachine.RegisterRule,
26
27 pub fn find(entries: []const CacheEntry, pc: usize) ?*const CacheEntry {
28 assert(pc != 0);
29 const idx = std.hash.int(pc) % entries.len;
30 const entry = &entries[idx];
31 return if (entry.pc == pc) entry else null;
32 }
33
34 pub fn populate(entry: *const CacheEntry, entries: []CacheEntry) void {
35 const idx = std.hash.int(entry.pc) % entries.len;
36 entries[idx] = entry.*;
37 }
38
39 pub const empty: CacheEntry = .{
40 .pc = 0,
41 .cie = undefined,
42 .cfa_rule = undefined,
43 .num_rules = undefined,
44 .rules_regs = undefined,
45 .rules = undefined,
46 };
47};
48
49pub fn init(cpu_context: *const std.debug.cpu_context.Native) SelfUnwinder {
50 // `@constCast` is safe because we aren't going to store to the resulting pointer.
51 const raw_pc_ptr = regNative(@constCast(cpu_context), ip_reg_num) catch |err| switch (err) {
52 error.InvalidRegister => unreachable, // `ip_reg_num` is definitely valid
53 error.UnsupportedRegister => unreachable, // the implementation needs to support ip
54 error.IncompatibleRegisterSize => unreachable, // ip is definitely `usize`-sized
55 };
56 const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*);
57 return .{
58 .cpu_state = cpu_context.*,
59 .pc = pc,
60 .cfi_vm = .{},
61 .expr_vm = .{},
62 };
63}
64
65pub fn deinit(unwinder: *SelfUnwinder, gpa: Allocator) void {
66 unwinder.cfi_vm.deinit(gpa);
67 unwinder.expr_vm.deinit(gpa);
68 unwinder.* = undefined;
69}
70
71pub fn getFp(unwinder: *const SelfUnwinder) usize {
72 // `@constCast` is safe because we aren't going to store to the resulting pointer.
73 const ptr = regNative(@constCast(&unwinder.cpu_state), fp_reg_num) catch |err| switch (err) {
74 error.InvalidRegister => unreachable, // `fp_reg_num` is definitely valid
75 error.UnsupportedRegister => unreachable, // the implementation needs to support fp
76 error.IncompatibleRegisterSize => unreachable, // fp is a pointer so is `usize`-sized
77 };
78 return ptr.*;
79}
80
81/// Compute the rule set for the address `unwinder.pc` from the information in `unwind`. The caller
82/// may store the returned rule set in a simple fixed-size cache keyed on the `pc` field to avoid
83/// frequently recomputing register rules when unwinding many times.
84///
85/// To actually apply the computed rules, see `next`.
86pub fn computeRules(
87 unwinder: *SelfUnwinder,
88 gpa: Allocator,
89 unwind: *const Dwarf.Unwind,
90 load_offset: usize,
91 explicit_fde_offset: ?usize,
92) !CacheEntry {
93 assert(unwinder.pc != 0);
94
95 const pc_vaddr = unwinder.pc - load_offset;
96
97 const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
98 pc_vaddr,
99 @sizeOf(usize),
100 native_endian,
101 ) orelse return error.MissingDebugInfo;
102 const cie, const fde = try unwind.getFde(fde_offset, native_endian);
103
104 // `lookupPc` can return false positives, so check if the FDE *actually* includes the pc
105 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) {
106 return error.MissingDebugInfo;
107 }
108
109 unwinder.cfi_vm.reset();
110 const row = try unwinder.cfi_vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian);
111 const cols = unwinder.cfi_vm.rowColumns(&row);
112
113 if (cols.len > CacheEntry.max_regs) return error.UnsupportedDebugInfo;
114
115 var entry: CacheEntry = .{
116 .pc = unwinder.pc,
117 .cie = cie,
118 .cfa_rule = row.cfa,
119 .num_rules = @intCast(cols.len),
120 .rules_regs = undefined,
121 .rules = undefined,
122 };
123 for (cols, 0..) |col, i| {
124 entry.rules_regs[i] = col.register;
125 entry.rules[i] = col.rule;
126 }
127 return entry;
128}
129
130/// Applies the register rules given in `cache_entry` to the current state of `unwinder`. The caller
131/// is responsible for ensuring that `cache_entry` contains the correct rule set for `unwinder.pc`.
132///
133/// `unwinder.cpu_state` and `unwinder.pc` are updated to refer to the next frame, and this frame's
134/// return address is returned as a `usize`.
135pub fn next(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) std.debug.SelfInfoError!usize {
136 return unwinder.nextInner(gpa, cache_entry) catch |err| switch (err) {
137 error.OutOfMemory,
138 error.InvalidDebugInfo,
139 => |e| return e,
140
141 error.UnsupportedRegister,
142 error.UnimplementedExpressionCall,
143 error.UnimplementedOpcode,
144 error.UnimplementedUserOpcode,
145 error.UnimplementedTypedComparison,
146 error.UnimplementedTypeConversion,
147 error.UnknownExpressionOpcode,
148 => return error.UnsupportedDebugInfo,
149
150 error.ReadFailed,
151 error.EndOfStream,
152 error.Overflow,
153 error.IncompatibleRegisterSize,
154 error.InvalidRegister,
155 error.IncompleteExpressionContext,
156 error.InvalidCFAOpcode,
157 error.InvalidExpression,
158 error.InvalidFrameBase,
159 error.InvalidIntegralTypeSize,
160 error.InvalidSubExpression,
161 error.InvalidTypeLength,
162 error.TruncatedIntegralType,
163 error.DivisionByZero,
164 => return error.InvalidDebugInfo,
165 };
166}
167
168fn nextInner(unwinder: *SelfUnwinder, gpa: Allocator, cache_entry: *const CacheEntry) !usize {
169 const format = cache_entry.cie.format;
170 const return_address_register = cache_entry.cie.return_address_register;
171
172 const cfa = switch (cache_entry.cfa_rule) {
173 .none => return error.InvalidDebugInfo,
174 .reg_off => |ro| cfa: {
175 const ptr = try regNative(&unwinder.cpu_state, ro.register);
176 break :cfa try applyOffset(ptr.*, ro.offset);
177 },
178 .expression => |expr| cfa: {
179 // On all implemented architectures, the CFA is defined to be the previous frame's SP
180 const prev_cfa_val = (try regNative(&unwinder.cpu_state, sp_reg_num)).*;
181 unwinder.expr_vm.reset();
182 const value = try unwinder.expr_vm.run(expr, gpa, .{
183 .format = format,
184 .cpu_context = &unwinder.cpu_state,
185 }, prev_cfa_val) orelse return error.InvalidDebugInfo;
186 switch (value) {
187 .generic => |g| break :cfa g,
188 else => return error.InvalidDebugInfo,
189 }
190 },
191 };
192
193 // If unspecified, we'll use the default rule for the return address register, which is
194 // typically equivalent to `.undefined` (meaning there is no return address), but may be
195 // overriden by ABIs.
196 var has_return_address: bool = builtin.cpu.arch.isAARCH64() and
197 return_address_register >= 19 and
198 return_address_register <= 28;
199
200 // Create a copy of the CPU state, to which we will apply the new rules.
201 var new_cpu_state = unwinder.cpu_state;
202
203 // On all implemented architectures, the CFA is defined to be the previous frame's SP
204 (try regNative(&new_cpu_state, sp_reg_num)).* = cfa;
205
206 const rules_len = cache_entry.num_rules;
207 for (cache_entry.rules_regs[0..rules_len], cache_entry.rules[0..rules_len]) |register, rule| {
208 const new_val: union(enum) {
209 same,
210 undefined,
211 val: usize,
212 bytes: []const u8,
213 } = switch (rule) {
214 .default => val: {
215 // The default rule is typically equivalent to `.undefined`, but ABIs may override it.
216 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) {
217 break :val .same;
218 }
219 break :val .undefined;
220 },
221 .undefined => .undefined,
222 .same_value => .same,
223 .offset => |offset| val: {
224 const ptr: *const usize = @ptrFromInt(try applyOffset(cfa, offset));
225 break :val .{ .val = ptr.* };
226 },
227 .val_offset => |offset| .{ .val = try applyOffset(cfa, offset) },
228 .register => |r| .{ .bytes = try unwinder.cpu_state.dwarfRegisterBytes(r) },
229 .expression => |expr| val: {
230 unwinder.expr_vm.reset();
231 const value = try unwinder.expr_vm.run(expr, gpa, .{
232 .format = format,
233 .cpu_context = &unwinder.cpu_state,
234 }, cfa) orelse return error.InvalidDebugInfo;
235 const ptr: *const usize = switch (value) {
236 .generic => |addr| @ptrFromInt(addr),
237 else => return error.InvalidDebugInfo,
238 };
239 break :val .{ .val = ptr.* };
240 },
241 .val_expression => |expr| val: {
242 unwinder.expr_vm.reset();
243 const value = try unwinder.expr_vm.run(expr, gpa, .{
244 .format = format,
245 .cpu_context = &unwinder.cpu_state,
246 }, cfa) orelse return error.InvalidDebugInfo;
247 switch (value) {
248 .generic => |val| break :val .{ .val = val },
249 else => return error.InvalidDebugInfo,
250 }
251 },
252 };
253 switch (new_val) {
254 .same => {},
255 .undefined => {
256 const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register));
257 @memset(dest, undefined);
258 },
259 .val => |val| {
260 const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register));
261 if (dest.len != @sizeOf(usize)) return error.InvalidDebugInfo;
262 const dest_ptr: *align(1) usize = @ptrCast(dest);
263 dest_ptr.* = val;
264 },
265 .bytes => |src| {
266 const dest = try new_cpu_state.dwarfRegisterBytes(@intCast(register));
267 if (dest.len != src.len) return error.InvalidDebugInfo;
268 @memcpy(dest, src);
269 },
270 }
271 if (register == return_address_register) {
272 has_return_address = new_val != .undefined;
273 }
274 }
275
276 const return_address: usize = if (has_return_address) pc: {
277 const raw_ptr = try regNative(&new_cpu_state, return_address_register);
278 break :pc stripInstructionPtrAuthCode(raw_ptr.*);
279 } else 0;
280
281 (try regNative(&new_cpu_state, ip_reg_num)).* = return_address;
282
283 // The new CPU state is complete; flush changes.
284 unwinder.cpu_state = new_cpu_state;
285
286 // The caller will subtract 1 from the return address to get an address corresponding to the
287 // function call. However, if this is a signal frame, that's actually incorrect, because the
288 // "return address" we have is the instruction which triggered the signal (if the signal
289 // handler returned, the instruction would be re-run). Compensate for this by incrementing
290 // the address in that case.
291 const adjusted_ret_addr = if (cache_entry.cie.is_signal_frame) return_address +| 1 else return_address;
292
293 // We also want to do that same subtraction here to get the PC for the next frame's FDE.
294 // This is because if the callee was noreturn, then the function call might be the caller's
295 // last instruction, so `return_address` might actually point outside of it!
296 unwinder.pc = adjusted_ret_addr -| 1;
297
298 return adjusted_ret_addr;
299}
300
301pub fn regNative(ctx: *std.debug.cpu_context.Native, num: u16) error{
302 InvalidRegister,
303 UnsupportedRegister,
304 IncompatibleRegisterSize,
305}!*align(1) usize {
306 const bytes = try ctx.dwarfRegisterBytes(num);
307 if (bytes.len != @sizeOf(usize)) return error.IncompatibleRegisterSize;
308 return @ptrCast(bytes);
309}
310
311/// Since register rules are applied (usually) during a panic,
312/// checked addition / subtraction is used so that we can return
313/// an error and fall back to FP-based unwinding.
314fn applyOffset(base: usize, offset: i64) !usize {
315 return if (offset >= 0)
316 try std.math.add(usize, base, @as(usize, @intCast(offset)))
317 else
318 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
319}
320
321const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?;
322const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch);
323const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch);
324
325const std = @import("std");
326const Allocator = std.mem.Allocator;
327const Dwarf = std.debug.Dwarf;
328const assert = std.debug.assert;
329const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode;
330
331const builtin = @import("builtin");
332const native_endian = builtin.target.cpu.arch.endian();
333
334const SelfUnwinder = @This();
lib/std/debug/Dwarf/Unwind.zig+11-9
......@@ -530,16 +530,18 @@ pub fn prepare(
530530 };
531531 if (saw_terminator != expect_terminator) return bad();
532532
533 std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct {
534 fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
535 ctx;
536 return a.pc_begin < b.pc_begin;
537 }
538 }.lessThan);
533 if (need_lookup) {
534 std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct {
535 fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
536 ctx;
537 return a.pc_begin < b.pc_begin;
538 }
539 }.lessThan);
539540
540 // This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM.
541 const final_fdes = try fde_list.toOwnedSlice(gpa);
542 unwind.lookup = .{ .sorted_fdes = final_fdes };
541 // This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM.
542 const final_fdes = try fde_list.toOwnedSlice(gpa);
543 unwind.lookup = .{ .sorted_fdes = final_fdes };
544 }
543545}
544546
545547fn findCie(unwind: *const Unwind, offset: u64) ?*const CommonInformationEntry {
lib/std/debug/Dwarf/expression.zig+1-1
......@@ -10,7 +10,7 @@ const assert = std.debug.assert;
1010const testing = std.testing;
1111const Writer = std.Io.Writer;
1212
13const regNative = std.debug.SelfInfo.DwarfUnwindContext.regNative;
13const regNative = std.debug.Dwarf.SelfUnwinder.regNative;
1414
1515const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?;
1616const fp_reg_num = std.debug.Dwarf.fpRegNum(native_arch);
lib/std/debug/SelfInfo.zig deleted-551
......@@ -1,551 +0,0 @@
1//! Cross-platform abstraction for this binary's own debug information, with a
2//! goal of minimal code bloat and compilation speed penalty.
3
4const builtin = @import("builtin");
5const native_endian = native_arch.endian();
6const native_arch = builtin.cpu.arch;
7
8const std = @import("../std.zig");
9const mem = std.mem;
10const Allocator = std.mem.Allocator;
11const assert = std.debug.assert;
12const Dwarf = std.debug.Dwarf;
13const CpuContext = std.debug.cpu_context.Native;
14
15const stripInstructionPtrAuthCode = std.debug.stripInstructionPtrAuthCode;
16
17const root = @import("root");
18
19const SelfInfo = @This();
20
21/// Locks access to `modules`. However, does *not* lock the `Module.DebugInfo`, nor `lookup_cache`
22/// the implementation is responsible for locking as needed in its exposed methods.
23///
24/// TODO: to allow `SelfInfo` to work on freestanding, we currently just don't use this mutex there.
25/// That's a bad solution, but a better one depends on the standard library's general support for
26/// "bring your own OS" being improved.
27modules_mutex: switch (builtin.os.tag) {
28 else => std.Thread.Mutex,
29 .freestanding, .other => struct {
30 fn lock(_: @This()) void {}
31 fn unlock(_: @This()) void {}
32 },
33},
34/// Value is allocated into gpa to give it a stable pointer.
35modules: if (target_supported) std.AutoArrayHashMapUnmanaged(usize, *Module.DebugInfo) else void,
36lookup_cache: if (target_supported) Module.LookupCache else void,
37
38pub const Error = error{
39 /// The required debug info is invalid or corrupted.
40 InvalidDebugInfo,
41 /// The required debug info could not be found.
42 MissingDebugInfo,
43 /// The required debug info was found, and may be valid, but is not supported by this implementation.
44 UnsupportedDebugInfo,
45 /// The required debug info could not be read from disk due to some IO error.
46 ReadFailed,
47 OutOfMemory,
48 Unexpected,
49};
50
51/// Indicates whether the `SelfInfo` implementation has support for this target.
52pub const target_supported: bool = Module != void;
53
54/// Indicates whether the `SelfInfo` implementation has support for unwinding on this target.
55pub const supports_unwinding: bool = target_supported and Module.supports_unwinding;
56
57pub const UnwindContext = if (supports_unwinding) Module.UnwindContext;
58
59pub const init: SelfInfo = .{
60 .modules_mutex = .{},
61 .modules = .empty,
62 .lookup_cache = if (Module.LookupCache != void) .init,
63};
64
65pub fn deinit(self: *SelfInfo, gpa: Allocator) void {
66 for (self.modules.values()) |di| {
67 di.deinit(gpa);
68 gpa.destroy(di);
69 }
70 self.modules.deinit(gpa);
71 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);
72}
73
74pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
75 comptime assert(supports_unwinding);
76 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
77 const di: *Module.DebugInfo = di: {
78 self.modules_mutex.lock();
79 defer self.modules_mutex.unlock();
80 const gop = try self.modules.getOrPut(gpa, module.key());
81 if (gop.found_existing) break :di gop.value_ptr.*;
82 errdefer _ = self.modules.pop().?;
83 const di = try gpa.create(Module.DebugInfo);
84 di.* = .init;
85 gop.value_ptr.* = di;
86 break :di di;
87 };
88 return module.unwindFrame(gpa, di, context);
89}
90
91pub fn getSymbolAtAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
92 comptime assert(target_supported);
93 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
94 const di: *Module.DebugInfo = di: {
95 self.modules_mutex.lock();
96 defer self.modules_mutex.unlock();
97 const gop = try self.modules.getOrPut(gpa, module.key());
98 if (gop.found_existing) break :di gop.value_ptr.*;
99 errdefer _ = self.modules.pop().?;
100 const di = try gpa.create(Module.DebugInfo);
101 di.* = .init;
102 gop.value_ptr.* = di;
103 break :di di;
104 };
105 return module.getSymbolAtAddress(gpa, di, address);
106}
107
108pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
109 comptime assert(target_supported);
110 const module: Module = try .lookup(&self.lookup_cache, gpa, address);
111 if (module.name.len == 0) return error.MissingDebugInfo;
112 return module.name;
113}
114
115/// `void` indicates that `SelfInfo` is not supported for this target.
116///
117/// This type contains the target-specific implementation. Logically, a `Module` represents a subset
118/// of the executable with its own debug information. This typically corresponds to what ELF calls a
119/// module, i.e. a shared library or executable image, but could be anything. For instance, it would
120/// be valid to consider the entire application one module, or on the other hand to consider each
121/// object file a module.
122///
123/// Because different threads can collect stack traces concurrently, the implementation must be able
124/// to tolerate concurrent calls to any method it implements.
125///
126/// This type must must expose the following declarations:
127///
128/// ```
129/// /// Holds state cached by the implementation between calls to `lookup`.
130/// /// This may be `void`, in which case the inner declarations can be omitted.
131/// pub const LookupCache = struct {
132/// pub const init: LookupCache;
133/// pub fn deinit(lc: *LookupCache, gpa: Allocator) void;
134/// };
135/// /// Holds debug information associated with a particular `Module`.
136/// pub const DebugInfo = struct {
137/// pub const init: DebugInfo;
138/// };
139/// /// Finds the `Module` corresponding to `address`.
140/// pub fn lookup(lc: *LookupCache, gpa: Allocator, address: usize) SelfInfo.Error!Module;
141/// /// Returns a unique identifier for this `Module`, such as a load address.
142/// pub fn key(mod: *const Module) usize;
143/// /// Locates and loads location information for the symbol corresponding to `address`.
144/// pub fn getSymbolAtAddress(
145/// mod: *const Module,
146/// gpa: Allocator,
147/// di: *DebugInfo,
148/// address: usize,
149/// ) SelfInfo.Error!std.debug.Symbol;
150/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.
151/// pub const supports_unwinding: bool;
152/// /// Only required if `supports_unwinding == true`.
153/// pub const UnwindContext = struct {
154/// /// A PC value representing the location in the last frame.
155/// pc: usize,
156/// pub fn init(ctx: *std.debug.cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext;
157/// pub fn deinit(uc: *UnwindContext, gpa: Allocator) void;
158/// /// Returns the frame pointer associated with the last unwound stack frame. If the frame
159/// /// pointer is unknown, 0 may be returned instead.
160/// pub fn getFp(uc: *UnwindContext) usize;
161/// };
162/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame, and returns
163/// /// the frame's return address.
164/// pub fn unwindFrame(
165/// mod: *const Module,
166/// gpa: Allocator,
167/// di: *DebugInfo,
168/// ctx: *UnwindContext,
169/// ) SelfInfo.Error!usize;
170/// ```
171const Module: type = Module: {
172 // Allow overriding the target-specific `SelfInfo` implementation by exposing `root.debug.Module`.
173 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "Module")) {
174 break :Module root.debug.Module;
175 }
176 break :Module switch (builtin.os.tag) {
177 .linux,
178 .netbsd,
179 .freebsd,
180 .dragonfly,
181 .openbsd,
182 .solaris,
183 .illumos,
184 => @import("SelfInfo/ElfModule.zig"),
185
186 .macos,
187 .ios,
188 .watchos,
189 .tvos,
190 .visionos,
191 => @import("SelfInfo/DarwinModule.zig"),
192
193 .uefi,
194 .windows,
195 => @import("SelfInfo/WindowsModule.zig"),
196
197 else => void,
198 };
199};
200
201/// An implementation of `UnwindContext` useful for DWARF-based unwinders. The `Module.unwindFrame`
202/// implementation should wrap `DwarfUnwindContext.unwindFrame`.
203pub const DwarfUnwindContext = struct {
204 cfa: ?usize,
205 pc: usize,
206 cpu_context: CpuContext,
207 vm: Dwarf.Unwind.VirtualMachine,
208 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
209
210 pub const Cache = struct {
211 /// TODO: to allow `DwarfUnwindContext` to work on freestanding, we currently just don't use
212 /// this mutex there. That's a bad solution, but a better one depends on the standard
213 /// library's general support for "bring your own OS" being improved.
214 mutex: switch (builtin.os.tag) {
215 else => std.Thread.Mutex,
216 .freestanding, .other => struct {
217 fn lock(_: @This()) void {}
218 fn unlock(_: @This()) void {}
219 },
220 },
221 buf: [num_slots]Slot,
222 const num_slots = 2048;
223 const Slot = struct {
224 const max_regs = 32;
225 pc: usize,
226 cie: *const Dwarf.Unwind.CommonInformationEntry,
227 cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule,
228 rules_regs: [max_regs]u16,
229 rules: [max_regs]Dwarf.Unwind.VirtualMachine.RegisterRule,
230 num_rules: u8,
231 };
232 /// This is a function rather than a declaration to avoid lowering a very large struct value
233 /// into the binary when most of it is `undefined`.
234 pub fn init(c: *Cache) void {
235 c.mutex = .{};
236 for (&c.buf) |*slot| slot.pc = 0;
237 }
238 };
239
240 pub fn init(cpu_context: *const CpuContext) DwarfUnwindContext {
241 comptime assert(supports_unwinding);
242
243 // `@constCast` is safe because we aren't going to store to the resulting pointer.
244 const raw_pc_ptr = regNative(@constCast(cpu_context), ip_reg_num) catch |err| switch (err) {
245 error.InvalidRegister => unreachable, // `ip_reg_num` is definitely valid
246 error.UnsupportedRegister => unreachable, // the implementation needs to support ip
247 error.IncompatibleRegisterSize => unreachable, // ip is definitely `usize`-sized
248 };
249 const pc = stripInstructionPtrAuthCode(raw_pc_ptr.*);
250
251 return .{
252 .cfa = null,
253 .pc = pc,
254 .cpu_context = cpu_context.*,
255 .vm = .{},
256 .stack_machine = .{},
257 };
258 }
259
260 pub fn deinit(self: *DwarfUnwindContext, gpa: Allocator) void {
261 self.vm.deinit(gpa);
262 self.stack_machine.deinit(gpa);
263 self.* = undefined;
264 }
265
266 pub fn getFp(self: *const DwarfUnwindContext) usize {
267 // `@constCast` is safe because we aren't going to store to the resulting pointer.
268 const ptr = regNative(@constCast(&self.cpu_context), fp_reg_num) catch |err| switch (err) {
269 error.InvalidRegister => unreachable, // `fp_reg_num` is definitely valid
270 error.UnsupportedRegister => unreachable, // the implementation needs to support fp
271 error.IncompatibleRegisterSize => unreachable, // fp is a pointer so is `usize`-sized
272 };
273 return ptr.*;
274 }
275
276 /// Unwind a stack frame using DWARF unwinding info, updating the register context.
277 ///
278 /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.
279 /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter
280 /// may require lazily loading the data in those sections.
281 ///
282 /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when using macOS'
283 /// `__unwind_info` section.
284 pub fn unwindFrame(
285 context: *DwarfUnwindContext,
286 cache: *Cache,
287 gpa: Allocator,
288 unwind: *const Dwarf.Unwind,
289 load_offset: usize,
290 explicit_fde_offset: ?usize,
291 ) Error!usize {
292 return unwindFrameInner(context, cache, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {
293 error.InvalidDebugInfo,
294 error.MissingDebugInfo,
295 error.UnsupportedDebugInfo,
296 error.OutOfMemory,
297 => |e| return e,
298
299 error.UnsupportedAddrSize,
300 error.UnimplementedUserOpcode,
301 error.UnimplementedExpressionCall,
302 error.UnimplementedOpcode,
303 error.UnimplementedTypedComparison,
304 error.UnimplementedTypeConversion,
305 error.UnknownExpressionOpcode,
306 error.UnsupportedRegister,
307 => return error.UnsupportedDebugInfo,
308
309 error.InvalidRegister,
310 error.ReadFailed,
311 error.EndOfStream,
312 error.IncompatibleRegisterSize,
313 error.Overflow,
314 error.StreamTooLong,
315 error.InvalidOperand,
316 error.InvalidOpcode,
317 error.InvalidOperation,
318 error.InvalidCFARule,
319 error.IncompleteExpressionContext,
320 error.InvalidCFAOpcode,
321 error.InvalidExpression,
322 error.InvalidFrameBase,
323 error.InvalidIntegralTypeSize,
324 error.InvalidSubExpression,
325 error.InvalidTypeLength,
326 error.TruncatedIntegralType,
327 error.DivisionByZero,
328 error.InvalidExpressionValue,
329 error.NoExpressionValue,
330 error.RegisterSizeMismatch,
331 => return error.InvalidDebugInfo,
332 };
333 }
334 fn unwindFrameInner(
335 context: *DwarfUnwindContext,
336 cache: *Cache,
337 gpa: Allocator,
338 unwind: *const Dwarf.Unwind,
339 load_offset: usize,
340 explicit_fde_offset: ?usize,
341 ) !usize {
342 comptime assert(supports_unwinding);
343
344 if (context.pc == 0) return 0;
345
346 const pc_vaddr = context.pc - load_offset;
347
348 const cache_slot: Cache.Slot = slot: {
349 const slot_idx = std.hash.int(pc_vaddr) % Cache.num_slots;
350
351 {
352 cache.mutex.lock();
353 defer cache.mutex.unlock();
354 if (cache.buf[slot_idx].pc == pc_vaddr) break :slot cache.buf[slot_idx];
355 }
356
357 const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
358 pc_vaddr,
359 @sizeOf(usize),
360 native_endian,
361 ) orelse return error.MissingDebugInfo;
362 const cie, const fde = try unwind.getFde(fde_offset, native_endian);
363
364 // Check if the FDE *actually* includes the pc (`lookupPc` can return false positives).
365 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) {
366 return error.MissingDebugInfo;
367 }
368
369 context.vm.reset();
370
371 const row = try context.vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian);
372
373 if (row.columns.len > Cache.Slot.max_regs) return error.UnsupportedDebugInfo;
374
375 var slot: Cache.Slot = .{
376 .pc = pc_vaddr,
377 .cie = cie,
378 .cfa_rule = row.cfa,
379 .rules_regs = undefined,
380 .rules = undefined,
381 .num_rules = 0,
382 };
383 for (context.vm.rowColumns(&row)) |col| {
384 const i = slot.num_rules;
385 slot.rules_regs[i] = col.register;
386 slot.rules[i] = col.rule;
387 slot.num_rules += 1;
388 }
389
390 {
391 cache.mutex.lock();
392 defer cache.mutex.unlock();
393 cache.buf[slot_idx] = slot;
394 }
395
396 break :slot slot;
397 };
398
399 const format = cache_slot.cie.format;
400 const return_address_register = cache_slot.cie.return_address_register;
401
402 context.cfa = switch (cache_slot.cfa_rule) {
403 .none => return error.InvalidCFARule,
404 .reg_off => |ro| cfa: {
405 const ptr = try regNative(&context.cpu_context, ro.register);
406 break :cfa try applyOffset(ptr.*, ro.offset);
407 },
408 .expression => |expr| cfa: {
409 context.stack_machine.reset();
410 const value = try context.stack_machine.run(expr, gpa, .{
411 .format = format,
412 .cpu_context = &context.cpu_context,
413 }, context.cfa) orelse return error.NoExpressionValue;
414 switch (value) {
415 .generic => |g| break :cfa g,
416 else => return error.InvalidExpressionValue,
417 }
418 },
419 };
420
421 // If unspecified, we'll use the default rule for the return address register, which is
422 // typically equivalent to `.undefined` (meaning there is no return address), but may be
423 // overriden by ABIs.
424 var has_return_address: bool = builtin.cpu.arch.isAARCH64() and
425 return_address_register >= 19 and
426 return_address_register <= 28;
427
428 // Create a copy of the CPU context, to which we will apply the new rules.
429 var new_cpu_context = context.cpu_context;
430
431 // On all implemented architectures, the CFA is defined as being the previous frame's SP
432 (try regNative(&new_cpu_context, sp_reg_num)).* = context.cfa.?;
433
434 const rules_len = cache_slot.num_rules;
435 for (cache_slot.rules_regs[0..rules_len], cache_slot.rules[0..rules_len]) |register, rule| {
436 const new_val: union(enum) {
437 same,
438 undefined,
439 val: usize,
440 bytes: []const u8,
441 } = switch (rule) {
442 .default => val: {
443 // The default rule is typically equivalent to `.undefined`, but ABIs may override it.
444 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) {
445 break :val .same;
446 }
447 break :val .undefined;
448 },
449 .undefined => .undefined,
450 .same_value => .same,
451 .offset => |offset| val: {
452 const ptr: *const usize = @ptrFromInt(try applyOffset(context.cfa.?, offset));
453 break :val .{ .val = ptr.* };
454 },
455 .val_offset => |offset| .{ .val = try applyOffset(context.cfa.?, offset) },
456 .register => |r| .{ .bytes = try context.cpu_context.dwarfRegisterBytes(r) },
457 .expression => |expr| val: {
458 context.stack_machine.reset();
459 const value = try context.stack_machine.run(expr, gpa, .{
460 .format = format,
461 .cpu_context = &context.cpu_context,
462 }, context.cfa.?) orelse return error.NoExpressionValue;
463 const ptr: *const usize = switch (value) {
464 .generic => |addr| @ptrFromInt(addr),
465 else => return error.InvalidExpressionValue,
466 };
467 break :val .{ .val = ptr.* };
468 },
469 .val_expression => |expr| val: {
470 context.stack_machine.reset();
471 const value = try context.stack_machine.run(expr, gpa, .{
472 .format = format,
473 .cpu_context = &context.cpu_context,
474 }, context.cfa.?) orelse return error.NoExpressionValue;
475 switch (value) {
476 .generic => |val| break :val .{ .val = val },
477 else => return error.InvalidExpressionValue,
478 }
479 },
480 };
481 switch (new_val) {
482 .same => {},
483 .undefined => {
484 const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register));
485 @memset(dest, undefined);
486 },
487 .val => |val| {
488 const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register));
489 if (dest.len != @sizeOf(usize)) return error.RegisterSizeMismatch;
490 const dest_ptr: *align(1) usize = @ptrCast(dest);
491 dest_ptr.* = val;
492 },
493 .bytes => |src| {
494 const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register));
495 if (dest.len != src.len) return error.RegisterSizeMismatch;
496 @memcpy(dest, src);
497 },
498 }
499 if (register == return_address_register) {
500 has_return_address = new_val != .undefined;
501 }
502 }
503
504 const return_address: usize = if (has_return_address) pc: {
505 const raw_ptr = try regNative(&new_cpu_context, return_address_register);
506 break :pc stripInstructionPtrAuthCode(raw_ptr.*);
507 } else 0;
508
509 (try regNative(&new_cpu_context, ip_reg_num)).* = return_address;
510
511 // The new CPU context is complete; flush changes.
512 context.cpu_context = new_cpu_context;
513
514 // The caller will subtract 1 from the return address to get an address corresponding to the
515 // function call. However, if this is a signal frame, that's actually incorrect, because the
516 // "return address" we have is the instruction which triggered the signal (if the signal
517 // handler returned, the instruction would be re-run). Compensate for this by incrementing
518 // the address in that case.
519 const adjusted_ret_addr = if (cache_slot.cie.is_signal_frame) return_address +| 1 else return_address;
520
521 // We also want to do that same subtraction here to get the PC for the next frame's FDE.
522 // This is because if the callee was noreturn, then the function call might be the caller's
523 // last instruction, so `return_address` might actually point outside of it!
524 context.pc = adjusted_ret_addr -| 1;
525
526 return adjusted_ret_addr;
527 }
528 /// Since register rules are applied (usually) during a panic,
529 /// checked addition / subtraction is used so that we can return
530 /// an error and fall back to FP-based unwinding.
531 fn applyOffset(base: usize, offset: i64) !usize {
532 return if (offset >= 0)
533 try std.math.add(usize, base, @as(usize, @intCast(offset)))
534 else
535 try std.math.sub(usize, base, @as(usize, @intCast(-offset)));
536 }
537
538 pub fn regNative(ctx: *CpuContext, num: u16) error{
539 InvalidRegister,
540 UnsupportedRegister,
541 IncompatibleRegisterSize,
542 }!*align(1) usize {
543 const bytes = try ctx.dwarfRegisterBytes(num);
544 if (bytes.len != @sizeOf(usize)) return error.IncompatibleRegisterSize;
545 return @ptrCast(bytes);
546 }
547
548 const ip_reg_num = Dwarf.ipRegNum(native_arch).?;
549 const fp_reg_num = Dwarf.fpRegNum(native_arch);
550 const sp_reg_num = Dwarf.spRegNum(native_arch);
551};
lib/std/debug/SelfInfo/Darwin.zig created+993
......@@ -0,0 +1,993 @@
1mutex: std.Thread.Mutex,
2/// Accessed through `Module.Adapter`.
3modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false),
4ofiles: std.StringArrayHashMapUnmanaged(?OFile),
5
6pub const init: SelfInfo = .{
7 .mutex = .{},
8 .modules = .empty,
9 .ofiles = .empty,
10};
11pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
12 for (si.modules.keys()) |*module| {
13 unwind: {
14 const u = &(module.unwind orelse break :unwind catch break :unwind);
15 if (u.dwarf) |*dwarf| dwarf.deinit(gpa);
16 }
17 loaded: {
18 const l = &(module.loaded_macho orelse break :loaded catch break :loaded);
19 gpa.free(l.symbols);
20 posix.munmap(l.mapped_memory);
21 }
22 }
23 for (si.ofiles.values()) |*opt_ofile| {
24 const ofile = &(opt_ofile.* orelse continue);
25 ofile.dwarf.deinit(gpa);
26 ofile.symbols_by_name.deinit(gpa);
27 posix.munmap(ofile.mapped_memory);
28 }
29 si.modules.deinit(gpa);
30 si.ofiles.deinit(gpa);
31}
32
33pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
34 const module = try si.findModule(gpa, address);
35 defer si.mutex.unlock();
36
37 const loaded_macho = try module.getLoadedMachO(gpa);
38
39 const vaddr = address - loaded_macho.vaddr_offset;
40 const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown;
41
42 // offset of `address` from start of `symbol`
43 const address_symbol_offset = vaddr - symbol.addr;
44
45 // Take the symbol name from the N_FUN STAB entry, we're going to
46 // use it if we fail to find the DWARF infos
47 const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0);
48
49 // If any information is missing, we can at least return this from now on.
50 const sym_only_result: std.debug.Symbol = .{
51 .name = stab_symbol,
52 .compile_unit_name = null,
53 .source_location = null,
54 };
55
56 if (symbol.ofile == MachoSymbol.unknown_ofile) {
57 // We don't have STAB info, so can't track down the object file; all we can do is the symbol name.
58 return sym_only_result;
59 }
60
61 const o_file: *OFile = of: {
62 const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0);
63 const gop = try si.ofiles.getOrPut(gpa, path);
64 if (!gop.found_existing) {
65 gop.value_ptr.* = loadOFile(gpa, path) catch null;
66 }
67 if (gop.value_ptr.*) |*o_file| {
68 break :of o_file;
69 } else {
70 return sym_only_result;
71 }
72 };
73
74 const symbol_index = o_file.symbols_by_name.getKeyAdapted(
75 @as([]const u8, stab_symbol),
76 @as(OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }),
77 ) orelse return sym_only_result;
78 const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value;
79
80 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result;
81
82 return .{
83 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol,
84 .compile_unit_name = compile_unit.die.getAttrString(
85 &o_file.dwarf,
86 native_endian,
87 std.dwarf.AT.name,
88 o_file.dwarf.section(.debug_str),
89 compile_unit,
90 ) catch |err| switch (err) {
91 error.MissingDebugInfo, error.InvalidDebugInfo => null,
92 },
93 .source_location = o_file.dwarf.getLineNumberInfo(
94 gpa,
95 native_endian,
96 compile_unit,
97 symbol_ofile_vaddr + address_symbol_offset,
98 ) catch null,
99 };
100}
101pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
102 const module = try si.findModule(gpa, address);
103 defer si.mutex.unlock();
104 return module.name;
105}
106
107pub const can_unwind: bool = true;
108pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;
109/// Unwind a frame using MachO compact unwind info (from `__unwind_info`).
110/// If the compact encoding can't encode a way to unwind a frame, it will
111/// defer unwinding to DWARF, in which case `__eh_frame` will be used if available.
112pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
113 return unwindFrameInner(si, gpa, context) catch |err| switch (err) {
114 error.InvalidDebugInfo,
115 error.MissingDebugInfo,
116 error.UnsupportedDebugInfo,
117 error.ReadFailed,
118 error.OutOfMemory,
119 error.Unexpected,
120 => |e| return e,
121 error.UnsupportedRegister,
122 error.UnsupportedAddrSize,
123 error.UnimplementedUserOpcode,
124 => return error.UnsupportedDebugInfo,
125 error.Overflow,
126 error.EndOfStream,
127 error.StreamTooLong,
128 error.InvalidOpcode,
129 error.InvalidOperation,
130 error.InvalidOperand,
131 error.InvalidRegister,
132 error.IncompatibleRegisterSize,
133 => return error.InvalidDebugInfo,
134 };
135}
136fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize {
137 const module = try si.findModule(gpa, context.pc);
138 defer si.mutex.unlock();
139
140 const unwind: *Module.Unwind = try module.getUnwindInfo(gpa);
141
142 const ip_reg_num = comptime Dwarf.ipRegNum(builtin.target.cpu.arch).?;
143 const fp_reg_num = comptime Dwarf.fpRegNum(builtin.target.cpu.arch);
144 const sp_reg_num = comptime Dwarf.spRegNum(builtin.target.cpu.arch);
145
146 const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;
147 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;
148 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
149
150 const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
151 if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo;
152 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
153 if (indices.len == 0) return error.MissingDebugInfo;
154
155 // offset of the PC into the `__TEXT` segment
156 const pc_text_offset = context.pc - module.text_base;
157
158 const start_offset: u32, const first_level_offset: u32 = index: {
159 var left: usize = 0;
160 var len: usize = indices.len;
161 while (len > 1) {
162 const mid = left + len / 2;
163 if (pc_text_offset < indices[mid].functionOffset) {
164 len /= 2;
165 } else {
166 left = mid;
167 len -= len / 2;
168 }
169 }
170 break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
171 };
172 // An offset of 0 is a sentinel indicating a range does not have unwind info.
173 if (start_offset == 0) return error.MissingDebugInfo;
174
175 const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
176 if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo;
177 const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
178 unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
179 );
180
181 if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo;
182 const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
183
184 const entry: struct {
185 function_offset: usize,
186 raw_encoding: u32,
187 } = switch (kind.*) {
188 .REGULAR => entry: {
189 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo;
190 const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
191
192 const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
193 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
194 const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
195 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
196 );
197 if (entries.len == 0) return error.InvalidDebugInfo;
198
199 var left: usize = 0;
200 var len: usize = entries.len;
201 while (len > 1) {
202 const mid = left + len / 2;
203 if (pc_text_offset < entries[mid].functionOffset) {
204 len /= 2;
205 } else {
206 left = mid;
207 len -= len / 2;
208 }
209 }
210 break :entry .{
211 .function_offset = entries[left].functionOffset,
212 .raw_encoding = entries[left].encoding,
213 };
214 },
215 .COMPRESSED => entry: {
216 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo;
217 const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
218
219 const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
220 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
221 const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
222 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
223 );
224 if (entries.len == 0) return error.InvalidDebugInfo;
225
226 var left: usize = 0;
227 var len: usize = entries.len;
228 while (len > 1) {
229 const mid = left + len / 2;
230 if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
231 len /= 2;
232 } else {
233 left = mid;
234 len -= len / 2;
235 }
236 }
237 const entry = entries[left];
238
239 const function_offset = first_level_offset + entry.funcOffset;
240 if (entry.encodingIndex < common_encodings.len) {
241 break :entry .{
242 .function_offset = function_offset,
243 .raw_encoding = common_encodings[entry.encodingIndex],
244 };
245 }
246
247 const local_index = entry.encodingIndex - common_encodings.len;
248 const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
249 if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo;
250 const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
251 unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
252 );
253 if (local_index >= local_encodings.len) return error.InvalidDebugInfo;
254 break :entry .{
255 .function_offset = function_offset,
256 .raw_encoding = local_encodings[local_index],
257 };
258 },
259 else => return error.InvalidDebugInfo,
260 };
261
262 if (entry.raw_encoding == 0) return error.MissingDebugInfo;
263
264 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
265 const new_ip = switch (builtin.cpu.arch) {
266 .x86_64 => switch (encoding.mode.x86_64) {
267 .OLD => return error.UnsupportedDebugInfo,
268 .RBP_FRAME => ip: {
269 const frame = encoding.value.x86_64.frame;
270
271 const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
272 const new_sp = fp + 2 * @sizeOf(usize);
273
274 const ip_ptr = fp + @sizeOf(usize);
275 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
276 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
277
278 (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
279 (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
280 (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
281
282 const regs: [5]u3 = .{
283 frame.reg0,
284 frame.reg1,
285 frame.reg2,
286 frame.reg3,
287 frame.reg4,
288 };
289 for (regs, 0..) |reg, i| {
290 if (reg == 0) continue;
291 const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
292 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
293 (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*;
294 }
295
296 break :ip new_ip;
297 },
298 .STACK_IMMD,
299 .STACK_IND,
300 => ip: {
301 const frameless = encoding.value.x86_64.frameless;
302
303 const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
304 const stack_size: usize = stack_size: {
305 if (encoding.mode.x86_64 == .STACK_IMMD) {
306 break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
307 }
308 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
309 const sub_offset_addr =
310 module.text_base +
311 entry.function_offset +
312 frameless.stack.indirect.sub_offset;
313 // `sub_offset_addr` points to the offset of the literal within the instruction
314 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
315 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust);
316 };
317
318 // Decode the Lehmer-coded sequence of registers.
319 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
320
321 // Decode the variable-based permutation number into its digits. Each digit represents
322 // an index into the list of register numbers that weren't yet used in the sequence at
323 // the time the digit was added.
324 const reg_count = frameless.stack_reg_count;
325 const ip_ptr = ip_ptr: {
326 var digits: [6]u3 = undefined;
327 var accumulator: usize = frameless.stack_reg_permutation;
328 var base: usize = 2;
329 for (0..reg_count) |i| {
330 const div = accumulator / base;
331 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
332 accumulator = div;
333 base += 1;
334 }
335
336 var registers: [6]u3 = undefined;
337 var used_indices: [6]bool = @splat(false);
338 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
339 var unused_count: u8 = 0;
340 const unused_index = for (used_indices, 0..) |used, index| {
341 if (!used) {
342 if (target_unused_index == unused_count) break index;
343 unused_count += 1;
344 }
345 } else unreachable;
346 registers[i] = @intCast(unused_index + 1);
347 used_indices[unused_index] = true;
348 }
349
350 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
351 for (0..reg_count) |i| {
352 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
353 (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
354 reg_addr += @sizeOf(usize);
355 }
356
357 break :ip_ptr reg_addr;
358 };
359
360 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
361 const new_sp = ip_ptr + @sizeOf(usize);
362
363 (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
364 (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
365
366 break :ip new_ip;
367 },
368 .DWARF => {
369 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
370 const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf);
371 return context.next(gpa, &rules);
372 },
373 },
374 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
375 .OLD => return error.UnsupportedDebugInfo,
376 .FRAMELESS => ip: {
377 const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
378 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
379 const new_ip = (try dwarfRegNative(&context.cpu_state, 30)).*;
380 (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
381 break :ip new_ip;
382 },
383 .DWARF => {
384 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
385 const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf);
386 return context.next(gpa, &rules);
387 },
388 .FRAME => ip: {
389 const frame = encoding.value.arm64.frame;
390
391 const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
392 const ip_ptr = fp + @sizeOf(usize);
393
394 var reg_addr = fp - @sizeOf(usize);
395 inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
396 if (@field(frame.x_reg_pairs, field.name) != 0) {
397 (try dwarfRegNative(&context.cpu_state, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
398 reg_addr += @sizeOf(usize);
399 (try dwarfRegNative(&context.cpu_state, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
400 reg_addr += @sizeOf(usize);
401 }
402 }
403
404 inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
405 if (@field(frame.d_reg_pairs, field.name) != 0) {
406 // Only the lower half of the 128-bit V registers are restored during unwinding
407 {
408 const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 8 + i));
409 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
410 }
411 reg_addr += @sizeOf(usize);
412 {
413 const dest: *align(1) usize = @ptrCast(try context.cpu_state.dwarfRegisterBytes(64 + 9 + i));
414 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
415 }
416 reg_addr += @sizeOf(usize);
417 }
418 }
419
420 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
421 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
422
423 (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
424 (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
425
426 break :ip new_ip;
427 },
428 },
429 else => comptime unreachable, // unimplemented
430 };
431
432 const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip);
433
434 // Like `Dwarf.SelfUnwinder.next`, adjust our next lookup pc in case the `call` was this
435 // function's last instruction making `ret_addr` one byte past its end.
436 context.pc = ret_addr -| 1;
437
438 return ret_addr;
439}
440
441/// Acquires the mutex on success.
442fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module {
443 var info: std.c.dl_info = undefined;
444 if (std.c.dladdr(@ptrFromInt(address), &info) == 0) {
445 return error.MissingDebugInfo;
446 }
447 si.mutex.lock();
448 errdefer si.mutex.unlock();
449 const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(info.fbase), Module.Adapter{});
450 errdefer comptime unreachable;
451 if (!gop.found_existing) {
452 gop.key_ptr.* = .{
453 .text_base = @intFromPtr(info.fbase),
454 .name = std.mem.span(info.fname),
455 .unwind = null,
456 .loaded_macho = null,
457 };
458 }
459 return gop.key_ptr;
460}
461
462const Module = struct {
463 text_base: usize,
464 name: []const u8,
465 unwind: ?(Error!Unwind),
466 loaded_macho: ?(Error!LoadedMachO),
467
468 const Adapter = struct {
469 pub fn hash(_: Adapter, text_base: usize) u32 {
470 return @truncate(std.hash.int(text_base));
471 }
472 pub fn eql(_: Adapter, a_text_base: usize, b_module: Module, b_index: usize) bool {
473 _ = b_index;
474 return a_text_base == b_module.text_base;
475 }
476 };
477 const Context = struct {
478 pub fn hash(_: Context, module: Module) u32 {
479 return @truncate(std.hash.int(module.text_base));
480 }
481 pub fn eql(_: Context, a_module: Module, b_module: Module, b_index: usize) bool {
482 _ = b_index;
483 return a_module.text_base == b_module.text_base;
484 }
485 };
486
487 const Unwind = struct {
488 /// The slide applied to the `__unwind_info` and `__eh_frame` sections.
489 /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr.
490 vmaddr_slide: u64,
491 /// Backed by the in-memory section mapped by the loader.
492 unwind_info: ?[]const u8,
493 /// Backed by the in-memory `__eh_frame` section mapped by the loader.
494 dwarf: ?Dwarf.Unwind,
495 };
496
497 const LoadedMachO = struct {
498 mapped_memory: []align(std.heap.page_size_min) const u8,
499 symbols: []const MachoSymbol,
500 strings: []const u8,
501 /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is
502 /// because the segments in the file on disk might differ from the ones in memory. Normally
503 /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying:
504 /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in
505 /// the dyld cache (dyld actually restart itself from cache after loading it), and the two
506 /// versions have (very) different segment base addresses. It's sort of like a large slide
507 /// has been applied to all addresses in memory. For an optimal experience, we consider the
508 /// on-disk vmaddr instead of the in-memory one.
509 vaddr_offset: usize,
510 };
511
512 fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind {
513 if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa);
514 return if (module.unwind.?) |*unwind| unwind else |err| err;
515 }
516 fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind {
517 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
518
519 var it: macho.LoadCommandIterator = .{
520 .ncmds = header.ncmds,
521 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
522 };
523 const sections, const text_vmaddr = while (it.next()) |load_cmd| {
524 if (load_cmd.cmd() != .SEGMENT_64) continue;
525 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
526 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
527 break .{ load_cmd.getSections(), segment_cmd.vmaddr };
528 } else unreachable;
529
530 const vmaddr_slide = module.text_base - text_vmaddr;
531
532 var opt_unwind_info: ?[]const u8 = null;
533 var opt_eh_frame: ?[]const u8 = null;
534 for (sections) |sect| {
535 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
536 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
537 opt_unwind_info = sect_ptr[0..@intCast(sect.size)];
538 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
539 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
540 opt_eh_frame = sect_ptr[0..@intCast(sect.size)];
541 }
542 }
543 const eh_frame = opt_eh_frame orelse return .{
544 .vmaddr_slide = vmaddr_slide,
545 .unwind_info = opt_unwind_info,
546 .dwarf = null,
547 };
548 var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame);
549 errdefer dwarf.deinit(gpa);
550 // We don't need lookups, so this call is just for scanning CIEs.
551 dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) {
552 error.ReadFailed => unreachable, // it's all fixed buffers
553 error.InvalidDebugInfo,
554 error.MissingDebugInfo,
555 error.OutOfMemory,
556 => |e| return e,
557 error.EndOfStream,
558 error.Overflow,
559 error.StreamTooLong,
560 error.InvalidOperand,
561 error.InvalidOpcode,
562 error.InvalidOperation,
563 => return error.InvalidDebugInfo,
564 error.UnsupportedAddrSize,
565 error.UnsupportedDwarfVersion,
566 error.UnimplementedUserOpcode,
567 => return error.UnsupportedDebugInfo,
568 };
569
570 return .{
571 .vmaddr_slide = vmaddr_slide,
572 .unwind_info = opt_unwind_info,
573 .dwarf = dwarf,
574 };
575 }
576
577 fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO {
578 if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) {
579 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e,
580 else => error.ReadFailed,
581 };
582 return if (module.loaded_macho.?) |*lm| lm else |err| err;
583 }
584 fn loadMachO(module: *const Module, gpa: Allocator) Error!LoadedMachO {
585 const all_mapped_memory = try mapDebugInfoFile(module.name);
586 errdefer posix.munmap(all_mapped_memory);
587
588 // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
589 // binary": a simple file format which contains Mach-O binaries for multiple targets. For
590 // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
591 // for both ARM64 macOS and x86_64 macOS.
592 if (all_mapped_memory.len < 4) return error.InvalidDebugInfo;
593 const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*;
594 // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`.
595 const mapped_macho = switch (magic) {
596 macho.MH_MAGIC_64 => all_mapped_memory,
597
598 macho.FAT_CIGAM => mapped_macho: {
599 // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing
600 // is big-endian, so we'll be swapping some bytes.
601 if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo;
602 const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr);
603 const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header));
604 const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)];
605 const native_cpu_type = switch (builtin.cpu.arch) {
606 .x86_64 => macho.CPU_TYPE_X86_64,
607 .aarch64 => macho.CPU_TYPE_ARM64,
608 else => comptime unreachable,
609 };
610 for (archs) |*arch| {
611 if (@byteSwap(arch.cputype) != native_cpu_type) continue;
612 const offset = @byteSwap(arch.offset);
613 const size = @byteSwap(arch.size);
614 break :mapped_macho all_mapped_memory[offset..][0..size];
615 }
616 // Our native architecture was not present in the fat binary.
617 return error.MissingDebugInfo;
618 },
619
620 // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It
621 // will be fairly easy to add support here if necessary; it's very similar to above.
622 macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
623
624 else => return error.InvalidDebugInfo,
625 };
626
627 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr));
628 if (hdr.magic != macho.MH_MAGIC_64)
629 return error.InvalidDebugInfo;
630
631 const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: {
632 var it: macho.LoadCommandIterator = .{
633 .ncmds = hdr.ncmds,
634 .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
635 };
636 var symtab: ?macho.symtab_command = null;
637 var text_vmaddr: ?u64 = null;
638 while (it.next()) |cmd| switch (cmd.cmd()) {
639 .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
640 .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
641 if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
642 text_vmaddr = seg_cmd.vmaddr;
643 },
644 else => {},
645 };
646 break :lc_iter .{
647 symtab orelse return error.MissingDebugInfo,
648 text_vmaddr orelse return error.MissingDebugInfo,
649 };
650 };
651
652 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]);
653 const syms = syms_ptr[0..symtab.nsyms];
654 const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
655
656 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
657 defer symbols.deinit(gpa);
658
659 // This map is temporary; it is used only to detect duplicates here. This is
660 // necessary because we prefer to use STAB ("symbolic debugging table") symbols,
661 // but they might not be present, so we track normal symbols too.
662 // Indices match 1-1 with those of `symbols`.
663 var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty;
664 defer symbol_names.deinit(gpa);
665 try symbol_names.ensureUnusedCapacity(gpa, syms.len);
666
667 var ofile: u32 = undefined;
668 var last_sym: MachoSymbol = undefined;
669 var state: enum {
670 init,
671 oso_open,
672 oso_close,
673 bnsym,
674 fun_strx,
675 fun_size,
676 ensym,
677 } = .init;
678
679 for (syms) |*sym| {
680 if (sym.n_type.bits.is_stab == 0) {
681 if (sym.n_strx == 0) continue;
682 switch (sym.n_type.bits.type) {
683 .undf, .pbud, .indr, .abs, _ => continue,
684 .sect => {
685 const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
686 const gop = symbol_names.getOrPutAssumeCapacity(name);
687 if (!gop.found_existing) {
688 assert(gop.index == symbols.items.len);
689 symbols.appendAssumeCapacity(.{
690 .strx = sym.n_strx,
691 .addr = sym.n_value,
692 .ofile = MachoSymbol.unknown_ofile,
693 });
694 }
695 },
696 }
697 continue;
698 }
699
700 // TODO handle globals N_GSYM, and statics N_STSYM
701 switch (sym.n_type.stab) {
702 .oso => switch (state) {
703 .init, .oso_close => {
704 state = .oso_open;
705 ofile = sym.n_strx;
706 },
707 else => return error.InvalidDebugInfo,
708 },
709 .bnsym => switch (state) {
710 .oso_open, .ensym => {
711 state = .bnsym;
712 last_sym = .{
713 .strx = 0,
714 .addr = sym.n_value,
715 .ofile = ofile,
716 };
717 },
718 else => return error.InvalidDebugInfo,
719 },
720 .fun => switch (state) {
721 .bnsym => {
722 state = .fun_strx;
723 last_sym.strx = sym.n_strx;
724 },
725 .fun_strx => {
726 state = .fun_size;
727 },
728 else => return error.InvalidDebugInfo,
729 },
730 .ensym => switch (state) {
731 .fun_size => {
732 state = .ensym;
733 if (last_sym.strx != 0) {
734 const name = std.mem.sliceTo(strings[last_sym.strx..], 0);
735 const gop = symbol_names.getOrPutAssumeCapacity(name);
736 if (!gop.found_existing) {
737 assert(gop.index == symbols.items.len);
738 symbols.appendAssumeCapacity(last_sym);
739 } else {
740 symbols.items[gop.index] = last_sym;
741 }
742 }
743 },
744 else => return error.InvalidDebugInfo,
745 },
746 .so => switch (state) {
747 .init, .oso_close => {},
748 .oso_open, .ensym => {
749 state = .oso_close;
750 },
751 else => return error.InvalidDebugInfo,
752 },
753 else => {},
754 }
755 }
756
757 switch (state) {
758 .init => {
759 // Missing STAB symtab entries is still okay, unless there were also no normal symbols.
760 if (symbols.items.len == 0) return error.MissingDebugInfo;
761 },
762 .oso_close => {},
763 else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab
764 }
765
766 const symbols_slice = try symbols.toOwnedSlice(gpa);
767 errdefer gpa.free(symbols_slice);
768
769 // Even though lld emits symbols in ascending order, this debug code
770 // should work for programs linked in any valid way.
771 // This sort is so that we can binary search later.
772 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
773
774 return .{
775 .mapped_memory = all_mapped_memory,
776 .symbols = symbols_slice,
777 .strings = strings,
778 .vaddr_offset = module.text_base - text_vmaddr,
779 };
780 }
781};
782
783const OFile = struct {
784 mapped_memory: []align(std.heap.page_size_min) const u8,
785 dwarf: Dwarf,
786 strtab: []const u8,
787 symtab: []align(1) const macho.nlist_64,
788 /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed
789 /// through `SymbolAdapter`, so that the symbol name is used as the logical key.
790 symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true),
791
792 const SymbolAdapter = struct {
793 strtab: []const u8,
794 symtab: []align(1) const macho.nlist_64,
795 pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {
796 _ = ctx;
797 return @truncate(std.hash.Wyhash.hash(0, sym_name));
798 }
799 pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool {
800 _ = b_index;
801 const b_sym = ctx.symtab[b_sym_index];
802 const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0);
803 return mem.eql(u8, a_sym_name, b_sym_name);
804 }
805 };
806};
807
808const MachoSymbol = struct {
809 strx: u32,
810 addr: u64,
811 /// Value may be `unknown_ofile`.
812 ofile: u32,
813 const unknown_ofile = std.math.maxInt(u32);
814 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
815 _ = context;
816 return lhs.addr < rhs.addr;
817 }
818 /// Assumes that `symbols` is sorted in order of ascending `addr`.
819 fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
820 if (symbols.len == 0) return null; // no potential match
821 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
822 var left: usize = 0;
823 var len: usize = symbols.len;
824 while (len > 1) {
825 const mid = left + len / 2;
826 if (address < symbols[mid].addr) {
827 len /= 2;
828 } else {
829 left = mid;
830 len -= len / 2;
831 }
832 }
833 return &symbols[left];
834 }
835
836 test find {
837 const symbols: []const MachoSymbol = &.{
838 .{ .addr = 100, .strx = undefined, .ofile = undefined },
839 .{ .addr = 200, .strx = undefined, .ofile = undefined },
840 .{ .addr = 300, .strx = undefined, .ofile = undefined },
841 };
842
843 try testing.expectEqual(null, find(symbols, 0));
844 try testing.expectEqual(null, find(symbols, 99));
845 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
846 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
847 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
848
849 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
850 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
851 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
852
853 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
854 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
855 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
856 }
857};
858test {
859 _ = MachoSymbol;
860}
861
862/// Uses `mmap` to map the file at `path` into memory.
863fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
864 const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
865 error.FileNotFound => return error.MissingDebugInfo,
866 else => return error.ReadFailed,
867 };
868 defer file.close();
869
870 const file_end_pos = file.getEndPos() catch |err| switch (err) {
871 error.Unexpected => |e| return e,
872 else => return error.ReadFailed,
873 };
874 const file_len = std.math.cast(usize, file_end_pos) orelse return error.InvalidDebugInfo;
875
876 return posix.mmap(
877 null,
878 file_len,
879 posix.PROT.READ,
880 .{ .TYPE = .SHARED },
881 file.handle,
882 0,
883 ) catch |err| switch (err) {
884 error.Unexpected => |e| return e,
885 else => return error.ReadFailed,
886 };
887}
888
889fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
890 const mapped_mem = try mapDebugInfoFile(o_file_path);
891 errdefer posix.munmap(mapped_mem);
892
893 if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
894 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
895 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
896
897 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
898 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
899 var symtab_cmd: ?macho.symtab_command = null;
900 var it: macho.LoadCommandIterator = .{
901 .ncmds = hdr.ncmds,
902 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
903 };
904 while (it.next()) |cmd| switch (cmd.cmd()) {
905 .SEGMENT_64 => seg_cmd = cmd,
906 .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
907 else => {},
908 };
909 break :cmds .{
910 seg_cmd orelse return error.MissingDebugInfo,
911 symtab_cmd orelse return error.MissingDebugInfo,
912 };
913 };
914
915 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
916 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
917 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
918
919 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
920 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
921 const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
922
923 // TODO handle tentative (common) symbols
924 var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty;
925 defer symbols_by_name.deinit(gpa);
926 try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len));
927 for (symtab, 0..) |sym, sym_index| {
928 if (sym.n_strx == 0) continue;
929 switch (sym.n_type.bits.type) {
930 .undf => continue, // includes tentative symbols
931 .abs => continue,
932 else => {},
933 }
934 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
935 const gop = symbols_by_name.getOrPutAssumeCapacityAdapted(
936 @as([]const u8, sym_name),
937 @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }),
938 );
939 if (gop.found_existing) return error.InvalidDebugInfo;
940 gop.key_ptr.* = @intCast(sym_index);
941 }
942
943 var sections: Dwarf.SectionArray = @splat(null);
944 for (seg_cmd.getSections()) |sect| {
945 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
946
947 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
948 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
949 } else continue;
950
951 if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo;
952 const section_bytes = mapped_mem[sect.offset..][0..sect.size];
953 sections[section_index] = .{
954 .data = section_bytes,
955 .owned = false,
956 };
957 }
958
959 const missing_debug_info =
960 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
961 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
962 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
963 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
964 if (missing_debug_info) return error.MissingDebugInfo;
965
966 var dwarf: Dwarf = .{ .sections = sections };
967 errdefer dwarf.deinit(gpa);
968 try dwarf.open(gpa, native_endian);
969
970 return .{
971 .mapped_memory = mapped_mem,
972 .dwarf = dwarf,
973 .strtab = strtab,
974 .symtab = symtab,
975 .symbols_by_name = symbols_by_name.move(),
976 };
977}
978
979const std = @import("std");
980const Allocator = std.mem.Allocator;
981const Dwarf = std.debug.Dwarf;
982const Error = std.debug.SelfInfoError;
983const assert = std.debug.assert;
984const posix = std.posix;
985const macho = std.macho;
986const mem = std.mem;
987const testing = std.testing;
988const dwarfRegNative = std.debug.Dwarf.SelfUnwinder.regNative;
989
990const builtin = @import("builtin");
991const native_endian = builtin.target.cpu.arch.endian();
992
993const SelfInfo = @This();
lib/std/debug/SelfInfo/DarwinModule.zig deleted-954
......@@ -1,954 +0,0 @@
1/// The runtime address where __TEXT is loaded.
2text_base: usize,
3name: []const u8,
4
5pub fn key(m: *const DarwinModule) usize {
6 return m.text_base;
7}
8
9/// No cache needed, because `_dyld_get_image_header` etc are already fast.
10pub const LookupCache = void;
11pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!DarwinModule {
12 _ = cache;
13 _ = gpa;
14 var info: std.c.dl_info = undefined;
15 switch (std.c.dladdr(@ptrFromInt(address), &info)) {
16 0 => return error.MissingDebugInfo,
17 else => return .{
18 .name = std.mem.span(info.fname),
19 .text_base = @intFromPtr(info.fbase),
20 },
21 }
22}
23fn loadUnwindInfo(module: *const DarwinModule, gpa: Allocator, out: *DebugInfo) !void {
24 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
25
26 var it: macho.LoadCommandIterator = .{
27 .ncmds = header.ncmds,
28 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
29 };
30 const sections, const text_vmaddr = while (it.next()) |load_cmd| {
31 if (load_cmd.cmd() != .SEGMENT_64) continue;
32 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
33 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
34 break .{ load_cmd.getSections(), segment_cmd.vmaddr };
35 } else unreachable;
36
37 const vmaddr_slide = module.text_base - text_vmaddr;
38
39 var opt_unwind_info: ?[]const u8 = null;
40 var opt_eh_frame: ?[]const u8 = null;
41 for (sections) |sect| {
42 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
43 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
44 opt_unwind_info = sect_ptr[0..@intCast(sect.size)];
45 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
46 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
47 opt_eh_frame = sect_ptr[0..@intCast(sect.size)];
48 }
49 }
50 const eh_frame = opt_eh_frame orelse {
51 out.unwind = .{
52 .vmaddr_slide = vmaddr_slide,
53 .unwind_info = opt_unwind_info,
54 .dwarf = null,
55 .dwarf_cache = undefined,
56 };
57 return;
58 };
59 var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame);
60 errdefer dwarf.deinit(gpa);
61 // We don't need lookups, so this call is just for scanning CIEs.
62 dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) {
63 error.ReadFailed => unreachable, // it's all fixed buffers
64 error.InvalidDebugInfo,
65 error.MissingDebugInfo,
66 error.OutOfMemory,
67 => |e| return e,
68 error.EndOfStream,
69 error.Overflow,
70 error.StreamTooLong,
71 error.InvalidOperand,
72 error.InvalidOpcode,
73 error.InvalidOperation,
74 => return error.InvalidDebugInfo,
75 error.UnsupportedAddrSize,
76 error.UnsupportedDwarfVersion,
77 error.UnimplementedUserOpcode,
78 => return error.UnsupportedDebugInfo,
79 };
80
81 const dwarf_cache = try gpa.create(UnwindContext.Cache);
82 errdefer gpa.destroy(dwarf_cache);
83 dwarf_cache.init();
84
85 out.unwind = .{
86 .vmaddr_slide = vmaddr_slide,
87 .unwind_info = opt_unwind_info,
88 .dwarf = dwarf,
89 .dwarf_cache = dwarf_cache,
90 };
91}
92fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO {
93 const all_mapped_memory = try mapDebugInfoFile(module.name);
94 errdefer posix.munmap(all_mapped_memory);
95
96 // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
97 // binary": a simple file format which contains Mach-O binaries for multiple targets. For
98 // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
99 // for both ARM64 Macs and x86_64 Macs.
100 if (all_mapped_memory.len < 4) return error.InvalidDebugInfo;
101 const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*;
102 // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`.
103 const mapped_macho = switch (magic) {
104 macho.MH_MAGIC_64 => all_mapped_memory,
105
106 macho.FAT_CIGAM => mapped_macho: {
107 // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing
108 // is big-endian, so we'll be swapping some bytes.
109 if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo;
110 const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr);
111 const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header));
112 const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)];
113 const native_cpu_type = switch (builtin.cpu.arch) {
114 .x86_64 => macho.CPU_TYPE_X86_64,
115 .aarch64 => macho.CPU_TYPE_ARM64,
116 else => comptime unreachable,
117 };
118 for (archs) |*arch| {
119 if (@byteSwap(arch.cputype) != native_cpu_type) continue;
120 const offset = @byteSwap(arch.offset);
121 const size = @byteSwap(arch.size);
122 break :mapped_macho all_mapped_memory[offset..][0..size];
123 }
124 // Our native architecture was not present in the fat binary.
125 return error.MissingDebugInfo;
126 },
127
128 // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It
129 // will be fairly easy to add support here if necessary; it's very similar to above.
130 macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
131
132 else => return error.InvalidDebugInfo,
133 };
134
135 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr));
136 if (hdr.magic != macho.MH_MAGIC_64)
137 return error.InvalidDebugInfo;
138
139 const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: {
140 var it: macho.LoadCommandIterator = .{
141 .ncmds = hdr.ncmds,
142 .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
143 };
144 var symtab: ?macho.symtab_command = null;
145 var text_vmaddr: ?u64 = null;
146 while (it.next()) |cmd| switch (cmd.cmd()) {
147 .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
148 .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
149 if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
150 text_vmaddr = seg_cmd.vmaddr;
151 },
152 else => {},
153 };
154 break :lc_iter .{
155 symtab orelse return error.MissingDebugInfo,
156 text_vmaddr orelse return error.MissingDebugInfo,
157 };
158 };
159
160 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]);
161 const syms = syms_ptr[0..symtab.nsyms];
162 const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
163
164 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
165 defer symbols.deinit(gpa);
166
167 // This map is temporary; it is used only to detect duplicates here. This is
168 // necessary because we prefer to use STAB ("symbolic debugging table") symbols,
169 // but they might not be present, so we track normal symbols too.
170 // Indices match 1-1 with those of `symbols`.
171 var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty;
172 defer symbol_names.deinit(gpa);
173 try symbol_names.ensureUnusedCapacity(gpa, syms.len);
174
175 var ofile: u32 = undefined;
176 var last_sym: MachoSymbol = undefined;
177 var state: enum {
178 init,
179 oso_open,
180 oso_close,
181 bnsym,
182 fun_strx,
183 fun_size,
184 ensym,
185 } = .init;
186
187 for (syms) |*sym| {
188 if (sym.n_type.bits.is_stab == 0) {
189 if (sym.n_strx == 0) continue;
190 switch (sym.n_type.bits.type) {
191 .undf, .pbud, .indr, .abs, _ => continue,
192 .sect => {
193 const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
194 const gop = symbol_names.getOrPutAssumeCapacity(name);
195 if (!gop.found_existing) {
196 assert(gop.index == symbols.items.len);
197 symbols.appendAssumeCapacity(.{
198 .strx = sym.n_strx,
199 .addr = sym.n_value,
200 .ofile = MachoSymbol.unknown_ofile,
201 });
202 }
203 },
204 }
205 continue;
206 }
207
208 // TODO handle globals N_GSYM, and statics N_STSYM
209 switch (sym.n_type.stab) {
210 .oso => switch (state) {
211 .init, .oso_close => {
212 state = .oso_open;
213 ofile = sym.n_strx;
214 },
215 else => return error.InvalidDebugInfo,
216 },
217 .bnsym => switch (state) {
218 .oso_open, .ensym => {
219 state = .bnsym;
220 last_sym = .{
221 .strx = 0,
222 .addr = sym.n_value,
223 .ofile = ofile,
224 };
225 },
226 else => return error.InvalidDebugInfo,
227 },
228 .fun => switch (state) {
229 .bnsym => {
230 state = .fun_strx;
231 last_sym.strx = sym.n_strx;
232 },
233 .fun_strx => {
234 state = .fun_size;
235 },
236 else => return error.InvalidDebugInfo,
237 },
238 .ensym => switch (state) {
239 .fun_size => {
240 state = .ensym;
241 if (last_sym.strx != 0) {
242 const name = std.mem.sliceTo(strings[last_sym.strx..], 0);
243 const gop = symbol_names.getOrPutAssumeCapacity(name);
244 if (!gop.found_existing) {
245 assert(gop.index == symbols.items.len);
246 symbols.appendAssumeCapacity(last_sym);
247 } else {
248 symbols.items[gop.index] = last_sym;
249 }
250 }
251 },
252 else => return error.InvalidDebugInfo,
253 },
254 .so => switch (state) {
255 .init, .oso_close => {},
256 .oso_open, .ensym => {
257 state = .oso_close;
258 },
259 else => return error.InvalidDebugInfo,
260 },
261 else => {},
262 }
263 }
264
265 switch (state) {
266 .init => {
267 // Missing STAB symtab entries is still okay, unless there were also no normal symbols.
268 if (symbols.items.len == 0) return error.MissingDebugInfo;
269 },
270 .oso_close => {},
271 else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab
272 }
273
274 const symbols_slice = try symbols.toOwnedSlice(gpa);
275 errdefer gpa.free(symbols_slice);
276
277 // Even though lld emits symbols in ascending order, this debug code
278 // should work for programs linked in any valid way.
279 // This sort is so that we can binary search later.
280 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
281
282 return .{
283 .mapped_memory = all_mapped_memory,
284 .symbols = symbols_slice,
285 .strings = strings,
286 .ofiles = .empty,
287 .vaddr_offset = module.text_base - text_vmaddr,
288 };
289}
290pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
291 // We need the lock for a few things:
292 // * loading the Mach-O module
293 // * loading the referenced object file
294 // * scanning the DWARF of that object file
295 // * building the line number table of that object file
296 // That's enough that it doesn't really seem worth scoping the lock more tightly than the whole function..
297 di.mutex.lock();
298 defer di.mutex.unlock();
299
300 if (di.loaded_macho == null) di.loaded_macho = module.loadMachO(gpa) catch |err| switch (err) {
301 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| return e,
302 else => return error.ReadFailed,
303 };
304 const loaded_macho = &di.loaded_macho.?;
305
306 const vaddr = address - loaded_macho.vaddr_offset;
307 const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown;
308
309 // offset of `address` from start of `symbol`
310 const address_symbol_offset = vaddr - symbol.addr;
311
312 // Take the symbol name from the N_FUN STAB entry, we're going to
313 // use it if we fail to find the DWARF infos
314 const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0);
315
316 // If any information is missing, we can at least return this from now on.
317 const sym_only_result: std.debug.Symbol = .{
318 .name = stab_symbol,
319 .compile_unit_name = null,
320 .source_location = null,
321 };
322
323 if (symbol.ofile == MachoSymbol.unknown_ofile) {
324 // We don't have STAB info, so can't track down the object file; all we can do is the symbol name.
325 return sym_only_result;
326 }
327
328 const o_file: *DebugInfo.OFile = of: {
329 const gop = try loaded_macho.ofiles.getOrPut(gpa, symbol.ofile);
330 if (!gop.found_existing) {
331 const o_file_path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0);
332 gop.value_ptr.* = DebugInfo.loadOFile(gpa, o_file_path) catch {
333 _ = loaded_macho.ofiles.pop().?;
334 return sym_only_result;
335 };
336 }
337 break :of gop.value_ptr;
338 };
339
340 const symbol_index = o_file.symbols_by_name.getKeyAdapted(
341 @as([]const u8, stab_symbol),
342 @as(DebugInfo.OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }),
343 ) orelse return sym_only_result;
344 const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value;
345
346 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result;
347
348 return .{
349 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol,
350 .compile_unit_name = compile_unit.die.getAttrString(
351 &o_file.dwarf,
352 native_endian,
353 std.dwarf.AT.name,
354 o_file.dwarf.section(.debug_str),
355 compile_unit,
356 ) catch |err| switch (err) {
357 error.MissingDebugInfo, error.InvalidDebugInfo => null,
358 },
359 .source_location = o_file.dwarf.getLineNumberInfo(
360 gpa,
361 native_endian,
362 compile_unit,
363 symbol_ofile_vaddr + address_symbol_offset,
364 ) catch null,
365 };
366}
367pub const supports_unwinding: bool = true;
368pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
369/// Unwind a frame using MachO compact unwind info (from __unwind_info).
370/// If the compact encoding can't encode a way to unwind a frame, it will
371/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
372pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
373 return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {
374 error.InvalidDebugInfo,
375 error.MissingDebugInfo,
376 error.UnsupportedDebugInfo,
377 error.ReadFailed,
378 error.OutOfMemory,
379 error.Unexpected,
380 => |e| return e,
381 error.UnsupportedRegister,
382 => return error.UnsupportedDebugInfo,
383 error.InvalidRegister,
384 error.IncompatibleRegisterSize,
385 => return error.InvalidDebugInfo,
386 };
387}
388fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
389 const unwind: *DebugInfo.Unwind = u: {
390 di.mutex.lock();
391 defer di.mutex.unlock();
392 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
393 break :u &di.unwind.?;
394 };
395
396 const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;
397 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;
398 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
399
400 const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
401 if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo;
402 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
403 if (indices.len == 0) return error.MissingDebugInfo;
404
405 // offset of the PC into the `__TEXT` segment
406 const pc_text_offset = context.pc - module.text_base;
407
408 const start_offset: u32, const first_level_offset: u32 = index: {
409 var left: usize = 0;
410 var len: usize = indices.len;
411 while (len > 1) {
412 const mid = left + len / 2;
413 if (pc_text_offset < indices[mid].functionOffset) {
414 len /= 2;
415 } else {
416 left = mid;
417 len -= len / 2;
418 }
419 }
420 break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
421 };
422 // An offset of 0 is a sentinel indicating a range does not have unwind info.
423 if (start_offset == 0) return error.MissingDebugInfo;
424
425 const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
426 if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo;
427 const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
428 unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
429 );
430
431 if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo;
432 const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
433
434 const entry: struct {
435 function_offset: usize,
436 raw_encoding: u32,
437 } = switch (kind.*) {
438 .REGULAR => entry: {
439 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo;
440 const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
441
442 const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
443 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
444 const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
445 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
446 );
447 if (entries.len == 0) return error.InvalidDebugInfo;
448
449 var left: usize = 0;
450 var len: usize = entries.len;
451 while (len > 1) {
452 const mid = left + len / 2;
453 if (pc_text_offset < entries[mid].functionOffset) {
454 len /= 2;
455 } else {
456 left = mid;
457 len -= len / 2;
458 }
459 }
460 break :entry .{
461 .function_offset = entries[left].functionOffset,
462 .raw_encoding = entries[left].encoding,
463 };
464 },
465 .COMPRESSED => entry: {
466 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo;
467 const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
468
469 const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
470 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
471 const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
472 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
473 );
474 if (entries.len == 0) return error.InvalidDebugInfo;
475
476 var left: usize = 0;
477 var len: usize = entries.len;
478 while (len > 1) {
479 const mid = left + len / 2;
480 if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
481 len /= 2;
482 } else {
483 left = mid;
484 len -= len / 2;
485 }
486 }
487 const entry = entries[left];
488
489 const function_offset = first_level_offset + entry.funcOffset;
490 if (entry.encodingIndex < common_encodings.len) {
491 break :entry .{
492 .function_offset = function_offset,
493 .raw_encoding = common_encodings[entry.encodingIndex],
494 };
495 }
496
497 const local_index = entry.encodingIndex - common_encodings.len;
498 const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
499 if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo;
500 const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
501 unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
502 );
503 if (local_index >= local_encodings.len) return error.InvalidDebugInfo;
504 break :entry .{
505 .function_offset = function_offset,
506 .raw_encoding = local_encodings[local_index],
507 };
508 },
509 else => return error.InvalidDebugInfo,
510 };
511
512 if (entry.raw_encoding == 0) return error.MissingDebugInfo;
513
514 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
515 const new_ip = switch (builtin.cpu.arch) {
516 .x86_64 => switch (encoding.mode.x86_64) {
517 .OLD => return error.UnsupportedDebugInfo,
518 .RBP_FRAME => ip: {
519 const frame = encoding.value.x86_64.frame;
520
521 const fp = (try dwarfRegNative(&context.cpu_context, fp_reg_num)).*;
522 const new_sp = fp + 2 * @sizeOf(usize);
523
524 const ip_ptr = fp + @sizeOf(usize);
525 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
526 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
527
528 (try dwarfRegNative(&context.cpu_context, fp_reg_num)).* = new_fp;
529 (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp;
530 (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip;
531
532 const regs: [5]u3 = .{
533 frame.reg0,
534 frame.reg1,
535 frame.reg2,
536 frame.reg3,
537 frame.reg4,
538 };
539 for (regs, 0..) |reg, i| {
540 if (reg == 0) continue;
541 const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
542 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
543 (try dwarfRegNative(&context.cpu_context, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*;
544 }
545
546 break :ip new_ip;
547 },
548 .STACK_IMMD,
549 .STACK_IND,
550 => ip: {
551 const frameless = encoding.value.x86_64.frameless;
552
553 const sp = (try dwarfRegNative(&context.cpu_context, sp_reg_num)).*;
554 const stack_size: usize = stack_size: {
555 if (encoding.mode.x86_64 == .STACK_IMMD) {
556 break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
557 }
558 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
559 const sub_offset_addr =
560 module.text_base +
561 entry.function_offset +
562 frameless.stack.indirect.sub_offset;
563 // `sub_offset_addr` points to the offset of the literal within the instruction
564 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
565 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust);
566 };
567
568 // Decode the Lehmer-coded sequence of registers.
569 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
570
571 // Decode the variable-based permutation number into its digits. Each digit represents
572 // an index into the list of register numbers that weren't yet used in the sequence at
573 // the time the digit was added.
574 const reg_count = frameless.stack_reg_count;
575 const ip_ptr = ip_ptr: {
576 var digits: [6]u3 = undefined;
577 var accumulator: usize = frameless.stack_reg_permutation;
578 var base: usize = 2;
579 for (0..reg_count) |i| {
580 const div = accumulator / base;
581 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
582 accumulator = div;
583 base += 1;
584 }
585
586 var registers: [6]u3 = undefined;
587 var used_indices: [6]bool = @splat(false);
588 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
589 var unused_count: u8 = 0;
590 const unused_index = for (used_indices, 0..) |used, index| {
591 if (!used) {
592 if (target_unused_index == unused_count) break index;
593 unused_count += 1;
594 }
595 } else unreachable;
596 registers[i] = @intCast(unused_index + 1);
597 used_indices[unused_index] = true;
598 }
599
600 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
601 for (0..reg_count) |i| {
602 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
603 (try dwarfRegNative(&context.cpu_context, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
604 reg_addr += @sizeOf(usize);
605 }
606
607 break :ip_ptr reg_addr;
608 };
609
610 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
611 const new_sp = ip_ptr + @sizeOf(usize);
612
613 (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp;
614 (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip;
615
616 break :ip new_ip;
617 },
618 .DWARF => {
619 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
620 return context.unwindFrame(unwind.dwarf_cache, gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf);
621 },
622 },
623 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
624 .OLD => return error.UnsupportedDebugInfo,
625 .FRAMELESS => ip: {
626 const sp = (try dwarfRegNative(&context.cpu_context, sp_reg_num)).*;
627 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
628 const new_ip = (try dwarfRegNative(&context.cpu_context, 30)).*;
629 (try dwarfRegNative(&context.cpu_context, sp_reg_num)).* = new_sp;
630 break :ip new_ip;
631 },
632 .DWARF => {
633 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
634 return context.unwindFrame(unwind.dwarf_cache, gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf);
635 },
636 .FRAME => ip: {
637 const frame = encoding.value.arm64.frame;
638
639 const fp = (try dwarfRegNative(&context.cpu_context, fp_reg_num)).*;
640 const ip_ptr = fp + @sizeOf(usize);
641
642 var reg_addr = fp - @sizeOf(usize);
643 inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".fields, 0..) |field, i| {
644 if (@field(frame.x_reg_pairs, field.name) != 0) {
645 (try dwarfRegNative(&context.cpu_context, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
646 reg_addr += @sizeOf(usize);
647 (try dwarfRegNative(&context.cpu_context, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
648 reg_addr += @sizeOf(usize);
649 }
650 }
651
652 inline for (@typeInfo(@TypeOf(frame.d_reg_pairs)).@"struct".fields, 0..) |field, i| {
653 if (@field(frame.d_reg_pairs, field.name) != 0) {
654 // Only the lower half of the 128-bit V registers are restored during unwinding
655 {
656 const dest: *align(1) usize = @ptrCast(try context.cpu_context.dwarfRegisterBytes(64 + 8 + i));
657 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
658 }
659 reg_addr += @sizeOf(usize);
660 {
661 const dest: *align(1) usize = @ptrCast(try context.cpu_context.dwarfRegisterBytes(64 + 9 + i));
662 dest.* = @as(*const usize, @ptrFromInt(reg_addr)).*;
663 }
664 reg_addr += @sizeOf(usize);
665 }
666 }
667
668 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
669 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
670
671 (try dwarfRegNative(&context.cpu_context, fp_reg_num)).* = new_fp;
672 (try dwarfRegNative(&context.cpu_context, ip_reg_num)).* = new_ip;
673
674 break :ip new_ip;
675 },
676 },
677 else => comptime unreachable, // unimplemented
678 };
679
680 const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip);
681
682 // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this
683 // function's last instruction making `ret_addr` one byte past its end.
684 context.pc = ret_addr -| 1;
685
686 return ret_addr;
687}
688pub const DebugInfo = struct {
689 /// Held while checking and/or populating `unwind` or `loaded_macho`.
690 /// Once a field is populated and the pointer `&di.loaded_macho.?` or `&di.unwind.?` has been
691 /// gotten, the lock is released; i.e. it is not held while *using* the loaded info.
692 mutex: std.Thread.Mutex,
693
694 unwind: ?Unwind,
695 loaded_macho: ?LoadedMachO,
696
697 pub const init: DebugInfo = .{
698 .mutex = .{},
699
700 .unwind = null,
701 .loaded_macho = null,
702 };
703
704 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
705 if (di.loaded_macho) |*loaded_macho| {
706 for (loaded_macho.ofiles.values()) |*ofile| {
707 ofile.dwarf.deinit(gpa);
708 ofile.symbols_by_name.deinit(gpa);
709 posix.munmap(ofile.mapped_memory);
710 }
711 loaded_macho.ofiles.deinit(gpa);
712 gpa.free(loaded_macho.symbols);
713 posix.munmap(loaded_macho.mapped_memory);
714 }
715 }
716
717 const Unwind = struct {
718 /// The slide applied to the `__unwind_info` and `__eh_frame` sections.
719 /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr.
720 vmaddr_slide: u64,
721 /// Backed by the in-memory section mapped by the loader.
722 unwind_info: ?[]const u8,
723 /// Backed by the in-memory `__eh_frame` section mapped by the loader.
724 dwarf: ?Dwarf.Unwind,
725 /// This is `undefined` if `dwarf == null`.
726 dwarf_cache: *UnwindContext.Cache,
727 };
728
729 const LoadedMachO = struct {
730 mapped_memory: []align(std.heap.page_size_min) const u8,
731 symbols: []const MachoSymbol,
732 strings: []const u8,
733 /// Key is index into `strings` of the file path.
734 ofiles: std.AutoArrayHashMapUnmanaged(u32, OFile),
735 /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is
736 /// because the segments in the file on disk might differ from the ones in memory. Normally
737 /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying:
738 /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in
739 /// the dyld cache (dyld actually restart itself from cache after loading it), and the two
740 /// versions have (very) different segment base addresses. It's sort of like a large slide
741 /// has been applied to all addresses in memory. For an optimal experience, we consider the
742 /// on-disk vmaddr instead of the in-memory one.
743 vaddr_offset: usize,
744 };
745
746 const OFile = struct {
747 mapped_memory: []align(std.heap.page_size_min) const u8,
748 dwarf: Dwarf,
749 strtab: []const u8,
750 symtab: []align(1) const macho.nlist_64,
751 /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed
752 /// through `SymbolAdapter`, so that the symbol name is used as the logical key.
753 symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true),
754
755 const SymbolAdapter = struct {
756 strtab: []const u8,
757 symtab: []align(1) const macho.nlist_64,
758 pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {
759 _ = ctx;
760 return @truncate(std.hash.Wyhash.hash(0, sym_name));
761 }
762 pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool {
763 _ = b_index;
764 const b_sym = ctx.symtab[b_sym_index];
765 const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0);
766 return mem.eql(u8, a_sym_name, b_sym_name);
767 }
768 };
769 };
770
771 fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
772 const mapped_mem = try mapDebugInfoFile(o_file_path);
773 errdefer posix.munmap(mapped_mem);
774
775 if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
776 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
777 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
778
779 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
780 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
781 var symtab_cmd: ?macho.symtab_command = null;
782 var it: macho.LoadCommandIterator = .{
783 .ncmds = hdr.ncmds,
784 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
785 };
786 while (it.next()) |cmd| switch (cmd.cmd()) {
787 .SEGMENT_64 => seg_cmd = cmd,
788 .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
789 else => {},
790 };
791 break :cmds .{
792 seg_cmd orelse return error.MissingDebugInfo,
793 symtab_cmd orelse return error.MissingDebugInfo,
794 };
795 };
796
797 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
798 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
799 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
800
801 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
802 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
803 const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
804
805 // TODO handle tentative (common) symbols
806 var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty;
807 defer symbols_by_name.deinit(gpa);
808 try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len));
809 for (symtab, 0..) |sym, sym_index| {
810 if (sym.n_strx == 0) continue;
811 switch (sym.n_type.bits.type) {
812 .undf => continue, // includes tentative symbols
813 .abs => continue,
814 else => {},
815 }
816 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
817 const gop = symbols_by_name.getOrPutAssumeCapacityAdapted(
818 @as([]const u8, sym_name),
819 @as(DebugInfo.OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }),
820 );
821 if (gop.found_existing) return error.InvalidDebugInfo;
822 gop.key_ptr.* = @intCast(sym_index);
823 }
824
825 var sections: Dwarf.SectionArray = @splat(null);
826 for (seg_cmd.getSections()) |sect| {
827 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
828
829 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
830 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
831 } else continue;
832
833 if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo;
834 const section_bytes = mapped_mem[sect.offset..][0..sect.size];
835 sections[section_index] = .{
836 .data = section_bytes,
837 .owned = false,
838 };
839 }
840
841 const missing_debug_info =
842 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
843 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
844 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
845 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
846 if (missing_debug_info) return error.MissingDebugInfo;
847
848 var dwarf: Dwarf = .{ .sections = sections };
849 errdefer dwarf.deinit(gpa);
850 try dwarf.open(gpa, native_endian);
851
852 return .{
853 .mapped_memory = mapped_mem,
854 .dwarf = dwarf,
855 .strtab = strtab,
856 .symtab = symtab,
857 .symbols_by_name = symbols_by_name.move(),
858 };
859 }
860};
861
862const MachoSymbol = struct {
863 strx: u32,
864 addr: u64,
865 /// Value may be `unknown_ofile`.
866 ofile: u32,
867 const unknown_ofile = std.math.maxInt(u32);
868 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
869 _ = context;
870 return lhs.addr < rhs.addr;
871 }
872 /// Assumes that `symbols` is sorted in order of ascending `addr`.
873 fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
874 if (symbols.len == 0) return null; // no potential match
875 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
876 var left: usize = 0;
877 var len: usize = symbols.len;
878 while (len > 1) {
879 const mid = left + len / 2;
880 if (address < symbols[mid].addr) {
881 len /= 2;
882 } else {
883 left = mid;
884 len -= len / 2;
885 }
886 }
887 return &symbols[left];
888 }
889
890 test find {
891 const symbols: []const MachoSymbol = &.{
892 .{ .addr = 100, .strx = undefined, .ofile = undefined },
893 .{ .addr = 200, .strx = undefined, .ofile = undefined },
894 .{ .addr = 300, .strx = undefined, .ofile = undefined },
895 };
896
897 try testing.expectEqual(null, find(symbols, 0));
898 try testing.expectEqual(null, find(symbols, 99));
899 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
900 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
901 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
902
903 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
904 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
905 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
906
907 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
908 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
909 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
910 }
911};
912test {
913 _ = MachoSymbol;
914}
915
916const ip_reg_num = Dwarf.ipRegNum(builtin.target.cpu.arch).?;
917const fp_reg_num = Dwarf.fpRegNum(builtin.target.cpu.arch);
918const sp_reg_num = Dwarf.spRegNum(builtin.target.cpu.arch);
919
920/// Uses `mmap` to map the file at `path` into memory.
921fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
922 const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
923 error.FileNotFound => return error.MissingDebugInfo,
924 else => return error.ReadFailed,
925 };
926 defer file.close();
927
928 const file_len = std.math.cast(usize, try file.getEndPos()) orelse return error.InvalidDebugInfo;
929
930 return posix.mmap(
931 null,
932 file_len,
933 posix.PROT.READ,
934 .{ .TYPE = .SHARED },
935 file.handle,
936 0,
937 );
938}
939
940const DarwinModule = @This();
941
942const std = @import("../../std.zig");
943const Allocator = std.mem.Allocator;
944const Dwarf = std.debug.Dwarf;
945const assert = std.debug.assert;
946const macho = std.macho;
947const mem = std.mem;
948const posix = std.posix;
949const testing = std.testing;
950const Error = std.debug.SelfInfo.Error;
951const dwarfRegNative = std.debug.SelfInfo.DwarfUnwindContext.regNative;
952
953const builtin = @import("builtin");
954const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo/Elf.zig created+427
......@@ -0,0 +1,427 @@
1rwlock: std.Thread.RwLock,
2
3modules: std.ArrayList(Module),
4ranges: std.ArrayList(Module.Range),
5
6unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn,
7
8pub const init: SelfInfo = .{
9 .rwlock = .{},
10 .modules = .empty,
11 .ranges = .empty,
12 .unwind_cache = null,
13};
14pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
15 for (si.modules.items) |*mod| {
16 unwind: {
17 const u = &(mod.unwind orelse break :unwind catch break :unwind);
18 for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa);
19 }
20 loaded: {
21 const l = &(mod.loaded_elf orelse break :loaded catch break :loaded);
22 l.file.deinit(gpa);
23 }
24 }
25
26 si.modules.deinit(gpa);
27 si.ranges.deinit(gpa);
28 if (si.unwind_cache) |cache| gpa.free(cache);
29}
30
31pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
32 const module = try si.findModule(gpa, address, .exclusive);
33 defer si.rwlock.unlock();
34
35 const vaddr = address - module.load_offset;
36
37 const loaded_elf = try module.getLoadedElf(gpa);
38 if (loaded_elf.file.dwarf) |*dwarf| {
39 if (!loaded_elf.scanned_dwarf) {
40 dwarf.open(gpa, native_endian) catch |err| switch (err) {
41 error.InvalidDebugInfo,
42 error.MissingDebugInfo,
43 error.OutOfMemory,
44 => |e| return e,
45 error.EndOfStream,
46 error.Overflow,
47 error.ReadFailed,
48 error.StreamTooLong,
49 => return error.InvalidDebugInfo,
50 };
51 loaded_elf.scanned_dwarf = true;
52 }
53 if (dwarf.getSymbol(gpa, native_endian, vaddr)) |sym| {
54 return sym;
55 } else |err| switch (err) {
56 error.MissingDebugInfo => {},
57
58 error.InvalidDebugInfo,
59 error.OutOfMemory,
60 => |e| return e,
61
62 error.ReadFailed,
63 error.EndOfStream,
64 error.Overflow,
65 error.StreamTooLong,
66 => return error.InvalidDebugInfo,
67 }
68 }
69 // When DWARF is unavailable, fall back to searching the symtab.
70 return loaded_elf.file.searchSymtab(gpa, vaddr) catch |err| switch (err) {
71 error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo,
72 error.BadSymtab => return error.InvalidDebugInfo,
73 error.OutOfMemory => |e| return e,
74 };
75}
76pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
77 const module = try si.findModule(gpa, address, .shared);
78 defer si.rwlock.unlockShared();
79 if (module.name.len == 0) return error.MissingDebugInfo;
80 return module.name;
81}
82
83pub const can_unwind: bool = s: {
84 // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through
85 // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format.
86 const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
87 .linux => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
88 .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
89 .freebsd => &.{ .x86_64, .aarch64, .aarch64_be },
90 .openbsd => &.{.x86_64},
91 .solaris => &.{ .x86, .x86_64 },
92 .illumos => &.{ .x86, .x86_64 },
93 else => unreachable,
94 };
95 for (archs) |a| {
96 if (builtin.target.cpu.arch == a) break :s true;
97 }
98 break :s false;
99};
100comptime {
101 if (can_unwind) {
102 std.debug.assert(Dwarf.supportsUnwinding(&builtin.target));
103 }
104}
105pub const UnwindContext = Dwarf.SelfUnwinder;
106pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
107 comptime assert(can_unwind);
108
109 {
110 si.rwlock.lockShared();
111 defer si.rwlock.unlockShared();
112 if (si.unwind_cache) |cache| {
113 if (Dwarf.SelfUnwinder.CacheEntry.find(cache, context.pc)) |entry| {
114 return context.next(gpa, entry);
115 }
116 }
117 }
118
119 const module = try si.findModule(gpa, context.pc, .exclusive);
120 defer si.rwlock.unlock();
121
122 if (si.unwind_cache == null) {
123 si.unwind_cache = try gpa.alloc(Dwarf.SelfUnwinder.CacheEntry, 2048);
124 @memset(si.unwind_cache.?, .empty);
125 }
126
127 const unwind_sections = try module.getUnwindSections(gpa);
128 for (unwind_sections) |*unwind| {
129 if (context.computeRules(gpa, unwind, module.load_offset, null)) |entry| {
130 entry.populate(si.unwind_cache.?);
131 return context.next(gpa, &entry);
132 } else |err| switch (err) {
133 error.MissingDebugInfo => continue,
134
135 error.InvalidDebugInfo,
136 error.UnsupportedDebugInfo,
137 error.OutOfMemory,
138 => |e| return e,
139
140 error.EndOfStream,
141 error.StreamTooLong,
142 error.ReadFailed,
143 error.Overflow,
144 error.InvalidOpcode,
145 error.InvalidOperation,
146 error.InvalidOperand,
147 => return error.InvalidDebugInfo,
148
149 error.UnimplementedUserOpcode,
150 error.UnsupportedAddrSize,
151 => return error.UnsupportedDebugInfo,
152 }
153 }
154 return error.MissingDebugInfo;
155}
156
157const Module = struct {
158 load_offset: usize,
159 name: []const u8,
160 build_id: ?[]const u8,
161 gnu_eh_frame: ?[]const u8,
162
163 /// `null` means unwind information has not yet been loaded.
164 unwind: ?(Error!UnwindSections),
165
166 /// `null` means the ELF file has not yet been loaded.
167 loaded_elf: ?(Error!LoadedElf),
168
169 const LoadedElf = struct {
170 file: std.debug.ElfFile,
171 scanned_dwarf: bool,
172 };
173
174 const UnwindSections = struct {
175 buf: [2]Dwarf.Unwind,
176 len: usize,
177 };
178
179 const Range = struct {
180 start: usize,
181 len: usize,
182 /// Index into `modules`
183 module_index: usize,
184 };
185
186 /// Assumes we already hold an exclusive lock.
187 fn getUnwindSections(mod: *Module, gpa: Allocator) Error![]Dwarf.Unwind {
188 if (mod.unwind == null) mod.unwind = loadUnwindSections(mod, gpa);
189 const us = &(mod.unwind.? catch |err| return err);
190 return us.buf[0..us.len];
191 }
192 fn loadUnwindSections(mod: *Module, gpa: Allocator) Error!UnwindSections {
193 var us: UnwindSections = .{
194 .buf = undefined,
195 .len = 0,
196 };
197 if (mod.gnu_eh_frame) |section_bytes| {
198 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - mod.load_offset;
199 const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) {
200 error.ReadFailed => unreachable, // it's all fixed buffers
201 error.InvalidDebugInfo => |e| return e,
202 error.EndOfStream, error.Overflow => return error.InvalidDebugInfo,
203 error.UnsupportedAddrSize => return error.UnsupportedDebugInfo,
204 };
205 us.buf[us.len] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(mod.load_offset + header.eh_frame_vaddr))));
206 us.len += 1;
207 } else {
208 // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`
209 // section, but we'll have to load the binary to get at it.
210 const loaded = try mod.getLoadedElf(gpa);
211 // If both are present, we can't just pick one -- the info could be split between them.
212 // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one.
213 if (loaded.file.debug_frame) |*debug_frame| {
214 us.buf[us.len] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes);
215 us.len += 1;
216 }
217 if (loaded.file.eh_frame) |*eh_frame| {
218 us.buf[us.len] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes);
219 us.len += 1;
220 }
221 }
222 errdefer for (us.buf[0..us.len]) |*u| u.deinit(gpa);
223 for (us.buf[0..us.len]) |*u| u.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) {
224 error.ReadFailed => unreachable, // it's all fixed buffers
225 error.InvalidDebugInfo,
226 error.MissingDebugInfo,
227 error.OutOfMemory,
228 => |e| return e,
229 error.EndOfStream,
230 error.Overflow,
231 error.StreamTooLong,
232 error.InvalidOperand,
233 error.InvalidOpcode,
234 error.InvalidOperation,
235 => return error.InvalidDebugInfo,
236 error.UnsupportedAddrSize,
237 error.UnsupportedDwarfVersion,
238 error.UnimplementedUserOpcode,
239 => return error.UnsupportedDebugInfo,
240 };
241 return us;
242 }
243
244 /// Assumes we already hold an exclusive lock.
245 fn getLoadedElf(mod: *Module, gpa: Allocator) Error!*LoadedElf {
246 if (mod.loaded_elf == null) mod.loaded_elf = loadElf(mod, gpa);
247 return if (mod.loaded_elf.?) |*elf| elf else |err| err;
248 }
249 fn loadElf(mod: *Module, gpa: Allocator) Error!LoadedElf {
250 const load_result = if (mod.name.len > 0) res: {
251 var file = std.fs.cwd().openFile(mod.name, .{}) catch return error.MissingDebugInfo;
252 defer file.close();
253 break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(mod.name));
254 } else res: {
255 const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) {
256 error.OutOfMemory => |e| return e,
257 else => return error.ReadFailed,
258 };
259 defer gpa.free(path);
260 var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo;
261 defer file.close();
262 break :res std.debug.ElfFile.load(gpa, file, mod.build_id, &.native(path));
263 };
264
265 var elf_file = load_result catch |err| switch (err) {
266 error.OutOfMemory,
267 error.Unexpected,
268 => |e| return e,
269
270 error.Overflow,
271 error.TruncatedElfFile,
272 error.InvalidCompressedSection,
273 error.InvalidElfMagic,
274 error.InvalidElfVersion,
275 error.InvalidElfClass,
276 error.InvalidElfEndian,
277 => return error.InvalidDebugInfo,
278
279 error.SystemResources,
280 error.MemoryMappingNotSupported,
281 error.AccessDenied,
282 error.LockedMemoryLimitExceeded,
283 error.ProcessFdQuotaExceeded,
284 error.SystemFdQuotaExceeded,
285 => return error.ReadFailed,
286 };
287 errdefer elf_file.deinit(gpa);
288
289 if (elf_file.endian != native_endian) return error.InvalidDebugInfo;
290 if (elf_file.is_64 != (@sizeOf(usize) == 8)) return error.InvalidDebugInfo;
291
292 return .{
293 .file = elf_file,
294 .scanned_dwarf = false,
295 };
296 }
297};
298
299fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared, exclusive }) Error!*Module {
300 // With the requested lock, scan the module ranges looking for `address`.
301 switch (lock) {
302 .shared => si.rwlock.lockShared(),
303 .exclusive => si.rwlock.lock(),
304 }
305 for (si.ranges.items) |*range| {
306 if (address >= range.start and address < range.start + range.len) {
307 return &si.modules.items[range.module_index];
308 }
309 }
310 // The address wasn't in a known range. We will rebuild the module/range lists, since it's possible
311 // a new module was loaded. Upgrade to an exclusive lock if necessary.
312 switch (lock) {
313 .shared => {
314 si.rwlock.unlockShared();
315 si.rwlock.lock();
316 },
317 .exclusive => {},
318 }
319 // Rebuild module list with the exclusive lock.
320 {
321 errdefer si.rwlock.unlock();
322 for (si.modules.items) |*mod| {
323 unwind: {
324 const u = &(mod.unwind orelse break :unwind catch break :unwind);
325 for (u.buf[0..u.len]) |*unwind| unwind.deinit(gpa);
326 }
327 loaded: {
328 const l = &(mod.loaded_elf orelse break :loaded catch break :loaded);
329 l.file.deinit(gpa);
330 }
331 }
332 si.modules.clearRetainingCapacity();
333 si.ranges.clearRetainingCapacity();
334 var ctx: DlIterContext = .{ .si = si, .gpa = gpa };
335 try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback);
336 }
337 // Downgrade the lock back to shared if necessary.
338 switch (lock) {
339 .shared => {
340 si.rwlock.unlock();
341 si.rwlock.lockShared();
342 },
343 .exclusive => {},
344 }
345 // Scan the newly rebuilt module ranges.
346 for (si.ranges.items) |*range| {
347 if (address >= range.start and address < range.start + range.len) {
348 return &si.modules.items[range.module_index];
349 }
350 }
351 // Still nothing; unlock and error.
352 switch (lock) {
353 .shared => si.rwlock.unlockShared(),
354 .exclusive => si.rwlock.unlock(),
355 }
356 return error.MissingDebugInfo;
357}
358const DlIterContext = struct {
359 si: *SelfInfo,
360 gpa: Allocator,
361
362 fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
363 _ = size;
364
365 var build_id: ?[]const u8 = null;
366 var gnu_eh_frame: ?[]const u8 = null;
367
368 // Populate `build_id` and `gnu_eh_frame`
369 for (info.phdr[0..info.phnum]) |phdr| {
370 switch (phdr.p_type) {
371 std.elf.PT_NOTE => {
372 // Look for .note.gnu.build-id
373 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
374 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
375 const name_size = r.takeInt(u32, native_endian) catch continue;
376 const desc_size = r.takeInt(u32, native_endian) catch continue;
377 const note_type = r.takeInt(u32, native_endian) catch continue;
378 const name = r.take(name_size) catch continue;
379 if (note_type != std.elf.NT_GNU_BUILD_ID) continue;
380 if (!std.mem.eql(u8, name, "GNU\x00")) continue;
381 const desc = r.take(desc_size) catch continue;
382 build_id = desc;
383 },
384 std.elf.PT_GNU_EH_FRAME => {
385 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
386 gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
387 },
388 else => {},
389 }
390 }
391
392 const gpa = context.gpa;
393 const si = context.si;
394
395 const module_index = si.modules.items.len;
396 try si.modules.append(gpa, .{
397 .load_offset = info.addr,
398 // Android libc uses NULL instead of "" to mark the main program
399 .name = std.mem.sliceTo(info.name, 0) orelse "",
400 .build_id = build_id,
401 .gnu_eh_frame = gnu_eh_frame,
402 .unwind = null,
403 .loaded_elf = null,
404 });
405
406 for (info.phdr[0..info.phnum]) |phdr| {
407 if (phdr.p_type != std.elf.PT_LOAD) continue;
408 try context.si.ranges.append(gpa, .{
409 // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000
410 .start = info.addr +% phdr.p_vaddr,
411 .len = phdr.p_memsz,
412 .module_index = module_index,
413 });
414 }
415 }
416};
417
418const std = @import("std");
419const Allocator = std.mem.Allocator;
420const Dwarf = std.debug.Dwarf;
421const Error = std.debug.SelfInfoError;
422const assert = std.debug.assert;
423
424const builtin = @import("builtin");
425const native_endian = builtin.target.cpu.arch.endian();
426
427const SelfInfo = @This();
lib/std/debug/SelfInfo/ElfModule.zig deleted-349
......@@ -1,349 +0,0 @@
1load_offset: usize,
2name: []const u8,
3build_id: ?[]const u8,
4gnu_eh_frame: ?[]const u8,
5
6pub const LookupCache = struct {
7 rwlock: std.Thread.RwLock,
8 ranges: std.ArrayList(Range),
9 const Range = struct {
10 start: usize,
11 len: usize,
12 mod: ElfModule,
13 };
14 pub const init: LookupCache = .{
15 .rwlock = .{},
16 .ranges = .empty,
17 };
18 pub fn deinit(lc: *LookupCache, gpa: Allocator) void {
19 lc.ranges.deinit(gpa);
20 }
21};
22
23pub const DebugInfo = struct {
24 /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`.
25 /// Once data is populated and a pointer to the field has been gotten, the lock
26 /// is released; i.e. it is not held while *using* the loaded debug info.
27 mutex: std.Thread.Mutex,
28
29 loaded_elf: ?ElfFile,
30 scanned_dwarf: bool,
31 unwind: if (supports_unwinding) [2]?Dwarf.Unwind else void,
32 unwind_cache: if (supports_unwinding) *UnwindContext.Cache else void,
33
34 pub const init: DebugInfo = .{
35 .mutex = .{},
36 .loaded_elf = null,
37 .scanned_dwarf = false,
38 .unwind = if (supports_unwinding) @splat(null),
39 .unwind_cache = undefined,
40 };
41 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
42 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
43 if (supports_unwinding) {
44 if (di.unwind[0] != null) gpa.destroy(di.unwind_cache);
45 for (&di.unwind) |*opt_unwind| {
46 const unwind = &(opt_unwind.* orelse continue);
47 unwind.deinit(gpa);
48 }
49 }
50 }
51};
52
53pub fn key(m: ElfModule) usize {
54 return m.load_offset;
55}
56pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModule {
57 if (lookupInCache(cache, address)) |m| return m;
58
59 {
60 // Check a new module hasn't been loaded
61 cache.rwlock.lock();
62 defer cache.rwlock.unlock();
63 const DlIterContext = struct {
64 ranges: *std.ArrayList(LookupCache.Range),
65 gpa: Allocator,
66
67 fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
68 _ = size;
69
70 var mod: ElfModule = .{
71 .load_offset = info.addr,
72 // Android libc uses NULL instead of "" to mark the main program
73 .name = mem.sliceTo(info.name, 0) orelse "",
74 .build_id = null,
75 .gnu_eh_frame = null,
76 };
77
78 // Populate `build_id` and `gnu_eh_frame`
79 for (info.phdr[0..info.phnum]) |phdr| {
80 switch (phdr.p_type) {
81 elf.PT_NOTE => {
82 // Look for .note.gnu.build-id
83 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
84 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
85 const name_size = r.takeInt(u32, native_endian) catch continue;
86 const desc_size = r.takeInt(u32, native_endian) catch continue;
87 const note_type = r.takeInt(u32, native_endian) catch continue;
88 const name = r.take(name_size) catch continue;
89 if (note_type != elf.NT_GNU_BUILD_ID) continue;
90 if (!mem.eql(u8, name, "GNU\x00")) continue;
91 const desc = r.take(desc_size) catch continue;
92 mod.build_id = desc;
93 },
94 elf.PT_GNU_EH_FRAME => {
95 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
96 mod.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
97 },
98 else => {},
99 }
100 }
101
102 // Now that `mod` is populated, create the ranges
103 for (info.phdr[0..info.phnum]) |phdr| {
104 if (phdr.p_type != elf.PT_LOAD) continue;
105 try context.ranges.append(context.gpa, .{
106 // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000
107 .start = info.addr +% phdr.p_vaddr,
108 .len = phdr.p_memsz,
109 .mod = mod,
110 });
111 }
112 }
113 };
114 cache.ranges.clearRetainingCapacity();
115 var ctx: DlIterContext = .{
116 .ranges = &cache.ranges,
117 .gpa = gpa,
118 };
119 try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback);
120 }
121
122 if (lookupInCache(cache, address)) |m| return m;
123 return error.MissingDebugInfo;
124}
125fn lookupInCache(cache: *LookupCache, address: usize) ?ElfModule {
126 cache.rwlock.lockShared();
127 defer cache.rwlock.unlockShared();
128 for (cache.ranges.items) |*range| {
129 if (address >= range.start and address < range.start + range.len) {
130 return range.mod;
131 }
132 }
133 return null;
134}
135fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
136 std.debug.assert(di.loaded_elf == null);
137 std.debug.assert(!di.scanned_dwarf);
138
139 const load_result = if (module.name.len > 0) res: {
140 var file = std.fs.cwd().openFile(module.name, .{}) catch return error.MissingDebugInfo;
141 defer file.close();
142 break :res ElfFile.load(gpa, file, module.build_id, &.native(module.name));
143 } else res: {
144 const path = std.fs.selfExePathAlloc(gpa) catch |err| switch (err) {
145 error.OutOfMemory => |e| return e,
146 else => return error.ReadFailed,
147 };
148 defer gpa.free(path);
149 var file = std.fs.cwd().openFile(path, .{}) catch return error.MissingDebugInfo;
150 defer file.close();
151 break :res ElfFile.load(gpa, file, module.build_id, &.native(path));
152 };
153 di.loaded_elf = load_result catch |err| switch (err) {
154 error.OutOfMemory,
155 error.Unexpected,
156 => |e| return e,
157
158 error.Overflow,
159 error.TruncatedElfFile,
160 error.InvalidCompressedSection,
161 error.InvalidElfMagic,
162 error.InvalidElfVersion,
163 error.InvalidElfClass,
164 error.InvalidElfEndian,
165 => return error.InvalidDebugInfo,
166
167 error.SystemResources,
168 error.MemoryMappingNotSupported,
169 error.AccessDenied,
170 error.LockedMemoryLimitExceeded,
171 error.ProcessFdQuotaExceeded,
172 error.SystemFdQuotaExceeded,
173 => return error.ReadFailed,
174 };
175
176 const matches_native =
177 di.loaded_elf.?.endian == native_endian and
178 di.loaded_elf.?.is_64 == (@sizeOf(usize) == 8);
179
180 if (!matches_native) {
181 di.loaded_elf.?.deinit(gpa);
182 di.loaded_elf = null;
183 return error.InvalidDebugInfo;
184 }
185}
186pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, address: usize) Error!std.debug.Symbol {
187 const vaddr = address - module.load_offset;
188 {
189 di.mutex.lock();
190 defer di.mutex.unlock();
191 if (di.loaded_elf == null) try module.loadElf(gpa, di);
192 const loaded_elf = &di.loaded_elf.?;
193 // We need the lock if using DWARF, as we might scan the DWARF or build a line number table.
194 if (loaded_elf.dwarf) |*dwarf| {
195 if (!di.scanned_dwarf) {
196 dwarf.open(gpa, native_endian) catch |err| switch (err) {
197 error.InvalidDebugInfo,
198 error.MissingDebugInfo,
199 error.OutOfMemory,
200 => |e| return e,
201 error.EndOfStream,
202 error.Overflow,
203 error.ReadFailed,
204 error.StreamTooLong,
205 => return error.InvalidDebugInfo,
206 };
207 di.scanned_dwarf = true;
208 }
209 return dwarf.getSymbol(gpa, native_endian, vaddr) catch |err| switch (err) {
210 error.InvalidDebugInfo,
211 error.MissingDebugInfo,
212 error.OutOfMemory,
213 => |e| return e,
214 error.ReadFailed,
215 error.EndOfStream,
216 error.Overflow,
217 error.StreamTooLong,
218 => return error.InvalidDebugInfo,
219 };
220 }
221 // Otherwise, we're just going to scan the symtab, which we don't need the lock for; fall out of this block.
222 }
223 // When there's no DWARF available, fall back to searching the symtab.
224 return di.loaded_elf.?.searchSymtab(gpa, vaddr) catch |err| switch (err) {
225 error.NoSymtab, error.NoStrtab => return error.MissingDebugInfo,
226 error.BadSymtab => return error.InvalidDebugInfo,
227 error.OutOfMemory => |e| return e,
228 };
229}
230fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) Error!void {
231 unwind.prepare(gpa, @sizeOf(usize), native_endian, true, false) catch |err| switch (err) {
232 error.ReadFailed => unreachable, // it's all fixed buffers
233 error.InvalidDebugInfo,
234 error.MissingDebugInfo,
235 error.OutOfMemory,
236 => |e| return e,
237 error.EndOfStream,
238 error.Overflow,
239 error.StreamTooLong,
240 error.InvalidOperand,
241 error.InvalidOpcode,
242 error.InvalidOperation,
243 => return error.InvalidDebugInfo,
244 error.UnsupportedAddrSize,
245 error.UnsupportedDwarfVersion,
246 error.UnimplementedUserOpcode,
247 => return error.UnsupportedDebugInfo,
248 };
249}
250fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
251 var buf: [2]Dwarf.Unwind = undefined;
252 const unwinds: []Dwarf.Unwind = if (module.gnu_eh_frame) |section_bytes| unwinds: {
253 const section_vaddr: u64 = @intFromPtr(section_bytes.ptr) - module.load_offset;
254 const header = Dwarf.Unwind.EhFrameHeader.parse(section_vaddr, section_bytes, @sizeOf(usize), native_endian) catch |err| switch (err) {
255 error.ReadFailed => unreachable, // it's all fixed buffers
256 error.InvalidDebugInfo => |e| return e,
257 error.EndOfStream, error.Overflow => return error.InvalidDebugInfo,
258 error.UnsupportedAddrSize => return error.UnsupportedDebugInfo,
259 };
260 buf[0] = .initEhFrameHdr(header, section_vaddr, @ptrFromInt(@as(usize, @intCast(module.load_offset + header.eh_frame_vaddr))));
261 break :unwinds buf[0..1];
262 } else unwinds: {
263 // There is no `.eh_frame_hdr` section. There may still be an `.eh_frame` or `.debug_frame`
264 // section, but we'll have to load the binary to get at it.
265 if (di.loaded_elf == null) try module.loadElf(gpa, di);
266 const opt_debug_frame = &di.loaded_elf.?.debug_frame;
267 const opt_eh_frame = &di.loaded_elf.?.eh_frame;
268 var i: usize = 0;
269 // If both are present, we can't just pick one -- the info could be split between them.
270 // `.debug_frame` is likely to be the more complete section, so we'll prioritize that one.
271 if (opt_debug_frame.*) |*debug_frame| {
272 buf[i] = .initSection(.debug_frame, debug_frame.vaddr, debug_frame.bytes);
273 i += 1;
274 }
275 if (opt_eh_frame.*) |*eh_frame| {
276 buf[i] = .initSection(.eh_frame, eh_frame.vaddr, eh_frame.bytes);
277 i += 1;
278 }
279 if (i == 0) return error.MissingDebugInfo;
280 break :unwinds buf[0..i];
281 };
282 errdefer for (unwinds) |*u| u.deinit(gpa);
283 for (unwinds) |*u| try prepareUnwindLookup(u, gpa);
284
285 const unwind_cache = try gpa.create(UnwindContext.Cache);
286 errdefer gpa.destroy(unwind_cache);
287 unwind_cache.init();
288
289 switch (unwinds.len) {
290 0 => unreachable,
291 1 => di.unwind = .{ unwinds[0], null },
292 2 => di.unwind = .{ unwinds[0], unwinds[1] },
293 else => unreachable,
294 }
295 di.unwind_cache = unwind_cache;
296}
297pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
298 const unwinds: *const [2]?Dwarf.Unwind = u: {
299 di.mutex.lock();
300 defer di.mutex.unlock();
301 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
302 std.debug.assert(di.unwind[0] != null);
303 break :u &di.unwind;
304 };
305 for (unwinds) |*opt_unwind| {
306 const unwind = &(opt_unwind.* orelse break);
307 return context.unwindFrame(di.unwind_cache, gpa, unwind, module.load_offset, null) catch |err| switch (err) {
308 error.MissingDebugInfo => continue, // try the next one
309 else => |e| return e,
310 };
311 }
312 return error.MissingDebugInfo;
313}
314pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
315pub const supports_unwinding: bool = s: {
316 // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through
317 // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format.
318 const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
319 .linux => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
320 .netbsd => &.{ .x86, .x86_64, .aarch64, .aarch64_be },
321 .freebsd => &.{ .x86_64, .aarch64, .aarch64_be },
322 .openbsd => &.{.x86_64},
323 .solaris => &.{ .x86, .x86_64 },
324 .illumos => &.{ .x86, .x86_64 },
325 else => unreachable,
326 };
327 for (archs) |a| {
328 if (builtin.target.cpu.arch == a) break :s true;
329 }
330 break :s false;
331};
332comptime {
333 if (supports_unwinding) {
334 std.debug.assert(Dwarf.supportsUnwinding(&builtin.target));
335 }
336}
337
338const ElfModule = @This();
339
340const std = @import("../../std.zig");
341const Allocator = std.mem.Allocator;
342const Dwarf = std.debug.Dwarf;
343const ElfFile = std.debug.ElfFile;
344const elf = std.elf;
345const mem = std.mem;
346const Error = std.debug.SelfInfo.Error;
347
348const builtin = @import("builtin");
349const native_endian = builtin.target.cpu.arch.endian();
lib/std/debug/SelfInfo/Windows.zig created+559
......@@ -0,0 +1,559 @@
1mutex: std.Thread.Mutex,
2modules: std.ArrayListUnmanaged(Module),
3module_name_arena: std.heap.ArenaAllocator.State,
4
5pub const init: SelfInfo = .{
6 .mutex = .{},
7 .modules = .empty,
8 .module_name_arena = .{},
9};
10pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
11 for (si.modules.items) |*module| {
12 di: {
13 const di = &(module.di orelse break :di catch break :di);
14 di.deinit(gpa);
15 }
16 }
17 si.modules.deinit(gpa);
18
19 var module_name_arena = si.module_name_arena.promote(gpa);
20 module_name_arena.deinit();
21}
22
23pub fn getSymbol(si: *SelfInfo, gpa: Allocator, address: usize) Error!std.debug.Symbol {
24 si.mutex.lock();
25 defer si.mutex.unlock();
26 const module = try si.findModule(gpa, address);
27 const di = try module.getDebugInfo(gpa);
28 return di.getSymbol(gpa, address - module.base_address);
29}
30pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 {
31 si.mutex.lock();
32 defer si.mutex.unlock();
33 const module = try si.findModule(gpa, address);
34 return module.name;
35}
36
37pub const can_unwind: bool = switch (builtin.cpu.arch) {
38 else => true,
39 // On x86, `RtlVirtualUnwind` does not exist. We could in theory use `RtlCaptureStackBackTrace`
40 // instead, but on x86, it turns out that function is just... doing FP unwinding with esp! It's
41 // hard to find implementation details to confirm that, but the most authoritative source I have
42 // is an entry in the LLVM mailing list from 2020/08/16 which contains this quote:
43 //
44 // > x86 doesn't have what most architectures would consider an "unwinder" in the sense of
45 // > restoring registers; there is simply a linked list of frames that participate in SEH and
46 // > that desire to be called for a dynamic unwind operation, so RtlCaptureStackBackTrace
47 // > assumes that EBP-based frames are in use and walks an EBP-based frame chain on x86 - not
48 // > all x86 code is written with EBP-based frames so while even though we generally build the
49 // > OS that way, you might always run the risk of encountering external code that uses EBP as a
50 // > general purpose register for which such an unwind attempt for a stack trace would fail.
51 //
52 // Regardless, it's easy to effectively confirm this hypothesis just by compiling some code with
53 // `-fomit-frame-pointer -OReleaseFast` and observing that `RtlCaptureStackBackTrace` returns an
54 // empty trace when it's called in such an application. Note that without `-OReleaseFast` or
55 // similar, LLVM seems reluctant to ever clobber ebp, so you'll get a trace returned which just
56 // contains all of the kernel32/ntdll frames but none of your own. Don't be deceived---this is
57 // just coincidental!
58 //
59 // Anyway, the point is, the only stack walking primitive on x86-windows is FP unwinding. We
60 // *could* ask Microsoft to do that for us with `RtlCaptureStackBackTrace`... but better to just
61 // use our existing FP unwinder in `std.debug`!
62 .x86 => false,
63};
64pub const UnwindContext = struct {
65 pc: usize,
66 cur: windows.CONTEXT,
67 history_table: windows.UNWIND_HISTORY_TABLE,
68 pub fn init(ctx: *const std.debug.cpu_context.Native) UnwindContext {
69 return .{
70 .pc = @returnAddress(),
71 .cur = switch (builtin.cpu.arch) {
72 .x86_64 => std.mem.zeroInit(windows.CONTEXT, .{
73 .Rax = ctx.gprs.get(.rax),
74 .Rcx = ctx.gprs.get(.rcx),
75 .Rdx = ctx.gprs.get(.rdx),
76 .Rbx = ctx.gprs.get(.rbx),
77 .Rsp = ctx.gprs.get(.rsp),
78 .Rbp = ctx.gprs.get(.rbp),
79 .Rsi = ctx.gprs.get(.rsi),
80 .Rdi = ctx.gprs.get(.rdi),
81 .R8 = ctx.gprs.get(.r8),
82 .R9 = ctx.gprs.get(.r9),
83 .R10 = ctx.gprs.get(.r10),
84 .R11 = ctx.gprs.get(.r11),
85 .R12 = ctx.gprs.get(.r12),
86 .R13 = ctx.gprs.get(.r13),
87 .R14 = ctx.gprs.get(.r14),
88 .R15 = ctx.gprs.get(.r15),
89 .Rip = ctx.gprs.get(.rip),
90 }),
91 .aarch64, .aarch64_be => .{
92 .ContextFlags = 0,
93 .Cpsr = 0,
94 .DUMMYUNIONNAME = .{ .X = ctx.x },
95 .Sp = ctx.sp,
96 .Pc = ctx.pc,
97 .V = @splat(.{ .B = @splat(0) }),
98 .Fpcr = 0,
99 .Fpsr = 0,
100 .Bcr = @splat(0),
101 .Bvr = @splat(0),
102 .Wcr = @splat(0),
103 .Wvr = @splat(0),
104 },
105 .thumb => .{
106 .ContextFlags = 0,
107 .R0 = ctx.r[0],
108 .R1 = ctx.r[1],
109 .R2 = ctx.r[2],
110 .R3 = ctx.r[3],
111 .R4 = ctx.r[4],
112 .R5 = ctx.r[5],
113 .R6 = ctx.r[6],
114 .R7 = ctx.r[7],
115 .R8 = ctx.r[8],
116 .R9 = ctx.r[9],
117 .R10 = ctx.r[10],
118 .R11 = ctx.r[11],
119 .R12 = ctx.r[12],
120 .Sp = ctx.r[13],
121 .Lr = ctx.r[14],
122 .Pc = ctx.r[15],
123 .Cpsr = 0,
124 .Fpcsr = 0,
125 .Padding = 0,
126 .DUMMYUNIONNAME = .{ .S = @splat(0) },
127 .Bvr = @splat(0),
128 .Bcr = @splat(0),
129 .Wvr = @splat(0),
130 .Wcr = @splat(0),
131 .Padding2 = @splat(0),
132 },
133 else => comptime unreachable,
134 },
135 .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE),
136 };
137 }
138 pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void {
139 _ = ctx;
140 _ = gpa;
141 }
142 pub fn getFp(ctx: *UnwindContext) usize {
143 return ctx.cur.getRegs().bp;
144 }
145};
146pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
147 _ = si;
148 _ = gpa;
149
150 const current_regs = context.cur.getRegs();
151 var image_base: windows.DWORD64 = undefined;
152 if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| {
153 var handler_data: ?*anyopaque = null;
154 var establisher_frame: u64 = undefined;
155 _ = windows.ntdll.RtlVirtualUnwind(
156 windows.UNW_FLAG_NHANDLER,
157 image_base,
158 current_regs.ip,
159 runtime_function,
160 &context.cur,
161 &handler_data,
162 &establisher_frame,
163 null,
164 );
165 } else {
166 // leaf function
167 context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*);
168 context.cur.setSp(current_regs.sp + @sizeOf(usize));
169 }
170
171 const next_regs = context.cur.getRegs();
172 const tib = &windows.teb().NtTib;
173 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {
174 context.pc = 0;
175 return 0;
176 }
177 // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this
178 // function's last instruction making `next_regs.ip` one byte past its end.
179 context.pc = next_regs.ip -| 1;
180 return next_regs.ip;
181}
182
183const Module = struct {
184 base_address: usize,
185 size: u32,
186 name: []const u8,
187 handle: windows.HMODULE,
188
189 di: ?(Error!DebugInfo),
190
191 const DebugInfo = struct {
192 arena: std.heap.ArenaAllocator.State,
193 coff_image_base: u64,
194 mapped_file: ?MappedFile,
195 dwarf: ?Dwarf,
196 pdb: ?Pdb,
197 coff_section_headers: []coff.SectionHeader,
198
199 const MappedFile = struct {
200 file: fs.File,
201 section_handle: windows.HANDLE,
202 section_view: []const u8,
203 fn deinit(mf: *const MappedFile) void {
204 const process_handle = windows.GetCurrentProcess();
205 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS);
206 windows.CloseHandle(mf.section_handle);
207 mf.file.close();
208 }
209 };
210
211 fn deinit(di: *DebugInfo, gpa: Allocator) void {
212 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
213 if (di.pdb) |*pdb| {
214 pdb.file_reader.file.close();
215 pdb.deinit();
216 }
217 if (di.mapped_file) |*mf| mf.deinit();
218
219 var arena = di.arena.promote(gpa);
220 arena.deinit();
221 }
222
223 fn getSymbol(di: *DebugInfo, gpa: Allocator, vaddr: usize) Error!std.debug.Symbol {
224 pdb: {
225 const pdb = &(di.pdb orelse break :pdb);
226 var coff_section: *align(1) const coff.SectionHeader = undefined;
227 const mod_index = for (pdb.sect_contribs) |sect_contrib| {
228 if (sect_contrib.section > di.coff_section_headers.len) continue;
229 // Remember that SectionContribEntry.Section is 1-based.
230 coff_section = &di.coff_section_headers[sect_contrib.section - 1];
231
232 const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
233 const vaddr_end = vaddr_start + sect_contrib.size;
234 if (vaddr >= vaddr_start and vaddr < vaddr_end) {
235 break sect_contrib.module_index;
236 }
237 } else {
238 // we have no information to add to the address
239 break :pdb;
240 };
241 const module = pdb.getModule(mod_index) catch |err| switch (err) {
242 error.InvalidDebugInfo,
243 error.MissingDebugInfo,
244 error.OutOfMemory,
245 => |e| return e,
246
247 error.ReadFailed,
248 error.EndOfStream,
249 => return error.InvalidDebugInfo,
250 } orelse {
251 return error.InvalidDebugInfo; // bad module index
252 };
253 return .{
254 .name = pdb.getSymbolName(module, vaddr - coff_section.virtual_address),
255 .compile_unit_name = fs.path.basename(module.obj_file_name),
256 .source_location = pdb.getLineNumberInfo(module, vaddr - coff_section.virtual_address) catch null,
257 };
258 }
259 dwarf: {
260 const dwarf = &(di.dwarf orelse break :dwarf);
261 const dwarf_address = vaddr + di.coff_image_base;
262 return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch |err| switch (err) {
263 error.MissingDebugInfo => break :dwarf,
264
265 error.InvalidDebugInfo,
266 error.OutOfMemory,
267 => |e| return e,
268
269 error.ReadFailed,
270 error.EndOfStream,
271 error.Overflow,
272 error.StreamTooLong,
273 => return error.InvalidDebugInfo,
274 };
275 }
276 return error.MissingDebugInfo;
277 }
278 };
279
280 fn getDebugInfo(module: *Module, gpa: Allocator) Error!*DebugInfo {
281 if (module.di == null) module.di = loadDebugInfo(module, gpa);
282 return if (module.di.?) |*di| di else |err| err;
283 }
284 fn loadDebugInfo(module: *const Module, gpa: Allocator) Error!DebugInfo {
285 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
286 const mapped = mapped_ptr[0..module.size];
287 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
288
289 var arena_instance: std.heap.ArenaAllocator = .init(gpa);
290 errdefer arena_instance.deinit();
291 const arena = arena_instance.allocator();
292
293 // The string table is not mapped into memory by the loader, so if a section name is in the
294 // string table then we have to map the full image file from disk. This can happen when
295 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
296 const mapped_file: ?DebugInfo.MappedFile = mapped: {
297 if (!coff_obj.strtabRequired()) break :mapped null;
298 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
299 name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
300 const process_handle = windows.GetCurrentProcess();
301 const len = windows.kernel32.GetModuleFileNameExW(
302 process_handle,
303 module.handle,
304 name_buffer[4..],
305 windows.PATH_MAX_WIDE,
306 );
307 if (len == 0) return error.MissingDebugInfo;
308 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
309 error.Unexpected => |e| return e,
310 error.FileNotFound => return error.MissingDebugInfo,
311
312 error.FileTooBig,
313 error.IsDir,
314 error.NotDir,
315 error.SymLinkLoop,
316 error.NameTooLong,
317 error.InvalidUtf8,
318 error.InvalidWtf8,
319 error.BadPathName,
320 => return error.InvalidDebugInfo,
321
322 error.SystemResources,
323 error.WouldBlock,
324 error.AccessDenied,
325 error.ProcessNotFound,
326 error.PermissionDenied,
327 error.NoSpaceLeft,
328 error.DeviceBusy,
329 error.NoDevice,
330 error.SharingViolation,
331 error.PathAlreadyExists,
332 error.PipeBusy,
333 error.NetworkNotFound,
334 error.AntivirusInterference,
335 error.ProcessFdQuotaExceeded,
336 error.SystemFdQuotaExceeded,
337 error.FileLocksNotSupported,
338 error.FileBusy,
339 => return error.ReadFailed,
340 };
341 errdefer coff_file.close();
342 var section_handle: windows.HANDLE = undefined;
343 const create_section_rc = windows.ntdll.NtCreateSection(
344 &section_handle,
345 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
346 null,
347 null,
348 windows.PAGE_READONLY,
349 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
350 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
351 windows.SEC_COMMIT,
352 coff_file.handle,
353 );
354 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
355 errdefer windows.CloseHandle(section_handle);
356 var coff_len: usize = 0;
357 var section_view_ptr: ?[*]const u8 = null;
358 const map_section_rc = windows.ntdll.NtMapViewOfSection(
359 section_handle,
360 process_handle,
361 @ptrCast(&section_view_ptr),
362 null,
363 0,
364 null,
365 &coff_len,
366 .ViewUnmap,
367 0,
368 windows.PAGE_READONLY,
369 );
370 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
371 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS);
372 const section_view = section_view_ptr.?[0..coff_len];
373 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
374 break :mapped .{
375 .file = coff_file,
376 .section_handle = section_handle,
377 .section_view = section_view,
378 };
379 };
380 errdefer if (mapped_file) |*mf| mf.deinit();
381
382 const coff_image_base = coff_obj.getImageBase();
383
384 var opt_dwarf: ?Dwarf = dwarf: {
385 if (coff_obj.getSectionByName(".debug_info") == null) break :dwarf null;
386
387 var sections: Dwarf.SectionArray = undefined;
388 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
389 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| .{
390 .data = try coff_obj.getSectionDataAlloc(section_header, arena),
391 .owned = false,
392 } else null;
393 }
394 break :dwarf .{ .sections = sections };
395 };
396 errdefer if (opt_dwarf) |*dwarf| dwarf.deinit(gpa);
397
398 if (opt_dwarf) |*dwarf| {
399 dwarf.open(gpa, native_endian) catch |err| switch (err) {
400 error.Overflow,
401 error.EndOfStream,
402 error.StreamTooLong,
403 error.ReadFailed,
404 => return error.InvalidDebugInfo,
405
406 error.InvalidDebugInfo,
407 error.MissingDebugInfo,
408 error.OutOfMemory,
409 => |e| return e,
410 };
411 }
412
413 var opt_pdb: ?Pdb = pdb: {
414 const path = coff_obj.getPdbPath() catch {
415 return error.InvalidDebugInfo;
416 } orelse {
417 break :pdb null;
418 };
419 const pdb_file_open_result = if (fs.path.isAbsolute(path)) res: {
420 break :res std.fs.cwd().openFile(path, .{});
421 } else res: {
422 const self_dir = fs.selfExeDirPathAlloc(gpa) catch |err| switch (err) {
423 error.OutOfMemory, error.Unexpected => |e| return e,
424 else => return error.ReadFailed,
425 };
426 defer gpa.free(self_dir);
427 const abs_path = try fs.path.join(gpa, &.{ self_dir, path });
428 defer gpa.free(abs_path);
429 break :res std.fs.cwd().openFile(abs_path, .{});
430 };
431 const pdb_file = pdb_file_open_result catch |err| switch (err) {
432 error.FileNotFound, error.IsDir => break :pdb null,
433 else => return error.ReadFailed,
434 };
435 errdefer pdb_file.close();
436
437 const pdb_reader = try arena.create(std.fs.File.Reader);
438 pdb_reader.* = pdb_file.reader(try arena.alloc(u8, 4096));
439
440 var pdb = Pdb.init(gpa, pdb_reader) catch |err| switch (err) {
441 error.OutOfMemory, error.ReadFailed, error.Unexpected => |e| return e,
442 else => return error.InvalidDebugInfo,
443 };
444 errdefer pdb.deinit();
445 pdb.parseInfoStream() catch |err| switch (err) {
446 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
447 error.EndOfStream => return error.InvalidDebugInfo,
448
449 error.InvalidDebugInfo,
450 error.MissingDebugInfo,
451 error.OutOfMemory,
452 error.ReadFailed,
453 => |e| return e,
454 };
455 pdb.parseDbiStream() catch |err| switch (err) {
456 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
457
458 error.EndOfStream,
459 error.EOF,
460 error.StreamTooLong,
461 error.WriteFailed,
462 => return error.InvalidDebugInfo,
463
464 error.InvalidDebugInfo,
465 error.OutOfMemory,
466 error.ReadFailed,
467 => |e| return e,
468 };
469
470 if (!std.mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age)
471 return error.InvalidDebugInfo;
472
473 break :pdb pdb;
474 };
475 errdefer if (opt_pdb) |*pdb| {
476 pdb.file_reader.file.close();
477 pdb.deinit();
478 };
479
480 const coff_section_headers: []coff.SectionHeader = if (opt_pdb != null) csh: {
481 break :csh try coff_obj.getSectionHeadersAlloc(arena);
482 } else &.{};
483
484 return .{
485 .arena = arena_instance.state,
486 .coff_image_base = coff_image_base,
487 .mapped_file = mapped_file,
488 .dwarf = opt_dwarf,
489 .pdb = opt_pdb,
490 .coff_section_headers = coff_section_headers,
491 };
492 }
493};
494
495/// Assumes we already hold `si.mutex`.
496fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module {
497 for (si.modules.items) |*mod| {
498 if (address >= mod.base_address and address < mod.base_address + mod.size) {
499 return mod;
500 }
501 }
502
503 // A new module might have been loaded; rebuild the list.
504 {
505 for (si.modules.items) |*mod| {
506 const di = &(mod.di orelse continue catch continue);
507 di.deinit(gpa);
508 }
509 si.modules.clearRetainingCapacity();
510
511 var module_name_arena = si.module_name_arena.promote(gpa);
512 defer si.module_name_arena = module_name_arena.state;
513 _ = module_name_arena.reset(.retain_capacity);
514
515 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
516 if (handle == windows.INVALID_HANDLE_VALUE) {
517 return windows.unexpectedError(windows.GetLastError());
518 }
519 defer windows.CloseHandle(handle);
520 var entry: windows.MODULEENTRY32 = undefined;
521 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
522 var result = windows.kernel32.Module32First(handle, &entry);
523 while (result != 0) : (result = windows.kernel32.Module32Next(handle, &entry)) {
524 try si.modules.append(gpa, .{
525 .base_address = @intFromPtr(entry.modBaseAddr),
526 .size = entry.modBaseSize,
527 .name = try module_name_arena.allocator().dupe(
528 u8,
529 std.mem.sliceTo(&entry.szModule, 0),
530 ),
531 .handle = entry.hModule,
532 .di = null,
533 });
534 }
535 }
536
537 for (si.modules.items) |*mod| {
538 if (address >= mod.base_address and address < mod.base_address + mod.size) {
539 return mod;
540 }
541 }
542
543 return error.MissingDebugInfo;
544}
545
546const std = @import("std");
547const Allocator = std.mem.Allocator;
548const Dwarf = std.debug.Dwarf;
549const Pdb = std.debug.Pdb;
550const Error = std.debug.SelfInfoError;
551const assert = std.debug.assert;
552const coff = std.coff;
553const fs = std.fs;
554const windows = std.os.windows;
555
556const builtin = @import("builtin");
557const native_endian = builtin.target.cpu.arch.endian();
558
559const SelfInfo = @This();
lib/std/debug/SelfInfo/WindowsModule.zig deleted-442
......@@ -1,442 +0,0 @@
1base_address: usize,
2size: usize,
3name: []const u8,
4handle: windows.HMODULE,
5pub fn key(m: WindowsModule) usize {
6 return m.base_address;
7}
8pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) std.debug.SelfInfo.Error!WindowsModule {
9 if (lookupInCache(cache, address)) |m| return m;
10 {
11 // Check a new module hasn't been loaded
12 cache.rwlock.lock();
13 defer cache.rwlock.unlock();
14 cache.modules.clearRetainingCapacity();
15 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
16 if (handle == windows.INVALID_HANDLE_VALUE) {
17 return windows.unexpectedError(windows.GetLastError());
18 }
19 defer windows.CloseHandle(handle);
20 var entry: windows.MODULEENTRY32 = undefined;
21 entry.dwSize = @sizeOf(windows.MODULEENTRY32);
22 if (windows.kernel32.Module32First(handle, &entry) != 0) {
23 try cache.modules.append(gpa, entry);
24 while (windows.kernel32.Module32Next(handle, &entry) != 0) {
25 try cache.modules.append(gpa, entry);
26 }
27 }
28 }
29 if (lookupInCache(cache, address)) |m| return m;
30 return error.MissingDebugInfo;
31}
32pub fn getSymbolAtAddress(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, address: usize) std.debug.SelfInfo.Error!std.debug.Symbol {
33 // The `Pdb` API doesn't really allow us *any* thread-safe access, and the `Dwarf` API isn't
34 // great for it either; just lock the whole thing.
35 di.mutex.lock();
36 defer di.mutex.unlock();
37
38 if (!di.loaded) module.loadDebugInfo(gpa, di) catch |err| switch (err) {
39 error.OutOfMemory, error.InvalidDebugInfo, error.MissingDebugInfo, error.Unexpected => |e| return e,
40 error.FileNotFound => return error.MissingDebugInfo,
41 error.UnknownPDBVersion => return error.UnsupportedDebugInfo,
42 else => return error.ReadFailed,
43 };
44
45 // Translate the runtime address into a virtual address into the module
46 const vaddr = address - module.base_address;
47
48 if (di.pdb != null) {
49 if (di.getSymbolFromPdb(vaddr) catch return error.InvalidDebugInfo) |symbol| return symbol;
50 }
51
52 if (di.dwarf) |*dwarf| {
53 const dwarf_address = vaddr + di.coff_image_base;
54 return dwarf.getSymbol(gpa, native_endian, dwarf_address) catch return error.InvalidDebugInfo;
55 }
56
57 return error.MissingDebugInfo;
58}
59fn lookupInCache(cache: *LookupCache, address: usize) ?WindowsModule {
60 cache.rwlock.lockShared();
61 defer cache.rwlock.unlockShared();
62 for (cache.modules.items) |*entry| {
63 const base_address = @intFromPtr(entry.modBaseAddr);
64 if (address >= base_address and address < base_address + entry.modBaseSize) {
65 return .{
66 .base_address = base_address,
67 .size = entry.modBaseSize,
68 .name = std.mem.sliceTo(&entry.szModule, 0),
69 .handle = entry.hModule,
70 };
71 }
72 }
73 return null;
74}
75fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !void {
76 const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address);
77 const mapped = mapped_ptr[0..module.size];
78 var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo;
79 // The string table is not mapped into memory by the loader, so if a section name is in the
80 // string table then we have to map the full image file from disk. This can happen when
81 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
82 if (coff_obj.strtabRequired()) {
83 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
84 name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present
85 const process_handle = windows.GetCurrentProcess();
86 const len = windows.kernel32.GetModuleFileNameExW(
87 process_handle,
88 module.handle,
89 name_buffer[4..],
90 windows.PATH_MAX_WIDE,
91 );
92 if (len == 0) return error.MissingDebugInfo;
93 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
94 error.FileNotFound => return error.MissingDebugInfo,
95 else => |e| return e,
96 };
97 errdefer coff_file.close();
98 var section_handle: windows.HANDLE = undefined;
99 const create_section_rc = windows.ntdll.NtCreateSection(
100 &section_handle,
101 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
102 null,
103 null,
104 windows.PAGE_READONLY,
105 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
106 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
107 windows.SEC_COMMIT,
108 coff_file.handle,
109 );
110 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
111 errdefer windows.CloseHandle(section_handle);
112 var coff_len: usize = 0;
113 var section_view_ptr: ?[*]const u8 = null;
114 const map_section_rc = windows.ntdll.NtMapViewOfSection(
115 section_handle,
116 process_handle,
117 @ptrCast(&section_view_ptr),
118 null,
119 0,
120 null,
121 &coff_len,
122 .ViewUnmap,
123 0,
124 windows.PAGE_READONLY,
125 );
126 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
127 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS);
128 const section_view = section_view_ptr.?[0..coff_len];
129 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
130 di.mapped_file = .{
131 .file = coff_file,
132 .section_handle = section_handle,
133 .section_view = section_view,
134 };
135 }
136 di.coff_image_base = coff_obj.getImageBase();
137
138 if (coff_obj.getSectionByName(".debug_info")) |_| {
139 di.dwarf = .{};
140
141 inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
142 di.dwarf.?.sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
143 break :blk .{
144 .data = try coff_obj.getSectionDataAlloc(section_header, gpa),
145 .owned = true,
146 };
147 } else null;
148 }
149
150 try di.dwarf.?.open(gpa, native_endian);
151 }
152
153 if (coff_obj.getPdbPath() catch return error.InvalidDebugInfo) |raw_path| pdb: {
154 const path = blk: {
155 if (fs.path.isAbsolute(raw_path)) {
156 break :blk raw_path;
157 } else {
158 const self_dir = try fs.selfExeDirPathAlloc(gpa);
159 defer gpa.free(self_dir);
160 break :blk try fs.path.join(gpa, &.{ self_dir, raw_path });
161 }
162 };
163 defer if (path.ptr != raw_path.ptr) gpa.free(path);
164
165 const pdb_file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
166 error.FileNotFound, error.IsDir => break :pdb,
167 else => |e| return e,
168 };
169 errdefer pdb_file.close();
170
171 const pdb_reader = try gpa.create(std.fs.File.Reader);
172 errdefer gpa.destroy(pdb_reader);
173
174 pdb_reader.* = pdb_file.reader(try gpa.alloc(u8, 4096));
175 errdefer gpa.free(pdb_reader.interface.buffer);
176
177 var pdb: Pdb = try .init(gpa, pdb_reader);
178 errdefer pdb.deinit();
179 try pdb.parseInfoStream();
180 try pdb.parseDbiStream();
181
182 if (!mem.eql(u8, &coff_obj.guid, &pdb.guid) or coff_obj.age != pdb.age)
183 return error.InvalidDebugInfo;
184
185 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(gpa);
186
187 di.pdb = pdb;
188 }
189
190 di.loaded = true;
191}
192pub const LookupCache = struct {
193 rwlock: std.Thread.RwLock,
194 modules: std.ArrayListUnmanaged(windows.MODULEENTRY32),
195 pub const init: LookupCache = .{
196 .rwlock = .{},
197 .modules = .empty,
198 };
199 pub fn deinit(lc: *LookupCache, gpa: Allocator) void {
200 lc.modules.deinit(gpa);
201 }
202};
203pub const DebugInfo = struct {
204 mutex: std.Thread.Mutex,
205
206 loaded: bool,
207
208 coff_image_base: u64,
209 mapped_file: ?struct {
210 file: fs.File,
211 section_handle: windows.HANDLE,
212 section_view: []const u8,
213 },
214
215 dwarf: ?Dwarf,
216
217 pdb: ?Pdb,
218 /// Populated iff `pdb != null`; otherwise `&.{}`.
219 coff_section_headers: []coff.SectionHeader,
220
221 pub const init: DebugInfo = .{
222 .mutex = .{},
223 .loaded = false,
224 .coff_image_base = undefined,
225 .mapped_file = null,
226 .dwarf = null,
227 .pdb = null,
228 .coff_section_headers = &.{},
229 };
230
231 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
232 if (!di.loaded) return;
233 if (di.dwarf) |*dwarf| dwarf.deinit(gpa);
234 if (di.pdb) |*pdb| {
235 pdb.file_reader.file.close();
236 gpa.free(pdb.file_reader.interface.buffer);
237 gpa.destroy(pdb.file_reader);
238 pdb.deinit();
239 }
240 gpa.free(di.coff_section_headers);
241 if (di.mapped_file) |mapped| {
242 const process_handle = windows.GetCurrentProcess();
243 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mapped.section_view.ptr)) == .SUCCESS);
244 windows.CloseHandle(mapped.section_handle);
245 mapped.file.close();
246 }
247 }
248
249 fn getSymbolFromPdb(di: *DebugInfo, relocated_address: usize) !?std.debug.Symbol {
250 var coff_section: *align(1) const coff.SectionHeader = undefined;
251 const mod_index = for (di.pdb.?.sect_contribs) |sect_contrib| {
252 if (sect_contrib.section > di.coff_section_headers.len) continue;
253 // Remember that SectionContribEntry.Section is 1-based.
254 coff_section = &di.coff_section_headers[sect_contrib.section - 1];
255
256 const vaddr_start = coff_section.virtual_address + sect_contrib.offset;
257 const vaddr_end = vaddr_start + sect_contrib.size;
258 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
259 break sect_contrib.module_index;
260 }
261 } else {
262 // we have no information to add to the address
263 return null;
264 };
265
266 const module = try di.pdb.?.getModule(mod_index) orelse return error.InvalidDebugInfo;
267
268 return .{
269 .name = di.pdb.?.getSymbolName(
270 module,
271 relocated_address - coff_section.virtual_address,
272 ),
273 .compile_unit_name = fs.path.basename(module.obj_file_name),
274 .source_location = try di.pdb.?.getLineNumberInfo(
275 module,
276 relocated_address - coff_section.virtual_address,
277 ),
278 };
279 }
280};
281
282pub const supports_unwinding: bool = switch (builtin.cpu.arch) {
283 else => true,
284 // On x86, `RtlVirtualUnwind` does not exist. We could in theory use `RtlCaptureStackBackTrace`
285 // instead, but on x86, it turns out that function is just... doing FP unwinding with esp! It's
286 // hard to find implementation details to confirm that, but the most authoritative source I have
287 // is an entry in the LLVM mailing list from 2020/08/16 which contains this quote:
288 //
289 // > x86 doesn't have what most architectures would consider an "unwinder" in the sense of
290 // > restoring registers; there is simply a linked list of frames that participate in SEH and
291 // > that desire to be called for a dynamic unwind operation, so RtlCaptureStackBackTrace
292 // > assumes that EBP-based frames are in use and walks an EBP-based frame chain on x86 - not
293 // > all x86 code is written with EBP-based frames so while even though we generally build the
294 // > OS that way, you might always run the risk of encountering external code that uses EBP as a
295 // > general purpose register for which such an unwind attempt for a stack trace would fail.
296 //
297 // Regardless, it's easy to effectively confirm this hypothesis just by compiling some code with
298 // `-fomit-frame-pointer -OReleaseFast` and observing that `RtlCaptureStackBackTrace` returns an
299 // empty trace when it's called in such an application. Note that without `-OReleaseFast` or
300 // similar, LLVM seems reluctant to ever clobber ebp, so you'll get a trace returned which just
301 // contains all of the kernel32/ntdll frames but none of your own. Don't be deceived---this is
302 // just coincidental!
303 //
304 // Anyway, the point is, the only stack walking primitive on x86-windows is FP unwinding. We
305 // *could* ask Microsoft to do that for us with `RtlCaptureStackBackTrace`... but better to just
306 // use our existing FP unwinder in `std.debug`!
307 .x86 => false,
308};
309pub const UnwindContext = struct {
310 pc: usize,
311 cur: windows.CONTEXT,
312 history_table: windows.UNWIND_HISTORY_TABLE,
313 pub fn init(ctx: *const std.debug.cpu_context.Native) UnwindContext {
314 return .{
315 .pc = @returnAddress(),
316 .cur = switch (builtin.cpu.arch) {
317 .x86_64 => std.mem.zeroInit(windows.CONTEXT, .{
318 .Rax = ctx.gprs.get(.rax),
319 .Rcx = ctx.gprs.get(.rcx),
320 .Rdx = ctx.gprs.get(.rdx),
321 .Rbx = ctx.gprs.get(.rbx),
322 .Rsp = ctx.gprs.get(.rsp),
323 .Rbp = ctx.gprs.get(.rbp),
324 .Rsi = ctx.gprs.get(.rsi),
325 .Rdi = ctx.gprs.get(.rdi),
326 .R8 = ctx.gprs.get(.r8),
327 .R9 = ctx.gprs.get(.r9),
328 .R10 = ctx.gprs.get(.r10),
329 .R11 = ctx.gprs.get(.r11),
330 .R12 = ctx.gprs.get(.r12),
331 .R13 = ctx.gprs.get(.r13),
332 .R14 = ctx.gprs.get(.r14),
333 .R15 = ctx.gprs.get(.r15),
334 .Rip = ctx.gprs.get(.rip),
335 }),
336 .aarch64, .aarch64_be => .{
337 .ContextFlags = 0,
338 .Cpsr = 0,
339 .DUMMYUNIONNAME = .{ .X = ctx.x },
340 .Sp = ctx.sp,
341 .Pc = ctx.pc,
342 .V = @splat(.{ .B = @splat(0) }),
343 .Fpcr = 0,
344 .Fpsr = 0,
345 .Bcr = @splat(0),
346 .Bvr = @splat(0),
347 .Wcr = @splat(0),
348 .Wvr = @splat(0),
349 },
350 .thumb => .{
351 .ContextFlags = 0,
352 .R0 = ctx.r[0],
353 .R1 = ctx.r[1],
354 .R2 = ctx.r[2],
355 .R3 = ctx.r[3],
356 .R4 = ctx.r[4],
357 .R5 = ctx.r[5],
358 .R6 = ctx.r[6],
359 .R7 = ctx.r[7],
360 .R8 = ctx.r[8],
361 .R9 = ctx.r[9],
362 .R10 = ctx.r[10],
363 .R11 = ctx.r[11],
364 .R12 = ctx.r[12],
365 .Sp = ctx.r[13],
366 .Lr = ctx.r[14],
367 .Pc = ctx.r[15],
368 .Cpsr = 0,
369 .Fpcsr = 0,
370 .Padding = 0,
371 .DUMMYUNIONNAME = .{ .S = @splat(0) },
372 .Bvr = @splat(0),
373 .Bcr = @splat(0),
374 .Wvr = @splat(0),
375 .Wcr = @splat(0),
376 .Padding2 = @splat(0),
377 },
378 else => comptime unreachable,
379 },
380 .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE),
381 };
382 }
383 pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void {
384 _ = ctx;
385 _ = gpa;
386 }
387 pub fn getFp(ctx: *UnwindContext) usize {
388 return ctx.cur.getRegs().bp;
389 }
390};
391pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
392 _ = module;
393 _ = gpa;
394 _ = di;
395
396 const current_regs = context.cur.getRegs();
397 var image_base: windows.DWORD64 = undefined;
398 if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| {
399 var handler_data: ?*anyopaque = null;
400 var establisher_frame: u64 = undefined;
401 _ = windows.ntdll.RtlVirtualUnwind(
402 windows.UNW_FLAG_NHANDLER,
403 image_base,
404 current_regs.ip,
405 runtime_function,
406 &context.cur,
407 &handler_data,
408 &establisher_frame,
409 null,
410 );
411 } else {
412 // leaf function
413 context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*);
414 context.cur.setSp(current_regs.sp + @sizeOf(usize));
415 }
416
417 const next_regs = context.cur.getRegs();
418 const tib = &windows.teb().NtTib;
419 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {
420 context.pc = 0;
421 return 0;
422 }
423 // Like `DwarfUnwindContext.unwindFrame`, adjust our next lookup pc in case the `call` was this
424 // function's last instruction making `next_regs.ip` one byte past its end.
425 context.pc = next_regs.ip -| 1;
426 return next_regs.ip;
427}
428
429const WindowsModule = @This();
430
431const std = @import("../../std.zig");
432const Allocator = std.mem.Allocator;
433const Dwarf = std.debug.Dwarf;
434const Pdb = std.debug.Pdb;
435const assert = std.debug.assert;
436const coff = std.coff;
437const fs = std.fs;
438const mem = std.mem;
439const windows = std.os.windows;
440
441const builtin = @import("builtin");
442const native_endian = builtin.target.cpu.arch.endian();
test/standalone/coff_dwarf/main.zig+1-1
......@@ -14,7 +14,7 @@ pub fn main() void {
1414 var add_addr: usize = undefined;
1515 _ = add(1, 2, &add_addr);
1616
17 const symbol = di.getSymbolAtAddress(gpa, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err});
17 const symbol = di.getSymbol(gpa, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err});
1818 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
1919
2020 if (symbol.name == null) fatal("failed to resolve symbol name", .{});