feature. See also
. The project being documented here (as the example) is the Zig library itself.
MachOFile.load
pub fn load(gpa: Allocator, io: Io, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile
File
Code
pub fn load(gpa: Allocator, io: Io, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile {
switch (arch) {
.x86_64, .aarch64 => {},
else => unreachable,
}
const all_mapped_memory = try mapDebugInfoFile(io, path);
errdefer posix.munmap(all_mapped_memory);
// binary": a simple file format which contains Mach-O binaries for multiple targets. For
// instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images
// for both ARM64 macOS and x86_64 macOS.
if (all_mapped_memory.len < 4) return error.InvalidMachO;
const magic = std.mem.readInt(u32, all_mapped_memory.ptr[0..4], .little);
const mapped_macho = switch (magic) {
macho.MH_MAGIC_64 => all_mapped_memory,
macho.FAT_CIGAM => mapped_macho: {
var fat_r: Io.Reader = .fixed(all_mapped_memory);
const hdr = fat_r.takeStruct(macho.fat_header, .big) catch |err| switch (err) {
error.ReadFailed => unreachable,
error.EndOfStream => return error.InvalidMachO,
};
const want_cpu_type = switch (arch) {
.x86_64 => macho.CPU_TYPE_X86_64,
.aarch64 => macho.CPU_TYPE_ARM64,
else => unreachable,
};
for (0..hdr.nfat_arch) |_| {
const fat_arch = fat_r.takeStruct(macho.fat_arch, .big) catch |err| switch (err) {
error.ReadFailed => unreachable,
error.EndOfStream => return error.InvalidMachO,
};
if (fat_arch.cputype != want_cpu_type) continue;
if (fat_arch.offset + fat_arch.size > all_mapped_memory.len) return error.InvalidMachO;
break :mapped_macho all_mapped_memory[fat_arch.offset..][0..fat_arch.size];
}
return error.MissingDebugInfo;
},
// will be fairly easy to add support here if necessary; it's very similar to above.
macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo,
else => return error.InvalidMachO,
};
var r: Io.Reader = .fixed(mapped_macho);
const hdr = r.takeStruct(macho.mach_header_64, .little) catch |err| switch (err) {
error.ReadFailed => unreachable,
error.EndOfStream => return error.InvalidMachO,
};
if (hdr.magic != macho.MH_MAGIC_64)
return error.InvalidMachO;
const symtab: macho.symtab_command, const text_vmaddr: u64 = lcs: {
var it: macho.LoadCommandIterator = try .init(&hdr, mapped_macho[@sizeOf(macho.mach_header_64)..]);
var symtab: ?macho.symtab_command = null;
var text_vmaddr: ?u64 = null;
while (try it.next()) |cmd| switch (cmd.hdr.cmd) {
.SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidMachO,
.SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| {
if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue;
text_vmaddr = seg_cmd.vmaddr;
},
else => {},
};
break :lcs .{
symtab orelse return error.MissingDebugInfo,
text_vmaddr orelse return error.MissingDebugInfo,
};
};
const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1];
var symbols: std.ArrayList(Symbol) = try .initCapacity(gpa, symtab.nsyms);
defer symbols.deinit(gpa);
// necessary because we prefer to use STAB ("symbolic debugging table") symbols,
// but they might not be present, so we track normal symbols too.
// Indices match 1-1 with those of `symbols`.
var symbol_names: std.array_hash_map.String(void) = .empty;
defer symbol_names.deinit(gpa);
try symbol_names.ensureUnusedCapacity(gpa, symtab.nsyms);
var ofile: u32 = undefined;
var last_sym: Symbol = undefined;
var state: enum {
init,
oso_open,
oso_close,
bnsym,
fun_strx,
fun_size,
ensym,
} = .init;
var sym_r: Io.Reader = .fixed(mapped_macho[symtab.symoff..]);
for (0..symtab.nsyms) |_| {
const sym = sym_r.takeStruct(macho.nlist_64, .little) catch |err| switch (err) {
error.ReadFailed => unreachable,
error.EndOfStream => return error.InvalidMachO,
};
if (sym.n_type.bits.is_stab == 0) {
if (sym.n_strx == 0) continue;
switch (sym.n_type.bits.type) {
.undf, .pbud, .indr, .abs, _ => continue,
.sect => {
const name = std.mem.sliceTo(strings[sym.n_strx..], 0);
const gop = symbol_names.getOrPutAssumeCapacity(name);
if (!gop.found_existing) {
assert(gop.index == symbols.items.len);
symbols.appendAssumeCapacity(.{
.strx = sym.n_strx,
.addr = sym.n_value,
.ofile = Symbol.unknown_ofile,
});
}
},
}
continue;
}
//
// NOTE: ld64.lld and Apple's ld differ in STABS layout.
// Apple's ld emit N_BNSYM and N_ENSYM to mark the start and end of
// functions, while ld64.lld doesn't.
switch (sym.n_type.stab) {
.oso => switch (state) {
.init, .oso_close => {
state = .oso_open;
ofile = sym.n_strx;
},
else => return error.InvalidMachO,
},
.bnsym => switch (state) {
.oso_open, .ensym => {
state = .bnsym;
last_sym = .{
.strx = 0,
.addr = sym.n_value,
.ofile = ofile,
};
},
else => return error.InvalidMachO,
},
.fun => switch (state) {
.oso_open => {
state = .fun_strx;
last_sym = .{
.strx = sym.n_strx,
.addr = sym.n_value,
.ofile = ofile,
};
},
.bnsym => {
state = .fun_strx;
last_sym.strx = sym.n_strx;
},
.fun_strx => {
state = .fun_size;
},
.fun_size => {
if (last_sym.strx != 0) {
appendStabSymbol(&symbols, &symbol_names, strings, last_sym);
}
last_sym = .{
.strx = sym.n_strx,
.addr = sym.n_value,
.ofile = ofile,
};
state = .fun_strx;
},
else => return error.InvalidMachO,
},
.ensym => switch (state) {
.fun_size => {
state = .ensym;
if (last_sym.strx != 0) {
appendStabSymbol(&symbols, &symbol_names, strings, last_sym);
}
},
else => return error.InvalidMachO,
},
.so => switch (state) {
.init, .oso_close => {},
.oso_open, .ensym => {
state = .oso_close;
},
.fun_size => {
state = .oso_close;
if (last_sym.strx != 0) {
appendStabSymbol(&symbols, &symbol_names, strings, last_sym);
}
},
else => return error.InvalidMachO,
},
else => {},
}
}
switch (state) {
.init => {
if (symbols.items.len == 0) return error.MissingDebugInfo;
},
.oso_close => {},
else => return error.InvalidMachO,
}
const symbols_slice = try symbols.toOwnedSlice(gpa);
errdefer gpa.free(symbols_slice);
// should work for programs linked in any valid way.
// This sort is so that we can binary search later.
mem.sort(Symbol, symbols_slice, {}, Symbol.addressLessThan);
return .{
.mapped_memory = all_mapped_memory,
.symbols = symbols_slice,
.strings = strings,
.ofiles = .empty,
.text_vmaddr = text_vmaddr,
};
}