1mutex: Io.Mutex,
2/// Accessed through `Module.Adapter`.
3modules: std.array_hash_map.Custom(Module, void, Module.Context, false),
4
5pub const init: SelfInfo = .{
6 .mutex = .init,
7 .modules = .empty,
8};
9pub fn deinit(si: *SelfInfo, io: Io) void {
10 _ = io;
11 const gpa = std.debug.getDebugInfoAllocator();
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 file: {
18 const f = &(module.file orelse break :file catch break :file);
19 f.deinit(gpa);
20 }
21 }
22 si.modules.deinit(gpa);
23}
24
25pub fn getSymbols(
26 si: *SelfInfo,
27 io: Io,
28 symbol_allocator: Allocator,
29 text_arena: Allocator,
30 address: usize,
31 resolve_inline_callers: bool,
32 symbols: *std.ArrayList(std.debug.Symbol),
33) Error!void {
34 _ = resolve_inline_callers;
35 const gpa = std.debug.getDebugInfoAllocator();
36
37 const module = try si.findModule(gpa, io, address);
38 defer si.mutex.unlock(io);
39
40 const file = try module.getFile(gpa, io);
41
42 // This is not necessarily the same as the vmaddr_slide that dyld would report. This is
43 // because the segments in the file on disk might differ from the ones in memory. Normally
44 // we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying:
45 // it exists on disk (necessarily, because the kernel needs to load it!), but is also in
46 // the dyld cache (dyld actually restart itself from cache after loading it), and the two
47 // versions have (very) different segment base addresses. It's sort of like a large slide
48 // has been applied to all addresses in memory. For an optimal experience, we consider the
49 // on-disk vmaddr instead of the in-memory one.
50 const vaddr_offset = module.text_base - file.text_vmaddr;
51
52 const vaddr = address - vaddr_offset;
53
54 const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, io, vaddr) catch {
55 // Return at least the symbol name if available.
56 return symbols.append(symbol_allocator, .{
57 .name = try file.lookupSymbolName(vaddr),
58 .compile_unit_name = null,
59 .source_location = null,
60 });
61 };
62
63 const compile_unit = ofile_dwarf.findCompileUnit(native_endian, ofile_vaddr) catch {
64 // Return at least the symbol name if available.
65 return symbols.append(symbol_allocator, .{
66 .name = try file.lookupSymbolName(vaddr),
67 .compile_unit_name = null,
68 .source_location = null,
69 });
70 };
71
72 try symbols.append(symbol_allocator, .{
73 .name = ofile_dwarf.getSymbolName(ofile_vaddr) orelse
74 try file.lookupSymbolName(vaddr),
75 .compile_unit_name = compile_unit.die.getAttrString(
76 ofile_dwarf,
77 native_endian,
78 std.dwarf.AT.name,
79 ofile_dwarf.section(.debug_str),
80 compile_unit,
81 ) catch |err| switch (err) {
82 error.MissingDebugInfo, error.InvalidDebugInfo => null,
83 },
84 .source_location = ofile_dwarf.getLineNumberInfo(
85 gpa,
86 text_arena,
87 native_endian,
88 compile_unit,
89 ofile_vaddr,
90 ) catch null,
91 });
92}
93pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 {
94 _ = si;
95 _ = io;
96 return getModuleNameInner(address) orelse return error.MissingDebugInfo;
97}
98fn getModuleNameInner(address: usize) ?[]const u8 {
99 switch (builtin.target.os.tag) {
100 .macos => {
101 // This function is marked as deprecated; however, it is significantly more performant
102 // than `dladdr` (since the latter also does a very slow symbol lookup), so let's just
103 // use it for the better performance since it's still available.
104 return std.mem.span(std.c.dyld_image_path_containing_address(
105 @ptrFromInt(address),
106 ) orelse return null);
107 },
108 else => {
109 // On other Darwin systems, the function used above is entirely unavailable, so we have
110 // no choice but to use the slow `dladdr`.
111 var info: std.c.dl_info = undefined;
112 if (std.c.dladdr(@ptrFromInt(address), &info) == 0) {
113 return null;
114 }
115 return std.mem.span(info.fname);
116 },
117 }
118}
119pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize {
120 const gpa = std.debug.getDebugInfoAllocator();
121 const module = try si.findModule(gpa, io, address);
122 defer si.mutex.unlock(io);
123 const header: *std.macho.mach_header_64 = @ptrFromInt(module.text_base);
124 const raw_macho: [*]u8 = @ptrCast(header);
125 var it = macho.LoadCommandIterator.init(header, raw_macho[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds]) catch unreachable;
126 const text_vmaddr = while (it.next() catch unreachable) |load_cmd| {
127 if (load_cmd.hdr.cmd != .SEGMENT_64) continue;
128 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
129 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
130 break segment_cmd.vmaddr;
131 } else unreachable;
132 return module.text_base - text_vmaddr;
133}
134
135pub const can_unwind: bool = true;
136pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;
137/// Unwind a frame using MachO compact unwind info (from `__unwind_info`).
138/// If the compact encoding can't encode a way to unwind a frame, it will
139/// defer unwinding to DWARF, in which case `__eh_frame` will be used if available.
140pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) Error!usize {
141 return unwindFrameInner(si, io, context) catch |err| switch (err) {
142 error.InvalidDebugInfo,
143 error.MissingDebugInfo,
144 error.UnsupportedDebugInfo,
145 error.ReadFailed,
146 error.OutOfMemory,
147 error.Unexpected,
148 error.Canceled,
149 => |e| return e,
150
151 error.UnsupportedRegister,
152 error.UnsupportedAddrSize,
153 error.UnimplementedUserOpcode,
154 => return error.UnsupportedDebugInfo,
155
156 error.Overflow,
157 error.EndOfStream,
158 error.StreamTooLong,
159 error.InvalidOpcode,
160 error.InvalidOperation,
161 error.InvalidOperand,
162 error.InvalidRegister,
163 error.IncompatibleRegisterSize,
164 => return error.InvalidDebugInfo,
165 };
166}
167fn unwindFrameInner(si: *SelfInfo, io: Io, context: *UnwindContext) !usize {
168 const gpa = std.debug.getDebugInfoAllocator();
169 const module = try si.findModule(gpa, io, context.pc);
170 defer si.mutex.unlock(io);
171
172 const unwind: *Module.Unwind = try module.getUnwindInfo(gpa);
173
174 const ip_reg_num = comptime Dwarf.ipRegNum(builtin.target.cpu.arch).?;
175 const fp_reg_num = comptime Dwarf.fpRegNum(builtin.target.cpu.arch);
176 const sp_reg_num = comptime Dwarf.spRegNum(builtin.target.cpu.arch);
177
178 const unwind_info = unwind.unwind_info orelse return error.MissingDebugInfo;
179 if (unwind_info.len < @sizeOf(macho.unwind_info_section_header)) return error.InvalidDebugInfo;
180 const header: *align(1) const macho.unwind_info_section_header = @ptrCast(unwind_info);
181
182 const index_byte_count = header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry);
183 if (unwind_info.len < header.indexSectionOffset + index_byte_count) return error.InvalidDebugInfo;
184 const indices: []align(1) const macho.unwind_info_section_header_index_entry = @ptrCast(unwind_info[header.indexSectionOffset..][0..index_byte_count]);
185 if (indices.len == 0) return error.MissingDebugInfo;
186
187 // offset of the PC into the `__TEXT` segment
188 const pc_text_offset = context.pc - module.text_base;
189
190 const start_offset: u32, const first_level_offset: u32 = index: {
191 var left: usize = 0;
192 var len: usize = indices.len;
193 while (len > 1) {
194 const mid = left + len / 2;
195 if (pc_text_offset < indices[mid].functionOffset) {
196 len /= 2;
197 } else {
198 left = mid;
199 len -= len / 2;
200 }
201 }
202 break :index .{ indices[left].secondLevelPagesSectionOffset, indices[left].functionOffset };
203 };
204 // An offset of 0 is a sentinel indicating a range does not have unwind info.
205 if (start_offset == 0) return error.MissingDebugInfo;
206
207 const common_encodings_byte_count = header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t);
208 if (unwind_info.len < header.commonEncodingsArraySectionOffset + common_encodings_byte_count) return error.InvalidDebugInfo;
209 const common_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
210 unwind_info[header.commonEncodingsArraySectionOffset..][0..common_encodings_byte_count],
211 );
212
213 if (unwind_info.len < start_offset + @sizeOf(macho.UNWIND_SECOND_LEVEL)) return error.InvalidDebugInfo;
214 const kind: *align(1) const macho.UNWIND_SECOND_LEVEL = @ptrCast(unwind_info[start_offset..]);
215
216 const entry: struct {
217 function_offset: usize,
218 raw_encoding: u32,
219 } = switch (kind.*) {
220 .REGULAR => entry: {
221 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_regular_second_level_page_header)) return error.InvalidDebugInfo;
222 const page_header: *align(1) const macho.unwind_info_regular_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
223
224 const entries_byte_count = page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry);
225 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
226 const entries: []align(1) const macho.unwind_info_regular_second_level_entry = @ptrCast(
227 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
228 );
229 if (entries.len == 0) return error.InvalidDebugInfo;
230
231 var left: usize = 0;
232 var len: usize = entries.len;
233 while (len > 1) {
234 const mid = left + len / 2;
235 if (pc_text_offset < entries[mid].functionOffset) {
236 len /= 2;
237 } else {
238 left = mid;
239 len -= len / 2;
240 }
241 }
242 break :entry .{
243 .function_offset = entries[left].functionOffset,
244 .raw_encoding = entries[left].encoding,
245 };
246 },
247 .COMPRESSED => entry: {
248 if (unwind_info.len < start_offset + @sizeOf(macho.unwind_info_compressed_second_level_page_header)) return error.InvalidDebugInfo;
249 const page_header: *align(1) const macho.unwind_info_compressed_second_level_page_header = @ptrCast(unwind_info[start_offset..]);
250
251 const entries_byte_count = page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry);
252 if (unwind_info.len < start_offset + entries_byte_count) return error.InvalidDebugInfo;
253 const entries: []align(1) const macho.UnwindInfoCompressedEntry = @ptrCast(
254 unwind_info[start_offset + page_header.entryPageOffset ..][0..entries_byte_count],
255 );
256 if (entries.len == 0) return error.InvalidDebugInfo;
257
258 var left: usize = 0;
259 var len: usize = entries.len;
260 while (len > 1) {
261 const mid = left + len / 2;
262 if (pc_text_offset < first_level_offset + entries[mid].funcOffset) {
263 len /= 2;
264 } else {
265 left = mid;
266 len -= len / 2;
267 }
268 }
269 const entry = entries[left];
270
271 const function_offset = first_level_offset + entry.funcOffset;
272 if (entry.encodingIndex < common_encodings.len) {
273 break :entry .{
274 .function_offset = function_offset,
275 .raw_encoding = common_encodings[entry.encodingIndex],
276 };
277 }
278
279 const local_index = entry.encodingIndex - common_encodings.len;
280 const local_encodings_byte_count = page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t);
281 if (unwind_info.len < start_offset + page_header.encodingsPageOffset + local_encodings_byte_count) return error.InvalidDebugInfo;
282 const local_encodings: []align(1) const macho.compact_unwind_encoding_t = @ptrCast(
283 unwind_info[start_offset + page_header.encodingsPageOffset ..][0..local_encodings_byte_count],
284 );
285 if (local_index >= local_encodings.len) return error.InvalidDebugInfo;
286 break :entry .{
287 .function_offset = function_offset,
288 .raw_encoding = local_encodings[local_index],
289 };
290 },
291 else => return error.InvalidDebugInfo,
292 };
293
294 if (entry.raw_encoding == 0) return error.MissingDebugInfo;
295
296 const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding);
297 const new_ip = switch (builtin.cpu.arch) {
298 .x86_64 => switch (encoding.mode.x86_64) {
299 .OLD => return error.UnsupportedDebugInfo,
300 .RBP_FRAME => ip: {
301 const frame = encoding.value.x86_64.frame;
302
303 const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
304 const new_sp = fp + 2 * @sizeOf(usize);
305
306 const ip_ptr = fp + @sizeOf(usize);
307 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
308 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
309
310 (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
311 (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
312 (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
313
314 const regs: [5]u3 = .{
315 frame.reg0,
316 frame.reg1,
317 frame.reg2,
318 frame.reg3,
319 frame.reg4,
320 };
321 for (regs, 0..) |reg, i| {
322 if (reg == 0) continue;
323 const addr = fp - frame.frame_offset * @sizeOf(usize) + i * @sizeOf(usize);
324 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg);
325 (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(addr)).*;
326 }
327
328 break :ip new_ip;
329 },
330 .STACK_IMMD,
331 .STACK_IND,
332 => ip: {
333 const frameless = encoding.value.x86_64.frameless;
334
335 const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
336 const stack_size: usize = stack_size: {
337 if (encoding.mode.x86_64 == .STACK_IMMD) {
338 break :stack_size @as(usize, frameless.stack.direct.stack_size) * @sizeOf(usize);
339 }
340 // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function.
341 const sub_offset_addr =
342 module.text_base +
343 entry.function_offset +
344 frameless.stack.indirect.sub_offset;
345 // `sub_offset_addr` points to the offset of the literal within the instruction
346 const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*;
347 break :stack_size sub_operand + @sizeOf(usize) * @as(usize, frameless.stack.indirect.stack_adjust);
348 };
349
350 // Decode the Lehmer-coded sequence of registers.
351 // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h
352
353 // Decode the variable-based permutation number into its digits. Each digit represents
354 // an index into the list of register numbers that weren't yet used in the sequence at
355 // the time the digit was added.
356 const reg_count = frameless.stack_reg_count;
357 const ip_ptr = ip_ptr: {
358 var digits: [6]u3 = undefined;
359 var accumulator: usize = frameless.stack_reg_permutation;
360 var base: usize = 2;
361 for (0..reg_count) |i| {
362 const div = accumulator / base;
363 digits[digits.len - 1 - i] = @intCast(accumulator - base * div);
364 accumulator = div;
365 base += 1;
366 }
367
368 var registers: [6]u3 = undefined;
369 var used_indices: [6]bool = @splat(false);
370 for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| {
371 var unused_count: u8 = 0;
372 const unused_index = for (used_indices, 0..) |used, index| {
373 if (!used) {
374 if (target_unused_index == unused_count) break index;
375 unused_count += 1;
376 }
377 } else unreachable;
378 registers[i] = @intCast(unused_index + 1);
379 used_indices[unused_index] = true;
380 }
381
382 var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1);
383 for (0..reg_count) |i| {
384 const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]);
385 (try dwarfRegNative(&context.cpu_state, reg_number)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
386 reg_addr += @sizeOf(usize);
387 }
388
389 break :ip_ptr reg_addr;
390 };
391
392 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
393 const new_sp = ip_ptr + @sizeOf(usize);
394
395 (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
396 (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
397
398 break :ip new_ip;
399 },
400 .DWARF => {
401 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
402 const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf);
403 return context.next(gpa, &rules);
404 },
405 },
406 .aarch64 => switch (encoding.mode.arm64) {
407 .OLD => return error.UnsupportedDebugInfo,
408 .FRAMELESS => ip: {
409 const sp = (try dwarfRegNative(&context.cpu_state, sp_reg_num)).*;
410 const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16;
411 const new_ip = (try dwarfRegNative(&context.cpu_state, 30)).*;
412 (try dwarfRegNative(&context.cpu_state, sp_reg_num)).* = new_sp;
413 break :ip new_ip;
414 },
415 .DWARF => {
416 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
417 const rules = try context.computeRules(gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf);
418 return context.next(gpa, &rules);
419 },
420 .FRAME => ip: {
421 const frame = encoding.value.arm64.frame;
422
423 const fp = (try dwarfRegNative(&context.cpu_state, fp_reg_num)).*;
424 const ip_ptr = fp + @sizeOf(usize);
425
426 var reg_addr = fp - @sizeOf(usize);
427 inline for (@typeInfo(@TypeOf(frame.x_reg_pairs)).@"struct".field_names, 0..) |field_name, i| {
428 if (@field(frame.x_reg_pairs, field_name) != 0) {
429 (try dwarfRegNative(&context.cpu_state, 19 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
430 reg_addr += @sizeOf(usize);
431 (try dwarfRegNative(&context.cpu_state, 20 + i)).* = @as(*const usize, @ptrFromInt(reg_addr)).*;
432 reg_addr += @sizeOf(usize);
433 }
434 }
435
436 // We intentionally skip restoring `frame.d_reg_pairs`; we know we don't support
437 // vector registers in the AArch64 `cpu_context` anyway, so there's no reason to
438 // fail a legitimate unwind just because we're asked to restore the registers here.
439 // If some weird/broken unwind info tells us to read them later, we will fail then.
440 reg_addr += 16 * @as(usize, @popCount(@as(u4, @bitCast(frame.d_reg_pairs))));
441
442 const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*;
443 const new_fp = @as(*const usize, @ptrFromInt(fp)).*;
444
445 (try dwarfRegNative(&context.cpu_state, fp_reg_num)).* = new_fp;
446 (try dwarfRegNative(&context.cpu_state, ip_reg_num)).* = new_ip;
447
448 break :ip new_ip;
449 },
450 },
451 else => comptime unreachable, // unimplemented
452 };
453
454 const ret_addr = std.debug.stripInstructionPtrAuthCode(new_ip);
455
456 // Like `Dwarf.SelfUnwinder.next`, adjust our next lookup pc in case the `call` was this
457 // function's last instruction making `ret_addr` one byte past its end.
458 context.pc = ret_addr -| 1;
459
460 return ret_addr;
461}
462
463/// Acquires the mutex on success.
464fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!*Module {
465 const text_base: *anyopaque = switch (builtin.target.os.tag) {
466 .macos => base: {
467 // This function is marked as deprecated; however, it is significantly more performant
468 // than `dladdr` (since the latter also does a very slow symbol lookup), so let's just
469 // use it for the better performance since it's still available.
470 break :base std.c._dyld_get_image_header_containing_address(
471 @ptrFromInt(address),
472 ) orelse return error.MissingDebugInfo;
473 },
474 else => base: {
475 // On other Darwin systems, the function used above is entirely unavailable, so we have
476 // no choice but to use the slow `dladdr`.
477 var info: std.c.dl_info = undefined;
478 if (std.c.dladdr(@ptrFromInt(address), &info) == 0) {
479 return error.MissingDebugInfo;
480 }
481 break :base info.fbase;
482 },
483 };
484 try si.mutex.lock(io);
485 errdefer si.mutex.unlock(io);
486 const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(text_base), Module.Adapter{});
487 errdefer comptime unreachable;
488 if (!gop.found_existing) gop.key_ptr.* = .{
489 .text_base = @intFromPtr(text_base),
490 .unwind = null,
491 .file = null,
492 };
493 return gop.key_ptr;
494}
495
496const Module = struct {
497 text_base: usize,
498 unwind: ?(Error!Unwind),
499 file: ?(Error!MachOFile),
500
501 const Adapter = struct {
502 pub fn hash(_: Adapter, text_base: usize) u32 {
503 return @truncate(std.hash.int(text_base));
504 }
505 pub fn eql(_: Adapter, a_text_base: usize, b_module: Module, b_index: usize) bool {
506 _ = b_index;
507 return a_text_base == b_module.text_base;
508 }
509 };
510 const Context = struct {
511 pub fn hash(_: Context, module: Module) u32 {
512 return @truncate(std.hash.int(module.text_base));
513 }
514 pub fn eql(_: Context, a_module: Module, b_module: Module, b_index: usize) bool {
515 _ = b_index;
516 return a_module.text_base == b_module.text_base;
517 }
518 };
519
520 const Unwind = struct {
521 /// The slide applied to the `__unwind_info` and `__eh_frame` sections.
522 /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr.
523 vmaddr_slide: u64,
524 /// Backed by the in-memory section mapped by the loader.
525 unwind_info: ?[]const u8,
526 /// Backed by the in-memory `__eh_frame` section mapped by the loader.
527 dwarf: ?Dwarf.Unwind,
528 };
529
530 fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind {
531 if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa);
532 return if (module.unwind.?) |*unwind| unwind else |err| err;
533 }
534 fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind {
535 const header: *std.macho.mach_header_64 = @ptrFromInt(module.text_base);
536
537 const raw_macho: [*]u8 = @ptrCast(header);
538 var it = macho.LoadCommandIterator.init(header, raw_macho[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds]) catch unreachable;
539 const sections, const text_vmaddr = while (it.next() catch unreachable) |load_cmd| {
540 if (load_cmd.hdr.cmd != .SEGMENT_64) continue;
541 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
542 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
543 break .{ load_cmd.getSections(), segment_cmd.vmaddr };
544 } else unreachable;
545
546 const vmaddr_slide = module.text_base - text_vmaddr;
547
548 var opt_unwind_info: ?[]const u8 = null;
549 var opt_eh_frame: ?[]const u8 = null;
550 for (sections) |sect| {
551 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
552 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
553 opt_unwind_info = sect_ptr[0..@intCast(sect.size)];
554 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
555 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
556 opt_eh_frame = sect_ptr[0..@intCast(sect.size)];
557 }
558 }
559 const eh_frame = opt_eh_frame orelse return .{
560 .vmaddr_slide = vmaddr_slide,
561 .unwind_info = opt_unwind_info,
562 .dwarf = null,
563 };
564 var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame);
565 errdefer dwarf.deinit(gpa);
566 // We don't need lookups, so this call is just for scanning CIEs.
567 dwarf.prepare(gpa, @sizeOf(usize), native_endian, false, true) catch |err| switch (err) {
568 error.ReadFailed => unreachable, // it's all fixed buffers
569 error.InvalidDebugInfo,
570 error.MissingDebugInfo,
571 error.OutOfMemory,
572 => |e| return e,
573 error.EndOfStream,
574 error.Overflow,
575 error.StreamTooLong,
576 error.InvalidOperand,
577 error.InvalidOpcode,
578 error.InvalidOperation,
579 => return error.InvalidDebugInfo,
580 error.UnsupportedAddrSize,
581 error.UnsupportedDwarfVersion,
582 error.UnimplementedUserOpcode,
583 => return error.UnsupportedDebugInfo,
584 };
585
586 return .{
587 .vmaddr_slide = vmaddr_slide,
588 .unwind_info = opt_unwind_info,
589 .dwarf = dwarf,
590 };
591 }
592
593 fn getFile(module: *Module, gpa: Allocator, io: Io) Error!*MachOFile {
594 if (module.file == null) {
595 const path = getModuleNameInner(module.text_base).?;
596 module.file = MachOFile.load(gpa, io, path, builtin.cpu.arch) catch |err| switch (err) {
597 error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo,
598 error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e,
599 };
600 }
601 return if (module.file.?) |*f| f else |err| err;
602 }
603};
604
605const MachoSymbol = struct {
606 strx: u32,
607 addr: u64,
608 /// Value may be `unknown_ofile`.
609 ofile: u32,
610 const unknown_ofile = std.math.maxInt(u32);
611 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
612 _ = context;
613 return lhs.addr < rhs.addr;
614 }
615 /// Assumes that `symbols` is sorted in order of ascending `addr`.
616 fn find(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
617 if (symbols.len == 0) return null; // no potential match
618 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
619 var left: usize = 0;
620 var len: usize = symbols.len;
621 while (len > 1) {
622 const mid = left + len / 2;
623 if (address < symbols[mid].addr) {
624 len /= 2;
625 } else {
626 left = mid;
627 len -= len / 2;
628 }
629 }
630 return &symbols[left];
631 }
632
633 test find {
634 const symbols: []const MachoSymbol = &.{
635 .{ .addr = 100, .strx = undefined, .ofile = undefined },
636 .{ .addr = 200, .strx = undefined, .ofile = undefined },
637 .{ .addr = 300, .strx = undefined, .ofile = undefined },
638 };
639
640 try testing.expectEqual(null, find(symbols, 0));
641 try testing.expectEqual(null, find(symbols, 99));
642 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
643 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
644 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
645
646 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
647 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
648 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
649
650 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
651 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
652 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
653 }
654};
655test {
656 _ = MachoSymbol;
657}
658
659/// Uses `mmap` to map the file at `path` into memory.
660fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 {
661 const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
662 error.FileNotFound => return error.MissingDebugInfo,
663 else => return error.ReadFailed,
664 };
665 defer file.close(io);
666
667 const file_end_pos = file.length(io) catch |err| switch (err) {
668 error.Unexpected => |e| return e,
669 else => return error.ReadFailed,
670 };
671 const file_len = std.math.cast(usize, file_end_pos) orelse return error.InvalidDebugInfo;
672
673 return posix.mmap(
674 null,
675 file_len,
676 .{ .READ = true },
677 .{ .TYPE = .SHARED },
678 file.handle,
679 0,
680 ) catch |err| switch (err) {
681 error.Unexpected => |e| return e,
682 else => return error.ReadFailed,
683 };
684}
685
686const std = @import("std");
687const Io = std.Io;
688const Allocator = std.mem.Allocator;
689const Dwarf = std.debug.Dwarf;
690const Error = std.debug.SelfInfoError;
691const MachOFile = std.debug.MachOFile;
692const assert = std.debug.assert;
693const posix = std.posix;
694const macho = std.macho;
695const mem = std.mem;
696const testing = std.testing;
697const dwarfRegNative = std.debug.Dwarf.SelfUnwinder.regNative;
698
699const builtin = @import("builtin");
700const native_endian = builtin.target.cpu.arch.endian();
701
702const SelfInfo = @This();