authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-10-02 21:38:07+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-10-02 21:38:07+02:00
logbc4da9a90743c11c7c0b3e485f46d365d57d87b7
treef98c87a7e2f0b07d3510d3d9972ef5c9ef9f652d
parent14019a95a4f3519bc03d23f79eb3141b51a9b2c2
parenta4f95b1e619656b0306744177dc4f8662ae4879b
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25437 from alexrp/std-debug

`std.debug`: LoongArch and RISC-V unwind support + some minor cleanups

8 files changed, 1272 insertions(+), 1042 deletions(-)

lib/std/debug.zig+6-22
......@@ -61,28 +61,12 @@ pub const cpu_context = @import("debug/cpu_context.zig");
6161/// ```
6262pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo"))
6363 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,
64else switch (std.Target.ObjectFormat.default(native_os, native_arch)) {
65 .coff => if (native_os == .windows) @import("debug/SelfInfo/Windows.zig") else void,
66 .elf => @import("debug/SelfInfo/Elf.zig"),
67 .macho => @import("debug/SelfInfo/MachO.zig"),
68 .goff, .plan9, .spirv, .wasm, .xcoff => void,
69 .c, .hex, .raw => unreachable,
8670};
8771
8872pub const SelfInfoError = error{
lib/std/debug/Dwarf.zig+12-10
......@@ -1429,30 +1429,36 @@ pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u16 {
14291429/// Returns `null` for CPU architectures without an instruction pointer register.
14301430pub fn ipRegNum(arch: std.Target.Cpu.Arch) ?u16 {
14311431 return switch (arch) {
1432 .aarch64, .aarch64_be => 32,
1433 .arm, .armeb, .thumb, .thumbeb => 15,
1434 .loongarch32, .loongarch64 => 32,
1435 .riscv32, .riscv32be, .riscv64, .riscv64be => 32,
14321436 .x86 => 8,
14331437 .x86_64 => 16,
1434 .arm, .armeb, .thumb, .thumbeb => 15,
1435 .aarch64, .aarch64_be => 32,
14361438 else => null,
14371439 };
14381440}
14391441
14401442pub fn fpRegNum(arch: std.Target.Cpu.Arch) u16 {
14411443 return switch (arch) {
1444 .aarch64, .aarch64_be => 29,
1445 .arm, .armeb, .thumb, .thumbeb => 11,
1446 .loongarch32, .loongarch64 => 22,
1447 .riscv32, .riscv32be, .riscv64, .riscv64be => 8,
14421448 .x86 => 5,
14431449 .x86_64 => 6,
1444 .arm, .armeb, .thumb, .thumbeb => 11,
1445 .aarch64, .aarch64_be => 29,
14461450 else => unreachable,
14471451 };
14481452}
14491453
14501454pub fn spRegNum(arch: std.Target.Cpu.Arch) u16 {
14511455 return switch (arch) {
1456 .aarch64, .aarch64_be => 31,
1457 .arm, .armeb, .thumb, .thumbeb => 13,
1458 .loongarch32, .loongarch64 => 3,
1459 .riscv32, .riscv32be, .riscv64, .riscv64be => 2,
14521460 .x86 => 4,
14531461 .x86_64 => 7,
1454 .arm, .armeb, .thumb, .thumbeb => 13,
1455 .aarch64, .aarch64_be => 31,
14561462 else => unreachable,
14571463 };
14581464}
......@@ -1470,10 +1476,6 @@ pub fn supportsUnwinding(target: *const std.Target) bool {
14701476 .spirv64,
14711477 => false,
14721478
1473 // Enabling this causes relocation errors such as:
1474 // error: invalid relocation type R_RISCV_SUB32 at offset 0x20
1475 .riscv64, .riscv64be, .riscv32, .riscv32be => false,
1476
14771479 // Conservative guess. Feel free to update this logic with any targets
14781480 // that are known to not support Dwarf unwinding.
14791481 else => true,
lib/std/debug/Dwarf/Unwind/VirtualMachine.zig+12-1
......@@ -256,7 +256,18 @@ fn evalInstructions(
256256 .offset = cfa.offset_sf * cie.data_alignment_factor,
257257 } },
258258 .def_cfa_reg => |register| switch (vm.current_row.cfa) {
259 .none, .expression => return error.InvalidOperation,
259 .none => {
260 // According to the DWARF specification, this is not valid, because this
261 // instruction can only be used to replace the register if the rule is already a
262 // `.reg_off`. However, this is emitted in practice by GNU toolchains for some
263 // targets, and so by convention is interpreted as equivalent to `.def_cfa` with
264 // an offset of 0.
265 vm.current_row.cfa = .{ .reg_off = .{
266 .register = register,
267 .offset = 0,
268 } };
269 },
270 .expression => return error.InvalidOperation,
260271 .reg_off => |*ro| ro.register = register,
261272 },
262273 .def_cfa_offset => |offset| switch (vm.current_row.cfa) {
lib/std/debug/SelfInfo/Darwin.zig deleted-993
......@@ -1,993 +0,0 @@
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/Elf.zig+29-6
......@@ -84,12 +84,35 @@ pub const can_unwind: bool = s: {
8484 // Notably, we are yet to support unwinding on ARM. There, unwinding is not done through
8585 // `.eh_frame`, but instead with the `.ARM.exidx` section, which has a different format.
8686 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 },
87 .linux => &.{
88 .aarch64,
89 .aarch64_be,
90 .loongarch64,
91 .riscv32,
92 .riscv64,
93 .x86,
94 .x86_64,
95 },
96 .netbsd => &.{
97 .aarch64,
98 .aarch64_be,
99 .x86,
100 .x86_64,
101 },
102 .freebsd => &.{
103 .x86_64,
104 .aarch64,
105 },
106 .openbsd => &.{
107 .x86_64,
108 },
109 .solaris => &.{
110 .x86_64,
111 },
112 .illumos => &.{
113 .x86,
114 .x86_64,
115 },
93116 else => unreachable,
94117 };
95118 for (archs) |a| {
lib/std/debug/SelfInfo/MachO.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 => 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/Windows.zig+1-1
......@@ -88,7 +88,7 @@ pub const UnwindContext = struct {
8888 .R15 = ctx.gprs.get(.r15),
8989 .Rip = ctx.gprs.get(.rip),
9090 }),
91 .aarch64, .aarch64_be => .{
91 .aarch64 => .{
9292 .ContextFlags = 0,
9393 .Cpsr = 0,
9494 .DUMMYUNIONNAME = .{ .X = ctx.x },
lib/std/debug/cpu_context.zig+219-9
......@@ -4,10 +4,12 @@
44pub const Native = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "CpuContext"))
55 root.debug.CpuContext
66else switch (native_arch) {
7 .aarch64, .aarch64_be => Aarch64,
8 .arm, .armeb, .thumb, .thumbeb => Arm,
9 .loongarch32, .loongarch64 => LoongArch,
10 .riscv32, .riscv32be, .riscv64, .riscv64be => Riscv,
711 .x86 => X86,
812 .x86_64 => X86_64,
9 .arm, .armeb, .thumb, .thumbeb => Arm,
10 .aarch64, .aarch64_be => Aarch64,
1113 else => noreturn,
1214};
1315
......@@ -21,7 +23,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
2123 const uc: *const signal_ucontext_t = @ptrCast(@alignCast(ctx_ptr));
2224 return switch (native_arch) {
2325 .x86 => switch (native_os) {
24 .linux, .netbsd, .solaris, .illumos => .{ .gprs = .init(.{
26 .linux, .netbsd, .illumos => .{ .gprs = .init(.{
2527 .eax = uc.mcontext.gregs[std.posix.REG.EAX],
2628 .ecx = uc.mcontext.gregs[std.posix.REG.ECX],
2729 .edx = uc.mcontext.gregs[std.posix.REG.EDX],
......@@ -92,7 +94,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
9294 .r15 = @bitCast(uc.sc_r15),
9395 .rip = @bitCast(uc.sc_rip),
9496 }) },
95 .macos, .ios => .{ .gprs = .init(.{
97 .driverkit, .macos, .ios => .{ .gprs = .init(.{
9698 .rax = uc.mcontext.ss.rax,
9799 .rdx = uc.mcontext.ss.rdx,
98100 .rcx = uc.mcontext.ss.rcx,
......@@ -137,7 +139,7 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
137139 else => null,
138140 },
139141 .aarch64, .aarch64_be => switch (builtin.os.tag) {
140 .macos, .ios, .tvos, .watchos, .visionos => .{
142 .driverkit, .macos, .ios, .tvos, .watchos, .visionos => .{
141143 .x = uc.mcontext.ss.regs ++ @as([2]u64, .{
142144 uc.mcontext.ss.fp, // x29 = fp
143145 uc.mcontext.ss.lr, // x30 = lr
......@@ -173,6 +175,20 @@ pub fn fromPosixSignalContext(ctx_ptr: ?*const anyopaque) ?Native {
173175 },
174176 else => null,
175177 },
178 .loongarch64 => switch (builtin.os.tag) {
179 .linux => .{
180 .r = uc.mcontext.regs, // includes r0 (hardwired zero)
181 .pc = uc.mcontext.pc,
182 },
183 else => null,
184 },
185 .riscv32, .riscv64 => switch (builtin.os.tag) {
186 .linux => .{
187 .r = [1]usize{0} ++ uc.mcontext.gregs[1..].*, // r0 position is used for pc; replace with zero
188 .pc = uc.mcontext.gregs[0],
189 },
190 else => null,
191 },
176192 else => null,
177193 };
178194}
......@@ -209,7 +225,7 @@ pub fn fromWindowsContext(ctx: *const std.os.windows.CONTEXT) Native {
209225 .r15 = ctx.R15,
210226 .rip = ctx.Rip,
211227 }) },
212 .aarch64, .aarch64_be => .{
228 .aarch64 => .{
213229 .x = ctx.DUMMYUNIONNAME.X[0..31].*,
214230 .sp = ctx.Sp,
215231 .pc = ctx.Pc,
......@@ -371,7 +387,6 @@ pub const Arm = struct {
371387 pub fn dwarfRegisterBytes(ctx: *Arm, register_num: u16) DwarfRegisterError![]u8 {
372388 // DWARF for the Arm(r) Architecture § 4.1 "DWARF register names"
373389 switch (register_num) {
374 // The order of `Gpr` intentionally matches DWARF's mappings.
375390 0...15 => return @ptrCast(&ctx.r[register_num]),
376391
377392 64...95 => return error.UnsupportedRegister, // S0 - S31
......@@ -444,7 +459,6 @@ pub const Aarch64 = extern struct {
444459 pub fn dwarfRegisterBytes(ctx: *Aarch64, register_num: u16) DwarfRegisterError![]u8 {
445460 // DWARF for the Arm(r) 64-bit Architecture (AArch64) § 4.1 "DWARF register names"
446461 switch (register_num) {
447 // The order of `Gpr` intentionally matches DWARF's mappings.
448462 0...30 => return @ptrCast(&ctx.x[register_num]),
449463 31 => return @ptrCast(&ctx.sp),
450464 32 => return @ptrCast(&ctx.pc),
......@@ -467,11 +481,207 @@ pub const Aarch64 = extern struct {
467481 }
468482};
469483
484/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
485pub const LoongArch = extern struct {
486 /// The numbered general-purpose registers r0 - r31. r0 must be zero.
487 r: [32]usize,
488 pc: usize,
489
490 pub inline fn current() LoongArch {
491 var ctx: LoongArch = undefined;
492 asm volatile (if (@sizeOf(usize) == 8)
493 \\ st.d $zero, $t0, 0
494 \\ st.d $ra, $t0, 8
495 \\ st.d $tp, $t0, 16
496 \\ st.d $sp, $t0, 24
497 \\ st.d $a0, $t0, 32
498 \\ st.d $a1, $t0, 40
499 \\ st.d $a2, $t0, 48
500 \\ st.d $a3, $t0, 56
501 \\ st.d $a4, $t0, 64
502 \\ st.d $a5, $t0, 72
503 \\ st.d $a6, $t0, 80
504 \\ st.d $a7, $t0, 88
505 \\ st.d $t0, $t0, 96
506 \\ st.d $t1, $t0, 104
507 \\ st.d $t2, $t0, 112
508 \\ st.d $t3, $t0, 120
509 \\ st.d $t4, $t0, 128
510 \\ st.d $t5, $t0, 136
511 \\ st.d $t6, $t0, 144
512 \\ st.d $t7, $t0, 152
513 \\ st.d $t8, $t0, 160
514 \\ st.d $r21, $t0, 168
515 \\ st.d $fp, $t0, 176
516 \\ st.d $s0, $t0, 184
517 \\ st.d $s1, $t0, 192
518 \\ st.d $s2, $t0, 200
519 \\ st.d $s3, $t0, 208
520 \\ st.d $s4, $t0, 216
521 \\ st.d $s5, $t0, 224
522 \\ st.d $s6, $t0, 232
523 \\ st.d $s7, $t0, 240
524 \\ st.d $s8, $t0, 248
525 \\ bl 1f
526 \\1:
527 \\ st.d $ra, $t0, 256
528 \\ ld.d $ra, $t0, 8
529 else
530 \\ st.w $zero, $t0, 0
531 \\ st.w $ra, $t0, 4
532 \\ st.w $tp, $t0, 8
533 \\ st.w $sp, $t0, 12
534 \\ st.w $a0, $t0, 16
535 \\ st.w $a1, $t0, 20
536 \\ st.w $a2, $t0, 24
537 \\ st.w $a3, $t0, 28
538 \\ st.w $a4, $t0, 32
539 \\ st.w $a5, $t0, 36
540 \\ st.w $a6, $t0, 40
541 \\ st.w $a7, $t0, 44
542 \\ st.w $t0, $t0, 48
543 \\ st.w $t1, $t0, 52
544 \\ st.w $t2, $t0, 56
545 \\ st.w $t3, $t0, 60
546 \\ st.w $t4, $t0, 64
547 \\ st.w $t5, $t0, 68
548 \\ st.w $t6, $t0, 72
549 \\ st.w $t7, $t0, 76
550 \\ st.w $t8, $t0, 80
551 \\ st.w $r21, $t0, 84
552 \\ st.w $fp, $t0, 88
553 \\ st.w $s0, $t0, 92
554 \\ st.w $s1, $t0, 96
555 \\ st.w $s2, $t0, 100
556 \\ st.w $s3, $t0, 104
557 \\ st.w $s4, $t0, 108
558 \\ st.w $s5, $t0, 112
559 \\ st.w $s6, $t0, 116
560 \\ st.w $s7, $t0, 120
561 \\ st.w $s8, $t0, 124
562 \\ bl 1f
563 \\1:
564 \\ st.w $ra, $t0, 128
565 \\ ld.w $ra, $t0, 4
566 :
567 : [gprs] "{$r12}" (&ctx),
568 : .{ .memory = true });
569 return ctx;
570 }
571
572 pub fn dwarfRegisterBytes(ctx: *LoongArch, register_num: u16) DwarfRegisterError![]u8 {
573 switch (register_num) {
574 0...31 => return @ptrCast(&ctx.r[register_num]),
575 32 => return @ptrCast(&ctx.pc),
576
577 else => return error.InvalidRegister,
578 }
579 }
580};
581
582/// This is an `extern struct` so that inline assembly in `current` can use field offsets.
583pub const Riscv = extern struct {
584 /// The numbered general-purpose registers r0 - r31. r0 must be zero.
585 r: [32]usize,
586 pc: usize,
587
588 pub inline fn current() Riscv {
589 var ctx: Riscv = undefined;
590 asm volatile (if (@sizeOf(usize) == 8)
591 \\ sd zero, 0(t0)
592 \\ sd ra, 8(t0)
593 \\ sd sp, 16(t0)
594 \\ sd gp, 24(t0)
595 \\ sd tp, 32(t0)
596 \\ sd t0, 40(t0)
597 \\ sd t1, 48(t0)
598 \\ sd t2, 56(t0)
599 \\ sd s0, 64(t0)
600 \\ sd s1, 72(t0)
601 \\ sd a0, 80(t0)
602 \\ sd a1, 88(t0)
603 \\ sd a2, 96(t0)
604 \\ sd a3, 104(t0)
605 \\ sd a4, 112(t0)
606 \\ sd a5, 120(t0)
607 \\ sd a6, 128(t0)
608 \\ sd a7, 136(t0)
609 \\ sd s2, 144(t0)
610 \\ sd s3, 152(t0)
611 \\ sd s4, 160(t0)
612 \\ sd s5, 168(t0)
613 \\ sd s6, 176(t0)
614 \\ sd s7, 184(t0)
615 \\ sd s8, 192(t0)
616 \\ sd s9, 200(t0)
617 \\ sd s10, 208(t0)
618 \\ sd s11, 216(t0)
619 \\ sd t3, 224(t0)
620 \\ sd t4, 232(t0)
621 \\ sd t5, 240(t0)
622 \\ sd t6, 248(t0)
623 \\ jal ra, 1f
624 \\1:
625 \\ sd ra, 256(t0)
626 \\ ld ra, 8(t0)
627 else
628 \\ sw zero, 0(t0)
629 \\ sw ra, 4(t0)
630 \\ sw sp, 8(t0)
631 \\ sw gp, 12(t0)
632 \\ sw tp, 16(t0)
633 \\ sw t0, 20(t0)
634 \\ sw t1, 24(t0)
635 \\ sw t2, 28(t0)
636 \\ sw s0, 32(t0)
637 \\ sw s1, 36(t0)
638 \\ sw a0, 40(t0)
639 \\ sw a1, 44(t0)
640 \\ sw a2, 48(t0)
641 \\ sw a3, 52(t0)
642 \\ sw a4, 56(t0)
643 \\ sw a5, 60(t0)
644 \\ sw a6, 64(t0)
645 \\ sw a7, 68(t0)
646 \\ sw s2, 72(t0)
647 \\ sw s3, 76(t0)
648 \\ sw s4, 80(t0)
649 \\ sw s5, 84(t0)
650 \\ sw s6, 88(t0)
651 \\ sw s7, 92(t0)
652 \\ sw s8, 96(t0)
653 \\ sw s9, 100(t0)
654 \\ sw s10, 104(t0)
655 \\ sw s11, 108(t0)
656 \\ sw t3, 112(t0)
657 \\ sw t4, 116(t0)
658 \\ sw t5, 120(t0)
659 \\ sw t6, 124(t0)
660 \\ jal ra, 1f
661 \\1:
662 \\ sw ra, 128(t0)
663 \\ lw ra, 4(t0)
664 :
665 : [gprs] "{t0}" (&ctx),
666 : .{ .memory = true });
667 return ctx;
668 }
669
670 pub fn dwarfRegisterBytes(ctx: *Riscv, register_num: u16) DwarfRegisterError![]u8 {
671 switch (register_num) {
672 0...31 => return @ptrCast(&ctx.r[register_num]),
673 32 => return @ptrCast(&ctx.pc),
674
675 else => return error.InvalidRegister,
676 }
677 }
678};
679
470680const signal_ucontext_t = switch (native_os) {
471681 .linux => std.os.linux.ucontext_t,
472682 .emscripten => std.os.emscripten.ucontext_t,
473683 .freebsd => std.os.freebsd.ucontext_t,
474 .macos, .ios, .tvos, .watchos, .visionos => extern struct {
684 .driverkit, .macos, .ios, .tvos, .watchos, .visionos => extern struct {
475685 onstack: c_int,
476686 sigmask: std.c.sigset_t,
477687 stack: std.c.stack_t,