authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-11-19 12:01:49+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-11-20 10:42:20+00:00
log0caca625ebad92495a758e3121c91ba1f32774dd
treef7b60fd6792d92bf56c9762eabe231c6a0a99253
parent7b325e08c9b401b723345c33d12a37a832dedfab
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: split up Mach-O debug info handling

Like ELF, we now have `std.debug.MachOFile` for the host-independent parts, and `std.debug.SelfInfo.MachO` for logic requiring the file to correspond to the running program.

8 files changed, 606 insertions(+), 462 deletions(-)

lib/std/Build/Step/CheckObject.zig+14-15
......@@ -729,10 +729,10 @@ const MachODumper = struct {
729729 imports: std.ArrayListUnmanaged([]const u8) = .empty,
730730
731731 fn parse(ctx: *ObjectContext) !void {
732 var it = ctx.getLoadCommandIterator();
732 var it = try ctx.getLoadCommandIterator();
733733 var i: usize = 0;
734 while (it.next()) |cmd| {
735 switch (cmd.cmd()) {
734 while (try it.next()) |cmd| {
735 switch (cmd.hdr.cmd) {
736736 .SEGMENT_64 => {
737737 const seg = cmd.cast(macho.segment_command_64).?;
738738 try ctx.segments.append(ctx.gpa, seg);
......@@ -771,14 +771,13 @@ const MachODumper = struct {
771771 return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + off)), 0);
772772 }
773773
774 fn getLoadCommandIterator(ctx: ObjectContext) macho.LoadCommandIterator {
775 const data = ctx.data[@sizeOf(macho.mach_header_64)..][0..ctx.header.sizeofcmds];
776 return .{ .ncmds = ctx.header.ncmds, .buffer = data };
774 fn getLoadCommandIterator(ctx: ObjectContext) !macho.LoadCommandIterator {
775 return .init(&ctx.header, ctx.data[@sizeOf(macho.mach_header_64)..]);
777776 }
778777
779 fn getLoadCommand(ctx: ObjectContext, cmd: macho.LC) ?macho.LoadCommandIterator.LoadCommand {
780 var it = ctx.getLoadCommandIterator();
781 while (it.next()) |lc| if (lc.cmd() == cmd) {
778 fn getLoadCommand(ctx: ObjectContext, cmd: macho.LC) !?macho.LoadCommandIterator.LoadCommand {
779 var it = try ctx.getLoadCommandIterator();
780 while (try it.next()) |lc| if (lc.hdr.cmd == cmd) {
782781 return lc;
783782 };
784783 return null;
......@@ -872,9 +871,9 @@ const MachODumper = struct {
872871 \\LC {d}
873872 \\cmd {s}
874873 \\cmdsize {d}
875 , .{ index, @tagName(lc.cmd()), lc.cmdsize() });
874 , .{ index, @tagName(lc.hdr.cmd), lc.hdr.cmdsize });
876875
877 switch (lc.cmd()) {
876 switch (lc.hdr.cmd) {
878877 .SEGMENT_64 => {
879878 const seg = lc.cast(macho.segment_command_64).?;
880879 try writer.writeByte('\n');
......@@ -1592,9 +1591,9 @@ const MachODumper = struct {
15921591 .headers => {
15931592 try ObjectContext.dumpHeader(ctx.header, writer);
15941593
1595 var it = ctx.getLoadCommandIterator();
1594 var it = try ctx.getLoadCommandIterator();
15961595 var i: usize = 0;
1597 while (it.next()) |cmd| {
1596 while (try it.next()) |cmd| {
15981597 try ObjectContext.dumpLoadCommand(cmd, i, writer);
15991598 try writer.writeByte('\n');
16001599
......@@ -1615,7 +1614,7 @@ const MachODumper = struct {
16151614 .dyld_weak_bind,
16161615 .dyld_lazy_bind,
16171616 => {
1618 const cmd = ctx.getLoadCommand(.DYLD_INFO_ONLY) orelse
1617 const cmd = try ctx.getLoadCommand(.DYLD_INFO_ONLY) orelse
16191618 return step.fail("no dyld info found", .{});
16201619 const lc = cmd.cast(macho.dyld_info_command).?;
16211620
......@@ -1649,7 +1648,7 @@ const MachODumper = struct {
16491648 },
16501649
16511650 .exports => blk: {
1652 if (ctx.getLoadCommand(.DYLD_INFO_ONLY)) |cmd| {
1651 if (try ctx.getLoadCommand(.DYLD_INFO_ONLY)) |cmd| {
16531652 const lc = cmd.cast(macho.dyld_info_command).?;
16541653 if (lc.export_size > 0) {
16551654 const data = ctx.data[lc.export_off..][0..lc.export_size];
lib/std/debug.zig+1
......@@ -21,6 +21,7 @@ const root = @import("root");
2121pub const Dwarf = @import("debug/Dwarf.zig");
2222pub const Pdb = @import("debug/Pdb.zig");
2323pub const ElfFile = @import("debug/ElfFile.zig");
24pub const MachOFile = @import("debug/MachOFile.zig");
2425pub const Info = @import("debug/Info.zig");
2526pub const Coverage = @import("debug/Coverage.zig");
2627pub const cpu_context = @import("debug/cpu_context.zig");
lib/std/debug/MachOFile.zig created+501
......@@ -0,0 +1,501 @@
1mapped_memory: []align(std.heap.page_size_min) const u8,
2symbols: []const Symbol,
3strings: []const u8,
4text_vmaddr: u64,
5
6/// Key is index into `strings` of the file path.
7ofiles: std.AutoArrayHashMapUnmanaged(u32, Error!OFile),
8
9pub const Error = error{
10 InvalidMachO,
11 InvalidDwarf,
12 MissingDebugInfo,
13 UnsupportedDebugInfo,
14 ReadFailed,
15 OutOfMemory,
16};
17
18pub fn deinit(mf: *MachOFile, gpa: Allocator) void {
19 for (mf.ofiles.values()) |*maybe_of| {
20 const of = &(maybe_of.* catch continue);
21 posix.munmap(of.mapped_memory);
22 of.dwarf.deinit(gpa);
23 of.symbols_by_name.deinit(gpa);
24 }
25 mf.ofiles.deinit(gpa);
26 gpa.free(mf.symbols);
27 posix.munmap(mf.mapped_memory);
28}
29
30pub fn load(gpa: Allocator, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile {
31 switch (arch) {
32 .x86_64, .aarch64 => {},
33 else => unreachable,
34 }
35
36 const all_mapped_memory = try mapDebugInfoFile(path);
37 errdefer posix.munmap(all_mapped_memory);
38
39 // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
40 // binary": a simple file format which contains Mach-O binaries for multiple targets. For
41 // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
42 // for both ARM64 macOS and x86_64 macOS.
43 if (all_mapped_memory.len < 4) return error.InvalidMachO;
44 const magic = std.mem.readInt(u32, all_mapped_memory.ptr[0..4], .little);
45
46 // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`.
47 const mapped_macho = switch (magic) {
48 macho.MH_MAGIC_64 => all_mapped_memory,
49
50 macho.FAT_CIGAM => mapped_macho: {
51 // This is the universal binary format (aka a "fat binary").
52 var fat_r: Io.Reader = .fixed(all_mapped_memory);
53 const hdr = fat_r.takeStruct(macho.fat_header, .big) catch |err| switch (err) {
54 error.ReadFailed => unreachable,
55 error.EndOfStream => return error.InvalidMachO,
56 };
57 const want_cpu_type = switch (arch) {
58 .x86_64 => macho.CPU_TYPE_X86_64,
59 .aarch64 => macho.CPU_TYPE_ARM64,
60 else => unreachable,
61 };
62 for (0..hdr.nfat_arch) |_| {
63 const fat_arch = fat_r.takeStruct(macho.fat_arch, .big) catch |err| switch (err) {
64 error.ReadFailed => unreachable,
65 error.EndOfStream => return error.InvalidMachO,
66 };
67 if (fat_arch.cputype != want_cpu_type) continue;
68 if (fat_arch.offset + fat_arch.size > all_mapped_memory.len) return error.InvalidMachO;
69 break :mapped_macho all_mapped_memory[fat_arch.offset..][0..fat_arch.size];
70 }
71 // `arch` was not present in the fat binary.
72 return error.MissingDebugInfo;
73 },
74
75 // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It
76 // will be fairly easy to add support here if necessary; it's very similar to above.
77 macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
78
79 else => return error.InvalidMachO,
80 };
81
82 var r: Io.Reader = .fixed(mapped_macho);
83 const hdr = r.takeStruct(macho.mach_header_64, .little) catch |err| switch (err) {
84 error.ReadFailed => unreachable,
85 error.EndOfStream => return error.InvalidMachO,
86 };
87
88 if (hdr.magic != macho.MH_MAGIC_64)
89 return error.InvalidMachO;
90
91 const symtab: macho.symtab_command, const text_vmaddr: u64 = lcs: {
92 var it: macho.LoadCommandIterator = try .init(&hdr, mapped_macho[@sizeOf(macho.mach_header_64)..]);
93 var symtab: ?macho.symtab_command = null;
94 var text_vmaddr: ?u64 = null;
95 while (try it.next()) |cmd| switch (cmd.hdr.cmd) {
96 .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidMachO,
97 .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
98 if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
99 text_vmaddr = seg_cmd.vmaddr;
100 },
101 else => {},
102 };
103 break :lcs .{
104 symtab orelse return error.MissingDebugInfo,
105 text_vmaddr orelse return error.MissingDebugInfo,
106 };
107 };
108
109 const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
110
111 var symbols: std.ArrayList(Symbol) = try .initCapacity(gpa, symtab.nsyms);
112 defer symbols.deinit(gpa);
113
114 // This map is temporary; it is used only to detect duplicates here. This is
115 // necessary because we prefer to use STAB ("symbolic debugging table") symbols,
116 // but they might not be present, so we track normal symbols too.
117 // Indices match 1-1 with those of `symbols`.
118 var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty;
119 defer symbol_names.deinit(gpa);
120 try symbol_names.ensureUnusedCapacity(gpa, symtab.nsyms);
121
122 var ofile: u32 = undefined;
123 var last_sym: Symbol = undefined;
124 var state: enum {
125 init,
126 oso_open,
127 oso_close,
128 bnsym,
129 fun_strx,
130 fun_size,
131 ensym,
132 } = .init;
133
134 var sym_r: Io.Reader = .fixed(mapped_macho[symtab.symoff..]);
135 for (0..symtab.nsyms) |_| {
136 const sym = sym_r.takeStruct(macho.nlist_64, .little) catch |err| switch (err) {
137 error.ReadFailed => unreachable,
138 error.EndOfStream => return error.InvalidMachO,
139 };
140 if (sym.n_type.bits.is_stab == 0) {
141 if (sym.n_strx == 0) continue;
142 switch (sym.n_type.bits.type) {
143 .undf, .pbud, .indr, .abs, _ => continue,
144 .sect => {
145 const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
146 const gop = symbol_names.getOrPutAssumeCapacity(name);
147 if (!gop.found_existing) {
148 assert(gop.index == symbols.items.len);
149 symbols.appendAssumeCapacity(.{
150 .strx = sym.n_strx,
151 .addr = sym.n_value,
152 .ofile = Symbol.unknown_ofile,
153 });
154 }
155 },
156 }
157 continue;
158 }
159
160 // TODO handle globals N_GSYM, and statics N_STSYM
161 switch (sym.n_type.stab) {
162 .oso => switch (state) {
163 .init, .oso_close => {
164 state = .oso_open;
165 ofile = sym.n_strx;
166 },
167 else => return error.InvalidMachO,
168 },
169 .bnsym => switch (state) {
170 .oso_open, .ensym => {
171 state = .bnsym;
172 last_sym = .{
173 .strx = 0,
174 .addr = sym.n_value,
175 .ofile = ofile,
176 };
177 },
178 else => return error.InvalidMachO,
179 },
180 .fun => switch (state) {
181 .bnsym => {
182 state = .fun_strx;
183 last_sym.strx = sym.n_strx;
184 },
185 .fun_strx => {
186 state = .fun_size;
187 },
188 else => return error.InvalidMachO,
189 },
190 .ensym => switch (state) {
191 .fun_size => {
192 state = .ensym;
193 if (last_sym.strx != 0) {
194 const name = std.mem.sliceTo(strings[last_sym.strx..], 0);
195 const gop = symbol_names.getOrPutAssumeCapacity(name);
196 if (!gop.found_existing) {
197 assert(gop.index == symbols.items.len);
198 symbols.appendAssumeCapacity(last_sym);
199 } else {
200 symbols.items[gop.index] = last_sym;
201 }
202 }
203 },
204 else => return error.InvalidMachO,
205 },
206 .so => switch (state) {
207 .init, .oso_close => {},
208 .oso_open, .ensym => {
209 state = .oso_close;
210 },
211 else => return error.InvalidMachO,
212 },
213 else => {},
214 }
215 }
216
217 switch (state) {
218 .init => {
219 // Missing STAB symtab entries is still okay, unless there were also no normal symbols.
220 if (symbols.items.len == 0) return error.MissingDebugInfo;
221 },
222 .oso_close => {},
223 else => return error.InvalidMachO, // corrupted STAB entries in symtab
224 }
225
226 const symbols_slice = try symbols.toOwnedSlice(gpa);
227 errdefer gpa.free(symbols_slice);
228
229 // Even though lld emits symbols in ascending order, this debug code
230 // should work for programs linked in any valid way.
231 // This sort is so that we can binary search later.
232 mem.sort(Symbol, symbols_slice, {}, Symbol.addressLessThan);
233
234 return .{
235 .mapped_memory = all_mapped_memory,
236 .symbols = symbols_slice,
237 .strings = strings,
238 .ofiles = .empty,
239 .text_vmaddr = text_vmaddr,
240 };
241}
242pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct { *Dwarf, u64 } {
243 const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo;
244
245 if (symbol.ofile == Symbol.unknown_ofile) return error.MissingDebugInfo;
246
247 // offset of `address` from start of `symbol`
248 const address_symbol_offset = vaddr - symbol.addr;
249
250 // Take the symbol name from the N_FUN STAB entry, we're going to
251 // use it if we fail to find the DWARF infos
252 const stab_symbol = mem.sliceTo(mf.strings[symbol.strx..], 0);
253
254 const gop = try mf.ofiles.getOrPut(gpa, symbol.ofile);
255 if (!gop.found_existing) {
256 const name = mem.sliceTo(mf.strings[symbol.ofile..], 0);
257 gop.value_ptr.* = loadOFile(gpa, name);
258 }
259 const of = &(gop.value_ptr.* catch |err| return err);
260
261 const symbol_index = of.symbols_by_name.getKeyAdapted(
262 @as([]const u8, stab_symbol),
263 @as(OFile.SymbolAdapter, .{ .strtab = of.strtab, .symtab_raw = of.symtab_raw }),
264 ) orelse return error.MissingDebugInfo;
265
266 const symbol_ofile_vaddr = vaddr: {
267 var sym = of.symtab_raw[symbol_index];
268 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &sym);
269 break :vaddr sym.n_value;
270 };
271
272 return .{ &of.dwarf, symbol_ofile_vaddr + address_symbol_offset };
273}
274pub fn lookupSymbolName(mf: *MachOFile, vaddr: u64) error{MissingDebugInfo}![]const u8 {
275 const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo;
276 return mem.sliceTo(mf.strings[symbol.strx..], 0);
277}
278
279const OFile = struct {
280 mapped_memory: []align(std.heap.page_size_min) const u8,
281 dwarf: Dwarf,
282 strtab: []const u8,
283 symtab_raw: []align(1) const macho.nlist_64,
284 /// All named symbols in `symtab_raw`. Stored `u32` key is the index into `symtab_raw`. Accessed
285 /// through `SymbolAdapter`, so that the symbol name is used as the logical key.
286 symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true),
287
288 const SymbolAdapter = struct {
289 strtab: []const u8,
290 symtab_raw: []align(1) const macho.nlist_64,
291 pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {
292 _ = ctx;
293 return @truncate(std.hash.Wyhash.hash(0, sym_name));
294 }
295 pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool {
296 _ = b_index;
297 var b_sym = ctx.symtab_raw[b_sym_index];
298 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &b_sym);
299 const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0);
300 return mem.eql(u8, a_sym_name, b_sym_name);
301 }
302 };
303};
304
305const Symbol = struct {
306 strx: u32,
307 addr: u64,
308 /// Value may be `unknown_ofile`.
309 ofile: u32,
310 const unknown_ofile = std.math.maxInt(u32);
311 fn addressLessThan(context: void, lhs: Symbol, rhs: Symbol) bool {
312 _ = context;
313 return lhs.addr < rhs.addr;
314 }
315 /// Assumes that `symbols` is sorted in order of ascending `addr`.
316 fn find(symbols: []const Symbol, address: usize) ?*const Symbol {
317 if (symbols.len == 0) return null; // no potential match
318 if (address < symbols[0].addr) return null; // address is before the lowest-address symbol
319 var left: usize = 0;
320 var len: usize = symbols.len;
321 while (len > 1) {
322 const mid = left + len / 2;
323 if (address < symbols[mid].addr) {
324 len /= 2;
325 } else {
326 left = mid;
327 len -= len / 2;
328 }
329 }
330 return &symbols[left];
331 }
332
333 test find {
334 const symbols: []const Symbol = &.{
335 .{ .addr = 100, .strx = undefined, .ofile = undefined },
336 .{ .addr = 200, .strx = undefined, .ofile = undefined },
337 .{ .addr = 300, .strx = undefined, .ofile = undefined },
338 };
339
340 try testing.expectEqual(null, find(symbols, 0));
341 try testing.expectEqual(null, find(symbols, 99));
342 try testing.expectEqual(&symbols[0], find(symbols, 100).?);
343 try testing.expectEqual(&symbols[0], find(symbols, 150).?);
344 try testing.expectEqual(&symbols[0], find(symbols, 199).?);
345
346 try testing.expectEqual(&symbols[1], find(symbols, 200).?);
347 try testing.expectEqual(&symbols[1], find(symbols, 250).?);
348 try testing.expectEqual(&symbols[1], find(symbols, 299).?);
349
350 try testing.expectEqual(&symbols[2], find(symbols, 300).?);
351 try testing.expectEqual(&symbols[2], find(symbols, 301).?);
352 try testing.expectEqual(&symbols[2], find(symbols, 5000).?);
353 }
354};
355test {
356 _ = Symbol;
357}
358
359fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
360 const mapped_mem = try mapDebugInfoFile(o_file_path);
361 errdefer posix.munmap(mapped_mem);
362
363 var r: Io.Reader = .fixed(mapped_mem);
364 const hdr = r.takeStruct(macho.mach_header_64, .little) catch |err| switch (err) {
365 error.ReadFailed => unreachable,
366 error.EndOfStream => return error.InvalidMachO,
367 };
368 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidMachO;
369
370 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
371 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
372 var symtab_cmd: ?macho.symtab_command = null;
373 var it: macho.LoadCommandIterator = try .init(&hdr, mapped_mem[@sizeOf(macho.mach_header_64)..]);
374 while (try it.next()) |lc| switch (lc.hdr.cmd) {
375 .SEGMENT_64 => seg_cmd = lc,
376 .SYMTAB => symtab_cmd = lc.cast(macho.symtab_command) orelse return error.InvalidMachO,
377 else => {},
378 };
379 break :cmds .{
380 seg_cmd orelse return error.MissingDebugInfo,
381 symtab_cmd orelse return error.MissingDebugInfo,
382 };
383 };
384
385 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidMachO;
386 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidMachO;
387 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
388
389 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
390 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidMachO;
391 const symtab_raw: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
392
393 // TODO handle tentative (common) symbols
394 var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty;
395 defer symbols_by_name.deinit(gpa);
396 try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab_raw.len));
397 for (symtab_raw, 0..) |sym_raw, sym_index| {
398 var sym = sym_raw;
399 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &sym);
400 if (sym.n_strx == 0) continue;
401 switch (sym.n_type.bits.type) {
402 .undf => continue, // includes tentative symbols
403 .abs => continue,
404 else => {},
405 }
406 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
407 const gop = symbols_by_name.getOrPutAssumeCapacityAdapted(
408 @as([]const u8, sym_name),
409 @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab_raw = symtab_raw }),
410 );
411 if (gop.found_existing) return error.InvalidMachO;
412 gop.key_ptr.* = @intCast(sym_index);
413 }
414
415 var sections: Dwarf.SectionArray = @splat(null);
416 for (seg_cmd.getSections()) |sect_raw| {
417 var sect = sect_raw;
418 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.section_64, &sect);
419
420 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
421
422 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
423 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
424 } else continue;
425
426 if (mapped_mem.len < sect.offset + sect.size) return error.InvalidMachO;
427 const section_bytes = mapped_mem[sect.offset..][0..sect.size];
428 sections[section_index] = .{
429 .data = section_bytes,
430 .owned = false,
431 };
432 }
433
434 if (sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
435 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
436 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
437 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null)
438 {
439 return error.MissingDebugInfo;
440 }
441
442 var dwarf: Dwarf = .{ .sections = sections };
443 errdefer dwarf.deinit(gpa);
444 dwarf.open(gpa, .little) catch |err| switch (err) {
445 error.InvalidDebugInfo,
446 error.EndOfStream,
447 error.Overflow,
448 error.StreamTooLong,
449 => return error.InvalidDwarf,
450
451 error.MissingDebugInfo,
452 error.ReadFailed,
453 error.OutOfMemory,
454 => |e| return e,
455 };
456
457 return .{
458 .mapped_memory = mapped_mem,
459 .dwarf = dwarf,
460 .strtab = strtab,
461 .symtab_raw = symtab_raw,
462 .symbols_by_name = symbols_by_name.move(),
463 };
464}
465
466/// Uses `mmap` to map the file at `path` into memory.
467fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 {
468 const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
469 error.FileNotFound => return error.MissingDebugInfo,
470 else => return error.ReadFailed,
471 };
472 defer file.close();
473
474 const file_len = std.math.cast(
475 usize,
476 file.getEndPos() catch return error.ReadFailed,
477 ) orelse return error.ReadFailed;
478
479 return posix.mmap(
480 null,
481 file_len,
482 posix.PROT.READ,
483 .{ .TYPE = .SHARED },
484 file.handle,
485 0,
486 ) catch return error.ReadFailed;
487}
488
489const std = @import("std");
490const Allocator = std.mem.Allocator;
491const Dwarf = std.debug.Dwarf;
492const Io = std.Io;
493const assert = std.debug.assert;
494const posix = std.posix;
495const macho = std.macho;
496const mem = std.mem;
497const testing = std.testing;
498
499const builtin = @import("builtin");
500
501const MachOFile = @This();
lib/std/debug/SelfInfo/MachO.zig+47-397
......@@ -1,12 +1,10 @@
11mutex: std.Thread.Mutex,
22/// Accessed through `Module.Adapter`.
33modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false),
4ofiles: std.StringArrayHashMapUnmanaged(?OFile),
54
65pub const init: SelfInfo = .{
76 .mutex = .{},
87 .modules = .empty,
9 .ofiles = .empty,
108};
119pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
1210 for (si.modules.keys()) |*module| {
......@@ -14,20 +12,12 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
1412 const u = &(module.unwind orelse break :unwind catch break :unwind);
1513 if (u.dwarf) |*dwarf| dwarf.deinit(gpa);
1614 }
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);
15 file: {
16 const f = &(module.file orelse break :file catch break :file);
17 f.deinit(gpa);
2118 }
2219 }
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 }
2920 si.modules.deinit(gpa);
30 si.ofiles.deinit(gpa);
3121}
3222
3323pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {
......@@ -35,67 +25,55 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st
3525 const module = try si.findModule(gpa, address);
3626 defer si.mutex.unlock();
3727
38 const loaded_macho = try module.getLoadedMachO(gpa);
39
40 const vaddr = address - loaded_macho.vaddr_offset;
41 const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown;
28 const file = try module.getFile(gpa);
4229
43 // offset of `address` from start of `symbol`
44 const address_symbol_offset = vaddr - symbol.addr;
30 // This is not necessarily the same as the vmaddr_slide that dyld would report. This is
31 // because the segments in the file on disk might differ from the ones in memory. Normally
32 // we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying:
33 // it exists on disk (necessarily, because the kernel needs to load it!), but is also in
34 // the dyld cache (dyld actually restart itself from cache after loading it), and the two
35 // versions have (very) different segment base addresses. It's sort of like a large slide
36 // has been applied to all addresses in memory. For an optimal experience, we consider the
37 // on-disk vmaddr instead of the in-memory one.
38 const vaddr_offset = module.text_base - file.text_vmaddr;
4539
46 // Take the symbol name from the N_FUN STAB entry, we're going to
47 // use it if we fail to find the DWARF infos
48 const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0);
40 const vaddr = address - vaddr_offset;
4941
50 // If any information is missing, we can at least return this from now on.
51 const sym_only_result: std.debug.Symbol = .{
52 .name = stab_symbol,
53 .compile_unit_name = null,
54 .source_location = null,
42 const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, vaddr) catch {
43 // Return at least the symbol name if available.
44 return .{
45 .name = try file.lookupSymbolName(vaddr),
46 .compile_unit_name = null,
47 .source_location = null,
48 };
5549 };
5650
57 if (symbol.ofile == MachoSymbol.unknown_ofile) {
58 // We don't have STAB info, so can't track down the object file; all we can do is the symbol name.
59 return sym_only_result;
60 }
61
62 const o_file: *OFile = of: {
63 const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0);
64 const gop = try si.ofiles.getOrPut(gpa, path);
65 if (!gop.found_existing) {
66 gop.value_ptr.* = loadOFile(gpa, path) catch null;
67 }
68 if (gop.value_ptr.*) |*o_file| {
69 break :of o_file;
70 } else {
71 return sym_only_result;
72 }
51 const compile_unit = ofile_dwarf.findCompileUnit(native_endian, ofile_vaddr) catch {
52 // Return at least the symbol name if available.
53 return .{
54 .name = try file.lookupSymbolName(vaddr),
55 .compile_unit_name = null,
56 .source_location = null,
57 };
7358 };
7459
75 const symbol_index = o_file.symbols_by_name.getKeyAdapted(
76 @as([]const u8, stab_symbol),
77 @as(OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }),
78 ) orelse return sym_only_result;
79 const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value;
80
81 const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result;
82
8360 return .{
84 .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol,
61 .name = ofile_dwarf.getSymbolName(ofile_vaddr) orelse
62 try file.lookupSymbolName(vaddr),
8563 .compile_unit_name = compile_unit.die.getAttrString(
86 &o_file.dwarf,
64 ofile_dwarf,
8765 native_endian,
8866 std.dwarf.AT.name,
89 o_file.dwarf.section(.debug_str),
67 ofile_dwarf.section(.debug_str),
9068 compile_unit,
9169 ) catch |err| switch (err) {
9270 error.MissingDebugInfo, error.InvalidDebugInfo => null,
9371 },
94 .source_location = o_file.dwarf.getLineNumberInfo(
72 .source_location = ofile_dwarf.getLineNumberInfo(
9573 gpa,
9674 native_endian,
9775 compile_unit,
98 symbol_ofile_vaddr + address_symbol_offset,
76 ofile_vaddr,
9977 ) catch null,
10078 };
10179}
......@@ -447,7 +425,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module {
447425 .text_base = @intFromPtr(info.fbase),
448426 .name = std.mem.span(info.fname),
449427 .unwind = null,
450 .loaded_macho = null,
428 .file = null,
451429 };
452430 }
453431 return gop.key_ptr;
......@@ -457,7 +435,7 @@ const Module = struct {
457435 text_base: usize,
458436 name: []const u8,
459437 unwind: ?(Error!Unwind),
460 loaded_macho: ?(Error!LoadedMachO),
438 file: ?(Error!MachOFile),
461439
462440 const Adapter = struct {
463441 pub fn hash(_: Adapter, text_base: usize) u32 {
......@@ -488,34 +466,17 @@ const Module = struct {
488466 dwarf: ?Dwarf.Unwind,
489467 };
490468
491 const LoadedMachO = struct {
492 mapped_memory: []align(std.heap.page_size_min) const u8,
493 symbols: []const MachoSymbol,
494 strings: []const u8,
495 /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is
496 /// because the segments in the file on disk might differ from the ones in memory. Normally
497 /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying:
498 /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in
499 /// the dyld cache (dyld actually restart itself from cache after loading it), and the two
500 /// versions have (very) different segment base addresses. It's sort of like a large slide
501 /// has been applied to all addresses in memory. For an optimal experience, we consider the
502 /// on-disk vmaddr instead of the in-memory one.
503 vaddr_offset: usize,
504 };
505
506469 fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind {
507470 if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa);
508471 return if (module.unwind.?) |*unwind| unwind else |err| err;
509472 }
510473 fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind {
511 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
474 const header: *std.macho.mach_header_64 = @ptrFromInt(module.text_base);
512475
513 var it: macho.LoadCommandIterator = .{
514 .ncmds = header.ncmds,
515 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],
516 };
517 const sections, const text_vmaddr = while (it.next()) |load_cmd| {
518 if (load_cmd.cmd() != .SEGMENT_64) continue;
476 const raw_macho: [*]u8 = @ptrCast(header);
477 var it = macho.LoadCommandIterator.init(header, raw_macho[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds]) catch unreachable;
478 const sections, const text_vmaddr = while (it.next() catch unreachable) |load_cmd| {
479 if (load_cmd.hdr.cmd != .SEGMENT_64) continue;
519480 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
520481 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
521482 break .{ load_cmd.getSections(), segment_cmd.vmaddr };
......@@ -568,237 +529,15 @@ const Module = struct {
568529 };
569530 }
570531
571 fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO {
572 if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) {
573 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e,
574 else => error.ReadFailed,
575 };
576 return if (module.loaded_macho.?) |*lm| lm else |err| err;
577 }
578 fn loadMachO(module: *const Module, gpa: Allocator) Error!LoadedMachO {
579 const all_mapped_memory = try mapDebugInfoFile(module.name);
580 errdefer posix.munmap(all_mapped_memory);
581
582 // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal
583 // binary": a simple file format which contains Mach-O binaries for multiple targets. For
584 // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
585 // for both ARM64 macOS and x86_64 macOS.
586 if (all_mapped_memory.len < 4) return error.InvalidDebugInfo;
587 const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*;
588 // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`.
589 const mapped_macho = switch (magic) {
590 macho.MH_MAGIC_64 => all_mapped_memory,
591
592 macho.FAT_CIGAM => mapped_macho: {
593 // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing
594 // is big-endian, so we'll be swapping some bytes.
595 if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo;
596 const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr);
597 const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header));
598 const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)];
599 const native_cpu_type = switch (builtin.cpu.arch) {
600 .x86_64 => macho.CPU_TYPE_X86_64,
601 .aarch64 => macho.CPU_TYPE_ARM64,
602 else => comptime unreachable,
603 };
604 for (archs) |*arch| {
605 if (@byteSwap(arch.cputype) != native_cpu_type) continue;
606 const offset = @byteSwap(arch.offset);
607 const size = @byteSwap(arch.size);
608 break :mapped_macho all_mapped_memory[offset..][0..size];
609 }
610 // Our native architecture was not present in the fat binary.
611 return error.MissingDebugInfo;
612 },
613
614 // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It
615 // will be fairly easy to add support here if necessary; it's very similar to above.
616 macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
617
618 else => return error.InvalidDebugInfo,
619 };
620
621 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr));
622 if (hdr.magic != macho.MH_MAGIC_64)
623 return error.InvalidDebugInfo;
624
625 const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: {
626 var it: macho.LoadCommandIterator = .{
627 .ncmds = hdr.ncmds,
628 .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
629 };
630 var symtab: ?macho.symtab_command = null;
631 var text_vmaddr: ?u64 = null;
632 while (it.next()) |cmd| switch (cmd.cmd()) {
633 .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
634 .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
635 if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
636 text_vmaddr = seg_cmd.vmaddr;
637 },
638 else => {},
639 };
640 break :lc_iter .{
641 symtab orelse return error.MissingDebugInfo,
642 text_vmaddr orelse return error.MissingDebugInfo,
643 };
644 };
645
646 const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]);
647 const syms = syms_ptr[0..symtab.nsyms];
648 const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
649
650 var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len);
651 defer symbols.deinit(gpa);
652
653 // This map is temporary; it is used only to detect duplicates here. This is
654 // necessary because we prefer to use STAB ("symbolic debugging table") symbols,
655 // but they might not be present, so we track normal symbols too.
656 // Indices match 1-1 with those of `symbols`.
657 var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty;
658 defer symbol_names.deinit(gpa);
659 try symbol_names.ensureUnusedCapacity(gpa, syms.len);
660
661 var ofile: u32 = undefined;
662 var last_sym: MachoSymbol = undefined;
663 var state: enum {
664 init,
665 oso_open,
666 oso_close,
667 bnsym,
668 fun_strx,
669 fun_size,
670 ensym,
671 } = .init;
672
673 for (syms) |*sym| {
674 if (sym.n_type.bits.is_stab == 0) {
675 if (sym.n_strx == 0) continue;
676 switch (sym.n_type.bits.type) {
677 .undf, .pbud, .indr, .abs, _ => continue,
678 .sect => {
679 const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
680 const gop = symbol_names.getOrPutAssumeCapacity(name);
681 if (!gop.found_existing) {
682 assert(gop.index == symbols.items.len);
683 symbols.appendAssumeCapacity(.{
684 .strx = sym.n_strx,
685 .addr = sym.n_value,
686 .ofile = MachoSymbol.unknown_ofile,
687 });
688 }
689 },
690 }
691 continue;
692 }
693
694 // TODO handle globals N_GSYM, and statics N_STSYM
695 switch (sym.n_type.stab) {
696 .oso => switch (state) {
697 .init, .oso_close => {
698 state = .oso_open;
699 ofile = sym.n_strx;
700 },
701 else => return error.InvalidDebugInfo,
702 },
703 .bnsym => switch (state) {
704 .oso_open, .ensym => {
705 state = .bnsym;
706 last_sym = .{
707 .strx = 0,
708 .addr = sym.n_value,
709 .ofile = ofile,
710 };
711 },
712 else => return error.InvalidDebugInfo,
713 },
714 .fun => switch (state) {
715 .bnsym => {
716 state = .fun_strx;
717 last_sym.strx = sym.n_strx;
718 },
719 .fun_strx => {
720 state = .fun_size;
721 },
722 else => return error.InvalidDebugInfo,
723 },
724 .ensym => switch (state) {
725 .fun_size => {
726 state = .ensym;
727 if (last_sym.strx != 0) {
728 const name = std.mem.sliceTo(strings[last_sym.strx..], 0);
729 const gop = symbol_names.getOrPutAssumeCapacity(name);
730 if (!gop.found_existing) {
731 assert(gop.index == symbols.items.len);
732 symbols.appendAssumeCapacity(last_sym);
733 } else {
734 symbols.items[gop.index] = last_sym;
735 }
736 }
737 },
738 else => return error.InvalidDebugInfo,
739 },
740 .so => switch (state) {
741 .init, .oso_close => {},
742 .oso_open, .ensym => {
743 state = .oso_close;
744 },
745 else => return error.InvalidDebugInfo,
746 },
747 else => {},
748 }
749 }
750
751 switch (state) {
752 .init => {
753 // Missing STAB symtab entries is still okay, unless there were also no normal symbols.
754 if (symbols.items.len == 0) return error.MissingDebugInfo;
755 },
756 .oso_close => {},
757 else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab
758 }
759
760 const symbols_slice = try symbols.toOwnedSlice(gpa);
761 errdefer gpa.free(symbols_slice);
762
763 // Even though lld emits symbols in ascending order, this debug code
764 // should work for programs linked in any valid way.
765 // This sort is so that we can binary search later.
766 mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan);
767
768 return .{
769 .mapped_memory = all_mapped_memory,
770 .symbols = symbols_slice,
771 .strings = strings,
772 .vaddr_offset = module.text_base - text_vmaddr,
532 fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile {
533 if (module.file == null) module.file = MachOFile.load(gpa, module.name, builtin.cpu.arch) catch |err| switch (err) {
534 error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo,
535 error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e,
773536 };
537 return if (module.file.?) |*f| f else |err| err;
774538 }
775539};
776540
777const OFile = struct {
778 mapped_memory: []align(std.heap.page_size_min) const u8,
779 dwarf: Dwarf,
780 strtab: []const u8,
781 symtab: []align(1) const macho.nlist_64,
782 /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed
783 /// through `SymbolAdapter`, so that the symbol name is used as the logical key.
784 symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true),
785
786 const SymbolAdapter = struct {
787 strtab: []const u8,
788 symtab: []align(1) const macho.nlist_64,
789 pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 {
790 _ = ctx;
791 return @truncate(std.hash.Wyhash.hash(0, sym_name));
792 }
793 pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool {
794 _ = b_index;
795 const b_sym = ctx.symtab[b_sym_index];
796 const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0);
797 return mem.eql(u8, a_sym_name, b_sym_name);
798 }
799 };
800};
801
802541const MachoSymbol = struct {
803542 strx: u32,
804543 addr: u64,
......@@ -880,101 +619,12 @@ fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8
880619 };
881620}
882621
883fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile {
884 const mapped_mem = try mapDebugInfoFile(o_file_path);
885 errdefer posix.munmap(mapped_mem);
886
887 if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo;
888 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
889 if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo;
890
891 const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: {
892 var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null;
893 var symtab_cmd: ?macho.symtab_command = null;
894 var it: macho.LoadCommandIterator = .{
895 .ncmds = hdr.ncmds,
896 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
897 };
898 while (it.next()) |cmd| switch (cmd.cmd()) {
899 .SEGMENT_64 => seg_cmd = cmd,
900 .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo,
901 else => {},
902 };
903 break :cmds .{
904 seg_cmd orelse return error.MissingDebugInfo,
905 symtab_cmd orelse return error.MissingDebugInfo,
906 };
907 };
908
909 if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo;
910 if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo;
911 const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1];
912
913 const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64);
914 if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo;
915 const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]);
916
917 // TODO handle tentative (common) symbols
918 var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty;
919 defer symbols_by_name.deinit(gpa);
920 try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len));
921 for (symtab, 0..) |sym, sym_index| {
922 if (sym.n_strx == 0) continue;
923 switch (sym.n_type.bits.type) {
924 .undf => continue, // includes tentative symbols
925 .abs => continue,
926 else => {},
927 }
928 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
929 const gop = symbols_by_name.getOrPutAssumeCapacityAdapted(
930 @as([]const u8, sym_name),
931 @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }),
932 );
933 if (gop.found_existing) return error.InvalidDebugInfo;
934 gop.key_ptr.* = @intCast(sym_index);
935 }
936
937 var sections: Dwarf.SectionArray = @splat(null);
938 for (seg_cmd.getSections()) |sect| {
939 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
940
941 const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| {
942 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i;
943 } else continue;
944
945 if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo;
946 const section_bytes = mapped_mem[sect.offset..][0..sect.size];
947 sections[section_index] = .{
948 .data = section_bytes,
949 .owned = false,
950 };
951 }
952
953 const missing_debug_info =
954 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
955 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
956 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
957 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
958 if (missing_debug_info) return error.MissingDebugInfo;
959
960 var dwarf: Dwarf = .{ .sections = sections };
961 errdefer dwarf.deinit(gpa);
962 try dwarf.open(gpa, native_endian);
963
964 return .{
965 .mapped_memory = mapped_mem,
966 .dwarf = dwarf,
967 .strtab = strtab,
968 .symtab = symtab,
969 .symbols_by_name = symbols_by_name.move(),
970 };
971}
972
973622const std = @import("std");
974623const Io = std.Io;
975624const Allocator = std.mem.Allocator;
976625const Dwarf = std.debug.Dwarf;
977626const Error = std.debug.SelfInfoError;
627const MachOFile = std.debug.MachOFile;
978628const assert = std.debug.assert;
979629const posix = std.posix;
980630const macho = std.macho;
lib/std/macho.zig+35-33
......@@ -1902,74 +1902,76 @@ pub const data_in_code_entry = extern struct {
19021902};
19031903
19041904pub const LoadCommandIterator = struct {
1905 next_index: usize,
19051906 ncmds: usize,
1906 buffer: []const u8,
1907 index: usize = 0,
1907 r: std.Io.Reader,
19081908
19091909 pub const LoadCommand = struct {
19101910 hdr: load_command,
19111911 data: []const u8,
19121912
1913 pub fn cmd(lc: LoadCommand) LC {
1914 return lc.hdr.cmd;
1915 }
1916
1917 pub fn cmdsize(lc: LoadCommand) u32 {
1918 return lc.hdr.cmdsize;
1919 }
1920
19211913 pub fn cast(lc: LoadCommand, comptime Cmd: type) ?Cmd {
19221914 if (lc.data.len < @sizeOf(Cmd)) return null;
1923 return @as(*align(1) const Cmd, @ptrCast(lc.data.ptr)).*;
1915 const ptr: *align(1) const Cmd = @ptrCast(lc.data.ptr);
1916 var cmd = ptr.*;
1917 if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(Cmd, &cmd);
1918 return cmd;
19241919 }
19251920
19261921 /// Asserts LoadCommand is of type segment_command_64.
1922 /// If the native endian is not `.little`, the `section_64` values must be byte-swapped by the caller.
19271923 pub fn getSections(lc: LoadCommand) []align(1) const section_64 {
19281924 const segment_lc = lc.cast(segment_command_64).?;
1929 if (segment_lc.nsects == 0) return &[0]section_64{};
1930 const data = lc.data[@sizeOf(segment_command_64)..];
1931 const sections = @as([*]align(1) const section_64, @ptrCast(data.ptr))[0..segment_lc.nsects];
1932 return sections;
1925 const sects_ptr: [*]align(1) const section_64 = @ptrCast(lc.data[@sizeOf(segment_command_64)..]);
1926 return sects_ptr[0..segment_lc.nsects];
19331927 }
19341928
19351929 /// Asserts LoadCommand is of type dylib_command.
19361930 pub fn getDylibPathName(lc: LoadCommand) []const u8 {
19371931 const dylib_lc = lc.cast(dylib_command).?;
1938 const data = lc.data[dylib_lc.dylib.name..];
1939 return mem.sliceTo(data, 0);
1932 return mem.sliceTo(lc.data[dylib_lc.dylib.name..], 0);
19401933 }
19411934
19421935 /// Asserts LoadCommand is of type rpath_command.
19431936 pub fn getRpathPathName(lc: LoadCommand) []const u8 {
19441937 const rpath_lc = lc.cast(rpath_command).?;
1945 const data = lc.data[rpath_lc.path..];
1946 return mem.sliceTo(data, 0);
1938 return mem.sliceTo(lc.data[rpath_lc.path..], 0);
19471939 }
19481940
19491941 /// Asserts LoadCommand is of type build_version_command.
1942 /// If the native endian is not `.little`, the `build_tool_version` values must be byte-swapped by the caller.
19501943 pub fn getBuildVersionTools(lc: LoadCommand) []align(1) const build_tool_version {
19511944 const build_lc = lc.cast(build_version_command).?;
1952 const ntools = build_lc.ntools;
1953 if (ntools == 0) return &[0]build_tool_version{};
1954 const data = lc.data[@sizeOf(build_version_command)..];
1955 const tools = @as([*]align(1) const build_tool_version, @ptrCast(data.ptr))[0..ntools];
1956 return tools;
1945 const tools_ptr: [*]align(1) const build_tool_version = @ptrCast(lc.data[@sizeOf(build_version_command)..]);
1946 return tools_ptr[0..build_lc.ntools];
19571947 }
19581948 };
19591949
1960 pub fn next(it: *LoadCommandIterator) ?LoadCommand {
1961 if (it.index >= it.ncmds) return null;
1950 pub fn next(it: *LoadCommandIterator) error{InvalidMachO}!?LoadCommand {
1951 if (it.next_index >= it.ncmds) return null;
19621952
1963 const hdr = @as(*align(1) const load_command, @ptrCast(it.buffer.ptr)).*;
1964 const cmd = LoadCommand{
1965 .hdr = hdr,
1966 .data = it.buffer[0..hdr.cmdsize],
1953 const hdr = it.r.peekStruct(load_command, .little) catch |err| switch (err) {
1954 error.ReadFailed => unreachable,
1955 error.EndOfStream => return error.InvalidMachO,
1956 };
1957 const data = it.r.take(hdr.cmdsize) catch |err| switch (err) {
1958 error.ReadFailed => unreachable,
1959 error.EndOfStream => return error.InvalidMachO,
19671960 };
19681961
1969 it.buffer = it.buffer[hdr.cmdsize..];
1970 it.index += 1;
1962 it.next_index += 1;
1963 return .{ .hdr = hdr, .data = data };
1964 }
19711965
1972 return cmd;
1966 pub fn init(hdr: *const mach_header_64, cmds_buf_overlong: []const u8) error{InvalidMachO}!LoadCommandIterator {
1967 if (cmds_buf_overlong.len < hdr.sizeofcmds) return error.InvalidMachO;
1968 if (hdr.ncmds > 0 and hdr.sizeofcmds < @sizeOf(load_command)) return error.InvalidMachO;
1969 const cmds_buf = cmds_buf_overlong[0..hdr.sizeofcmds];
1970 return .{
1971 .next_index = 0,
1972 .ncmds = hdr.ncmds,
1973 .r = .fixed(cmds_buf),
1974 };
19731975 }
19741976};
19751977
src/link/MachO.zig+2-2
......@@ -4167,7 +4167,7 @@ pub const Platform = struct {
41674167 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
41684168 /// the extracted minimum platform version.
41694169 pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
4170 switch (lc.cmd()) {
4170 switch (lc.hdr.cmd) {
41714171 .BUILD_VERSION => {
41724172 const cmd = lc.cast(macho.build_version_command).?;
41734173 return .{
......@@ -4200,7 +4200,7 @@ pub const Platform = struct {
42004200 // We can't distinguish Mac Catalyst here, but this is legacy stuff anyway.
42014201 const cmd = lc.cast(macho.version_min_command).?;
42024202 return .{
4203 .os_tag = switch (lc.cmd()) {
4203 .os_tag = switch (lc.hdr.cmd) {
42044204 .VERSION_MIN_IPHONEOS => .ios,
42054205 .VERSION_MIN_MACOSX => .macos,
42064206 .VERSION_MIN_TVOS => .tvos,
src/link/MachO/Dylib.zig+2-5
......@@ -90,11 +90,8 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
9090 if (amt != lc_buffer.len) return error.InputOutput;
9191 }
9292
93 var it = LoadCommandIterator{
94 .ncmds = header.ncmds,
95 .buffer = lc_buffer,
96 };
97 while (it.next()) |cmd| switch (cmd.cmd()) {
93 var it = LoadCommandIterator.init(&header, lc_buffer) catch |err| std.debug.panic("bad dylib: {t}", .{err});
94 while (it.next() catch |err| std.debug.panic("bad dylib: {t}", .{err})) |cmd| switch (cmd.hdr.cmd) {
9895 .ID_DYLIB => {
9996 self.id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName());
10097 },
src/link/MachO/Object.zig+4-10
......@@ -109,11 +109,8 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
109109 if (amt != self.header.?.sizeofcmds) return error.InputOutput;
110110 }
111111
112 var it = LoadCommandIterator{
113 .ncmds = self.header.?.ncmds,
114 .buffer = lc_buffer,
115 };
116 while (it.next()) |lc| switch (lc.cmd()) {
112 var it = LoadCommandIterator.init(&self.header.?, lc_buffer) catch |err| std.debug.panic("bad object: {t}", .{err});
113 while (it.next() catch |err| std.debug.panic("bad object: {t}", .{err})) |lc| switch (lc.hdr.cmd) {
117114 .SEGMENT_64 => {
118115 const sections = lc.getSections();
119116 try self.sections.ensureUnusedCapacity(gpa, sections.len);
......@@ -1644,11 +1641,8 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
16441641 if (amt != self.header.?.sizeofcmds) return error.InputOutput;
16451642 }
16461643
1647 var it = LoadCommandIterator{
1648 .ncmds = self.header.?.ncmds,
1649 .buffer = lc_buffer,
1650 };
1651 while (it.next()) |lc| switch (lc.cmd()) {
1644 var it = LoadCommandIterator.init(&self.header.?, lc_buffer) catch |err| std.debug.panic("bad object: {t}", .{err});
1645 while (it.next() catch |err| std.debug.panic("bad object: {t}", .{err})) |lc| switch (lc.hdr.cmd) {
16521646 .SYMTAB => {
16531647 const cmd = lc.cast(macho.symtab_command).?;
16541648 try self.strtab.resize(gpa, cmd.strsize);