1mapped_memory: []align(std.heap.page_size_min) const u8,
2symbols: []const Symbol,
3strings: []const u8,
4text_vmaddr: u64,
5uuid: ?Uuid,
6adjacent_dsym: ?DsymFile,
7
8/// Key is index into `strings` of the file path.
9ofiles: std.array_hash_map.Auto(u32, Error!OFile),
10
11pub const Error = error{
12 InvalidMachO,
13 InvalidDwarf,
14 MissingDebugInfo,
15 UnsupportedDebugInfo,
16 ReadFailed,
17 OutOfMemory,
18};
19
20pub fn deinit(mf: *MachOFile, gpa: Allocator) void {
21 if (mf.adjacent_dsym) |*dsym| dsym.deinit(gpa);
22 for (mf.ofiles.values()) |*maybe_of| {
23 const of = &(maybe_of.* catch continue);
24 posix.munmap(of.mapped_memory);
25 of.dwarf.deinit(gpa);
26 of.symbols_by_name.deinit(gpa);
27 }
28 mf.ofiles.deinit(gpa);
29 gpa.free(mf.symbols);
30 posix.munmap(mf.mapped_memory);
31}
32
33pub fn load(gpa: Allocator, io: Io, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile {
34 switch (arch) {
35 .x86_64, .aarch64 => {},
36 else => unreachable,
37 }
38
39 const all_mapped_memory = try mapDebugInfoFile(io, path);
40 errdefer posix.munmap(all_mapped_memory);
41
42 const mapped_macho = try selectMachOSlice(all_mapped_memory, arch);
43
44 var r: Io.Reader = .fixed(mapped_macho);
45 const hdr = r.takeStruct(macho.mach_header_64, .little) catch |err| switch (err) {
46 error.ReadFailed => unreachable,
47 error.EndOfStream => return error.InvalidMachO,
48 };
49
50 if (hdr.magic != macho.MH_MAGIC_64)
51 return error.InvalidMachO;
52
53 const symtab: macho.symtab_command, const text_vmaddr: u64, const uuid: ?Uuid = lcs: {
54 var it: macho.LoadCommandIterator = try .init(&hdr, mapped_macho[@sizeOf(macho.mach_header_64)..]);
55 var symtab: ?macho.symtab_command = null;
56 var text_vmaddr: ?u64 = null;
57 var uuid: ?Uuid = null;
58 while (try it.next()) |cmd| switch (cmd.hdr.cmd) {
59 .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidMachO,
60 .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
61 if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
62 text_vmaddr = seg_cmd.vmaddr;
63 },
64 .UUID => if (cmd.cast(macho.uuid_command)) |uuid_cmd| {
65 uuid = uuid_cmd.uuid;
66 },
67 else => {},
68 };
69 break :lcs .{
70 symtab orelse return error.MissingDebugInfo,
71 text_vmaddr orelse return error.MissingDebugInfo,
72 uuid,
73 };
74 };
75
76 const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
77
78 var symbols: std.ArrayList(Symbol) = try .initCapacity(gpa, symtab.nsyms);
79 defer symbols.deinit(gpa);
80
81 // This map is temporary; it is used only to detect duplicates here. This is
82 // necessary because we prefer to use STAB ("symbolic debugging table") symbols,
83 // but they might not be present, so we track normal symbols too.
84 // Indices match 1-1 with those of `symbols`.
85 var symbol_names: std.array_hash_map.String(void) = .empty;
86 defer symbol_names.deinit(gpa);
87 try symbol_names.ensureUnusedCapacity(gpa, symtab.nsyms);
88
89 var ofile: u32 = undefined;
90 var last_sym: Symbol = undefined;
91 var state: enum {
92 init,
93 oso_open,
94 oso_close,
95 bnsym,
96 fun_strx,
97 fun_size,
98 ensym,
99 } = .init;
100
101 var sym_r: Io.Reader = .fixed(mapped_macho[symtab.symoff..]);
102 for (0..symtab.nsyms) |_| {
103 const sym = sym_r.takeStruct(macho.nlist_64, .little) catch |err| switch (err) {
104 error.ReadFailed => unreachable,
105 error.EndOfStream => return error.InvalidMachO,
106 };
107 if (sym.n_type.bits.is_stab == 0) {
108 if (sym.n_strx == 0) continue;
109 switch (sym.n_type.bits.type) {
110 .undf, .pbud, .indr, .abs, _ => continue,
111 .sect => {
112 const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
113 const gop = symbol_names.getOrPutAssumeCapacity(name);
114 if (!gop.found_existing) {
115 assert(gop.index == symbols.items.len);
116 symbols.appendAssumeCapacity(.{
117 .strx = sym.n_strx,
118 .addr = sym.n_value,
119 .ofile = Symbol.unknown_ofile,
120 });
121 }
122 },
123 }
124 continue;
125 }
126
127 // TODO handle globals N_GSYM, and statics N_STSYM
128 //
129 // NOTE: ld64.lld and Apple's ld differ in STABS layout.
130 // Apple's ld emit N_BNSYM and N_ENSYM to mark the start and end of
131 // functions, while ld64.lld doesn't.
132 switch (sym.n_type.stab) {
133 .oso => switch (state) {
134 .init, .oso_close => {
135 state = .oso_open;
136 ofile = sym.n_strx;
137 },
138 else => return error.InvalidMachO,
139 },
140 .bnsym => switch (state) {
141 .oso_open, .ensym => {
142 state = .bnsym;
143 last_sym = .{
144 .strx = 0,
145 .addr = sym.n_value,
146 .ofile = ofile,
147 };
148 },
149 else => return error.InvalidMachO,
150 },
151 .fun => switch (state) {
152 .oso_open => {
153 state = .fun_strx;
154 last_sym = .{
155 .strx = sym.n_strx,
156 .addr = sym.n_value,
157 .ofile = ofile,
158 };
159 },
160 .bnsym => {
161 state = .fun_strx;
162 last_sym.strx = sym.n_strx;
163 },
164 .fun_strx => {
165 state = .fun_size;
166 },
167 .fun_size => {
168 if (last_sym.strx != 0) {
169 appendStabSymbol(&symbols, &symbol_names, strings, last_sym);
170 }
171 last_sym = .{
172 .strx = sym.n_strx,
173 .addr = sym.n_value,
174 .ofile = ofile,
175 };
176 state = .fun_strx;
177 },
178 else => return error.InvalidMachO,
179 },
180 .ensym => switch (state) {
181 .fun_size => {
182 state = .ensym;
183 if (last_sym.strx != 0) {
184 appendStabSymbol(&symbols, &symbol_names, strings, last_sym);
185 }
186 },
187 else => return error.InvalidMachO,
188 },
189 .so => switch (state) {
190 .init, .oso_close => {},
191 .oso_open, .ensym => {
192 state = .oso_close;
193 },
194 .fun_size => {
195 state = .oso_close;
196 if (last_sym.strx != 0) {
197 appendStabSymbol(&symbols, &symbol_names, strings, last_sym);
198 }
199 },
200 else => return error.InvalidMachO,
201 },
202 else => {},
203 }
204 }
205
206 switch (state) {
207 .init => {
208 // Missing STAB symtab entries is still okay, unless there were also no normal symbols.
209 if (symbols.items.len == 0) return error.MissingDebugInfo;
210 },
211 .oso_close => {},
212 else => return error.InvalidMachO, // corrupted STAB entries in symtab
213 }
214
215 const symbols_slice = try symbols.toOwnedSlice(gpa);
216 errdefer gpa.free(symbols_slice);
217
218 // Even though lld emits symbols in ascending order, this debug code
219 // should work for programs linked in any valid way.
220 // This sort is so that we can binary search later.
221 mem.sort(Symbol, symbols_slice, {}, Symbol.addressLessThan);
222
223 const adjacent_dsym = if (uuid) |expected_uuid|
224 try loadAdjacentDsym(gpa, io, path, arch, expected_uuid)
225 else
226 null;
227
228 return .{
229 .mapped_memory = all_mapped_memory,
230 .symbols = symbols_slice,
231 .strings = strings,
232 .ofiles = .empty,
233 .text_vmaddr = text_vmaddr,
234 .uuid = uuid,
235 .adjacent_dsym = adjacent_dsym,
236 };
237}
238
239pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, io: Io, vaddr: u64) !struct { *Dwarf, u64 } {
240 if (mf.adjacent_dsym) |*dsym| {
241 return .{ &dsym.dwarf, vaddr };
242 }
243
244 const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo;
245
246 if (symbol.ofile == Symbol.unknown_ofile) return error.MissingDebugInfo;
247
248 // offset of `address` from start of `symbol`
249 const address_symbol_offset = vaddr - symbol.addr;
250
251 // Take the symbol name from the N_FUN STAB entry, we're going to
252 // use it if we fail to find the DWARF infos
253 const stab_symbol = mem.sliceTo(mf.strings[symbol.strx..], 0);
254
255 const gop = try mf.ofiles.getOrPut(gpa, symbol.ofile);
256 if (!gop.found_existing) {
257 const name = mem.sliceTo(mf.strings[symbol.ofile..], 0);
258 gop.value_ptr.* = loadOFile(gpa, io, name);
259 }
260 const of = &(gop.value_ptr.* catch |err| return err);
261
262 const symbol_index = of.symbols_by_name.getKeyAdapted(
263 @as([]const u8, stab_symbol),
264 @as(OFile.SymbolAdapter, .{ .strtab = of.strtab, .symtab_raw = of.symtab_raw }),
265 ) orelse return error.MissingDebugInfo;
266
267 const symbol_ofile_vaddr = vaddr: {
268 var sym = of.symtab_raw[symbol_index];
269 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &sym);
270 break :vaddr sym.n_value;
271 };
272
273 return .{ &of.dwarf, symbol_ofile_vaddr + address_symbol_offset };
274}
275pub fn lookupSymbolName(mf: *MachOFile, vaddr: u64) error{MissingDebugInfo}![]const u8 {
276 const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo;
277 return mem.sliceTo(mf.strings[symbol.strx..], 0);
278}
279
280const OFile = struct {
281 mapped_memory: []align(std.heap.page_size_min) const u8,
282 dwarf: Dwarf,
283 strtab: []const u8,
284 symtab_raw: []align(1) const macho.nlist_64,
285 /// All named symbols in `symtab_raw`. Stored `u32` key is the index into `symtab_raw`. Accessed
286 /// through `SymbolAdapter`, so that the symbol name is used as the logical key.
287 symbols_by_name: std.array_hash_map.Custom(u32, void, void, true),
288
289 const SymbolAdapter = struct {
290 strtab: []const u8,
291 symtab_raw: []align(1) const macho.nlist_64,
292 pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {
293 _ = ctx;
294 return @truncate(std.hash.Wyhash.hash(0, sym_name));
295 }
296 pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool {
297 _ = b_index;
298 var b_sym = ctx.symtab_raw[b_sym_index];
299 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &b_sym);
300 const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0);
301 return mem.eql(u8, a_sym_name, b_sym_name);
302 }
303 };
304};
305
306const DsymFile = struct {
307 mapped_memory: []align(std.heap.page_size_min) const u8,
308 dwarf: Dwarf,
309
310 fn deinit(df: *DsymFile, gpa: Allocator) void {
311 df.dwarf.deinit(gpa);
312 posix.munmap(df.mapped_memory);
313 }
314};
315
316const Symbol = struct {
317 strx: u32,
318 addr: u64,
319 /// Value may be `unknown_ofile`.
320 ofile: u32,
321 const unknown_ofile = std.math.maxInt(u32);
322 fn addressLessThan(context: void, lhs: Symbol, rhs: Symbol) bool {
323 _ = context;
324 return lhs.addr < rhs.addr;
325 }
326 /// Assumes that `symbols` is sorted in order of ascending `addr`.
327 fn find(symbols: []const Symbol, address: usize) ?*const Symbol {
328 if (symbols.len == 0) return null; // no potential match
329 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
330 var left: usize = 0;
331 var len: usize = symbols.len;
332 while (len > 1) {
333 const mid = left + len / 2;
334 if (address < symbols[mid].addr) {
335 len /= 2;
336 } else {
337 left = mid;
338 len -= len / 2;
339 }
340 }
341 return &symbols[left];
342 }
343
344 test find {
345 const symbols: []const Symbol = &.{
346 .{ .addr = 100, .strx = undefined, .ofile = undefined },
347 .{ .addr = 200, .strx = undefined, .ofile = undefined },
348 .{ .addr = 300, .strx = undefined, .ofile = undefined },
349 };
350
351 try testing.expectEqual(null, find(symbols, 0));
352 try testing.expectEqual(null, find(symbols, 99));
353 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
354 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
355 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
356
357 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
358 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
359 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
360
361 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
362 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
363 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
364 }
365};
366test {
367 _ = Symbol;
368}
369
370fn appendStabSymbol(
371 symbols: *std.ArrayList(Symbol),
372 symbol_names: *std.array_hash_map.String(void),
373 strings: []const u8,
374 last_sym: Symbol,
375) void {
376 const name = std.mem.sliceTo(strings[last_sym.strx..], 0);
377 const gop = symbol_names.getOrPutAssumeCapacity(name);
378 if (!gop.found_existing) {
379 assert(gop.index == symbols.items.len);
380 symbols.appendAssumeCapacity(last_sym);
381 } else {
382 symbols.items[gop.index] = last_sym;
383 }
384}
385
386fn loadAdjacentDsym(
387 gpa: Allocator,
388 io: Io,
389 binary_path: []const u8,
390 arch: std.Target.Cpu.Arch,
391 uuid: Uuid,
392) Error!?DsymFile {
393 const s = std.fs.path.sep_str;
394 const dsym_path = try std.fmt.allocPrint(
395 gpa,
396 "{s}.dSYM" ++ s ++ "Contents" ++ s ++ "Resources" ++ s ++ "DWARF" ++ s ++ "{s}",
397 .{ binary_path, std.fs.path.basename(binary_path) },
398 );
399 defer gpa.free(dsym_path);
400 return loadDsymFile(gpa, io, dsym_path, arch, uuid) catch |err| switch (err) {
401 error.MissingDebugInfo,
402 error.InvalidMachO,
403 error.InvalidDwarf,
404 error.UnsupportedDebugInfo,
405 error.ReadFailed,
406 => null,
407 error.OutOfMemory => |e| return e,
408 };
409}
410
411fn loadDsymFile(
412 gpa: Allocator,
413 io: Io,
414 path: []const u8,
415 arch: std.Target.Cpu.Arch,
416 expected_uuid: Uuid,
417) Error!DsymFile {
418 const all_mapped_memory = try mapDebugInfoFile(io, path);
419 errdefer posix.munmap(all_mapped_memory);
420 const mapped_macho = try selectMachOSlice(all_mapped_memory, arch);
421
422 var r: Io.Reader = .fixed(mapped_macho);
423 const hdr = r.takeStruct(macho.mach_header_64, .little) catch |err| switch (err) {
424 error.ReadFailed => unreachable,
425 error.EndOfStream => return error.InvalidMachO,
426 };
427 if (hdr.magic != macho.MH_MAGIC_64) return error.InvalidMachO;
428 if (hdr.filetype != macho.MH_DSYM) return error.MissingDebugInfo;
429
430 var uuid: ?Uuid = null;
431 var dwarf_sections: ?[]align(1) const macho.section_64 = null;
432
433 var it: macho.LoadCommandIterator = try .init(&hdr, mapped_macho[@sizeOf(macho.mach_header_64)..]);
434 while (try it.next()) |lc| switch (lc.hdr.cmd) {
435 .SEGMENT_64 => if (lc.cast(macho.segment_command_64)) |seg_cmd| {
436 if (!mem.eql(u8, "__DWARF", seg_cmd.segName())) continue;
437 dwarf_sections = lc.getSections();
438 },
439 .UUID => if (lc.cast(macho.uuid_command)) |uuid_cmd| {
440 uuid = uuid_cmd.uuid;
441 },
442 else => {},
443 };
444
445 const actual_uuid = uuid orelse return error.MissingDebugInfo;
446 if (!mem.eql(u8, &actual_uuid, &expected_uuid)) return error.MissingDebugInfo;
447
448 return .{
449 .mapped_memory = all_mapped_memory,
450 .dwarf = try loadDwarfFromSections(gpa, mapped_macho, dwarf_sections orelse return error.MissingDebugInfo),
451 };
452}
453
454fn loadOFile(gpa: Allocator, io: Io, o_file_name: []const u8) !OFile {
455 const all_mapped_memory, const mapped_ofile = map: {
456 const open_paren = paren: {
457 if (std.mem.endsWith(u8, o_file_name, ")")) {
458 if (std.mem.findScalarLast(u8, o_file_name, '(')) |i| {
459 break :paren i;
460 }
461 }
462 // Not an archive, just a normal path to a .o file
463 const m = try mapDebugInfoFile(io, o_file_name);
464 break :map .{ m, m };
465 };
466
467 // We have the form 'path/to/archive.a(entry.o)'. Map the archive and find the object file in question.
468
469 const archive_path = o_file_name[0..open_paren];
470 const target_name_in_archive = o_file_name[open_paren + 1 .. o_file_name.len - 1];
471 const mapped_archive = try mapDebugInfoFile(io, archive_path);
472 errdefer posix.munmap(mapped_archive);
473
474 var ar_reader: Io.Reader = .fixed(mapped_archive);
475 const ar_magic = ar_reader.take(8) catch return error.InvalidMachO;
476 if (!std.mem.eql(u8, ar_magic, "!<arch>\n")) return error.InvalidMachO;
477 while (true) {
478 if (ar_reader.seek == ar_reader.buffer.len) return error.MissingDebugInfo;
479
480 const raw_name = ar_reader.takeArray(16) catch return error.InvalidMachO;
481 ar_reader.discardAll(12 + 6 + 6 + 8) catch return error.InvalidMachO;
482 const raw_size = ar_reader.takeArray(10) catch return error.InvalidMachO;
483 const file_magic = ar_reader.takeArray(2) catch return error.InvalidMachO;
484 if (!std.mem.eql(u8, file_magic, "`\n")) return error.InvalidMachO;
485
486 const size = std.fmt.parseInt(u32, mem.sliceTo(raw_size, ' '), 10) catch return error.InvalidMachO;
487 const raw_data = ar_reader.take(size) catch return error.InvalidMachO;
488
489 const entry_name: []const u8, const entry_contents: []const u8 = entry: {
490 if (!std.mem.startsWith(u8, raw_name, "#1/")) {
491 break :entry .{ mem.sliceTo(raw_name, '/'), raw_data };
492 }
493 const len = std.fmt.parseInt(u32, mem.sliceTo(raw_name[3..], ' '), 10) catch return error.InvalidMachO;
494 if (len > size) return error.InvalidMachO;
495 break :entry .{ mem.sliceTo(raw_data[0..len], 0), raw_data[len..] };
496 };
497
498 if (std.mem.eql(u8, entry_name, target_name_in_archive)) {
499 break :map .{ mapped_archive, entry_contents };
500 }
501 }
502 };
503 errdefer posix.munmap(all_mapped_memory);
504
505 var r: Io.Reader = .fixed(mapped_ofile);
506 const hdr = r.takeStruct(macho.mach_header_64, .little) catch |err| switch (err) {
507 error.ReadFailed => unreachable,
508 error.EndOfStream => return error.InvalidMachO,
509 };
510 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidMachO;
511
512 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
513 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
514 var symtab_cmd: ?macho.symtab_command = null;
515 var it: macho.LoadCommandIterator = try .init(&hdr, mapped_ofile[@sizeOf(macho.mach_header_64)..]);
516 while (try it.next()) |lc| switch (lc.hdr.cmd) {
517 .SEGMENT_64 => seg_cmd = lc,
518 .SYMTAB => symtab_cmd = lc.cast(macho.symtab_command) orelse return error.InvalidMachO,
519 else => {},
520 };
521 break :cmds .{
522 seg_cmd orelse return error.MissingDebugInfo,
523 symtab_cmd orelse return error.MissingDebugInfo,
524 };
525 };
526
527 if (mapped_ofile.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidMachO;
528 if (mapped_ofile[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidMachO;
529 const strtab = mapped_ofile[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
530
531 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
532 if (mapped_ofile.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidMachO;
533 const symtab_raw: []align(1) const macho.nlist_64 = @ptrCast(mapped_ofile[symtab_cmd.symoff..][0..n_sym_bytes]);
534
535 // TODO handle tentative (common) symbols
536 var symbols_by_name: std.array_hash_map.Custom(u32, void, void, true) = .empty;
537 defer symbols_by_name.deinit(gpa);
538 try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab_raw.len));
539 for (symtab_raw, 0..) |sym_raw, sym_index| {
540 var sym = sym_raw;
541 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &sym);
542 if (sym.n_strx == 0) continue;
543 switch (sym.n_type.bits.type) {
544 .undf => continue, // includes tentative symbols
545 .abs => continue,
546 else => {},
547 }
548 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
549 const gop = symbols_by_name.getOrPutAssumeCapacityAdapted(
550 @as([]const u8, sym_name),
551 @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab_raw = symtab_raw }),
552 );
553 if (gop.found_existing) return error.InvalidMachO;
554 gop.key_ptr.* = @intCast(sym_index);
555 }
556
557 const dwarf = try loadDwarfFromSections(gpa, mapped_ofile, seg_cmd.getSections());
558
559 return .{
560 .mapped_memory = all_mapped_memory,
561 .dwarf = dwarf,
562 .strtab = strtab,
563 .symtab_raw = symtab_raw,
564 .symbols_by_name = symbols_by_name.move(),
565 };
566}
567
568fn loadDwarfFromSections(
569 gpa: Allocator,
570 mapped_macho: []const u8,
571 section_headers: []align(1) const macho.section_64,
572) !Dwarf {
573 var sections: Dwarf.SectionArray = @splat(null);
574 for (section_headers) |sect_raw| {
575 var sect = sect_raw;
576 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.section_64, &sect);
577
578 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
579
580 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".field_names, 0..) |field_name, i| {
581 const section_name_long = "__" ++ field_name;
582 // Some dwarf section names don't fit in the `sectname` buffer, so they are truncated.
583 const section_name_trunc = section_name_long[0..@min(section_name_long.len, sect.sectname.len)];
584 if (mem.eql(u8, section_name_trunc, sect.sectName())) break i;
585 } else continue;
586
587 if (mapped_macho.len < sect.offset + sect.size) return error.InvalidMachO;
588 const section_bytes = mapped_macho[sect.offset..][0..sect.size];
589 sections[section_index] = .{
590 .data = section_bytes,
591 .owned = false,
592 };
593 }
594
595 if (sections[@backingInt(Dwarf.Section.Id.debug_info)] == null or
596 sections[@backingInt(Dwarf.Section.Id.debug_abbrev)] == null or
597 sections[@backingInt(Dwarf.Section.Id.debug_str)] == null or
598 sections[@backingInt(Dwarf.Section.Id.debug_line)] == null)
599 {
600 return error.MissingDebugInfo;
601 }
602
603 var dwarf: Dwarf = .{ .sections = sections };
604 errdefer dwarf.deinit(gpa);
605 dwarf.open(gpa, .little) catch |err| switch (err) {
606 error.InvalidDebugInfo,
607 error.EndOfStream,
608 error.Overflow,
609 error.StreamTooLong,
610 => return error.InvalidDwarf,
611
612 error.MissingDebugInfo,
613 error.ReadFailed,
614 error.OutOfMemory,
615 => |e| return e,
616 };
617
618 return dwarf;
619}
620
621fn selectMachOSlice(
622 all_mapped_memory: []align(std.heap.page_size_min) const u8,
623 arch: std.Target.Cpu.Arch,
624) Error![]const u8 {
625 // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
626 // binary": a simple file format which contains Mach-O binaries for multiple targets. For
627 // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
628 // for both ARM64 macOS and x86_64 macOS.
629 if (all_mapped_memory.len < 4) return error.InvalidMachO;
630 const magic = std.mem.readInt(u32, all_mapped_memory.ptr[0..4], .little);
631
632 // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`.
633 const mapped_macho = switch (magic) {
634 macho.MH_MAGIC_64 => all_mapped_memory,
635
636 macho.FAT_CIGAM => mapped_macho: {
637 // This is the universal binary format (aka a "fat binary").
638 var fat_r: Io.Reader = .fixed(all_mapped_memory);
639 const hdr = fat_r.takeStruct(macho.fat_header, .big) catch |err| switch (err) {
640 error.ReadFailed => unreachable,
641 error.EndOfStream => return error.InvalidMachO,
642 };
643 const want_cpu_type = switch (arch) {
644 .x86_64 => macho.CPU_TYPE_X86_64,
645 .aarch64 => macho.CPU_TYPE_ARM64,
646 else => unreachable,
647 };
648 for (0..hdr.nfat_arch) |_| {
649 const fat_arch = fat_r.takeStruct(macho.fat_arch, .big) catch |err| switch (err) {
650 error.ReadFailed => unreachable,
651 error.EndOfStream => return error.InvalidMachO,
652 };
653 if (fat_arch.cputype != want_cpu_type) continue;
654 if (fat_arch.offset + fat_arch.size > all_mapped_memory.len) return error.InvalidMachO;
655 break :mapped_macho all_mapped_memory[fat_arch.offset..][0..fat_arch.size];
656 }
657 // `arch` was not present in the fat binary.
658 return error.MissingDebugInfo;
659 },
660
661 // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It
662 // will be fairly easy to add support here if necessary; it's very similar to above.
663 macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
664
665 else => return error.InvalidMachO,
666 };
667 return mapped_macho;
668}
669
670/// Uses `mmap` to map the file at `path` into memory.
671fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) const u8 {
672 const file = Io.Dir.cwd().openFile(io, path, .{}) catch |err| switch (err) {
673 error.FileNotFound => return error.MissingDebugInfo,
674 else => return error.ReadFailed,
675 };
676 defer file.close(io);
677
678 const file_len = std.math.cast(
679 usize,
680 file.length(io) catch return error.ReadFailed,
681 ) orelse return error.ReadFailed;
682
683 return posix.mmap(
684 null,
685 file_len,
686 .{ .READ = true },
687 .{ .TYPE = .SHARED },
688 file.handle,
689 0,
690 ) catch return error.ReadFailed;
691}
692
693const std = @import("std");
694const Allocator = std.mem.Allocator;
695const Dwarf = std.debug.Dwarf;
696const Io = std.Io;
697const assert = std.debug.assert;
698const posix = std.posix;
699const macho = std.macho;
700const mem = std.mem;
701const testing = std.testing;
702
703const builtin = @import("builtin");
704
705const Uuid = @FieldType(macho.uuid_command, "uuid");
706const MachOFile = @This();