Merge pull request #20650 from ziglang/parallel-macho

The tale of parallel MachO: part 1
This commit is contained in:
Jakub Konka 2024-07-18 22:23:30 +02:00 committed by GitHub
commit 16604a93b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 4786 additions and 3446 deletions

View File

@ -620,7 +620,7 @@ fn addCompilerStep(b: *std.Build, options: AddCompilerStepOptions) *std.Build.St
.root_source_file = b.path("src/main.zig"), .root_source_file = b.path("src/main.zig"),
.target = options.target, .target = options.target,
.optimize = options.optimize, .optimize = options.optimize,
.max_rss = 7_000_000_000, .max_rss = 7_100_000_000,
.strip = options.strip, .strip = options.strip,
.sanitize_thread = options.sanitize_thread, .sanitize_thread = options.sanitize_thread,
.single_threaded = options.single_threaded, .single_threaded = options.single_threaded,

View File

@ -17787,8 +17787,7 @@ fn zirBuiltinSrc(
}; };
const file_name_val = v: { const file_name_val = v: {
// The compiler must not call realpath anywhere. const file_name = fn_owner_decl.getFileScope(mod).sub_file_path;
const file_name = try fn_owner_decl.getFileScope(mod).fullPath(sema.arena);
const array_ty = try pt.intern(.{ .array_type = .{ const array_ty = try pt.intern(.{ .array_type = .{
.len = file_name.len, .len = file_name.len,
.sentinel = .zero_u8, .sentinel = .zero_u8,

View File

@ -12362,8 +12362,9 @@ fn genCall(self: *Self, info: union(enum) {
try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index }, .{}); try self.genSetReg(.rax, Type.usize, .{ .lea_got = sym_index }, .{});
try self.asmRegister(.{ ._, .call }, .rax); try self.asmRegister(.{ ._, .call }, .rax);
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| { } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
const sym_index = try macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, func.owner_decl); const zo = macho_file.getZigObject().?;
const sym = macho_file.getSymbol(sym_index); const sym_index = try zo.getOrCreateMetadataForDecl(macho_file, func.owner_decl);
const sym = zo.symbols.items[sym_index];
try self.genSetReg( try self.genSetReg(
.rax, .rax,
Type.usize, Type.usize,
@ -15396,9 +15397,10 @@ fn genLazySymbolRef(
else => unreachable, else => unreachable,
} }
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| { } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
const sym_index = macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, pt, lazy_sym) catch |err| const zo = macho_file.getZigObject().?;
const sym_index = zo.getOrCreateMetadataForLazySymbol(macho_file, pt, lazy_sym) catch |err|
return self.fail("{s} creating lazy symbol", .{@errorName(err)}); return self.fail("{s} creating lazy symbol", .{@errorName(err)});
const sym = macho_file.getSymbol(sym_index); const sym = zo.symbols.items[sym_index];
switch (tag) { switch (tag) {
.lea, .call => try self.genSetReg( .lea, .call => try self.genSetReg(
reg, reg,

View File

@ -51,12 +51,12 @@ pub fn emitMir(emit: *Emit) Error!void {
}); });
} else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| { } else if (emit.lower.bin_file.cast(link.File.MachO)) |macho_file| {
// Add relocation to the decl. // Add relocation to the decl.
const atom = macho_file.getSymbol(symbol.atom_index).getAtom(macho_file).?; const zo = macho_file.getZigObject().?;
const sym_index = macho_file.getZigObject().?.symbols.items[symbol.sym_index]; const atom = zo.symbols.items[symbol.atom_index].getAtom(macho_file).?;
try atom.addReloc(macho_file, .{ try atom.addReloc(macho_file, .{
.tag = .@"extern", .tag = .@"extern",
.offset = end_offset - 4, .offset = end_offset - 4,
.target = sym_index, .target = symbol.sym_index,
.addend = 0, .addend = 0,
.type = .branch, .type = .branch,
.meta = .{ .meta = .{
@ -160,11 +160,11 @@ pub fn emitMir(emit: *Emit) Error!void {
.Obj => true, .Obj => true,
.Lib => emit.lower.link_mode == .static, .Lib => emit.lower.link_mode == .static,
}; };
const atom = macho_file.getSymbol(data.atom_index).getAtom(macho_file).?; const zo = macho_file.getZigObject().?;
const sym_index = macho_file.getZigObject().?.symbols.items[data.sym_index]; const atom = zo.symbols.items[data.atom_index].getAtom(macho_file).?;
const sym = macho_file.getSymbol(sym_index); const sym = &zo.symbols.items[data.sym_index];
if (sym.flags.needs_zig_got and !is_obj_or_static_lib) { if (sym.flags.needs_zig_got and !is_obj_or_static_lib) {
_ = try sym.getOrCreateZigGotEntry(sym_index, macho_file); _ = try sym.getOrCreateZigGotEntry(data.sym_index, macho_file);
} }
const @"type": link.File.MachO.Relocation.Type = if (sym.flags.needs_zig_got and !is_obj_or_static_lib) const @"type": link.File.MachO.Relocation.Type = if (sym.flags.needs_zig_got and !is_obj_or_static_lib)
.zig_got_load .zig_got_load
@ -179,7 +179,7 @@ pub fn emitMir(emit: *Emit) Error!void {
try atom.addReloc(macho_file, .{ try atom.addReloc(macho_file, .{
.tag = .@"extern", .tag = .@"extern",
.offset = @intCast(end_offset - 4), .offset = @intCast(end_offset - 4),
.target = sym_index, .target = data.sym_index,
.addend = 0, .addend = 0,
.type = @"type", .type = @"type",
.meta = .{ .meta = .{

View File

@ -425,8 +425,8 @@ fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand)
else => unreachable, else => unreachable,
}; };
} else if (lower.bin_file.cast(link.File.MachO)) |macho_file| { } else if (lower.bin_file.cast(link.File.MachO)) |macho_file| {
const sym_index = macho_file.getZigObject().?.symbols.items[sym.sym_index]; const zo = macho_file.getZigObject().?;
const macho_sym = macho_file.getSymbol(sym_index); const macho_sym = zo.symbols.items[sym.sym_index];
if (macho_sym.flags.tlv) { if (macho_sym.flags.tlv) {
_ = lower.reloc(.{ .linker_reloc = sym }); _ = lower.reloc(.{ .linker_reloc = sym });

View File

@ -901,15 +901,16 @@ fn genDeclRef(
} }
return GenResult.mcv(.{ .load_symbol = sym.esym_index }); return GenResult.mcv(.{ .load_symbol = sym.esym_index });
} else if (lf.cast(link.File.MachO)) |macho_file| { } else if (lf.cast(link.File.MachO)) |macho_file| {
const zo = macho_file.getZigObject().?;
if (is_extern) { if (is_extern) {
const name = decl.name.toSlice(ip); const name = decl.name.toSlice(ip);
const lib_name = if (decl.getOwnedVariable(zcu)) |ov| ov.lib_name.toSlice(ip) else null; const lib_name = if (decl.getOwnedVariable(zcu)) |ov| ov.lib_name.toSlice(ip) else null;
const sym_index = try macho_file.getGlobalSymbol(name, lib_name); const sym_index = try macho_file.getGlobalSymbol(name, lib_name);
macho_file.getSymbol(macho_file.getZigObject().?.symbols.items[sym_index]).flags.needs_got = true; zo.symbols.items[sym_index].flags.needs_got = true;
return GenResult.mcv(.{ .load_symbol = sym_index }); return GenResult.mcv(.{ .load_symbol = sym_index });
} }
const sym_index = try macho_file.getZigObject().?.getOrCreateMetadataForDecl(macho_file, decl_index); const sym_index = try zo.getOrCreateMetadataForDecl(macho_file, decl_index);
const sym = macho_file.getSymbol(sym_index); const sym = zo.symbols.items[sym_index];
if (is_threadlocal) { if (is_threadlocal) {
return GenResult.mcv(.{ .load_tlv = sym.nlist_idx }); return GenResult.mcv(.{ .load_tlv = sym.nlist_idx });
} }
@ -956,7 +957,7 @@ fn genUnnamedConst(
}, },
.macho => { .macho => {
const macho_file = lf.cast(link.File.MachO).?; const macho_file = lf.cast(link.File.MachO).?;
const local = macho_file.getSymbol(local_sym_index); const local = macho_file.getZigObject().?.symbols.items[local_sym_index];
return GenResult.mcv(.{ .load_symbol = local.nlist_idx }); return GenResult.mcv(.{ .load_symbol = local.nlist_idx });
}, },
.coff => { .coff => {

File diff suppressed because it is too large Load Diff

View File

@ -67,9 +67,9 @@ pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index:
mem.eql(u8, name, SYMDEF64_SORTED)) continue; mem.eql(u8, name, SYMDEF64_SORTED)) continue;
const object = Object{ const object = Object{
.archive = .{
.path = try gpa.dupe(u8, path),
.offset = pos, .offset = pos,
.in_archive = .{
.path = try gpa.dupe(u8, path),
.size = hdr_size, .size = hdr_size,
}, },
.path = try gpa.dupe(u8, name), .path = try gpa.dupe(u8, name),

View File

@ -47,16 +47,6 @@ pub fn getFile(self: Atom, macho_file: *MachO) File {
return macho_file.getFile(self.file).?; return macho_file.getFile(self.file).?;
} }
pub fn getData(self: Atom, macho_file: *MachO, buffer: []u8) !void {
assert(buffer.len == self.size);
switch (self.getFile(macho_file)) {
.internal => |x| try x.getAtomData(self, buffer),
.object => |x| try x.getAtomData(macho_file, self, buffer),
.zig_object => |x| try x.getAtomData(macho_file, self, buffer),
else => unreachable,
}
}
pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation { pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation {
return switch (self.getFile(macho_file)) { return switch (self.getFile(macho_file)) {
.dylib => unreachable, .dylib => unreachable,
@ -88,17 +78,18 @@ pub fn getPriority(self: Atom, macho_file: *MachO) u64 {
} }
pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index { pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index {
if (!self.flags.unwind) return &[0]UnwindInfo.Record.Index{}; const extra = self.getExtra(macho_file);
const extra = self.getExtra(macho_file).?;
return switch (self.getFile(macho_file)) { return switch (self.getFile(macho_file)) {
.dylib, .zig_object, .internal => unreachable, .dylib => unreachable,
.object => |x| x.unwind_records.items[extra.unwind_index..][0..extra.unwind_count], .zig_object, .internal => &[0]UnwindInfo.Record.Index{},
.object => |x| x.unwind_records_indexes.items[extra.unwind_index..][0..extra.unwind_count],
}; };
} }
pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void { pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void {
const object = self.getFile(macho_file).object;
for (self.getUnwindRecords(macho_file)) |cu_index| { for (self.getUnwindRecords(macho_file)) |cu_index| {
const cu = macho_file.getUnwindRecord(cu_index); const cu = object.getUnwindRecord(cu_index);
cu.alive = false; cu.alive = false;
if (cu.getFdePtr(macho_file)) |fde| { if (cu.getFdePtr(macho_file)) |fde| {
@ -108,44 +99,39 @@ pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void {
} }
pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk { pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk {
assert(self.flags.thunk); const extra = self.getExtra(macho_file);
const extra = self.getExtra(macho_file).?;
return macho_file.getThunk(extra.thunk); return macho_file.getThunk(extra.thunk);
} }
pub fn getLiteralPoolIndex(self: Atom, macho_file: *MachO) ?MachO.LiteralPool.Index {
if (!self.flags.literal_pool) return null;
return self.getExtra(macho_file).?.literal_index;
}
const AddExtraOpts = struct { const AddExtraOpts = struct {
thunk: ?u32 = null, thunk: ?u32 = null,
rel_index: ?u32 = null, rel_index: ?u32 = null,
rel_count: ?u32 = null, rel_count: ?u32 = null,
rel_out_index: ?u32 = null,
rel_out_count: ?u32 = null,
unwind_index: ?u32 = null, unwind_index: ?u32 = null,
unwind_count: ?u32 = null, unwind_count: ?u32 = null,
literal_index: ?u32 = null, literal_pool_index: ?u32 = null,
literal_symbol_index: ?u32 = null,
}; };
pub fn addExtra(atom: *Atom, opts: AddExtraOpts, macho_file: *MachO) !void { pub fn addExtra(atom: *Atom, opts: AddExtraOpts, macho_file: *MachO) void {
if (atom.getExtra(macho_file) == null) { const file = atom.getFile(macho_file);
atom.extra = try macho_file.addAtomExtra(.{}); var extra = file.getAtomExtra(atom.extra);
}
var extra = atom.getExtra(macho_file).?;
inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
if (@field(opts, field.name)) |x| { if (@field(opts, field.name)) |x| {
@field(extra, field.name) = x; @field(extra, field.name) = x;
} }
} }
atom.setExtra(extra, macho_file); file.setAtomExtra(atom.extra, extra);
} }
pub inline fn getExtra(atom: Atom, macho_file: *MachO) ?Extra { pub inline fn getExtra(atom: Atom, macho_file: *MachO) Extra {
return macho_file.getAtomExtra(atom.extra); return atom.getFile(macho_file).getAtomExtra(atom.extra);
} }
pub inline fn setExtra(atom: Atom, extra: Extra, macho_file: *MachO) void { pub inline fn setExtra(atom: Atom, extra: Extra, macho_file: *MachO) void {
macho_file.setAtomExtra(atom.extra, extra); atom.getFile(macho_file).setAtomExtra(atom.extra, extra);
} }
pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
@ -227,7 +213,8 @@ pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
/// File offset relocation happens transparently, so it is not included in /// File offset relocation happens transparently, so it is not included in
/// this calculation. /// this calculation.
pub fn capacity(self: Atom, macho_file: *MachO) u64 { pub fn capacity(self: Atom, macho_file: *MachO) u64 {
const next_addr = if (macho_file.getAtom(self.next_index)) |next| const zo = macho_file.getZigObject().?;
const next_addr = if (zo.getAtom(self.next_index)) |next|
next.getAddress(macho_file) next.getAddress(macho_file)
else else
std.math.maxInt(u32); std.math.maxInt(u32);
@ -236,7 +223,8 @@ pub fn capacity(self: Atom, macho_file: *MachO) u64 {
pub fn freeListEligible(self: Atom, macho_file: *MachO) bool { pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
// No need to keep a free list node for the last block. // No need to keep a free list node for the last block.
const next = macho_file.getAtom(self.next_index) orelse return false; const zo = macho_file.getZigObject().?;
const next = zo.getAtom(self.next_index) orelse return false;
const cap = next.getAddress(macho_file) - self.getAddress(macho_file); const cap = next.getAddress(macho_file) - self.getAddress(macho_file);
const ideal_cap = MachO.padToIdeal(self.size); const ideal_cap = MachO.padToIdeal(self.size);
if (cap <= ideal_cap) return false; if (cap <= ideal_cap) return false;
@ -245,6 +233,7 @@ pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
} }
pub fn allocate(self: *Atom, macho_file: *MachO) !void { pub fn allocate(self: *Atom, macho_file: *MachO) !void {
const zo = macho_file.getZigObject().?;
const sect = &macho_file.sections.items(.header)[self.out_n_sect]; const sect = &macho_file.sections.items(.header)[self.out_n_sect];
const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect]; const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect]; const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
@ -264,7 +253,7 @@ pub fn allocate(self: *Atom, macho_file: *MachO) !void {
var i: usize = free_list.items.len; var i: usize = free_list.items.len;
while (i < free_list.items.len) { while (i < free_list.items.len) {
const big_atom_index = free_list.items[i]; const big_atom_index = free_list.items[i];
const big_atom = macho_file.getAtom(big_atom_index).?; const big_atom = zo.getAtom(big_atom_index).?;
// We now have a pointer to a live atom that has too much capacity. // We now have a pointer to a live atom that has too much capacity.
// Is it enough that we could fit this new atom? // Is it enough that we could fit this new atom?
const cap = big_atom.capacity(macho_file); const cap = big_atom.capacity(macho_file);
@ -296,7 +285,7 @@ pub fn allocate(self: *Atom, macho_file: *MachO) !void {
free_list_removal = i; free_list_removal = i;
} }
break :blk new_start_vaddr; break :blk new_start_vaddr;
} else if (macho_file.getAtom(last_atom_index.*)) |last| { } else if (zo.getAtom(last_atom_index.*)) |last| {
const ideal_capacity = MachO.padToIdeal(last.size); const ideal_capacity = MachO.padToIdeal(last.size);
const ideal_capacity_end_vaddr = last.value + ideal_capacity; const ideal_capacity_end_vaddr = last.value + ideal_capacity;
const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr); const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr);
@ -316,7 +305,7 @@ pub fn allocate(self: *Atom, macho_file: *MachO) !void {
}); });
const expand_section = if (atom_placement) |placement_index| const expand_section = if (atom_placement) |placement_index|
macho_file.getAtom(placement_index).?.next_index == 0 zo.getAtom(placement_index).?.next_index == 0
else else
true; true;
if (expand_section) { if (expand_section) {
@ -341,15 +330,15 @@ pub fn allocate(self: *Atom, macho_file: *MachO) !void {
// This function can also reallocate an atom. // This function can also reallocate an atom.
// In this case we need to "unplug" it from its previous location before // In this case we need to "unplug" it from its previous location before
// plugging it in to its new location. // plugging it in to its new location.
if (macho_file.getAtom(self.prev_index)) |prev| { if (zo.getAtom(self.prev_index)) |prev| {
prev.next_index = self.next_index; prev.next_index = self.next_index;
} }
if (macho_file.getAtom(self.next_index)) |next| { if (zo.getAtom(self.next_index)) |next| {
next.prev_index = self.prev_index; next.prev_index = self.prev_index;
} }
if (atom_placement) |big_atom_index| { if (atom_placement) |big_atom_index| {
const big_atom = macho_file.getAtom(big_atom_index).?; const big_atom = zo.getAtom(big_atom_index).?;
self.prev_index = big_atom_index; self.prev_index = big_atom_index;
self.next_index = big_atom.next_index; self.next_index = big_atom.next_index;
big_atom.next_index = self.atom_index; big_atom.next_index = self.atom_index;
@ -379,6 +368,7 @@ pub fn free(self: *Atom, macho_file: *MachO) void {
const comp = macho_file.base.comp; const comp = macho_file.base.comp;
const gpa = comp.gpa; const gpa = comp.gpa;
const zo = macho_file.getZigObject().?;
const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect]; const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect]; const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
var already_have_free_list_node = false; var already_have_free_list_node = false;
@ -397,9 +387,9 @@ pub fn free(self: *Atom, macho_file: *MachO) void {
} }
} }
if (macho_file.getAtom(last_atom_index.*)) |last_atom| { if (zo.getAtom(last_atom_index.*)) |last_atom| {
if (last_atom.atom_index == self.atom_index) { if (last_atom.atom_index == self.atom_index) {
if (macho_file.getAtom(self.prev_index)) |_| { if (zo.getAtom(self.prev_index)) |_| {
// TODO shrink the section size here // TODO shrink the section size here
last_atom_index.* = self.prev_index; last_atom_index.* = self.prev_index;
} else { } else {
@ -408,7 +398,7 @@ pub fn free(self: *Atom, macho_file: *MachO) void {
} }
} }
if (macho_file.getAtom(self.prev_index)) |prev| { if (zo.getAtom(self.prev_index)) |prev| {
prev.next_index = self.next_index; prev.next_index = self.next_index;
if (!already_have_free_list_node and prev.*.freeListEligible(macho_file)) { if (!already_have_free_list_node and prev.*.freeListEligible(macho_file)) {
// The free list is heuristics, it doesn't have to be perfect, so we can // The free list is heuristics, it doesn't have to be perfect, so we can
@ -419,7 +409,7 @@ pub fn free(self: *Atom, macho_file: *MachO) void {
self.prev_index = 0; self.prev_index = 0;
} }
if (macho_file.getAtom(self.next_index)) |next| { if (zo.getAtom(self.next_index)) |next| {
next.prev_index = self.prev_index; next.prev_index = self.prev_index;
} else { } else {
self.next_index = 0; self.next_index = 0;
@ -437,8 +427,7 @@ pub fn addReloc(self: *Atom, macho_file: *MachO, reloc: Relocation) !void {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const file = self.getFile(macho_file); const file = self.getFile(macho_file);
assert(file == .zig_object); assert(file == .zig_object);
assert(self.flags.relocs); var extra = self.getExtra(macho_file);
var extra = self.getExtra(macho_file).?;
const rels = &file.zig_object.relocs.items[extra.rel_index]; const rels = &file.zig_object.relocs.items[extra.rel_index];
try rels.append(gpa, reloc); try rels.append(gpa, reloc);
extra.rel_count += 1; extra.rel_count += 1;
@ -446,9 +435,8 @@ pub fn addReloc(self: *Atom, macho_file: *MachO, reloc: Relocation) !void {
} }
pub fn freeRelocs(self: *Atom, macho_file: *MachO) void { pub fn freeRelocs(self: *Atom, macho_file: *MachO) void {
if (!self.flags.relocs) return;
self.getFile(macho_file).zig_object.freeAtomRelocs(self.*, macho_file); self.getFile(macho_file).zig_object.freeAtomRelocs(self.*, macho_file);
var extra = self.getExtra(macho_file).?; var extra = self.getExtra(macho_file);
extra.rel_count = 0; extra.rel_count = 0;
self.setExtra(extra, macho_file); self.setExtra(extra, macho_file);
} }
@ -458,11 +446,6 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
defer tracy.end(); defer tracy.end();
assert(self.flags.alive); assert(self.flags.alive);
const dynrel_ctx = switch (self.getFile(macho_file)) {
.zig_object => |x| &x.dynamic_relocs,
.object => |x| &x.dynamic_relocs,
else => unreachable,
};
const relocs = self.getRelocs(macho_file); const relocs = self.getRelocs(macho_file);
for (relocs) |rel| { for (relocs) |rel| {
@ -470,7 +453,7 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
switch (rel.type) { switch (rel.type) {
.branch => { .branch => {
const symbol = rel.getTargetSymbol(macho_file); const symbol = rel.getTargetSymbol(self, macho_file);
if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) { if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) {
symbol.flags.stubs = true; symbol.flags.stubs = true;
if (symbol.flags.weak) { if (symbol.flags.weak) {
@ -485,7 +468,7 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
.got_load_page, .got_load_page,
.got_load_pageoff, .got_load_pageoff,
=> { => {
const symbol = rel.getTargetSymbol(macho_file); const symbol = rel.getTargetSymbol(self, macho_file);
if (symbol.flags.import or if (symbol.flags.import or
(symbol.flags.@"export" and symbol.flags.weak) or (symbol.flags.@"export" and symbol.flags.weak) or
symbol.flags.interposable or symbol.flags.interposable or
@ -499,18 +482,18 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
}, },
.zig_got_load => { .zig_got_load => {
assert(rel.getTargetSymbol(macho_file).flags.has_zig_got); assert(rel.getTargetSymbol(self, macho_file).flags.has_zig_got);
}, },
.got => { .got => {
rel.getTargetSymbol(macho_file).flags.needs_got = true; rel.getTargetSymbol(self, macho_file).flags.needs_got = true;
}, },
.tlv, .tlv,
.tlvp_page, .tlvp_page,
.tlvp_pageoff, .tlvp_pageoff,
=> { => {
const symbol = rel.getTargetSymbol(macho_file); const symbol = rel.getTargetSymbol(self, macho_file);
if (!symbol.flags.tlv) { if (!symbol.flags.tlv) {
try macho_file.reportParseError2( try macho_file.reportParseError2(
self.getFile(macho_file).getIndex(), self.getFile(macho_file).getIndex(),
@ -529,27 +512,21 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
.unsigned => { .unsigned => {
if (rel.meta.length == 3) { // TODO this really should check if this is pointer width if (rel.meta.length == 3) { // TODO this really should check if this is pointer width
if (rel.tag == .@"extern") { if (rel.tag == .@"extern") {
const symbol = rel.getTargetSymbol(macho_file); const symbol = rel.getTargetSymbol(self, macho_file);
if (symbol.isTlvInit(macho_file)) { if (symbol.isTlvInit(macho_file)) {
macho_file.has_tlv = true; macho_file.has_tlv = true;
continue; continue;
} }
if (symbol.flags.import) { if (symbol.flags.import) {
dynrel_ctx.bind_relocs += 1;
if (symbol.flags.weak) { if (symbol.flags.weak) {
dynrel_ctx.weak_bind_relocs += 1;
macho_file.binds_to_weak = true; macho_file.binds_to_weak = true;
} }
continue; continue;
} }
if (symbol.flags.@"export" and symbol.flags.weak) { if (symbol.flags.@"export" and symbol.flags.weak) {
dynrel_ctx.weak_bind_relocs += 1;
macho_file.binds_to_weak = true; macho_file.binds_to_weak = true;
} else if (symbol.flags.interposable) {
dynrel_ctx.bind_relocs += 1;
} }
} }
dynrel_ctx.rebase_relocs += 1;
} }
}, },
@ -568,14 +545,15 @@ pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool { fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool {
if (rel.tag == .local) return false; if (rel.tag == .local) return false;
const sym = rel.getTargetSymbol(macho_file); const file = self.getFile(macho_file);
if (sym.getFile(macho_file) == null) { const ref = file.getSymbolRef(rel.target, macho_file);
if (ref.getFile(macho_file) == null) {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const gop = try macho_file.undefs.getOrPut(gpa, rel.target); const gop = try macho_file.undefs.getOrPut(gpa, file.getGlobals()[rel.target]);
if (!gop.found_existing) { if (!gop.found_existing) {
gop.value_ptr.* = .{}; gop.value_ptr.* = .{};
} }
try gop.value_ptr.append(gpa, self.atom_index); try gop.value_ptr.append(gpa, .{ .index = self.atom_index, .file = self.file });
return true; return true;
} }
@ -591,7 +569,7 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
const name = self.getName(macho_file); const name = self.getName(macho_file);
const relocs = self.getRelocs(macho_file); const relocs = self.getRelocs(macho_file);
relocs_log.debug("{x}: {s}", .{ self.getAddress(macho_file), name }); relocs_log.debug("{x}: {s}", .{ self.value, name });
var has_error = false; var has_error = false;
var stream = std.io.fixedBufferStream(buffer); var stream = std.io.fixedBufferStream(buffer);
@ -602,7 +580,7 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null; const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null;
if (rel.tag == .@"extern") { if (rel.tag == .@"extern") {
if (rel.getTargetSymbol(macho_file).getFile(macho_file) == null) continue; if (rel.getTargetSymbol(self, macho_file).getFile(macho_file) == null) continue;
} }
try stream.seekTo(rel_offset); try stream.seekTo(rel_offset);
@ -610,13 +588,19 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
switch (err) { switch (err) {
error.RelaxFail => { error.RelaxFail => {
const target = switch (rel.tag) { const target = switch (rel.tag) {
.@"extern" => rel.getTargetSymbol(macho_file).getName(macho_file), .@"extern" => rel.getTargetSymbol(self, macho_file).getName(macho_file),
.local => rel.getTargetAtom(macho_file).getName(macho_file), .local => rel.getTargetAtom(self, macho_file).getName(macho_file),
}; };
try macho_file.reportParseError2( try macho_file.reportParseError2(
file.getIndex(), file.getIndex(),
"{s}: 0x{x}: 0x{x}: failed to relax relocation: type {s}, target {s}", "{s}: 0x{x}: 0x{x}: failed to relax relocation: type {}, target {s}",
.{ name, self.getAddress(macho_file), rel.offset, @tagName(rel.type), target }, .{
name,
self.getAddress(macho_file),
rel.offset,
rel.fmtPretty(macho_file.getTarget().cpu.arch),
target,
},
); );
has_error = true; has_error = true;
}, },
@ -649,14 +633,12 @@ fn resolveRelocInner(
) ResolveError!void { ) ResolveError!void {
const cpu_arch = macho_file.getTarget().cpu.arch; const cpu_arch = macho_file.getTarget().cpu.arch;
const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow; const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow;
const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect];
const seg = macho_file.segments.items[seg_id];
const P = @as(i64, @intCast(self.getAddress(macho_file))) + @as(i64, @intCast(rel_offset)); const P = @as(i64, @intCast(self.getAddress(macho_file))) + @as(i64, @intCast(rel_offset));
const A = rel.addend + rel.getRelocAddend(cpu_arch); const A = rel.addend + rel.getRelocAddend(cpu_arch);
const S: i64 = @intCast(rel.getTargetAddress(macho_file)); const S: i64 = @intCast(rel.getTargetAddress(self, macho_file));
const G: i64 = @intCast(rel.getGotTargetAddress(macho_file)); const G: i64 = @intCast(rel.getGotTargetAddress(self, macho_file));
const TLS = @as(i64, @intCast(macho_file.getTlsAddress())); const TLS = @as(i64, @intCast(macho_file.getTlsAddress()));
const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(macho_file))) else 0; const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(self, macho_file))) else 0;
// Address of the __got_zig table entry if any. // Address of the __got_zig table entry if any.
const ZIG_GOT = @as(i64, @intCast(rel.getZigGotTargetAddress(macho_file))); const ZIG_GOT = @as(i64, @intCast(rel.getZigGotTargetAddress(macho_file)));
@ -674,21 +656,21 @@ fn resolveRelocInner(
}.divExact; }.divExact;
switch (rel.tag) { switch (rel.tag) {
.local => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] atom({d})", .{ .local => relocs_log.debug(" {x}<+{d}>: {}: [=> {x}] atom({d})", .{
P, P,
rel_offset, rel_offset,
@tagName(rel.type), rel.fmtPretty(cpu_arch),
S + A - SUB, S + A - SUB,
rel.getTargetAtom(macho_file).atom_index, rel.getTargetAtom(self, macho_file).atom_index,
}), }),
.@"extern" => relocs_log.debug(" {x}<+{d}>: {s}: [=> {x}] G({x}) ZG({x}) ({s})", .{ .@"extern" => relocs_log.debug(" {x}<+{d}>: {}: [=> {x}] G({x}) ZG({x}) ({s})", .{
P, P,
rel_offset, rel_offset,
@tagName(rel.type), rel.fmtPretty(cpu_arch),
S + A - SUB, S + A - SUB,
G + A, G + A,
ZIG_GOT + A, ZIG_GOT + A,
rel.getTargetSymbol(macho_file).getName(macho_file), rel.getTargetSymbol(self, macho_file).getName(macho_file),
}), }),
} }
@ -699,34 +681,13 @@ fn resolveRelocInner(
assert(!rel.meta.pcrel); assert(!rel.meta.pcrel);
if (rel.meta.length == 3) { if (rel.meta.length == 3) {
if (rel.tag == .@"extern") { if (rel.tag == .@"extern") {
const sym = rel.getTargetSymbol(macho_file); const sym = rel.getTargetSymbol(self, macho_file);
if (sym.isTlvInit(macho_file)) { if (sym.isTlvInit(macho_file)) {
try writer.writeInt(u64, @intCast(S - TLS), .little); try writer.writeInt(u64, @intCast(S - TLS), .little);
return; return;
} }
const entry = bind.Entry{ if (sym.flags.import) return;
.target = rel.target,
.offset = @as(u64, @intCast(P)) - seg.vmaddr,
.segment_id = seg_id,
.addend = A,
};
if (sym.flags.import) {
macho_file.bind.entries.appendAssumeCapacity(entry);
if (sym.flags.weak) {
macho_file.weak_bind.entries.appendAssumeCapacity(entry);
} }
return;
}
if (sym.flags.@"export" and sym.flags.weak) {
macho_file.weak_bind.entries.appendAssumeCapacity(entry);
} else if (sym.flags.interposable) {
macho_file.bind.entries.appendAssumeCapacity(entry);
}
}
macho_file.rebase.entries.appendAssumeCapacity(.{
.offset = @as(u64, @intCast(P)) - seg.vmaddr,
.segment_id = seg_id,
});
try writer.writeInt(u64, @bitCast(S + A - SUB), .little); try writer.writeInt(u64, @bitCast(S + A - SUB), .little);
} else if (rel.meta.length == 2) { } else if (rel.meta.length == 2) {
try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little); try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little);
@ -750,7 +711,7 @@ fn resolveRelocInner(
.aarch64 => { .aarch64 => {
const disp: i28 = math.cast(i28, S + A - P) orelse blk: { const disp: i28 = math.cast(i28, S + A - P) orelse blk: {
const thunk = self.getThunk(macho_file); const thunk = self.getThunk(macho_file);
const S_: i64 = @intCast(thunk.getTargetAddress(rel.target, macho_file)); const S_: i64 = @intCast(thunk.getTargetAddress(rel.getTargetSymbolRef(self, macho_file), macho_file));
break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow; break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow;
}; };
aarch64.writeBranchImm(disp, code[rel_offset..][0..4]); aarch64.writeBranchImm(disp, code[rel_offset..][0..4]);
@ -763,7 +724,7 @@ fn resolveRelocInner(
assert(rel.tag == .@"extern"); assert(rel.tag == .@"extern");
assert(rel.meta.length == 2); assert(rel.meta.length == 2);
assert(rel.meta.pcrel); assert(rel.meta.pcrel);
if (rel.getTargetSymbol(macho_file).flags.has_got) { if (rel.getTargetSymbol(self, macho_file).flags.has_got) {
try writer.writeInt(i32, @intCast(G + A - P), .little); try writer.writeInt(i32, @intCast(G + A - P), .little);
} else { } else {
try x86_64.relaxGotLoad(self, code[rel_offset - 3 ..], rel, macho_file); try x86_64.relaxGotLoad(self, code[rel_offset - 3 ..], rel, macho_file);
@ -786,7 +747,7 @@ fn resolveRelocInner(
assert(rel.tag == .@"extern"); assert(rel.tag == .@"extern");
assert(rel.meta.length == 2); assert(rel.meta.length == 2);
assert(rel.meta.pcrel); assert(rel.meta.pcrel);
const sym = rel.getTargetSymbol(macho_file); const sym = rel.getTargetSymbol(self, macho_file);
if (sym.flags.tlv_ptr) { if (sym.flags.tlv_ptr) {
const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
try writer.writeInt(i32, @intCast(S_ + A - P), .little); try writer.writeInt(i32, @intCast(S_ + A - P), .little);
@ -809,7 +770,7 @@ fn resolveRelocInner(
assert(rel.tag == .@"extern"); assert(rel.tag == .@"extern");
assert(rel.meta.length == 2); assert(rel.meta.length == 2);
assert(rel.meta.pcrel); assert(rel.meta.pcrel);
const sym = rel.getTargetSymbol(macho_file); const sym = rel.getTargetSymbol(self, macho_file);
const source = math.cast(u64, P) orelse return error.Overflow; const source = math.cast(u64, P) orelse return error.Overflow;
const target = target: { const target = target: {
const target = switch (rel.type) { const target = switch (rel.type) {
@ -868,7 +829,7 @@ fn resolveRelocInner(
assert(rel.meta.length == 2); assert(rel.meta.length == 2);
assert(!rel.meta.pcrel); assert(!rel.meta.pcrel);
const sym = rel.getTargetSymbol(macho_file); const sym = rel.getTargetSymbol(self, macho_file);
const target = target: { const target = target: {
const target = if (sym.flags.tlv_ptr) blk: { const target = if (sym.flags.tlv_ptr) blk: {
const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
@ -945,11 +906,11 @@ const x86_64 = struct {
}, },
else => |x| { else => |x| {
var err = try macho_file.addErrorWithNotes(2); var err = try macho_file.addErrorWithNotes(2);
try err.addMsg(macho_file, "{s}: 0x{x}: 0x{x}: failed to relax relocation of type {s}", .{ try err.addMsg(macho_file, "{s}: 0x{x}: 0x{x}: failed to relax relocation of type {}", .{
self.getName(macho_file), self.getName(macho_file),
self.getAddress(macho_file), self.getAddress(macho_file),
rel.offset, rel.offset,
@tagName(rel.type), rel.fmtPretty(.x86_64),
}); });
try err.addNote(macho_file, "expected .mov instruction but found .{s}", .{@tagName(x)}); try err.addNote(macho_file, "expected .mov instruction but found .{s}", .{@tagName(x)});
try err.addNote(macho_file, "while parsing {}", .{self.getFile(macho_file).fmtPath()}); try err.addNote(macho_file, "while parsing {}", .{self.getFile(macho_file).fmtPath()});
@ -1012,48 +973,49 @@ pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 {
} }
} }
pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.ArrayList(macho.relocation_info)) !void { pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: []macho.relocation_info) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const cpu_arch = macho_file.getTarget().cpu.arch; const cpu_arch = macho_file.getTarget().cpu.arch;
const relocs = self.getRelocs(macho_file); const relocs = self.getRelocs(macho_file);
var stream = std.io.fixedBufferStream(code);
var i: usize = 0;
for (relocs) |rel| { for (relocs) |rel| {
const rel_offset = rel.offset - self.off; defer i += 1;
const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow;
const r_address: i32 = math.cast(i32, self.value + rel_offset) orelse return error.Overflow; const r_address: i32 = math.cast(i32, self.value + rel_offset) orelse return error.Overflow;
assert(r_address >= 0);
const r_symbolnum = r_symbolnum: { const r_symbolnum = r_symbolnum: {
const r_symbolnum: u32 = switch (rel.tag) { const r_symbolnum: u32 = switch (rel.tag) {
.local => rel.getTargetAtom(macho_file).out_n_sect + 1, .local => rel.getTargetAtom(self, macho_file).out_n_sect + 1,
.@"extern" => rel.getTargetSymbol(macho_file).getOutputSymtabIndex(macho_file).?, .@"extern" => rel.getTargetSymbol(self, macho_file).getOutputSymtabIndex(macho_file).?,
}; };
break :r_symbolnum math.cast(u24, r_symbolnum) orelse return error.Overflow; break :r_symbolnum math.cast(u24, r_symbolnum) orelse return error.Overflow;
}; };
const r_extern = rel.tag == .@"extern"; const r_extern = rel.tag == .@"extern";
var addend = rel.addend + rel.getRelocAddend(cpu_arch); var addend = rel.addend + rel.getRelocAddend(cpu_arch);
if (rel.tag == .local) { if (rel.tag == .local) {
const target: i64 = @intCast(rel.getTargetAddress(macho_file)); const target: i64 = @intCast(rel.getTargetAddress(self, macho_file));
addend += target; addend += target;
} }
try stream.seekTo(rel_offset);
switch (cpu_arch) { switch (cpu_arch) {
.aarch64 => { .aarch64 => {
if (rel.type == .unsigned) switch (rel.meta.length) { if (rel.type == .unsigned) switch (rel.meta.length) {
0, 1 => unreachable, 0, 1 => unreachable,
2 => try stream.writer().writeInt(i32, @truncate(addend), .little), 2 => mem.writeInt(i32, code[rel_offset..][0..4], @truncate(addend), .little),
3 => try stream.writer().writeInt(i64, addend, .little), 3 => mem.writeInt(i64, code[rel_offset..][0..8], addend, .little),
} else if (addend > 0) { } else if (addend > 0) {
buffer.appendAssumeCapacity(.{ buffer[i] = .{
.r_address = r_address, .r_address = r_address,
.r_symbolnum = @bitCast(math.cast(i24, addend) orelse return error.Overflow), .r_symbolnum = @bitCast(math.cast(i24, addend) orelse return error.Overflow),
.r_pcrel = 0, .r_pcrel = 0,
.r_length = 2, .r_length = 2,
.r_extern = 0, .r_extern = 0,
.r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND), .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND),
}); };
i += 1;
} }
const r_type: macho.reloc_type_arm64 = switch (rel.type) { const r_type: macho.reloc_type_arm64 = switch (rel.type) {
@ -1077,14 +1039,14 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.Arra
.tlv, .tlv,
=> unreachable, => unreachable,
}; };
buffer.appendAssumeCapacity(.{ buffer[i] = .{
.r_address = r_address, .r_address = r_address,
.r_symbolnum = r_symbolnum, .r_symbolnum = r_symbolnum,
.r_pcrel = @intFromBool(rel.meta.pcrel), .r_pcrel = @intFromBool(rel.meta.pcrel),
.r_extern = @intFromBool(r_extern), .r_extern = @intFromBool(r_extern),
.r_length = rel.meta.length, .r_length = rel.meta.length,
.r_type = @intFromEnum(r_type), .r_type = @intFromEnum(r_type),
}); };
}, },
.x86_64 => { .x86_64 => {
if (rel.meta.pcrel) { if (rel.meta.pcrel) {
@ -1096,8 +1058,8 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.Arra
} }
switch (rel.meta.length) { switch (rel.meta.length) {
0, 1 => unreachable, 0, 1 => unreachable,
2 => try stream.writer().writeInt(i32, @truncate(addend), .little), 2 => mem.writeInt(i32, code[rel_offset..][0..4], @truncate(addend), .little),
3 => try stream.writer().writeInt(i64, addend, .little), 3 => mem.writeInt(i64, code[rel_offset..][0..8], addend, .little),
} }
const r_type: macho.reloc_type_x86_64 = switch (rel.type) { const r_type: macho.reloc_type_x86_64 = switch (rel.type) {
@ -1121,18 +1083,20 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: *std.Arra
.tlvp_pageoff, .tlvp_pageoff,
=> unreachable, => unreachable,
}; };
buffer.appendAssumeCapacity(.{ buffer[i] = .{
.r_address = r_address, .r_address = r_address,
.r_symbolnum = r_symbolnum, .r_symbolnum = r_symbolnum,
.r_pcrel = @intFromBool(rel.meta.pcrel), .r_pcrel = @intFromBool(rel.meta.pcrel),
.r_extern = @intFromBool(r_extern), .r_extern = @intFromBool(r_extern),
.r_length = rel.meta.length, .r_length = rel.meta.length,
.r_type = @intFromEnum(r_type), .r_type = @intFromEnum(r_type),
}); };
}, },
else => unreachable, else => unreachable,
} }
} }
assert(i == buffer.len);
} }
pub fn format( pub fn format(
@ -1170,18 +1134,18 @@ fn format2(
_ = unused_fmt_string; _ = unused_fmt_string;
const atom = ctx.atom; const atom = ctx.atom;
const macho_file = ctx.macho_file; const macho_file = ctx.macho_file;
try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : nreloc({d})", .{ const file = atom.getFile(macho_file);
try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : nreloc({d}) : thunk({d})", .{
atom.atom_index, atom.getName(macho_file), atom.getAddress(macho_file), atom.atom_index, atom.getName(macho_file), atom.getAddress(macho_file),
atom.out_n_sect, atom.alignment, atom.size, atom.out_n_sect, atom.alignment, atom.size,
atom.getRelocs(macho_file).len, atom.getRelocs(macho_file).len, atom.getExtra(macho_file).thunk,
}); });
if (atom.flags.thunk) try writer.print(" : thunk({d})", .{atom.getExtra(macho_file).?.thunk});
if (!atom.flags.alive) try writer.writeAll(" : [*]"); if (!atom.flags.alive) try writer.writeAll(" : [*]");
if (atom.flags.unwind) { if (atom.getUnwindRecords(macho_file).len > 0) {
try writer.writeAll(" : unwind{ "); try writer.writeAll(" : unwind{ ");
const extra = atom.getExtra(macho_file).?; const extra = atom.getExtra(macho_file);
for (atom.getUnwindRecords(macho_file), extra.unwind_index..) |index, i| { for (atom.getUnwindRecords(macho_file), extra.unwind_index..) |index, i| {
const rec = macho_file.getUnwindRecord(index); const rec = file.object.getUnwindRecord(index);
try writer.print("{d}", .{index}); try writer.print("{d}", .{index});
if (!rec.alive) try writer.writeAll("([*])"); if (!rec.alive) try writer.writeAll("([*])");
if (i < extra.unwind_index + extra.unwind_count - 1) try writer.writeAll(", "); if (i < extra.unwind_index + extra.unwind_count - 1) try writer.writeAll(", ");
@ -1198,18 +1162,6 @@ pub const Flags = packed struct {
/// Specifies if this atom has been visited during garbage collection. /// Specifies if this atom has been visited during garbage collection.
visited: bool = false, visited: bool = false,
/// Whether this atom has a range extension thunk.
thunk: bool = false,
/// Whether this atom has any relocations.
relocs: bool = false,
/// Whether this atom has any unwind records.
unwind: bool = false,
/// Whether this atom has LiteralPool entry.
literal_pool: bool = false,
}; };
pub const Extra = struct { pub const Extra = struct {
@ -1222,6 +1174,12 @@ pub const Extra = struct {
/// Count of relocations belonging to this atom. /// Count of relocations belonging to this atom.
rel_count: u32 = 0, rel_count: u32 = 0,
/// Start index of relocations being written out to file for this atom.
rel_out_index: u32 = 0,
/// Count of relocations written out to file for this atom.
rel_out_count: u32 = 0,
/// Start index of relocations belonging to this atom. /// Start index of relocations belonging to this atom.
unwind_index: u32 = 0, unwind_index: u32 = 0,
@ -1229,14 +1187,16 @@ pub const Extra = struct {
unwind_count: u32 = 0, unwind_count: u32 = 0,
/// Index into LiteralPool entry for this atom. /// Index into LiteralPool entry for this atom.
literal_index: u32 = 0, literal_pool_index: u32 = 0,
/// Index into the File's symbol table for local symbol representing this literal atom.
literal_symbol_index: u32 = 0,
}; };
pub const Alignment = @import("../../InternPool.zig").Alignment; pub const Alignment = @import("../../InternPool.zig").Alignment;
const aarch64 = @import("../aarch64.zig"); const aarch64 = @import("../aarch64.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const bind = @import("dyld_info/bind.zig");
const macho = std.macho; const macho = std.macho;
const math = std.math; const math = std.math;
const mem = std.mem; const mem = std.mem;

View File

@ -175,8 +175,9 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) u64
} }
pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void { pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void {
const zo = macho_file.getZigObject().?;
for (self.relocs.items) |*reloc| { for (self.relocs.items) |*reloc| {
const sym = macho_file.getSymbol(reloc.target); const sym = zo.symbols.items[reloc.target];
const sym_name = sym.getName(macho_file); const sym_name = sym.getName(macho_file);
const addr = switch (reloc.type) { const addr = switch (reloc.type) {
.direct_load => sym.getAddress(.{}, macho_file), .direct_load => sym.getAddress(.{}, macho_file),
@ -382,23 +383,22 @@ pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {
cmd.symoff = off; cmd.symoff = off;
try self.symtab.resize(gpa, cmd.nsyms); try self.symtab.resize(gpa, cmd.nsyms);
try self.strtab.ensureUnusedCapacity(gpa, cmd.strsize - 1); try self.strtab.resize(gpa, cmd.strsize);
self.strtab.items[0] = 0;
if (macho_file.getZigObject()) |zo| { if (macho_file.getZigObject()) |zo| {
zo.writeSymtab(macho_file, self); zo.writeSymtab(macho_file, self);
} }
for (macho_file.objects.items) |index| { for (macho_file.objects.items) |index| {
try macho_file.getFile(index).?.writeSymtab(macho_file, self); macho_file.getFile(index).?.writeSymtab(macho_file, self);
} }
for (macho_file.dylibs.items) |index| { for (macho_file.dylibs.items) |index| {
try macho_file.getFile(index).?.writeSymtab(macho_file, self); macho_file.getFile(index).?.writeSymtab(macho_file, self);
} }
if (macho_file.getInternalObject()) |internal| { if (macho_file.getInternalObject()) |internal| {
internal.writeSymtab(macho_file, self); internal.writeSymtab(macho_file, self);
} }
assert(self.strtab.items.len == cmd.strsize);
try self.file.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff); try self.file.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff);
return off + cmd.nsyms * @sizeOf(macho.nlist_64); return off + cmd.nsyms * @sizeOf(macho.nlist_64);

View File

@ -6,7 +6,9 @@ strtab: std.ArrayListUnmanaged(u8) = .{},
id: ?Id = null, id: ?Id = null,
ordinal: u16 = 0, ordinal: u16 = 0,
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, symbols: std.ArrayListUnmanaged(Symbol) = .{},
symbols_extra: std.ArrayListUnmanaged(u32) = .{},
globals: std.ArrayListUnmanaged(MachO.SymbolResolver.Index) = .{},
dependents: std.ArrayListUnmanaged(Id) = .{}, dependents: std.ArrayListUnmanaged(Id) = .{},
rpaths: std.StringArrayHashMapUnmanaged(void) = .{}, rpaths: std.StringArrayHashMapUnmanaged(void) = .{},
umbrella: File.Index = 0, umbrella: File.Index = 0,
@ -37,6 +39,8 @@ pub fn deinit(self: *Dylib, allocator: Allocator) void {
self.strtab.deinit(allocator); self.strtab.deinit(allocator);
if (self.id) |*id| id.deinit(allocator); if (self.id) |*id| id.deinit(allocator);
self.symbols.deinit(allocator); self.symbols.deinit(allocator);
self.symbols_extra.deinit(allocator);
self.globals.deinit(allocator);
for (self.dependents.items) |*id| { for (self.dependents.items) |*id| {
id.deinit(allocator); id.deinit(allocator);
} }
@ -494,50 +498,52 @@ fn addObjCExport(
pub fn initSymbols(self: *Dylib, macho_file: *MachO) !void { pub fn initSymbols(self: *Dylib, macho_file: *MachO) !void {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
try self.symbols.ensureTotalCapacityPrecise(gpa, self.exports.items(.name).len); const nsyms = self.exports.items(.name).len;
try self.symbols.ensureTotalCapacityPrecise(gpa, nsyms);
try self.symbols_extra.ensureTotalCapacityPrecise(gpa, nsyms * @sizeOf(Symbol.Extra));
try self.globals.ensureTotalCapacityPrecise(gpa, nsyms);
self.globals.resize(gpa, nsyms) catch unreachable;
@memset(self.globals.items, 0);
for (self.exports.items(.name)) |noff| { for (self.exports.items(.name), self.exports.items(.flags)) |noff, flags| {
const name = self.getString(noff); const index = self.addSymbolAssumeCapacity();
const off = try macho_file.strings.insert(gpa, name); const symbol = &self.symbols.items[index];
const gop = try macho_file.getOrCreateGlobal(off); symbol.name = noff;
self.symbols.addOneAssumeCapacity().* = gop.index; symbol.extra = self.addSymbolExtraAssumeCapacity(.{});
symbol.flags.weak = flags.weak;
symbol.flags.tlv = flags.tlv;
symbol.visibility = .global;
} }
} }
pub fn resolveSymbols(self: *Dylib, macho_file: *MachO) void { pub fn resolveSymbols(self: *Dylib, macho_file: *MachO) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
if (!self.explicit and !self.hoisted) return; if (!self.explicit and !self.hoisted) return;
for (self.symbols.items, self.exports.items(.flags)) |index, flags| { const gpa = macho_file.base.comp.gpa;
const global = macho_file.getSymbol(index);
if (self.asFile().getSymbolRank(.{ for (self.exports.items(.flags), self.globals.items, 0..) |flags, *global, i| {
.weak = flags.weak, const gop = try macho_file.resolver.getOrPut(gpa, .{
}) < global.getSymbolRank(macho_file)) { .index = @intCast(i),
global.value = 0; .file = self.index,
global.atom = 0; }, macho_file);
global.nlist_idx = 0; if (!gop.found_existing) {
global.file = self.index; gop.ref.* = .{ .index = 0, .file = 0 };
global.flags.weak = flags.weak;
global.flags.tlv = flags.tlv;
global.flags.dyn_ref = false;
global.flags.tentative = false;
global.visibility = .global;
}
} }
global.* = gop.index;
if (gop.ref.getFile(macho_file) == null) {
gop.ref.* = .{ .index = @intCast(i), .file = self.index };
continue;
} }
pub fn resetGlobals(self: *Dylib, macho_file: *MachO) void { if (self.asFile().getSymbolRank(.{
for (self.symbols.items) |sym_index| { .weak = flags.weak,
const sym = macho_file.getSymbol(sym_index); }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {
const name = sym.name; gop.ref.* = .{ .index = @intCast(i), .file = self.index };
const global = sym.flags.global; }
const weak_ref = sym.flags.weak_ref;
sym.* = .{};
sym.name = name;
sym.flags.global = global;
sym.flags.weak_ref = weak_ref;
} }
} }
@ -550,30 +556,31 @@ pub fn markReferenced(self: *Dylib, macho_file: *MachO) void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
for (self.symbols.items) |global_index| { for (0..self.symbols.items.len) |i| {
const global = macho_file.getSymbol(global_index); const ref = self.getSymbolRef(@intCast(i), macho_file);
const file_ptr = global.getFile(macho_file) orelse continue; const file = ref.getFile(macho_file) orelse continue;
if (file_ptr.getIndex() != self.index) continue; if (file.getIndex() != self.index) continue;
const global = ref.getSymbol(macho_file).?;
if (global.isLocal()) continue; if (global.isLocal()) continue;
self.referenced = true; self.referenced = true;
break; break;
} }
} }
pub fn calcSymtabSize(self: *Dylib, macho_file: *MachO) !void { pub fn calcSymtabSize(self: *Dylib, macho_file: *MachO) void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
for (self.symbols.items) |global_index| { for (self.symbols.items, 0..) |*sym, i| {
const global = macho_file.getSymbol(global_index); const ref = self.getSymbolRef(@intCast(i), macho_file);
const file_ptr = global.getFile(macho_file) orelse continue; const file = ref.getFile(macho_file) orelse continue;
if (file_ptr.getIndex() != self.index) continue; if (file.getIndex() != self.index) continue;
if (global.isLocal()) continue; if (sym.isLocal()) continue;
assert(global.flags.import); assert(sym.flags.import);
global.flags.output_symtab = true; sym.flags.output_symtab = true;
try global.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file); sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
self.output_symtab_ctx.nimports += 1; self.output_symtab_ctx.nimports += 1;
self.output_symtab_ctx.strsize += @as(u32, @intCast(global.getName(macho_file).len + 1)); self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
} }
} }
@ -581,17 +588,20 @@ pub fn writeSymtab(self: Dylib, macho_file: *MachO, ctx: anytype) void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
for (self.symbols.items) |global_index| { var n_strx = self.output_symtab_ctx.stroff;
const global = macho_file.getSymbol(global_index); for (self.symbols.items, 0..) |sym, i| {
const file = global.getFile(macho_file) orelse continue; const ref = self.getSymbolRef(@intCast(i), macho_file);
const file = ref.getFile(macho_file) orelse continue;
if (file.getIndex() != self.index) continue; if (file.getIndex() != self.index) continue;
const idx = global.getOutputSymtabIndex(macho_file) orelse continue; const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
ctx.strtab.appendSliceAssumeCapacity(global.getName(macho_file));
ctx.strtab.appendAssumeCapacity(0);
const out_sym = &ctx.symtab.items[idx]; const out_sym = &ctx.symtab.items[idx];
out_sym.n_strx = n_strx; out_sym.n_strx = n_strx;
global.setOutputSym(macho_file, out_sym); sym.setOutputSym(macho_file, out_sym);
const name = sym.getName(macho_file);
@memcpy(ctx.strtab.items[n_strx..][0..name.len], name);
n_strx += @intCast(name.len);
ctx.strtab.items[n_strx] = 0;
n_strx += 1;
} }
} }
@ -605,7 +615,7 @@ fn addString(self: *Dylib, allocator: Allocator, name: []const u8) !u32 {
return off; return off;
} }
pub inline fn getString(self: Dylib, off: u32) [:0]const u8 { pub fn getString(self: Dylib, off: u32) [:0]const u8 {
assert(off < self.strtab.items.len); assert(off < self.strtab.items.len);
return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0); return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
} }
@ -614,6 +624,66 @@ pub fn asFile(self: *Dylib) File {
return .{ .dylib = self }; return .{ .dylib = self };
} }
fn addSymbol(self: *Dylib, allocator: Allocator) !Symbol.Index {
try self.symbols.ensureUnusedCapacity(allocator, 1);
return self.addSymbolAssumeCapacity();
}
fn addSymbolAssumeCapacity(self: *Dylib) Symbol.Index {
const index: Symbol.Index = @intCast(self.symbols.items.len);
const symbol = self.symbols.addOneAssumeCapacity();
symbol.* = .{ .file = self.index };
return index;
}
pub fn getSymbolRef(self: Dylib, index: Symbol.Index, macho_file: *MachO) MachO.Ref {
const global_index = self.globals.items[index];
if (macho_file.resolver.get(global_index)) |ref| return ref;
return .{ .index = index, .file = self.index };
}
pub fn addSymbolExtra(self: *Dylib, allocator: Allocator, extra: Symbol.Extra) !u32 {
const fields = @typeInfo(Symbol.Extra).Struct.fields;
try self.symbols_extra.ensureUnusedCapacity(allocator, fields.len);
return self.addSymbolExtraAssumeCapacity(extra);
}
fn addSymbolExtraAssumeCapacity(self: *Dylib, extra: Symbol.Extra) u32 {
const index = @as(u32, @intCast(self.symbols_extra.items.len));
const fields = @typeInfo(Symbol.Extra).Struct.fields;
inline for (fields) |field| {
self.symbols_extra.appendAssumeCapacity(switch (field.type) {
u32 => @field(extra, field.name),
else => @compileError("bad field type"),
});
}
return index;
}
pub fn getSymbolExtra(self: Dylib, index: u32) Symbol.Extra {
const fields = @typeInfo(Symbol.Extra).Struct.fields;
var i: usize = index;
var result: Symbol.Extra = undefined;
inline for (fields) |field| {
@field(result, field.name) = switch (field.type) {
u32 => self.symbols_extra.items[i],
else => @compileError("bad field type"),
};
i += 1;
}
return result;
}
pub fn setSymbolExtra(self: *Dylib, index: u32, extra: Symbol.Extra) void {
const fields = @typeInfo(Symbol.Extra).Struct.fields;
inline for (fields, 0..) |field, i| {
self.symbols_extra.items[index + i] = switch (field.type) {
u32 => @field(extra, field.name),
else => @compileError("bad field type"),
};
}
}
pub fn format( pub fn format(
self: *Dylib, self: *Dylib,
comptime unused_fmt_string: []const u8, comptime unused_fmt_string: []const u8,
@ -648,10 +718,16 @@ fn formatSymtab(
_ = unused_fmt_string; _ = unused_fmt_string;
_ = options; _ = options;
const dylib = ctx.dylib; const dylib = ctx.dylib;
const macho_file = ctx.macho_file;
try writer.writeAll(" globals\n"); try writer.writeAll(" globals\n");
for (dylib.symbols.items) |index| { for (dylib.symbols.items, 0..) |sym, i| {
const global = ctx.macho_file.getSymbol(index); const ref = dylib.getSymbolRef(@intCast(i), macho_file);
try writer.print(" {}\n", .{global.fmt(ctx.macho_file)}); if (ref.getFile(macho_file) == null) {
// TODO any better way of handling this?
try writer.print(" {s} : unclaimed\n", .{sym.getName(macho_file)});
} else {
try writer.print(" {}\n", .{ref.getSymbol(macho_file).?.fmt(macho_file)});
}
} }
} }

View File

@ -1,13 +1,28 @@
index: File.Index, index: File.Index,
sections: std.MultiArrayList(Section) = .{}, sections: std.MultiArrayList(Section) = .{},
atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, atoms: std.ArrayListUnmanaged(Atom) = .{},
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, atoms_indexes: std.ArrayListUnmanaged(Atom.Index) = .{},
atoms_extra: std.ArrayListUnmanaged(u32) = .{},
symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
strtab: std.ArrayListUnmanaged(u8) = .{},
symbols: std.ArrayListUnmanaged(Symbol) = .{},
symbols_extra: std.ArrayListUnmanaged(u32) = .{},
globals: std.ArrayListUnmanaged(MachO.SymbolResolver.Index) = .{},
objc_methnames: std.ArrayListUnmanaged(u8) = .{}, objc_methnames: std.ArrayListUnmanaged(u8) = .{},
objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64), objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64),
num_rebase_relocs: u32 = 0, force_undefined: std.ArrayListUnmanaged(Symbol.Index) = .{},
entry_index: ?Symbol.Index = null,
dyld_stub_binder_index: ?Symbol.Index = null,
dyld_private_index: ?Symbol.Index = null,
objc_msg_send_index: ?Symbol.Index = null,
mh_execute_header_index: ?Symbol.Index = null,
mh_dylib_header_index: ?Symbol.Index = null,
dso_handle_index: ?Symbol.Index = null,
boundary_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
output_symtab_ctx: MachO.SymtabCtx = .{}, output_symtab_ctx: MachO.SymtabCtx = .{},
pub fn deinit(self: *InternalObject, allocator: Allocator) void { pub fn deinit(self: *InternalObject, allocator: Allocator) void {
@ -16,39 +31,224 @@ pub fn deinit(self: *InternalObject, allocator: Allocator) void {
} }
self.sections.deinit(allocator); self.sections.deinit(allocator);
self.atoms.deinit(allocator); self.atoms.deinit(allocator);
self.atoms_indexes.deinit(allocator);
self.atoms_extra.deinit(allocator);
self.symtab.deinit(allocator);
self.strtab.deinit(allocator);
self.symbols.deinit(allocator); self.symbols.deinit(allocator);
self.symbols_extra.deinit(allocator);
self.globals.deinit(allocator);
self.objc_methnames.deinit(allocator); self.objc_methnames.deinit(allocator);
self.force_undefined.deinit(allocator);
self.boundary_symbols.deinit(allocator);
} }
pub fn addSymbol(self: *InternalObject, name: [:0]const u8, macho_file: *MachO) !Symbol.Index { pub fn init(self: *InternalObject, allocator: Allocator) !void {
// Atom at index 0 is reserved as null atom.
try self.atoms.append(allocator, .{});
try self.atoms_extra.append(allocator, 0);
// Null byte in strtab
try self.strtab.append(allocator, 0);
}
pub fn initSymbols(self: *InternalObject, macho_file: *MachO) !void {
const newSymbolAssumeCapacity = struct {
fn newSymbolAssumeCapacity(obj: *InternalObject, name: u32, args: struct {
type: u8 = macho.N_UNDF | macho.N_EXT,
desc: u16 = 0,
}) Symbol.Index {
const index = obj.addSymbolAssumeCapacity();
const symbol = &obj.symbols.items[index];
symbol.name = name;
symbol.extra = obj.addSymbolExtraAssumeCapacity(.{});
symbol.flags.dyn_ref = args.desc & macho.REFERENCED_DYNAMICALLY != 0;
symbol.visibility = if (args.type & macho.N_EXT != 0) blk: {
break :blk if (args.type & macho.N_PEXT != 0) .hidden else .global;
} else .local;
const nlist_idx: u32 = @intCast(obj.symtab.items.len);
const nlist = obj.symtab.addOneAssumeCapacity();
nlist.* = .{
.n_strx = name,
.n_type = args.type,
.n_sect = 0,
.n_desc = args.desc,
.n_value = 0,
};
symbol.nlist_idx = nlist_idx;
return index;
}
}.newSymbolAssumeCapacity;
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
try self.symbols.ensureUnusedCapacity(gpa, 1); var nsyms = macho_file.base.comp.force_undefined_symbols.keys().len;
const off = try macho_file.strings.insert(gpa, name); nsyms += 1; // dyld_stub_binder
const gop = try macho_file.getOrCreateGlobal(off); nsyms += 1; // _objc_msgSend
self.symbols.addOneAssumeCapacity().* = gop.index; if (!macho_file.base.isDynLib()) {
const sym = macho_file.getSymbol(gop.index); nsyms += 1; // entry
sym.file = self.index; nsyms += 1; // __mh_execute_header
sym.value = 0; } else {
sym.atom = 0; nsyms += 1; // __mh_dylib_header
sym.nlist_idx = 0; }
sym.flags = .{ .global = true }; nsyms += 1; // ___dso_handle
return gop.index; nsyms += 1; // dyld_private
try self.symbols.ensureTotalCapacityPrecise(gpa, nsyms);
try self.symbols_extra.ensureTotalCapacityPrecise(gpa, nsyms * @sizeOf(Symbol.Extra));
try self.symtab.ensureTotalCapacityPrecise(gpa, nsyms);
try self.globals.ensureTotalCapacityPrecise(gpa, nsyms);
self.globals.resize(gpa, nsyms) catch unreachable;
@memset(self.globals.items, 0);
try self.force_undefined.ensureTotalCapacityPrecise(gpa, macho_file.base.comp.force_undefined_symbols.keys().len);
for (macho_file.base.comp.force_undefined_symbols.keys()) |name| {
self.force_undefined.addOneAssumeCapacity().* = newSymbolAssumeCapacity(self, try self.addString(gpa, name), .{});
}
self.dyld_stub_binder_index = newSymbolAssumeCapacity(self, try self.addString(gpa, "dyld_stub_binder"), .{});
self.objc_msg_send_index = newSymbolAssumeCapacity(self, try self.addString(gpa, "_objc_msgSend"), .{});
if (!macho_file.base.isDynLib()) {
self.entry_index = newSymbolAssumeCapacity(self, try self.addString(gpa, macho_file.entry_name orelse "_main"), .{});
self.mh_execute_header_index = newSymbolAssumeCapacity(self, try self.addString(gpa, "__mh_execute_header"), .{
.type = macho.N_SECT | macho.N_EXT,
.desc = macho.REFERENCED_DYNAMICALLY,
});
} else {
self.mh_dylib_header_index = newSymbolAssumeCapacity(self, try self.addString(gpa, "__mh_dylib_header"), .{
.type = macho.N_SECT | macho.N_EXT,
});
}
self.dso_handle_index = newSymbolAssumeCapacity(self, try self.addString(gpa, "___dso_handle"), .{
.type = macho.N_SECT | macho.N_EXT,
});
self.dyld_private_index = newSymbolAssumeCapacity(self, try self.addString(gpa, "dyld_private"), .{
.type = macho.N_SECT,
});
}
pub fn resolveSymbols(self: *InternalObject, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
for (self.symtab.items, self.globals.items, 0..) |nlist, *global, i| {
const gop = try macho_file.resolver.getOrPut(gpa, .{
.index = @intCast(i),
.file = self.index,
}, macho_file);
if (!gop.found_existing) {
gop.ref.* = .{ .index = 0, .file = 0 };
}
global.* = gop.index;
if (nlist.undf()) continue;
if (gop.ref.getFile(macho_file) == null) {
gop.ref.* = .{ .index = @intCast(i), .file = self.index };
continue;
}
if (self.asFile().getSymbolRank(.{
.archive = false,
.weak = false,
.tentative = false,
}) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {
gop.ref.* = .{ .index = @intCast(i), .file = self.index };
}
}
}
pub fn resolveBoundarySymbols(self: *InternalObject, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
var boundary_symbols = std.StringArrayHashMap(MachO.Ref).init(gpa);
defer boundary_symbols.deinit();
for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object;
for (object.symbols.items, 0..) |sym, i| {
const nlist = object.symtab.items(.nlist)[i];
if (!nlist.undf() or !nlist.ext()) continue;
const ref = object.getSymbolRef(@intCast(i), macho_file);
if (ref.getFile(macho_file) != null) continue;
const name = sym.getName(macho_file);
if (mem.startsWith(u8, name, "segment$start$") or
mem.startsWith(u8, name, "segment$stop$") or
mem.startsWith(u8, name, "section$start$") or
mem.startsWith(u8, name, "section$stop$"))
{
const gop = try boundary_symbols.getOrPut(name);
if (!gop.found_existing) {
gop.value_ptr.* = .{ .index = @intCast(i), .file = index };
}
}
}
}
const nsyms = boundary_symbols.values().len;
try self.boundary_symbols.ensureTotalCapacityPrecise(gpa, nsyms);
try self.symbols.ensureUnusedCapacity(gpa, nsyms);
try self.symtab.ensureUnusedCapacity(gpa, nsyms);
try self.symbols_extra.ensureUnusedCapacity(gpa, nsyms * @sizeOf(Symbol.Extra));
try self.globals.ensureUnusedCapacity(gpa, nsyms);
for (boundary_symbols.keys(), boundary_symbols.values()) |name, ref| {
const name_off = try self.addString(gpa, name);
const sym_index = self.addSymbolAssumeCapacity();
self.boundary_symbols.appendAssumeCapacity(sym_index);
const sym = &self.symbols.items[sym_index];
sym.name = name_off;
sym.visibility = .local;
const nlist_idx: u32 = @intCast(self.symtab.items.len);
const nlist = self.symtab.addOneAssumeCapacity();
nlist.* = .{
.n_strx = name_off,
.n_type = macho.N_SECT,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
};
sym.nlist_idx = nlist_idx;
sym.extra = self.addSymbolExtraAssumeCapacity(.{});
const idx = ref.getFile(macho_file).?.object.globals.items[ref.index];
self.globals.addOneAssumeCapacity().* = idx;
macho_file.resolver.values.items[idx - 1] = .{ .index = sym_index, .file = self.index };
}
}
pub fn markLive(self: *InternalObject, macho_file: *MachO) void {
const tracy = trace(@src());
defer tracy.end();
for (0..self.symbols.items.len) |i| {
const nlist = self.symtab.items[i];
if (!nlist.ext()) continue;
const ref = self.getSymbolRef(@intCast(i), macho_file);
const file = ref.getFile(macho_file) orelse continue;
if (file == .object and !file.object.alive) {
file.object.alive = true;
file.object.markLive(macho_file);
}
}
} }
/// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs. /// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs.
pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !Atom.Index { pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !Symbol.Index {
const methname_atom_index = try self.addObjcMethnameSection(sym_name, macho_file); const methname_sym_index = try self.addObjcMethnameSection(sym_name, macho_file);
return try self.addObjcSelrefsSection(methname_atom_index, macho_file); return try self.addObjcSelrefsSection(methname_sym_index, macho_file);
} }
fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Atom.Index { fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Symbol.Index {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const atom_index = try macho_file.addAtom(); const atom_index = try self.addAtom(gpa);
try self.atoms.append(gpa, atom_index); try self.atoms_indexes.append(gpa, atom_index);
const atom = self.getAtom(atom_index).?;
const atom = macho_file.getAtom(atom_index).?;
atom.atom_index = atom_index;
atom.file = self.index;
atom.size = methname.len + 1; atom.size = methname.len + 1;
atom.alignment = .@"1"; atom.alignment = .@"1";
@ -64,17 +264,32 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil
try self.objc_methnames.ensureUnusedCapacity(gpa, methname.len + 1); try self.objc_methnames.ensureUnusedCapacity(gpa, methname.len + 1);
self.objc_methnames.writer(gpa).print("{s}\x00", .{methname}) catch unreachable; self.objc_methnames.writer(gpa).print("{s}\x00", .{methname}) catch unreachable;
const name_str = try self.addString(gpa, "ltmp");
const sym_index = try self.addSymbol(gpa);
const sym = &self.symbols.items[sym_index];
sym.name = name_str;
sym.atom_ref = .{ .index = atom_index, .file = self.index };
sym.extra = try self.addSymbolExtra(gpa, .{});
const nlist_idx: u32 = @intCast(self.symtab.items.len);
const nlist = try self.symtab.addOne(gpa);
nlist.* = .{
.n_strx = name_str,
.n_type = macho.N_SECT,
.n_sect = @intCast(n_sect + 1),
.n_desc = 0,
.n_value = 0,
};
sym.nlist_idx = nlist_idx;
try self.globals.append(gpa, 0);
return atom_index; return atom_index;
} }
fn addObjcSelrefsSection(self: *InternalObject, methname_atom_index: Atom.Index, macho_file: *MachO) !Atom.Index { fn addObjcSelrefsSection(self: *InternalObject, methname_sym_index: Symbol.Index, macho_file: *MachO) !Symbol.Index {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const atom_index = try macho_file.addAtom(); const atom_index = try self.addAtom(gpa);
try self.atoms.append(gpa, atom_index); try self.atoms_indexes.append(gpa, atom_index);
const atom = self.getAtom(atom_index).?;
const atom = macho_file.getAtom(atom_index).?;
atom.atom_index = atom_index;
atom.file = self.index;
atom.size = @sizeOf(u64); atom.size = @sizeOf(u64);
atom.alignment = .@"8"; atom.alignment = .@"8";
@ -90,9 +305,9 @@ fn addObjcSelrefsSection(self: *InternalObject, methname_atom_index: Atom.Index,
const relocs = &self.sections.items(.relocs)[n_sect]; const relocs = &self.sections.items(.relocs)[n_sect];
try relocs.ensureUnusedCapacity(gpa, 1); try relocs.ensureUnusedCapacity(gpa, 1);
relocs.appendAssumeCapacity(.{ relocs.appendAssumeCapacity(.{
.tag = .local, .tag = .@"extern",
.offset = 0, .offset = 0,
.target = methname_atom_index, .target = methname_sym_index,
.addend = 0, .addend = 0,
.type = .unsigned, .type = .unsigned,
.meta = .{ .meta = .{
@ -102,140 +317,285 @@ fn addObjcSelrefsSection(self: *InternalObject, methname_atom_index: Atom.Index,
.has_subtractor = false, .has_subtractor = false,
}, },
}); });
try atom.addExtra(.{ .rel_index = 0, .rel_count = 1 }, macho_file); atom.addExtra(.{ .rel_index = 0, .rel_count = 1 }, macho_file);
atom.flags.relocs = true;
self.num_rebase_relocs += 1;
return atom_index; const sym_index = try self.addSymbol(gpa);
const sym = &self.symbols.items[sym_index];
sym.atom_ref = .{ .index = atom_index, .file = self.index };
sym.extra = try self.addSymbolExtra(gpa, .{});
const nlist_idx: u32 = @intCast(self.symtab.items.len);
const nlist = try self.symtab.addOne(gpa);
nlist.* = .{
.n_strx = 0,
.n_type = macho.N_SECT,
.n_sect = @intCast(n_sect + 1),
.n_desc = 0,
.n_value = 0,
};
sym.nlist_idx = nlist_idx;
try self.globals.append(gpa, 0);
atom.addExtra(.{ .literal_symbol_index = sym_index }, macho_file);
return sym_index;
} }
pub fn resolveLiterals(self: InternalObject, lp: *MachO.LiteralPool, macho_file: *MachO) !void { pub fn resolveObjcMsgSendSymbols(self: *InternalObject, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
var objc_msgsend_syms = std.StringArrayHashMap(MachO.Ref).init(gpa);
defer objc_msgsend_syms.deinit();
for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object;
for (object.symbols.items, 0..) |sym, i| {
const nlist = object.symtab.items(.nlist)[i];
if (!nlist.ext()) continue;
if (!nlist.undf()) continue;
const ref = object.getSymbolRef(@intCast(i), macho_file);
if (ref.getFile(macho_file) != null) continue;
const name = sym.getName(macho_file);
if (mem.startsWith(u8, name, "_objc_msgSend$")) {
const gop = try objc_msgsend_syms.getOrPut(name);
if (!gop.found_existing) {
gop.value_ptr.* = .{ .index = @intCast(i), .file = index };
}
}
}
}
for (objc_msgsend_syms.keys(), objc_msgsend_syms.values()) |sym_name, ref| {
const name = MachO.eatPrefix(sym_name, "_objc_msgSend$").?;
const selrefs_index = try self.addObjcMsgsendSections(name, macho_file);
const name_off = try self.addString(gpa, sym_name);
const sym_index = try self.addSymbol(gpa);
const sym = &self.symbols.items[sym_index];
sym.name = name_off;
sym.visibility = .hidden;
const nlist_idx: u32 = @intCast(self.symtab.items.len);
const nlist = try self.symtab.addOne(gpa);
nlist.* = .{
.n_strx = name_off,
.n_type = macho.N_SECT | macho.N_EXT | macho.N_PEXT,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
};
sym.nlist_idx = nlist_idx;
sym.extra = try self.addSymbolExtra(gpa, .{ .objc_selrefs = selrefs_index });
sym.flags.objc_stubs = true;
const idx = ref.getFile(macho_file).?.object.globals.items[ref.index];
try self.globals.append(gpa, idx);
macho_file.resolver.values.items[idx - 1] = .{ .index = sym_index, .file = self.index };
}
}
pub fn resolveLiterals(self: *InternalObject, lp: *MachO.LiteralPool, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
var buffer = std.ArrayList(u8).init(gpa); var buffer = std.ArrayList(u8).init(gpa);
defer buffer.deinit(); defer buffer.deinit();
const slice = self.sections.slice(); const slice = self.sections.slice();
for (slice.items(.header), self.atoms.items, 0..) |header, atom_index, n_sect| { for (slice.items(.header), self.getAtoms()) |header, atom_index| {
if (Object.isCstringLiteral(header) or Object.isFixedSizeLiteral(header)) { if (!Object.isPtrLiteral(header)) continue;
const data = try self.getSectionData(@intCast(n_sect)); const atom = self.getAtom(atom_index).?;
const atom = macho_file.getAtom(atom_index).?;
const res = try lp.insert(gpa, header.type(), data);
if (!res.found_existing) {
res.atom.* = atom_index;
}
atom.flags.literal_pool = true;
try atom.addExtra(.{ .literal_index = res.index }, macho_file);
} else if (Object.isPtrLiteral(header)) {
const atom = macho_file.getAtom(atom_index).?;
const relocs = atom.getRelocs(macho_file); const relocs = atom.getRelocs(macho_file);
assert(relocs.len == 1); assert(relocs.len == 1);
const rel = relocs[0]; const rel = relocs[0];
assert(rel.tag == .local); assert(rel.tag == .@"extern");
const target = macho_file.getAtom(rel.target).?; const target = rel.getTargetSymbol(atom.*, macho_file).getAtom(macho_file).?;
const addend = std.math.cast(u32, rel.addend) orelse return error.Overflow;
const target_size = std.math.cast(usize, target.size) orelse return error.Overflow; const target_size = std.math.cast(usize, target.size) orelse return error.Overflow;
try buffer.ensureUnusedCapacity(target_size); try buffer.ensureUnusedCapacity(target_size);
buffer.resize(target_size) catch unreachable; buffer.resize(target_size) catch unreachable;
try target.getData(macho_file, buffer.items); @memcpy(buffer.items, try self.getSectionData(target.n_sect));
const res = try lp.insert(gpa, header.type(), buffer.items[addend..]); const res = try lp.insert(gpa, header.type(), buffer.items);
buffer.clearRetainingCapacity(); buffer.clearRetainingCapacity();
if (!res.found_existing) { if (!res.found_existing) {
res.atom.* = atom_index; res.ref.* = .{ .index = atom.getExtra(macho_file).literal_symbol_index, .file = self.index };
} } else {
atom.flags.literal_pool = true; const lp_sym = lp.getSymbol(res.index, macho_file);
try atom.addExtra(.{ .literal_index = res.index }, macho_file); const lp_atom = lp_sym.getAtom(macho_file).?;
lp_atom.alignment = lp_atom.alignment.max(atom.alignment);
atom.flags.alive = false;
} }
atom.addExtra(.{ .literal_pool_index = res.index }, macho_file);
} }
} }
pub fn dedupLiterals(self: InternalObject, lp: MachO.LiteralPool, macho_file: *MachO) void { pub fn dedupLiterals(self: *InternalObject, lp: MachO.LiteralPool, macho_file: *MachO) void {
for (self.atoms.items) |atom_index| { const tracy = trace(@src());
const atom = macho_file.getAtom(atom_index) orelse continue; defer tracy.end();
for (self.getAtoms()) |atom_index| {
const atom = self.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue; if (!atom.flags.alive) continue;
if (!atom.flags.relocs) continue;
const relocs = blk: { const relocs = blk: {
const extra = atom.getExtra(macho_file).?; const extra = atom.getExtra(macho_file);
const relocs = self.sections.items(.relocs)[atom.n_sect].items; const relocs = self.sections.items(.relocs)[atom.n_sect].items;
break :blk relocs[extra.rel_index..][0..extra.rel_count]; break :blk relocs[extra.rel_index..][0..extra.rel_count];
}; };
for (relocs) |*rel| switch (rel.tag) { for (relocs) |*rel| {
.local => { if (rel.tag != .@"extern") continue;
const target = macho_file.getAtom(rel.target).?; const target_sym_ref = rel.getTargetSymbolRef(atom.*, macho_file);
if (target.getLiteralPoolIndex(macho_file)) |lp_index| { const file = target_sym_ref.getFile(macho_file) orelse continue;
const lp_atom = lp.getAtom(lp_index, macho_file); if (file.getIndex() != self.index) continue;
if (target.atom_index != lp_atom.atom_index) { const target_sym = target_sym_ref.getSymbol(macho_file).?;
lp_atom.alignment = lp_atom.alignment.max(target.alignment); const target_atom = target_sym.getAtom(macho_file) orelse continue;
target.flags.alive = false; if (!Object.isPtrLiteral(target_atom.getInputSection(macho_file))) continue;
rel.target = lp_atom.atom_index; const lp_index = target_atom.getExtra(macho_file).literal_pool_index;
const lp_sym = lp.getSymbol(lp_index, macho_file);
const lp_atom_ref = lp_sym.atom_ref;
if (target_atom.atom_index != lp_atom_ref.index or target_atom.file != lp_atom_ref.file) {
target_sym.atom_ref = lp_atom_ref;
} }
} }
},
.@"extern" => {
const target_sym = rel.getTargetSymbol(macho_file);
if (target_sym.getAtom(macho_file)) |target_atom| {
if (target_atom.getLiteralPoolIndex(macho_file)) |lp_index| {
const lp_atom = lp.getAtom(lp_index, macho_file);
if (target_atom.atom_index != lp_atom.atom_index) {
lp_atom.alignment = lp_atom.alignment.max(target_atom.alignment);
target_atom.flags.alive = false;
target_sym.atom = lp_atom.atom_index;
}
}
}
},
};
} }
for (self.symbols.items) |sym_index| { for (self.symbols.items) |*sym| {
const sym = macho_file.getSymbol(sym_index);
if (!sym.flags.objc_stubs) continue; if (!sym.flags.objc_stubs) continue;
var extra = sym.getExtra(macho_file).?; const extra = sym.getExtra(macho_file);
const atom = macho_file.getAtom(extra.objc_selrefs).?; const file = sym.getFile(macho_file).?;
if (atom.getLiteralPoolIndex(macho_file)) |lp_index| { if (file.getIndex() != self.index) continue;
const lp_atom = lp.getAtom(lp_index, macho_file); const tsym = switch (file) {
if (atom.atom_index != lp_atom.atom_index) { .dylib => unreachable,
lp_atom.alignment = lp_atom.alignment.max(atom.alignment); inline else => |x| &x.symbols.items[extra.objc_selrefs],
atom.flags.alive = false; };
extra.objc_selrefs = lp_atom.atom_index; const atom = tsym.getAtom(macho_file) orelse continue;
sym.setExtra(extra, macho_file); if (!Object.isPtrLiteral(atom.getInputSection(macho_file))) continue;
const lp_index = atom.getExtra(macho_file).literal_pool_index;
const lp_sym = lp.getSymbol(lp_index, macho_file);
const lp_atom_ref = lp_sym.atom_ref;
if (atom.atom_index != lp_atom_ref.index or atom.file != lp_atom_ref.file) {
tsym.atom_ref = lp_atom_ref;
}
}
}
pub fn scanRelocs(self: *InternalObject, macho_file: *MachO) void {
const tracy = trace(@src());
defer tracy.end();
if (self.getEntryRef(macho_file)) |ref| {
if (ref.getFile(macho_file) != null) {
const sym = ref.getSymbol(macho_file).?;
if (sym.flags.import) sym.flags.stubs = true;
}
}
if (self.getDyldStubBinderRef(macho_file)) |ref| {
if (ref.getFile(macho_file) != null) {
const sym = ref.getSymbol(macho_file).?;
sym.flags.needs_got = true;
}
}
if (self.getObjcMsgSendRef(macho_file)) |ref| {
if (ref.getFile(macho_file) != null) {
const sym = ref.getSymbol(macho_file).?;
// TODO is it always needed, or only if we are synthesising fast stubs
sym.flags.needs_got = true;
}
}
}
pub fn allocateSyntheticSymbols(self: *InternalObject, macho_file: *MachO) void {
const text_seg = macho_file.getTextSegment();
if (self.mh_execute_header_index) |index| {
const ref = self.getSymbolRef(index, macho_file);
if (ref.getFile(macho_file)) |file| {
if (file.getIndex() == self.index) {
const sym = &self.symbols.items[index];
sym.value = text_seg.vmaddr;
}
}
}
if (macho_file.data_sect_index) |idx| {
const sect = macho_file.sections.items(.header)[idx];
for (&[_]?Symbol.Index{
self.dso_handle_index,
self.mh_dylib_header_index,
self.dyld_private_index,
}) |maybe_index| {
if (maybe_index) |index| {
const ref = self.getSymbolRef(index, macho_file);
if (ref.getFile(macho_file)) |file| {
if (file.getIndex() == self.index) {
const sym = &self.symbols.items[index];
sym.value = sect.addr;
sym.out_n_sect = idx;
}
}
} }
} }
} }
} }
pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void { pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) void {
for (self.symbols.items) |sym_index| { for (self.symbols.items, 0..) |*sym, i| {
const sym = macho_file.getSymbol(sym_index); const ref = self.getSymbolRef(@intCast(i), macho_file);
if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue; const file = ref.getFile(macho_file) orelse continue;
if (file.getIndex() != self.index) continue;
if (sym.getName(macho_file).len == 0) continue;
sym.flags.output_symtab = true; sym.flags.output_symtab = true;
if (sym.isLocal()) { if (sym.isLocal()) {
try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file); sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
self.output_symtab_ctx.nlocals += 1; self.output_symtab_ctx.nlocals += 1;
} else if (sym.flags.@"export") { } else if (sym.flags.@"export") {
try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file); sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
self.output_symtab_ctx.nexports += 1; self.output_symtab_ctx.nexports += 1;
} else { } else {
assert(sym.flags.import); assert(sym.flags.import);
try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file); sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
self.output_symtab_ctx.nimports += 1; self.output_symtab_ctx.nimports += 1;
} }
self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1)); self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
} }
} }
pub fn writeAtoms(self: *InternalObject, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
for (self.getAtoms()) |atom_index| {
const atom = self.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
const sect = atom.getInputSection(macho_file);
if (sect.isZerofill()) continue;
const off = std.math.cast(usize, atom.value) orelse return error.Overflow;
const size = std.math.cast(usize, atom.size) orelse return error.Overflow;
const buffer = macho_file.sections.items(.out)[atom.out_n_sect].items[off..][0..size];
@memcpy(buffer, try self.getSectionData(atom.n_sect));
try atom.resolveRelocs(macho_file, buffer);
}
}
pub fn writeSymtab(self: InternalObject, macho_file: *MachO, ctx: anytype) void { pub fn writeSymtab(self: InternalObject, macho_file: *MachO, ctx: anytype) void {
for (self.symbols.items) |sym_index| { var n_strx = self.output_symtab_ctx.stroff;
const sym = macho_file.getSymbol(sym_index); for (self.symbols.items, 0..) |sym, i| {
if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue; const ref = self.getSymbolRef(@intCast(i), macho_file);
const file = ref.getFile(macho_file) orelse continue;
if (file.getIndex() != self.index) continue;
const idx = sym.getOutputSymtabIndex(macho_file) orelse continue; const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
ctx.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
ctx.strtab.appendAssumeCapacity(0);
const out_sym = &ctx.symtab.items[idx]; const out_sym = &ctx.symtab.items[idx];
out_sym.n_strx = n_strx; out_sym.n_strx = n_strx;
sym.setOutputSym(macho_file, out_sym); sym.setOutputSym(macho_file, out_sym);
const name = sym.getName(macho_file);
@memcpy(ctx.strtab.items[n_strx..][0..name.len], name);
n_strx += @intCast(name.len);
ctx.strtab.items[n_strx] = 0;
n_strx += 1;
} }
} }
@ -264,32 +624,173 @@ fn getSectionData(self: *const InternalObject, index: u32) error{Overflow}![]con
@panic("ref to non-existent section"); @panic("ref to non-existent section");
} }
pub fn getAtomData(self: *const InternalObject, atom: Atom, buffer: []u8) error{Overflow}!void { pub fn addString(self: *InternalObject, allocator: Allocator, name: []const u8) !u32 {
assert(buffer.len == atom.size); const off: u32 = @intCast(self.strtab.items.len);
const data = try self.getSectionData(atom.n_sect); try self.strtab.ensureUnusedCapacity(allocator, name.len + 1);
const off = std.math.cast(usize, atom.off) orelse return error.Overflow; self.strtab.appendSliceAssumeCapacity(name);
const size = std.math.cast(usize, atom.size) orelse return error.Overflow; self.strtab.appendAssumeCapacity(0);
@memcpy(buffer, data[off..][0..size]); return off;
}
pub fn getAtomRelocs(self: *const InternalObject, atom: Atom, macho_file: *MachO) []const Relocation {
if (!atom.flags.relocs) return &[0]Relocation{};
const extra = atom.getExtra(macho_file).?;
const relocs = self.sections.items(.relocs)[atom.n_sect];
return relocs.items[extra.rel_index..][0..extra.rel_count];
} }
pub fn getString(self: InternalObject, off: u32) [:0]const u8 { pub fn getString(self: InternalObject, off: u32) [:0]const u8 {
_ = self; assert(off < self.strtab.items.len);
_ = off; return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);
// We don't have any local strings for synthetic atoms.
return "";
} }
pub fn asFile(self: *InternalObject) File { pub fn asFile(self: *InternalObject) File {
return .{ .internal = self }; return .{ .internal = self };
} }
pub fn getAtomRelocs(self: *const InternalObject, atom: Atom, macho_file: *MachO) []const Relocation {
const extra = atom.getExtra(macho_file);
const relocs = self.sections.items(.relocs)[atom.n_sect];
return relocs.items[extra.rel_index..][0..extra.rel_count];
}
fn addAtom(self: *InternalObject, allocator: Allocator) !Atom.Index {
const atom_index: Atom.Index = @intCast(self.atoms.items.len);
const atom = try self.atoms.addOne(allocator);
atom.* = .{
.file = self.index,
.atom_index = atom_index,
.extra = try self.addAtomExtra(allocator, .{}),
};
return atom_index;
}
pub fn getAtom(self: *InternalObject, atom_index: Atom.Index) ?*Atom {
if (atom_index == 0) return null;
assert(atom_index < self.atoms.items.len);
return &self.atoms.items[atom_index];
}
pub fn getAtoms(self: InternalObject) []const Atom.Index {
return self.atoms_indexes.items;
}
fn addAtomExtra(self: *InternalObject, allocator: Allocator, extra: Atom.Extra) !u32 {
const fields = @typeInfo(Atom.Extra).Struct.fields;
try self.atoms_extra.ensureUnusedCapacity(allocator, fields.len);
return self.addAtomExtraAssumeCapacity(extra);
}
fn addAtomExtraAssumeCapacity(self: *InternalObject, extra: Atom.Extra) u32 {
const index = @as(u32, @intCast(self.atoms_extra.items.len));
const fields = @typeInfo(Atom.Extra).Struct.fields;
inline for (fields) |field| {
self.atoms_extra.appendAssumeCapacity(switch (field.type) {
u32 => @field(extra, field.name),
else => @compileError("bad field type"),
});
}
return index;
}
pub fn getAtomExtra(self: InternalObject, index: u32) Atom.Extra {
const fields = @typeInfo(Atom.Extra).Struct.fields;
var i: usize = index;
var result: Atom.Extra = undefined;
inline for (fields) |field| {
@field(result, field.name) = switch (field.type) {
u32 => self.atoms_extra.items[i],
else => @compileError("bad field type"),
};
i += 1;
}
return result;
}
pub fn setAtomExtra(self: *InternalObject, index: u32, extra: Atom.Extra) void {
assert(index > 0);
const fields = @typeInfo(Atom.Extra).Struct.fields;
inline for (fields, 0..) |field, i| {
self.atoms_extra.items[index + i] = switch (field.type) {
u32 => @field(extra, field.name),
else => @compileError("bad field type"),
};
}
}
pub fn getEntryRef(self: InternalObject, macho_file: *MachO) ?MachO.Ref {
const index = self.entry_index orelse return null;
return self.getSymbolRef(index, macho_file);
}
pub fn getDyldStubBinderRef(self: InternalObject, macho_file: *MachO) ?MachO.Ref {
const index = self.dyld_stub_binder_index orelse return null;
return self.getSymbolRef(index, macho_file);
}
pub fn getDyldPrivateRef(self: InternalObject, macho_file: *MachO) ?MachO.Ref {
const index = self.dyld_private_index orelse return null;
return self.getSymbolRef(index, macho_file);
}
pub fn getObjcMsgSendRef(self: InternalObject, macho_file: *MachO) ?MachO.Ref {
const index = self.objc_msg_send_index orelse return null;
return self.getSymbolRef(index, macho_file);
}
pub fn addSymbol(self: *InternalObject, allocator: Allocator) !Symbol.Index {
try self.symbols.ensureUnusedCapacity(allocator, 1);
return self.addSymbolAssumeCapacity();
}
pub fn addSymbolAssumeCapacity(self: *InternalObject) Symbol.Index {
const index: Symbol.Index = @intCast(self.symbols.items.len);
const symbol = self.symbols.addOneAssumeCapacity();
symbol.* = .{ .file = self.index };
return index;
}
pub fn getSymbolRef(self: InternalObject, index: Symbol.Index, macho_file: *MachO) MachO.Ref {
const global_index = self.globals.items[index];
if (macho_file.resolver.get(global_index)) |ref| return ref;
return .{ .index = index, .file = self.index };
}
pub fn addSymbolExtra(self: *InternalObject, allocator: Allocator, extra: Symbol.Extra) !u32 {
const fields = @typeInfo(Symbol.Extra).Struct.fields;
try self.symbols_extra.ensureUnusedCapacity(allocator, fields.len);
return self.addSymbolExtraAssumeCapacity(extra);
}
fn addSymbolExtraAssumeCapacity(self: *InternalObject, extra: Symbol.Extra) u32 {
const index = @as(u32, @intCast(self.symbols_extra.items.len));
const fields = @typeInfo(Symbol.Extra).Struct.fields;
inline for (fields) |field| {
self.symbols_extra.appendAssumeCapacity(switch (field.type) {
u32 => @field(extra, field.name),
else => @compileError("bad field type"),
});
}
return index;
}
pub fn getSymbolExtra(self: InternalObject, index: u32) Symbol.Extra {
const fields = @typeInfo(Symbol.Extra).Struct.fields;
var i: usize = index;
var result: Symbol.Extra = undefined;
inline for (fields) |field| {
@field(result, field.name) = switch (field.type) {
u32 => self.symbols_extra.items[i],
else => @compileError("bad field type"),
};
i += 1;
}
return result;
}
pub fn setSymbolExtra(self: *InternalObject, index: u32, extra: Symbol.Extra) void {
const fields = @typeInfo(Symbol.Extra).Struct.fields;
inline for (fields, 0..) |field, i| {
self.symbols_extra.items[index + i] = switch (field.type) {
u32 => @field(extra, field.name),
else => @compileError("bad field type"),
};
}
}
const FormatContext = struct { const FormatContext = struct {
self: *InternalObject, self: *InternalObject,
macho_file: *MachO, macho_file: *MachO,
@ -311,8 +812,8 @@ fn formatAtoms(
_ = unused_fmt_string; _ = unused_fmt_string;
_ = options; _ = options;
try writer.writeAll(" atoms\n"); try writer.writeAll(" atoms\n");
for (ctx.self.atoms.items) |atom_index| { for (ctx.self.getAtoms()) |atom_index| {
const atom = ctx.macho_file.getAtom(atom_index).?; const atom = ctx.self.getAtom(atom_index) orelse continue;
try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)}); try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)});
} }
} }
@ -332,10 +833,17 @@ fn formatSymtab(
) !void { ) !void {
_ = unused_fmt_string; _ = unused_fmt_string;
_ = options; _ = options;
const macho_file = ctx.macho_file;
const self = ctx.self;
try writer.writeAll(" symbols\n"); try writer.writeAll(" symbols\n");
for (ctx.self.symbols.items) |index| { for (self.symbols.items, 0..) |sym, i| {
const global = ctx.macho_file.getSymbol(index); const ref = self.getSymbolRef(@intCast(i), macho_file);
try writer.print(" {}\n", .{global.fmt(ctx.macho_file)}); if (ref.getFile(macho_file) == null) {
// TODO any better way of handling this?
try writer.print(" {s} : unclaimed\n", .{sym.getName(macho_file)});
} else {
try writer.print(" {}\n", .{ref.getSymbol(macho_file).?.fmt(macho_file)});
}
} }
} }
@ -354,6 +862,7 @@ const assert = std.debug.assert;
const macho = std.macho; const macho = std.macho;
const mem = std.mem; const mem = std.mem;
const std = @import("std"); const std = @import("std");
const trace = @import("../../tracy.zig").trace;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Atom = @import("Atom.zig"); const Atom = @import("Atom.zig");

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
tag: enum { @"extern", local }, tag: Tag,
offset: u32, offset: u32,
target: u32, target: u32,
addend: i64, addend: i64,
@ -10,34 +10,44 @@ meta: packed struct {
symbolnum: u24, symbolnum: u24,
}, },
pub fn getTargetSymbol(rel: Relocation, macho_file: *MachO) *Symbol { pub fn getTargetSymbolRef(rel: Relocation, atom: Atom, macho_file: *MachO) MachO.Ref {
assert(rel.tag == .@"extern"); assert(rel.tag == .@"extern");
return macho_file.getSymbol(rel.target); return atom.getFile(macho_file).getSymbolRef(rel.target, macho_file);
} }
pub fn getTargetAtom(rel: Relocation, macho_file: *MachO) *Atom { pub fn getTargetSymbol(rel: Relocation, atom: Atom, macho_file: *MachO) *Symbol {
assert(rel.tag == .@"extern");
const ref = atom.getFile(macho_file).getSymbolRef(rel.target, macho_file);
return ref.getSymbol(macho_file).?;
}
pub fn getTargetAtom(rel: Relocation, atom: Atom, macho_file: *MachO) *Atom {
assert(rel.tag == .local); assert(rel.tag == .local);
return macho_file.getAtom(rel.target).?; return atom.getFile(macho_file).getAtom(rel.target).?;
} }
pub fn getTargetAddress(rel: Relocation, macho_file: *MachO) u64 { pub fn getTargetAddress(rel: Relocation, atom: Atom, macho_file: *MachO) u64 {
return switch (rel.tag) { return switch (rel.tag) {
.local => rel.getTargetAtom(macho_file).getAddress(macho_file), .local => rel.getTargetAtom(atom, macho_file).getAddress(macho_file),
.@"extern" => rel.getTargetSymbol(macho_file).getAddress(.{}, macho_file), .@"extern" => rel.getTargetSymbol(atom, macho_file).getAddress(.{}, macho_file),
}; };
} }
pub fn getGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 { pub fn getGotTargetAddress(rel: Relocation, atom: Atom, macho_file: *MachO) u64 {
return switch (rel.tag) { return switch (rel.tag) {
.local => 0, .local => 0,
.@"extern" => rel.getTargetSymbol(macho_file).getGotAddress(macho_file), .@"extern" => rel.getTargetSymbol(atom, macho_file).getGotAddress(macho_file),
}; };
} }
pub fn getZigGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 { pub fn getZigGotTargetAddress(rel: Relocation, macho_file: *MachO) u64 {
const zo = macho_file.getZigObject() orelse return 0;
return switch (rel.tag) { return switch (rel.tag) {
.local => 0, .local => 0,
.@"extern" => rel.getTargetSymbol(macho_file).getZigGotAddress(macho_file), .@"extern" => {
const ref = zo.getSymbolRef(rel.target, macho_file);
return ref.getSymbol(macho_file).?.getZigGotAddress(macho_file);
},
}; };
} }
@ -155,6 +165,8 @@ pub const Type = enum {
unsigned, unsigned,
}; };
const Tag = enum { local, @"extern" };
const assert = std.debug.assert; const assert = std.debug.assert;
const macho = std.macho; const macho = std.macho;
const math = std.math; const math = std.math;

View File

@ -9,17 +9,16 @@ name: u32 = 0,
/// File where this symbol is defined. /// File where this symbol is defined.
file: File.Index = 0, file: File.Index = 0,
/// Atom containing this symbol if any. /// Reference to Atom containing this symbol if any.
/// Index of 0 means there is no associated atom with this symbol.
/// Use `getAtom` to get the pointer to the atom. /// Use `getAtom` to get the pointer to the atom.
atom: Atom.Index = 0, atom_ref: MachO.Ref = .{ .index = 0, .file = 0 },
/// Assigned output section index for this symbol. /// Assigned output section index for this symbol.
out_n_sect: u8 = 0, out_n_sect: u8 = 0,
/// Index of the source nlist this symbol references. /// Index of the source nlist this symbol references.
/// Use `getNlist` to pull the nlist from the relevant file. /// Use `getNlist` to pull the nlist from the relevant file.
nlist_idx: Index = 0, nlist_idx: u32 = 0,
/// Misc flags for the symbol packaged as packed struct for compression. /// Misc flags for the symbol packaged as packed struct for compression.
flags: Flags = .{}, flags: Flags = .{},
@ -55,16 +54,19 @@ pub fn weakRef(symbol: Symbol, macho_file: *MachO) bool {
} }
pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 { pub fn getName(symbol: Symbol, macho_file: *MachO) [:0]const u8 {
if (symbol.flags.global) return macho_file.strings.getAssumeExists(symbol.name);
return switch (symbol.getFile(macho_file).?) { return switch (symbol.getFile(macho_file).?) {
.dylib => unreachable, // There are no local symbols for dylibs
.zig_object => |x| x.strtab.getAssumeExists(symbol.name), .zig_object => |x| x.strtab.getAssumeExists(symbol.name),
inline else => |x| x.getString(symbol.name), inline else => |x| x.getString(symbol.name),
}; };
} }
pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom { pub fn getAtom(symbol: Symbol, macho_file: *MachO) ?*Atom {
return macho_file.getAtom(symbol.atom); return symbol.atom_ref.getAtom(macho_file);
}
pub fn getOutputSectionIndex(symbol: Symbol, macho_file: *MachO) u8 {
if (symbol.getAtom(macho_file)) |atom| return atom.out_n_sect;
return symbol.out_n_sect;
} }
pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File { pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
@ -75,8 +77,10 @@ pub fn getFile(symbol: Symbol, macho_file: *MachO) ?File {
pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 { pub fn getNlist(symbol: Symbol, macho_file: *MachO) macho.nlist_64 {
const file = symbol.getFile(macho_file).?; const file = symbol.getFile(macho_file).?;
return switch (file) { return switch (file) {
.dylib => unreachable,
.zig_object => unreachable,
.object => |x| x.symtab.items(.nlist)[symbol.nlist_idx], .object => |x| x.symtab.items(.nlist)[symbol.nlist_idx],
else => unreachable, .internal => |x| x.symtab.items[symbol.nlist_idx],
}; };
} }
@ -124,33 +128,35 @@ pub fn getAddress(symbol: Symbol, opts: struct {
pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 { pub fn getGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.has_got) return 0; if (!symbol.flags.has_got) return 0;
const extra = symbol.getExtra(macho_file).?; const extra = symbol.getExtra(macho_file);
return macho_file.got.getAddress(extra.got, macho_file); return macho_file.got.getAddress(extra.got, macho_file);
} }
pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 { pub fn getStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.stubs) return 0; if (!symbol.flags.stubs) return 0;
const extra = symbol.getExtra(macho_file).?; const extra = symbol.getExtra(macho_file);
return macho_file.stubs.getAddress(extra.stubs, macho_file); return macho_file.stubs.getAddress(extra.stubs, macho_file);
} }
pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 { pub fn getObjcStubsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.objc_stubs) return 0; if (!symbol.flags.objc_stubs) return 0;
const extra = symbol.getExtra(macho_file).?; const extra = symbol.getExtra(macho_file);
return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file); return macho_file.objc_stubs.getAddress(extra.objc_stubs, macho_file);
} }
pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 { pub fn getObjcSelrefsAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.objc_stubs) return 0; if (!symbol.flags.objc_stubs) return 0;
const extra = symbol.getExtra(macho_file).?; const extra = symbol.getExtra(macho_file);
const atom = macho_file.getAtom(extra.objc_selrefs).?; const file = symbol.getFile(macho_file).?;
assert(atom.flags.alive); return switch (file) {
return atom.getAddress(macho_file); .dylib, .zig_object => unreachable,
inline else => |x| x.symbols.items[extra.objc_selrefs].getAddress(.{}, macho_file),
};
} }
pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 { pub fn getTlvPtrAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.tlv_ptr) return 0; if (!symbol.flags.tlv_ptr) return 0;
const extra = symbol.getExtra(macho_file).?; const extra = symbol.getExtra(macho_file);
return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file); return macho_file.tlv_ptr.getAddress(extra.tlv_ptr, macho_file);
} }
@ -162,14 +168,14 @@ const GetOrCreateZigGotEntryResult = struct {
pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult { pub fn getOrCreateZigGotEntry(symbol: *Symbol, symbol_index: Index, macho_file: *MachO) !GetOrCreateZigGotEntryResult {
assert(!macho_file.base.isRelocatable()); assert(!macho_file.base.isRelocatable());
assert(symbol.flags.needs_zig_got); assert(symbol.flags.needs_zig_got);
if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).?.zig_got }; if (symbol.flags.has_zig_got) return .{ .found_existing = true, .index = symbol.getExtra(macho_file).zig_got };
const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file); const index = try macho_file.zig_got.addSymbol(symbol_index, macho_file);
return .{ .found_existing = false, .index = index }; return .{ .found_existing = false, .index = index };
} }
pub fn getZigGotAddress(symbol: Symbol, macho_file: *MachO) u64 { pub fn getZigGotAddress(symbol: Symbol, macho_file: *MachO) u64 {
if (!symbol.flags.has_zig_got) return 0; if (!symbol.flags.has_zig_got) return 0;
const extras = symbol.getExtra(macho_file).?; const extras = symbol.getExtra(macho_file);
return macho_file.zig_got.entryAddress(extras.zig_got, macho_file); return macho_file.zig_got.entryAddress(extras.zig_got, macho_file);
} }
@ -180,7 +186,7 @@ pub fn getOutputSymtabIndex(symbol: Symbol, macho_file: *MachO) ?u32 {
const symtab_ctx = switch (file) { const symtab_ctx = switch (file) {
inline else => |x| x.output_symtab_ctx, inline else => |x| x.output_symtab_ctx,
}; };
var idx = symbol.getExtra(macho_file).?.symtab; var idx = symbol.getExtra(macho_file).symtab;
if (symbol.isLocal()) { if (symbol.isLocal()) {
idx += symtab_ctx.ilocal; idx += symtab_ctx.ilocal;
} else if (symbol.flags.@"export") { } else if (symbol.flags.@"export") {
@ -202,11 +208,8 @@ const AddExtraOpts = struct {
symtab: ?u32 = null, symtab: ?u32 = null,
}; };
pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void { pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) void {
if (symbol.getExtra(macho_file) == null) { var extra = symbol.getExtra(macho_file);
symbol.extra = try macho_file.addSymbolExtra(.{});
}
var extra = symbol.getExtra(macho_file).?;
inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| {
if (@field(opts, field.name)) |x| { if (@field(opts, field.name)) |x| {
@field(extra, field.name) = x; @field(extra, field.name) = x;
@ -215,18 +218,22 @@ pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) !void {
symbol.setExtra(extra, macho_file); symbol.setExtra(extra, macho_file);
} }
pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) ?Extra { pub inline fn getExtra(symbol: Symbol, macho_file: *MachO) Extra {
return macho_file.getSymbolExtra(symbol.extra); return switch (symbol.getFile(macho_file).?) {
inline else => |x| x.getSymbolExtra(symbol.extra),
};
} }
pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void { pub inline fn setExtra(symbol: Symbol, extra: Extra, macho_file: *MachO) void {
macho_file.setSymbolExtra(symbol.extra, extra); return switch (symbol.getFile(macho_file).?) {
inline else => |x| x.setSymbolExtra(symbol.extra, extra),
};
} }
pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void { pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) void {
if (symbol.isLocal()) { if (symbol.isLocal()) {
out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT; out.n_type = if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1); out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1);
out.n_desc = 0; out.n_desc = 0;
out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file); out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
@ -238,7 +245,7 @@ pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) vo
assert(symbol.visibility == .global); assert(symbol.visibility == .global);
out.n_type = macho.N_EXT; out.n_type = macho.N_EXT;
out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT; out.n_type |= if (symbol.flags.abs) macho.N_ABS else macho.N_SECT;
out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.out_n_sect + 1); out.n_sect = if (symbol.flags.abs) 0 else @intCast(symbol.getOutputSectionIndex(macho_file) + 1);
out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file); out.n_value = symbol.getAddress(.{ .stubs = false }, macho_file);
out.n_desc = 0; out.n_desc = 0;
@ -318,15 +325,20 @@ fn format2(
symbol.getAddress(.{}, ctx.macho_file), symbol.getAddress(.{}, ctx.macho_file),
}); });
if (symbol.getFile(ctx.macho_file)) |file| { if (symbol.getFile(ctx.macho_file)) |file| {
if (symbol.out_n_sect != 0) { if (symbol.getOutputSectionIndex(ctx.macho_file) != 0) {
try writer.print(" : sect({d})", .{symbol.out_n_sect}); try writer.print(" : sect({d})", .{symbol.getOutputSectionIndex(ctx.macho_file)});
} }
if (symbol.getAtom(ctx.macho_file)) |atom| { if (symbol.getAtom(ctx.macho_file)) |atom| {
try writer.print(" : atom({d})", .{atom.atom_index}); try writer.print(" : atom({d})", .{atom.atom_index});
} }
var buf: [2]u8 = .{'_'} ** 2; var buf: [3]u8 = .{'_'} ** 3;
if (symbol.flags.@"export") buf[0] = 'E'; if (symbol.flags.@"export") buf[0] = 'E';
if (symbol.flags.import) buf[1] = 'I'; if (symbol.flags.import) buf[1] = 'I';
switch (symbol.visibility) {
.local => buf[2] = 'L',
.hidden => buf[2] = 'H',
.global => buf[2] = 'G',
}
try writer.print(" : {s}", .{&buf}); try writer.print(" : {s}", .{&buf});
if (symbol.flags.weak) try writer.writeAll(" : weak"); if (symbol.flags.weak) try writer.writeAll(" : weak");
if (symbol.isSymbolStab(ctx.macho_file)) try writer.writeAll(" : stab"); if (symbol.isSymbolStab(ctx.macho_file)) try writer.writeAll(" : stab");
@ -346,11 +358,6 @@ pub const Flags = packed struct {
/// Whether the symbol is exported at runtime. /// Whether the symbol is exported at runtime.
@"export": bool = false, @"export": bool = false,
/// Whether the symbol is effectively an extern and takes part in global
/// symbol resolution. Then, its name will be saved in global string interning
/// table.
global: bool = false,
/// Whether this symbol is weak. /// Whether this symbol is weak.
weak: bool = false, weak: bool = false,
@ -400,6 +407,14 @@ pub const Visibility = enum {
global, global,
hidden, hidden,
local, local,
pub fn rank(vis: Visibility) u2 {
return switch (vis) {
.local => 2,
.hidden => 1,
.global => 0,
};
}
}; };
pub const Extra = struct { pub const Extra = struct {

View File

@ -1,10 +1,10 @@
/// List of all unwind records gathered from all objects and sorted /// List of all unwind records gathered from all objects and sorted
/// by allocated relative function address within the section. /// by allocated relative function address within the section.
records: std.ArrayListUnmanaged(Record.Index) = .{}, records: std.ArrayListUnmanaged(Record.Ref) = .{},
/// List of all personalities referenced by either unwind info entries /// List of all personalities referenced by either unwind info entries
/// or __eh_frame entries. /// or __eh_frame entries.
personalities: [max_personalities]Symbol.Index = undefined, personalities: [max_personalities]MachO.Ref = undefined,
personalities_count: u2 = 0, personalities_count: u2 = 0,
/// List of common encodings sorted in descending order with the most common first. /// List of common encodings sorted in descending order with the most common first.
@ -25,10 +25,10 @@ pub fn deinit(info: *UnwindInfo, allocator: Allocator) void {
info.lsdas_lookup.deinit(allocator); info.lsdas_lookup.deinit(allocator);
} }
fn canFold(macho_file: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool { fn canFold(macho_file: *MachO, lhs_ref: Record.Ref, rhs_ref: Record.Ref) bool {
const cpu_arch = macho_file.getTarget().cpu.arch; const cpu_arch = macho_file.getTarget().cpu.arch;
const lhs = macho_file.getUnwindRecord(lhs_index); const lhs = lhs_ref.getUnwindRecord(macho_file);
const rhs = macho_file.getUnwindRecord(rhs_index); const rhs = rhs_ref.getUnwindRecord(macho_file);
if (cpu_arch == .x86_64) { if (cpu_arch == .x86_64) {
if (lhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND) or if (lhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND) or
rhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND)) return false; rhs.enc.getMode() == @intFromEnum(macho.UNWIND_X86_64_MODE.STACK_IND)) return false;
@ -42,27 +42,31 @@ fn canFold(macho_file: *MachO, lhs_index: Record.Index, rhs_index: Record.Index)
} }
pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void { pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
log.debug("generating unwind info", .{}); log.debug("generating unwind info", .{});
// Collect all unwind records // Collect all unwind records
for (macho_file.sections.items(.atoms)) |atoms| { for (macho_file.sections.items(.atoms)) |atoms| {
for (atoms.items) |atom_index| { for (atoms.items) |ref| {
const atom = macho_file.getAtom(atom_index) orelse continue; const atom = ref.getAtom(macho_file) orelse continue;
if (!atom.flags.alive) continue; if (!atom.flags.alive) continue;
const recs = atom.getUnwindRecords(macho_file); const recs = atom.getUnwindRecords(macho_file);
const file = atom.getFile(macho_file);
try info.records.ensureUnusedCapacity(gpa, recs.len); try info.records.ensureUnusedCapacity(gpa, recs.len);
for (recs) |rec| { for (recs) |rec| {
if (!macho_file.getUnwindRecord(rec).alive) continue; if (!file.object.getUnwindRecord(rec).alive) continue;
info.records.appendAssumeCapacity(rec); info.records.appendAssumeCapacity(.{ .record = rec, .file = file.getIndex() });
} }
} }
} }
// Encode records // Encode records
for (info.records.items) |index| { for (info.records.items) |ref| {
const rec = macho_file.getUnwindRecord(index); const rec = ref.getUnwindRecord(macho_file);
if (rec.getFde(macho_file)) |fde| { if (rec.getFde(macho_file)) |fde| {
rec.enc.setDwarfSectionOffset(@intCast(fde.out_offset)); rec.enc.setDwarfSectionOffset(@intCast(fde.out_offset));
if (fde.getLsdaAtom(macho_file)) |lsda| { if (fde.getLsdaAtom(macho_file)) |lsda| {
@ -72,27 +76,31 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
} }
const cie = fde.getCie(macho_file); const cie = fde.getCie(macho_file);
if (cie.getPersonality(macho_file)) |_| { if (cie.getPersonality(macho_file)) |_| {
const personality_index = try info.getOrPutPersonalityFunction(cie.personality.?.index); // TODO handle error const object = cie.getObject(macho_file);
const sym_ref = object.getSymbolRef(cie.personality.?.index, macho_file);
const personality_index = try info.getOrPutPersonalityFunction(sym_ref); // TODO handle error
rec.enc.setPersonalityIndex(personality_index + 1); rec.enc.setPersonalityIndex(personality_index + 1);
} }
} else if (rec.getPersonality(macho_file)) |_| { } else if (rec.getPersonality(macho_file)) |_| {
const personality_index = try info.getOrPutPersonalityFunction(rec.personality.?); // TODO handle error const object = rec.getObject(macho_file);
const sym_ref = object.getSymbolRef(rec.personality.?, macho_file);
const personality_index = try info.getOrPutPersonalityFunction(sym_ref); // TODO handle error
rec.enc.setPersonalityIndex(personality_index + 1); rec.enc.setPersonalityIndex(personality_index + 1);
} }
} }
// Sort by assigned relative address within each output section // Sort by assigned relative address within each output section
const sortFn = struct { const sortFn = struct {
fn sortFn(ctx: *MachO, lhs_index: Record.Index, rhs_index: Record.Index) bool { fn sortFn(ctx: *MachO, lhs_ref: Record.Ref, rhs_ref: Record.Ref) bool {
const lhs = ctx.getUnwindRecord(lhs_index); const lhs = lhs_ref.getUnwindRecord(ctx);
const rhs = ctx.getUnwindRecord(rhs_index); const rhs = rhs_ref.getUnwindRecord(ctx);
const lhsa = lhs.getAtom(ctx); const lhsa = lhs.getAtom(ctx);
const rhsa = rhs.getAtom(ctx); const rhsa = rhs.getAtom(ctx);
if (lhsa.out_n_sect == rhsa.out_n_sect) return lhs.getAtomAddress(ctx) < rhs.getAtomAddress(ctx); if (lhsa.out_n_sect == rhsa.out_n_sect) return lhs.getAtomAddress(ctx) < rhs.getAtomAddress(ctx);
return lhsa.out_n_sect < rhsa.out_n_sect; return lhsa.out_n_sect < rhsa.out_n_sect;
} }
}.sortFn; }.sortFn;
mem.sort(Record.Index, info.records.items, macho_file, sortFn); mem.sort(Record.Ref, info.records.items, macho_file, sortFn);
// Fold the records // Fold the records
// Any adjacent two records that share encoding can be folded into one. // Any adjacent two records that share encoding can be folded into one.
@ -101,8 +109,8 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
var j: usize = 1; var j: usize = 1;
while (j < info.records.items.len) : (j += 1) { while (j < info.records.items.len) : (j += 1) {
if (canFold(macho_file, info.records.items[i], info.records.items[j])) { if (canFold(macho_file, info.records.items[i], info.records.items[j])) {
const rec = macho_file.getUnwindRecord(info.records.items[i]); const rec = info.records.items[i].getUnwindRecord(macho_file);
rec.length += macho_file.getUnwindRecord(info.records.items[j]).length + 1; rec.length += info.records.items[j].getUnwindRecord(macho_file).length + 1;
} else { } else {
i += 1; i += 1;
info.records.items[i] = info.records.items[j]; info.records.items[i] = info.records.items[j];
@ -111,14 +119,15 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
info.records.shrinkAndFree(gpa, i + 1); info.records.shrinkAndFree(gpa, i + 1);
} }
for (info.records.items) |rec_index| { for (info.records.items) |ref| {
const rec = macho_file.getUnwindRecord(rec_index); const rec = ref.getUnwindRecord(macho_file);
const atom = rec.getAtom(macho_file); const atom = rec.getAtom(macho_file);
log.debug("@{x}-{x} : {s} : rec({d}) : {}", .{ log.debug("@{x}-{x} : {s} : rec({d}) : object({d}) : {}", .{
rec.getAtomAddress(macho_file), rec.getAtomAddress(macho_file),
rec.getAtomAddress(macho_file) + rec.length, rec.getAtomAddress(macho_file) + rec.length,
atom.getName(macho_file), atom.getName(macho_file),
rec_index, ref.record,
ref.file,
rec.enc, rec.enc,
}); });
} }
@ -161,8 +170,8 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
).init(gpa); ).init(gpa);
defer common_encodings_counts.deinit(); defer common_encodings_counts.deinit();
for (info.records.items) |rec_index| { for (info.records.items) |ref| {
const rec = macho_file.getUnwindRecord(rec_index); const rec = ref.getUnwindRecord(macho_file);
if (rec.enc.isDwarf(macho_file)) continue; if (rec.enc.isDwarf(macho_file)) continue;
const gop = try common_encodings_counts.getOrPut(rec.enc); const gop = try common_encodings_counts.getOrPut(rec.enc);
if (!gop.found_existing) { if (!gop.found_existing) {
@ -190,7 +199,7 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
{ {
var i: u32 = 0; var i: u32 = 0;
while (i < info.records.items.len) { while (i < info.records.items.len) {
const rec = macho_file.getUnwindRecord(info.records.items[i]); const rec = info.records.items[i].getUnwindRecord(macho_file);
const range_start_max: u64 = rec.getAtomAddress(macho_file) + compressed_entry_func_offset_mask; const range_start_max: u64 = rec.getAtomAddress(macho_file) + compressed_entry_func_offset_mask;
var encoding_count: u9 = info.common_encodings_count; var encoding_count: u9 = info.common_encodings_count;
var space_left: u32 = second_level_page_words - var space_left: u32 = second_level_page_words -
@ -202,7 +211,7 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
}; };
while (space_left >= 1 and i < info.records.items.len) { while (space_left >= 1 and i < info.records.items.len) {
const next = macho_file.getUnwindRecord(info.records.items[i]); const next = info.records.items[i].getUnwindRecord(macho_file);
const is_dwarf = next.enc.isDwarf(macho_file); const is_dwarf = next.enc.isDwarf(macho_file);
if (next.getAtomAddress(macho_file) >= range_start_max) { if (next.getAtomAddress(macho_file) >= range_start_max) {
@ -244,8 +253,8 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
// Save records having an LSDA pointer // Save records having an LSDA pointer
log.debug("LSDA pointers:", .{}); log.debug("LSDA pointers:", .{});
try info.lsdas_lookup.ensureTotalCapacityPrecise(gpa, info.records.items.len); try info.lsdas_lookup.ensureTotalCapacityPrecise(gpa, info.records.items.len);
for (info.records.items, 0..) |index, i| { for (info.records.items, 0..) |ref, i| {
const rec = macho_file.getUnwindRecord(index); const rec = ref.getUnwindRecord(macho_file);
info.lsdas_lookup.appendAssumeCapacity(@intCast(info.lsdas.items.len)); info.lsdas_lookup.appendAssumeCapacity(@intCast(info.lsdas.items.len));
if (rec.getLsdaAtom(macho_file)) |lsda| { if (rec.getLsdaAtom(macho_file)) |lsda| {
log.debug(" @{x} => lsda({d})", .{ rec.getAtomAddress(macho_file), lsda.atom_index }); log.debug(" @{x} => lsda({d})", .{ rec.getAtomAddress(macho_file), lsda.atom_index });
@ -255,6 +264,9 @@ pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
} }
pub fn calcSize(info: UnwindInfo) usize { pub fn calcSize(info: UnwindInfo) usize {
const tracy = trace(@src());
defer tracy.end();
var total_size: usize = 0; var total_size: usize = 0;
total_size += @sizeOf(macho.unwind_info_section_header); total_size += @sizeOf(macho.unwind_info_section_header);
total_size += total_size +=
@ -291,8 +303,8 @@ pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count])); try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count]));
for (info.personalities[0..info.personalities_count]) |sym_index| { for (info.personalities[0..info.personalities_count]) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
try writer.writeInt(u32, @intCast(sym.getGotAddress(macho_file) - seg.vmaddr), .little); try writer.writeInt(u32, @intCast(sym.getGotAddress(macho_file) - seg.vmaddr), .little);
} }
@ -301,7 +313,7 @@ pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
(info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry)))); (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry))));
for (info.pages.items, 0..) |page, i| { for (info.pages.items, 0..) |page, i| {
assert(page.count > 0); assert(page.count > 0);
const rec = macho_file.getUnwindRecord(info.records.items[page.start]); const rec = info.records.items[page.start].getUnwindRecord(macho_file);
try writer.writeStruct(macho.unwind_info_section_header_index_entry{ try writer.writeStruct(macho.unwind_info_section_header_index_entry{
.functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)), .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
.secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)), .secondLevelPagesSectionOffset = @as(u32, @intCast(pages_base_offset + i * second_level_page_bytes)),
@ -310,7 +322,7 @@ pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
}); });
} }
const last_rec = macho_file.getUnwindRecord(info.records.items[info.records.items.len - 1]); const last_rec = info.records.items[info.records.items.len - 1].getUnwindRecord(macho_file);
const sentinel_address = @as(u32, @intCast(last_rec.getAtomAddress(macho_file) + last_rec.length - seg.vmaddr)); const sentinel_address = @as(u32, @intCast(last_rec.getAtomAddress(macho_file) + last_rec.length - seg.vmaddr));
try writer.writeStruct(macho.unwind_info_section_header_index_entry{ try writer.writeStruct(macho.unwind_info_section_header_index_entry{
.functionOffset = sentinel_address, .functionOffset = sentinel_address,
@ -320,7 +332,7 @@ pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
}); });
for (info.lsdas.items) |index| { for (info.lsdas.items) |index| {
const rec = macho_file.getUnwindRecord(info.records.items[index]); const rec = info.records.items[index].getUnwindRecord(macho_file);
try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{ try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{
.functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)), .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
.lsdaOffset = @as(u32, @intCast(rec.getLsdaAddress(macho_file) - seg.vmaddr)), .lsdaOffset = @as(u32, @intCast(rec.getLsdaAddress(macho_file) - seg.vmaddr)),
@ -340,13 +352,13 @@ pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
@memset(buffer[stream.pos..], 0); @memset(buffer[stream.pos..], 0);
} }
fn getOrPutPersonalityFunction(info: *UnwindInfo, sym_index: Symbol.Index) error{TooManyPersonalities}!u2 { fn getOrPutPersonalityFunction(info: *UnwindInfo, ref: MachO.Ref) error{TooManyPersonalities}!u2 {
comptime var index: u2 = 0; comptime var index: u2 = 0;
inline while (index < max_personalities) : (index += 1) { inline while (index < max_personalities) : (index += 1) {
if (info.personalities[index] == sym_index) { if (info.personalities[index].eql(ref)) {
return index; return index;
} else if (index == info.personalities_count) { } else if (index == info.personalities_count) {
info.personalities[index] = sym_index; info.personalities[index] = ref;
info.personalities_count += 1; info.personalities_count += 1;
return index; return index;
} }
@ -461,16 +473,17 @@ pub const Record = struct {
} }
pub fn getAtom(rec: Record, macho_file: *MachO) *Atom { pub fn getAtom(rec: Record, macho_file: *MachO) *Atom {
return macho_file.getAtom(rec.atom).?; return rec.getObject(macho_file).getAtom(rec.atom).?;
} }
pub fn getLsdaAtom(rec: Record, macho_file: *MachO) ?*Atom { pub fn getLsdaAtom(rec: Record, macho_file: *MachO) ?*Atom {
return macho_file.getAtom(rec.lsda); return rec.getObject(macho_file).getAtom(rec.lsda);
} }
pub fn getPersonality(rec: Record, macho_file: *MachO) ?*Symbol { pub fn getPersonality(rec: Record, macho_file: *MachO) ?*Symbol {
const personality = rec.personality orelse return null; const personality = rec.personality orelse return null;
return macho_file.getSymbol(personality); const object = rec.getObject(macho_file);
return object.getSymbolRef(personality, macho_file).getSymbol(macho_file);
} }
pub fn getFde(rec: Record, macho_file: *MachO) ?Fde { pub fn getFde(rec: Record, macho_file: *MachO) ?Fde {
@ -537,6 +550,15 @@ pub const Record = struct {
} }
pub const Index = u32; pub const Index = u32;
const Ref = struct {
record: Index,
file: File.Index,
pub fn getUnwindRecord(ref: Ref, macho_file: *MachO) *Record {
return macho_file.getFile(ref.file).?.object.getUnwindRecord(ref.record);
}
};
}; };
const max_personalities = 3; const max_personalities = 3;
@ -635,8 +657,8 @@ const Page = struct {
.entryCount = page.count, .entryCount = page.count,
}); });
for (info.records.items[page.start..][0..page.count]) |index| { for (info.records.items[page.start..][0..page.count]) |ref| {
const rec = macho_file.getUnwindRecord(index); const rec = ref.getUnwindRecord(macho_file);
try writer.writeStruct(macho.unwind_info_regular_second_level_entry{ try writer.writeStruct(macho.unwind_info_regular_second_level_entry{
.functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)), .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
.encoding = rec.enc.enc, .encoding = rec.enc.enc,
@ -658,9 +680,9 @@ const Page = struct {
} }
assert(page.count > 0); assert(page.count > 0);
const first_rec = macho_file.getUnwindRecord(info.records.items[page.start]); const first_rec = info.records.items[page.start].getUnwindRecord(macho_file);
for (info.records.items[page.start..][0..page.count]) |index| { for (info.records.items[page.start..][0..page.count]) |ref| {
const rec = macho_file.getUnwindRecord(index); const rec = ref.getUnwindRecord(macho_file);
const enc_index = blk: { const enc_index = blk: {
if (info.getCommonEncoding(rec.enc)) |id| break :blk id; if (info.getCommonEncoding(rec.enc)) |id| break :blk id;
const ncommon = info.common_encodings_count; const ncommon = info.common_encodings_count;

File diff suppressed because it is too large Load Diff

View File

@ -17,16 +17,16 @@ pub fn gcAtoms(macho_file: *MachO) !void {
fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho_file: *MachO) !void { fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho_file: *MachO) !void {
for (objects) |index| { for (objects) |index| {
const object = macho_file.getFile(index).?; const object = macho_file.getFile(index).?;
for (object.getSymbols()) |sym_index| { for (object.getSymbols(), 0..) |*sym, i| {
const sym = macho_file.getSymbol(sym_index); const ref = object.getSymbolRef(@intCast(i), macho_file);
const file = sym.getFile(macho_file) orelse continue; const file = ref.getFile(macho_file) orelse continue;
if (file.getIndex() != index) continue; if (file.getIndex() != index) continue;
if (sym.flags.no_dead_strip or (macho_file.base.isDynLib() and sym.visibility == .global)) if (sym.flags.no_dead_strip or (macho_file.base.isDynLib() and sym.visibility == .global))
try markSymbol(sym, roots, macho_file); try markSymbol(sym, roots, macho_file);
} }
for (object.getAtoms()) |atom_index| { for (object.getAtoms()) |atom_index| {
const atom = macho_file.getAtom(atom_index).?; const atom = object.getAtom(atom_index) orelse continue;
const isec = atom.getInputSection(macho_file); const isec = atom.getInputSection(macho_file);
switch (isec.type()) { switch (isec.type()) {
macho.S_MOD_INIT_FUNC_POINTERS, macho.S_MOD_INIT_FUNC_POINTERS,
@ -41,8 +41,9 @@ fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho
} }
for (macho_file.objects.items) |index| { for (macho_file.objects.items) |index| {
for (macho_file.getFile(index).?.object.unwind_records.items) |cu_index| { const object = macho_file.getFile(index).?.object;
const cu = macho_file.getUnwindRecord(cu_index); for (object.unwind_records_indexes.items) |cu_index| {
const cu = object.getUnwindRecord(cu_index);
if (!cu.alive) continue; if (!cu.alive) continue;
if (cu.getFde(macho_file)) |fde| { if (cu.getFde(macho_file)) |fde| {
if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file); if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
@ -50,22 +51,30 @@ fn collectRoots(roots: *std.ArrayList(*Atom), objects: []const File.Index, macho
} }
} }
for (macho_file.undefined_symbols.items) |sym_index| { if (macho_file.getInternalObject()) |obj| {
const sym = macho_file.getSymbol(sym_index); for (obj.force_undefined.items) |sym_index| {
const ref = obj.getSymbolRef(sym_index, macho_file);
if (ref.getFile(macho_file) != null) {
const sym = ref.getSymbol(macho_file).?;
try markSymbol(sym, roots, macho_file); try markSymbol(sym, roots, macho_file);
} }
}
for (&[_]?Symbol.Index{ for (&[_]?Symbol.Index{
macho_file.entry_index, obj.entry_index,
macho_file.dyld_stub_binder_index, obj.dyld_stub_binder_index,
macho_file.objc_msg_send_index, obj.objc_msg_send_index,
}) |index| { }) |index| {
if (index) |idx| { if (index) |idx| {
const sym = macho_file.getSymbol(idx); const ref = obj.getSymbolRef(idx, macho_file);
if (ref.getFile(macho_file) != null) {
const sym = ref.getSymbol(macho_file).?;
try markSymbol(sym, roots, macho_file); try markSymbol(sym, roots, macho_file);
} }
} }
} }
}
}
fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), macho_file: *MachO) !void { fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), macho_file: *MachO) !void {
const atom = sym.getAtom(macho_file) orelse return; const atom = sym.getAtom(macho_file) orelse return;
@ -88,8 +97,9 @@ fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void {
loop = false; loop = false;
for (objects) |index| { for (objects) |index| {
for (macho_file.getFile(index).?.getAtoms()) |atom_index| { const file = macho_file.getFile(index).?;
const atom = macho_file.getAtom(atom_index).?; for (file.getAtoms()) |atom_index| {
const atom = file.getAtom(atom_index) orelse continue;
const isec = atom.getInputSection(macho_file); const isec = atom.getInputSection(macho_file);
if (isec.isDontDeadStripIfReferencesLive() and if (isec.isDontDeadStripIfReferencesLive() and
!(mem.eql(u8, isec.sectName(), "__eh_frame") or !(mem.eql(u8, isec.sectName(), "__eh_frame") or
@ -119,16 +129,20 @@ fn markLive(atom: *Atom, macho_file: *MachO) void {
for (atom.getRelocs(macho_file)) |rel| { for (atom.getRelocs(macho_file)) |rel| {
const target_atom = switch (rel.tag) { const target_atom = switch (rel.tag) {
.local => rel.getTargetAtom(macho_file), .local => rel.getTargetAtom(atom.*, macho_file),
.@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file), .@"extern" => blk: {
const ref = rel.getTargetSymbolRef(atom.*, macho_file);
break :blk if (ref.getSymbol(macho_file)) |sym| sym.getAtom(macho_file) else null;
},
}; };
if (target_atom) |ta| { if (target_atom) |ta| {
if (markAtom(ta)) markLive(ta, macho_file); if (markAtom(ta)) markLive(ta, macho_file);
} }
} }
const file = atom.getFile(macho_file);
for (atom.getUnwindRecords(macho_file)) |cu_index| { for (atom.getUnwindRecords(macho_file)) |cu_index| {
const cu = macho_file.getUnwindRecord(cu_index); const cu = file.object.getUnwindRecord(cu_index);
const cu_atom = cu.getAtom(macho_file); const cu_atom = cu.getAtom(macho_file);
if (markAtom(cu_atom)) markLive(cu_atom, macho_file); if (markAtom(cu_atom)) markLive(cu_atom, macho_file);
@ -149,8 +163,11 @@ fn markLive(atom: *Atom, macho_file: *MachO) void {
fn refersLive(atom: *Atom, macho_file: *MachO) bool { fn refersLive(atom: *Atom, macho_file: *MachO) bool {
for (atom.getRelocs(macho_file)) |rel| { for (atom.getRelocs(macho_file)) |rel| {
const target_atom = switch (rel.tag) { const target_atom = switch (rel.tag) {
.local => rel.getTargetAtom(macho_file), .local => rel.getTargetAtom(atom.*, macho_file),
.@"extern" => rel.getTargetSymbol(macho_file).getAtom(macho_file), .@"extern" => blk: {
const ref = rel.getTargetSymbolRef(atom.*, macho_file);
break :blk if (ref.getSymbol(macho_file)) |sym| sym.getAtom(macho_file) else null;
},
}; };
if (target_atom) |ta| { if (target_atom) |ta| {
if (ta.flags.alive) return true; if (ta.flags.alive) return true;
@ -161,8 +178,9 @@ fn refersLive(atom: *Atom, macho_file: *MachO) bool {
fn prune(objects: []const File.Index, macho_file: *MachO) void { fn prune(objects: []const File.Index, macho_file: *MachO) void {
for (objects) |index| { for (objects) |index| {
for (macho_file.getFile(index).?.getAtoms()) |atom_index| { const file = macho_file.getFile(index).?;
const atom = macho_file.getAtom(atom_index).?; for (file.getAtoms()) |atom_index| {
const atom = file.getAtom(atom_index) orelse continue;
if (atom.flags.alive and !atom.flags.visited) { if (atom.flags.alive and !atom.flags.visited) {
atom.flags.alive = false; atom.flags.alive = false;
atom.markUnwindRecordsDead(macho_file); atom.markUnwindRecordsDead(macho_file);

View File

@ -1,14 +1,3 @@
const Rebase = @This();
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.link_dyld_info);
const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;
entries: std.ArrayListUnmanaged(Entry) = .{}, entries: std.ArrayListUnmanaged(Entry) = .{},
buffer: std.ArrayListUnmanaged(u8) = .{}, buffer: std.ArrayListUnmanaged(u8) = .{},
@ -30,11 +19,107 @@ pub fn deinit(rebase: *Rebase, gpa: Allocator) void {
rebase.buffer.deinit(gpa); rebase.buffer.deinit(gpa);
} }
pub fn size(rebase: Rebase) u64 { pub fn updateSize(rebase: *Rebase, macho_file: *MachO) !void {
return @as(u64, @intCast(rebase.buffer.items.len)); const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 2);
defer objects.deinit();
objects.appendSliceAssumeCapacity(macho_file.objects.items);
if (macho_file.getZigObject()) |obj| objects.appendAssumeCapacity(obj.index);
if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
for (objects.items) |index| {
const file = macho_file.getFile(index).?;
for (file.getAtoms()) |atom_index| {
const atom = file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
if (atom.getInputSection(macho_file).isZerofill()) continue;
const atom_addr = atom.getAddress(macho_file);
const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
const seg = macho_file.segments.items[seg_id];
for (atom.getRelocs(macho_file)) |rel| {
if (rel.type != .unsigned or rel.meta.length != 3) continue;
if (rel.tag == .@"extern") {
const sym = rel.getTargetSymbol(atom.*, macho_file);
if (sym.isTlvInit(macho_file)) continue;
if (sym.flags.import) continue;
}
const rel_offset = rel.offset - atom.off;
try rebase.entries.append(gpa, .{
.offset = atom_addr + rel_offset - seg.vmaddr,
.segment_id = seg_id,
});
}
}
} }
pub fn finalize(rebase: *Rebase, gpa: Allocator) !void { if (macho_file.zig_got_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (0..macho_file.zig_got.entries.items.len) |idx| {
const addr = macho_file.zig_got.entryAddress(@intCast(idx), macho_file);
try rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
}
}
if (macho_file.got_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.got.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
if (!sym.flags.import) {
try rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
}
}
}
if (macho_file.la_symbol_ptr_sect_index) |sid| {
const sect = macho_file.sections.items(.header)[sid];
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = sect.addr + idx * @sizeOf(u64);
const rebase_entry = Rebase.Entry{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
};
if ((sym.flags.import and !sym.flags.weak) or !sym.flags.import) {
try rebase.entries.append(gpa, rebase_entry);
}
}
}
if (macho_file.tlv_ptr_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
if (!sym.flags.import) {
try rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
}
}
}
try rebase.finalize(gpa);
macho_file.dyld_info_cmd.rebase_size = mem.alignForward(u32, @intCast(rebase.buffer.items.len), @alignOf(u64));
}
fn finalize(rebase: *Rebase, gpa: Allocator) !void {
if (rebase.entries.items.len == 0) return; if (rebase.entries.items.len == 0) return;
const writer = rebase.buffer.writer(gpa); const writer = rebase.buffer.writer(gpa);
@ -198,7 +283,6 @@ fn done(writer: anytype) !void {
} }
pub fn write(rebase: Rebase, writer: anytype) !void { pub fn write(rebase: Rebase, writer: anytype) !void {
if (rebase.size() == 0) return;
try writer.writeAll(rebase.buffer.items); try writer.writeAll(rebase.buffer.items);
} }
@ -574,3 +658,17 @@ test "rebase - composite" {
macho.REBASE_OPCODE_DONE, macho.REBASE_OPCODE_DONE,
}, rebase.buffer.items); }, rebase.buffer.items);
} }
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.link_dyld_info);
const macho = std.macho;
const mem = std.mem;
const testing = std.testing;
const trace = @import("../../../tracy.zig").trace;
const Allocator = mem.Allocator;
const File = @import("../file.zig").File;
const MachO = @import("../../MachO.zig");
const Rebase = @This();

View File

@ -28,165 +28,207 @@
//! After the optional exported symbol information is a byte of how many edges (0-255) that //! After the optional exported symbol information is a byte of how many edges (0-255) that
//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of //! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of
//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to. //! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to.
const Trie = @This();
const std = @import("std"); /// The root node of the trie.
const mem = std.mem; root: ?Node.Index = null,
const leb = std.leb; buffer: std.ArrayListUnmanaged(u8) = .{},
const log = std.log.scoped(.macho); nodes: std.MultiArrayList(Node) = .{},
const macho = std.macho;
const testing = std.testing;
const assert = std.debug.assert;
const Allocator = mem.Allocator;
pub const Node = struct {
base: *Trie,
/// Terminal info associated with this node.
/// If this node is not a terminal node, info is null.
terminal_info: ?struct {
/// Export flags associated with this exported symbol.
export_flags: u64,
/// VM address offset wrt to the section this symbol is defined against.
vmaddr_offset: u64,
} = null,
/// Offset of this node in the trie output byte stream.
trie_offset: ?u64 = null,
/// List of all edges originating from this node.
edges: std.ArrayListUnmanaged(Edge) = .{}, edges: std.ArrayListUnmanaged(Edge) = .{},
node_dirty: bool = true, /// Insert a symbol into the trie, updating the prefixes in the process.
/// This operation may change the layout of the trie by splicing edges in
/// certain circumstances.
fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
// const tracy = trace(@src());
// defer tracy.end();
/// Edge connecting to nodes in the trie. const node_index = try self.putNode(self.root.?, allocator, symbol.name);
pub const Edge = struct { const slice = self.nodes.slice();
from: *Node, slice.items(.is_terminal)[node_index] = true;
to: *Node, slice.items(.vmaddr_offset)[node_index] = symbol.vmaddr_offset;
label: []u8, slice.items(.export_flags)[node_index] = symbol.export_flags;
fn deinit(self: *Edge, allocator: Allocator) void {
self.to.deinit(allocator);
allocator.destroy(self.to);
allocator.free(self.label);
self.from = undefined;
self.to = undefined;
self.label = undefined;
}
};
fn deinit(self: *Node, allocator: Allocator) void {
for (self.edges.items) |*edge| {
edge.deinit(allocator);
}
self.edges.deinit(allocator);
} }
/// Inserts a new node starting from `self`. /// Inserts a new node starting at `node_index`.
fn put(self: *Node, allocator: Allocator, label: []const u8) !*Node { fn putNode(self: *Trie, node_index: Node.Index, allocator: Allocator, label: []const u8) !Node.Index {
// Check for match with edges from this node. // Check for match with edges from this node.
for (self.edges.items) |*edge| { for (self.nodes.items(.edges)[node_index].items) |edge_index| {
const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.to; const edge = &self.edges.items[edge_index];
const match = mem.indexOfDiff(u8, edge.label, label) orelse return edge.node;
if (match == 0) continue; if (match == 0) continue;
if (match == edge.label.len) return edge.to.put(allocator, label[match..]); if (match == edge.label.len) return self.putNode(edge.node, allocator, label[match..]);
// Found a match, need to splice up nodes. // Found a match, need to splice up nodes.
// From: A -> B // From: A -> B
// To: A -> C -> B // To: A -> C -> B
const mid = try allocator.create(Node); const mid_index = try self.addNode(allocator);
mid.* = .{ .base = self.base }; const to_label = edge.label[match..];
const to_label = try allocator.dupe(u8, edge.label[match..]); const to_node = edge.node;
allocator.free(edge.label); edge.node = mid_index;
const to_node = edge.to; edge.label = label[0..match];
edge.to = mid;
edge.label = try allocator.dupe(u8, label[0..match]);
self.base.node_count += 1;
try mid.edges.append(allocator, .{ const new_edge_index = try self.addEdge(allocator);
.from = mid, const new_edge = &self.edges.items[new_edge_index];
.to = to_node, new_edge.node = to_node;
.label = to_label, new_edge.label = to_label;
}); try self.nodes.items(.edges)[mid_index].append(allocator, new_edge_index);
return if (match == label.len) mid else mid.put(allocator, label[match..]); return if (match == label.len) mid_index else self.putNode(mid_index, allocator, label[match..]);
} }
// Add a new node. // Add a new node.
const node = try allocator.create(Node); const new_node_index = try self.addNode(allocator);
node.* = .{ .base = self.base }; const new_edge_index = try self.addEdge(allocator);
self.base.node_count += 1; const new_edge = &self.edges.items[new_edge_index];
new_edge.node = new_node_index;
new_edge.label = label;
try self.nodes.items(.edges)[node_index].append(allocator, new_edge_index);
try self.edges.append(allocator, .{ return new_node_index;
.from = self, }
.to = node,
.label = try allocator.dupe(u8, label), pub fn updateSize(self: *Trie, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
try self.init(gpa);
try self.nodes.ensureUnusedCapacity(gpa, macho_file.resolver.values.items.len * 2);
try self.edges.ensureUnusedCapacity(gpa, macho_file.resolver.values.items.len * 2);
const seg = macho_file.getTextSegment();
for (macho_file.resolver.values.items) |ref| {
if (ref.getFile(macho_file) == null) continue;
const sym = ref.getSymbol(macho_file).?;
if (!sym.flags.@"export") continue;
if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue;
var flags: u64 = if (sym.flags.abs)
macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
else if (sym.flags.tlv)
macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL
else
macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR;
if (sym.flags.weak) {
flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
macho_file.weak_defines = true;
macho_file.binds_to_weak = true;
}
try self.put(gpa, .{
.name = sym.getName(macho_file),
.vmaddr_offset = sym.getAddress(.{ .stubs = false }, macho_file) - seg.vmaddr,
.export_flags = flags,
}); });
return node;
} }
/// Recursively parses the node from the input byte stream. try self.finalize(gpa);
fn read(self: *Node, allocator: Allocator, reader: anytype) Trie.ReadError!usize {
self.node_dirty = true;
const trie_offset = try reader.context.getPos();
self.trie_offset = trie_offset;
var nread: usize = 0; macho_file.dyld_info_cmd.export_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
const node_size = try leb.readUleb128(u64, reader);
if (node_size > 0) {
const export_flags = try leb.readUleb128(u64, reader);
// TODO Parse special flags.
assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
const vmaddr_offset = try leb.readUleb128(u64, reader);
self.terminal_info = .{
.export_flags = export_flags,
.vmaddr_offset = vmaddr_offset,
};
} }
const nedges = try reader.readByte(); /// Finalizes this trie for writing to a byte stream.
self.base.node_count += nedges; /// This step performs multiple passes through the trie ensuring
/// there are no gaps after every `Node` is ULEB128 encoded.
/// Call this method before trying to `write` the trie to a byte stream.
fn finalize(self: *Trie, allocator: Allocator) !void {
const tracy = trace(@src());
defer tracy.end();
nread += (try reader.context.getPos()) - trie_offset; var ordered_nodes = std.ArrayList(Node.Index).init(allocator);
defer ordered_nodes.deinit();
try ordered_nodes.ensureTotalCapacityPrecise(self.nodes.items(.is_terminal).len);
var i: usize = 0; var fifo = std.fifo.LinearFifo(Node.Index, .Dynamic).init(allocator);
while (i < nedges) : (i += 1) { defer fifo.deinit();
const edge_start_pos = try reader.context.getPos();
const label = blk: { try fifo.writeItem(self.root.?);
var label_buf = std.ArrayList(u8).init(allocator);
while (true) { while (fifo.readItem()) |next_index| {
const next = try reader.readByte(); const edges = &self.nodes.items(.edges)[next_index];
if (next == @as(u8, 0)) for (edges.items) |edge_index| {
break; const edge = self.edges.items[edge_index];
try label_buf.append(next); try fifo.writeItem(edge.node);
} }
break :blk try label_buf.toOwnedSlice(); ordered_nodes.appendAssumeCapacity(next_index);
}
var more: bool = true;
var size: u32 = 0;
while (more) {
size = 0;
more = false;
for (ordered_nodes.items) |node_index| {
const res = try self.finalizeNode(node_index, size);
size += res.node_size;
if (res.updated) more = true;
}
}
try self.buffer.ensureTotalCapacityPrecise(allocator, size);
for (ordered_nodes.items) |node_index| {
try self.writeNode(node_index, self.buffer.writer(allocator));
}
}
const FinalizeNodeResult = struct {
/// Current size of this node in bytes.
node_size: u32,
/// True if the trie offset of this node in the output byte stream
/// would need updating; false otherwise.
updated: bool,
}; };
const seek_to = try leb.readUleb128(u64, reader); /// Updates offset of this node in the output byte stream.
const return_pos = try reader.context.getPos(); fn finalizeNode(self: *Trie, node_index: Node.Index, offset_in_trie: u32) !FinalizeNodeResult {
var stream = std.io.countingWriter(std.io.null_writer);
const writer = stream.writer();
const slice = self.nodes.slice();
nread += return_pos - edge_start_pos; var node_size: u32 = 0;
try reader.context.seekTo(seek_to); if (slice.items(.is_terminal)[node_index]) {
const export_flags = slice.items(.export_flags)[node_index];
const vmaddr_offset = slice.items(.vmaddr_offset)[node_index];
try leb.writeULEB128(writer, export_flags);
try leb.writeULEB128(writer, vmaddr_offset);
try leb.writeULEB128(writer, stream.bytes_written);
} else {
node_size += 1; // 0x0 for non-terminal nodes
}
node_size += 1; // 1 byte for edge count
const node = try allocator.create(Node); for (slice.items(.edges)[node_index].items) |edge_index| {
node.* = .{ .base = self.base }; const edge = &self.edges.items[edge_index];
const next_node_offset = slice.items(.trie_offset)[edge.node];
nread += try node.read(allocator, reader); node_size += @intCast(edge.label.len + 1);
try self.edges.append(allocator, .{ try leb.writeULEB128(writer, next_node_offset);
.from = self,
.to = node,
.label = label,
});
try reader.context.seekTo(return_pos);
} }
return nread; const trie_offset = slice.items(.trie_offset)[node_index];
const updated = offset_in_trie != trie_offset;
slice.items(.trie_offset)[node_index] = offset_in_trie;
node_size += @intCast(stream.bytes_written);
return .{ .node_size = node_size, .updated = updated };
}
fn init(self: *Trie, allocator: Allocator) !void {
assert(self.root == null);
self.root = try self.addNode(allocator);
}
pub fn deinit(self: *Trie, allocator: Allocator) void {
for (self.nodes.items(.edges)) |*edges| {
edges.deinit(allocator);
}
self.nodes.deinit(allocator);
self.edges.deinit(allocator);
self.buffer.deinit(allocator);
}
pub fn write(self: Trie, writer: anytype) !void {
if (self.buffer.items.len == 0) return;
try writer.writeAll(self.buffer.items);
} }
/// Writes this node to a byte stream. /// Writes this node to a byte stream.
@ -195,22 +237,27 @@ pub const Node = struct {
/// iterate over `Trie.ordered_nodes` and call this method on each node. /// iterate over `Trie.ordered_nodes` and call this method on each node.
/// This is one of the requirements of the MachO. /// This is one of the requirements of the MachO.
/// Panics if `finalize` was not called before calling this method. /// Panics if `finalize` was not called before calling this method.
fn write(self: Node, writer: anytype) !void { fn writeNode(self: *Trie, node_index: Node.Index, writer: anytype) !void {
assert(!self.node_dirty); const slice = self.nodes.slice();
if (self.terminal_info) |info| { const edges = slice.items(.edges)[node_index];
const is_terminal = slice.items(.is_terminal)[node_index];
const export_flags = slice.items(.export_flags)[node_index];
const vmaddr_offset = slice.items(.vmaddr_offset)[node_index];
if (is_terminal) {
// Terminal node info: encode export flags and vmaddr offset of this symbol. // Terminal node info: encode export flags and vmaddr offset of this symbol.
var info_buf: [@sizeOf(u64) * 2]u8 = undefined; var info_buf: [@sizeOf(u64) * 2]u8 = undefined;
var info_stream = std.io.fixedBufferStream(&info_buf); var info_stream = std.io.fixedBufferStream(&info_buf);
// TODO Implement for special flags. // TODO Implement for special flags.
assert(info.export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and assert(export_flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT == 0 and
info.export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0); export_flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER == 0);
try leb.writeUleb128(info_stream.writer(), info.export_flags); try leb.writeULEB128(info_stream.writer(), export_flags);
try leb.writeUleb128(info_stream.writer(), info.vmaddr_offset); try leb.writeULEB128(info_stream.writer(), vmaddr_offset);
// Encode the size of the terminal node info. // Encode the size of the terminal node info.
var size_buf: [@sizeOf(u64)]u8 = undefined; var size_buf: [@sizeOf(u64)]u8 = undefined;
var size_stream = std.io.fixedBufferStream(&size_buf); var size_stream = std.io.fixedBufferStream(&size_buf);
try leb.writeUleb128(size_stream.writer(), info_stream.pos); try leb.writeULEB128(size_stream.writer(), info_stream.pos);
// Now, write them to the output stream. // Now, write them to the output stream.
try writer.writeAll(size_buf[0..size_stream.pos]); try writer.writeAll(size_buf[0..size_stream.pos]);
@ -220,77 +267,29 @@ pub const Node = struct {
try writer.writeByte(0); try writer.writeByte(0);
} }
// Write number of edges (max legal number of edges is 256). // Write number of edges (max legal number of edges is 256).
try writer.writeByte(@as(u8, @intCast(self.edges.items.len))); try writer.writeByte(@as(u8, @intCast(edges.items.len)));
for (self.edges.items) |edge| { for (edges.items) |edge_index| {
const edge = self.edges.items[edge_index];
// Write edge label and offset to next node in trie. // Write edge label and offset to next node in trie.
try writer.writeAll(edge.label); try writer.writeAll(edge.label);
try writer.writeByte(0); try writer.writeByte(0);
try leb.writeUleb128(writer, edge.to.trie_offset.?); try leb.writeULEB128(writer, slice.items(.trie_offset)[edge.node]);
} }
} }
const FinalizeResult = struct { fn addNode(self: *Trie, allocator: Allocator) !Node.Index {
/// Current size of this node in bytes. const index: Node.Index = @intCast(try self.nodes.addOne(allocator));
node_size: u64, self.nodes.set(index, .{});
return index;
/// True if the trie offset of this node in the output byte stream
/// would need updating; false otherwise.
updated: bool,
};
/// Updates offset of this node in the output byte stream.
fn finalize(self: *Node, offset_in_trie: u64) !FinalizeResult {
var stream = std.io.countingWriter(std.io.null_writer);
const writer = stream.writer();
var node_size: u64 = 0;
if (self.terminal_info) |info| {
try leb.writeUleb128(writer, info.export_flags);
try leb.writeUleb128(writer, info.vmaddr_offset);
try leb.writeUleb128(writer, stream.bytes_written);
} else {
node_size += 1; // 0x0 for non-terminal nodes
}
node_size += 1; // 1 byte for edge count
for (self.edges.items) |edge| {
const next_node_offset = edge.to.trie_offset orelse 0;
node_size += edge.label.len + 1;
try leb.writeUleb128(writer, next_node_offset);
} }
const trie_offset = self.trie_offset orelse 0; fn addEdge(self: *Trie, allocator: Allocator) !Edge.Index {
const updated = offset_in_trie != trie_offset; const index: Edge.Index = @intCast(self.edges.items.len);
self.trie_offset = offset_in_trie; const edge = try self.edges.addOne(allocator);
self.node_dirty = false; edge.* = .{};
node_size += stream.bytes_written; return index;
return FinalizeResult{ .node_size = node_size, .updated = updated };
} }
};
/// The root node of the trie.
root: ?*Node = null,
/// If you want to access nodes ordered in DFS fashion,
/// you should call `finalize` first since the nodes
/// in this container are not guaranteed to not be stale
/// if more insertions took place after the last `finalize`
/// call.
ordered_nodes: std.ArrayListUnmanaged(*Node) = .{},
/// The size of the trie in bytes.
/// This value may be outdated if there were additional
/// insertions performed after `finalize` was called.
/// Call `finalize` before accessing this value to ensure
/// it is up-to-date.
size: u64 = 0,
/// Number of nodes currently in the trie.
node_count: usize = 0,
trie_dirty: bool = true,
/// Export symbol that is to be placed in the trie. /// Export symbol that is to be placed in the trie.
pub const ExportSymbol = struct { pub const ExportSymbol = struct {
@ -305,186 +304,34 @@ pub const ExportSymbol = struct {
export_flags: u64, export_flags: u64,
}; };
/// Insert a symbol into the trie, updating the prefixes in the process. const Node = struct {
/// This operation may change the layout of the trie by splicing edges in is_terminal: bool = false,
/// certain circumstances.
pub fn put(self: *Trie, allocator: Allocator, symbol: ExportSymbol) !void {
const node = try self.root.?.put(allocator, symbol.name);
node.terminal_info = .{
.vmaddr_offset = symbol.vmaddr_offset,
.export_flags = symbol.export_flags,
};
self.trie_dirty = true;
}
/// Finalizes this trie for writing to a byte stream. /// Export flags associated with this exported symbol.
/// This step performs multiple passes through the trie ensuring export_flags: u64 = 0,
/// there are no gaps after every `Node` is ULEB128 encoded.
/// Call this method before trying to `write` the trie to a byte stream.
pub fn finalize(self: *Trie, allocator: Allocator) !void {
if (!self.trie_dirty) return;
self.ordered_nodes.shrinkRetainingCapacity(0); /// VM address offset wrt to the section this symbol is defined against.
try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count); vmaddr_offset: u64 = 0,
var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator); /// Offset of this node in the trie output byte stream.
defer fifo.deinit(); trie_offset: u32 = 0,
try fifo.writeItem(self.root.?); /// List of all edges originating from this node.
edges: std.ArrayListUnmanaged(Edge.Index) = .{},
while (fifo.readItem()) |next| { const Index = u32;
for (next.edges.items) |*edge| {
try fifo.writeItem(edge.to);
}
self.ordered_nodes.appendAssumeCapacity(next);
}
var more: bool = true;
while (more) {
self.size = 0;
more = false;
for (self.ordered_nodes.items) |node| {
const res = try node.finalize(self.size);
self.size += res.node_size;
if (res.updated) more = true;
}
}
self.trie_dirty = false;
}
const ReadError = error{
OutOfMemory,
EndOfStream,
Overflow,
}; };
/// Parse the trie from a byte stream. /// Edge connecting nodes in the trie.
pub fn read(self: *Trie, allocator: Allocator, reader: anytype) ReadError!usize { const Edge = struct {
return self.root.?.read(allocator, reader); /// Target node in the trie.
} node: Node.Index = 0,
/// Write the trie to a byte stream. /// Matching prefix.
/// Panics if the trie was not finalized using `finalize` before calling this method. label: []const u8 = "",
pub fn write(self: Trie, writer: anytype) !void {
assert(!self.trie_dirty);
for (self.ordered_nodes.items) |node| {
try node.write(writer);
}
}
pub fn init(self: *Trie, allocator: Allocator) !void { const Index = u32;
assert(self.root == null); };
const root = try allocator.create(Node);
root.* = .{ .base = self };
self.root = root;
self.node_count += 1;
}
pub fn deinit(self: *Trie, allocator: Allocator) void {
if (self.root) |root| {
root.deinit(allocator);
allocator.destroy(root);
}
self.ordered_nodes.deinit(allocator);
}
test "Trie node count" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
try testing.expectEqual(@as(usize, 1), trie.node_count);
try testing.expect(trie.root != null);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 2), trie.node_count);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 2), trie.node_count);
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
// Inserting the same node shouldn't update the trie.
try trie.put(gpa, .{
.name = "__mh_execute_header",
.vmaddr_offset = 0x1000,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expectEqual(@as(usize, 4), trie.node_count);
}
test "Trie basic" {
const gpa = testing.allocator;
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
// root --- _st ---> node
try trie.put(gpa, .{
.name = "_st",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
try testing.expect(mem.eql(u8, trie.root.?.edges.items[0].label, "_st"));
{
// root --- _st ---> node --- art ---> node
try trie.put(gpa, .{
.name = "_start",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
const nextEdge = &trie.root.?.edges.items[0];
try testing.expect(mem.eql(u8, nextEdge.label, "_st"));
try testing.expect(nextEdge.to.edges.items.len == 1);
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art"));
}
{
// root --- _ ---> node --- st ---> node --- art ---> node
// |
// | --- main ---> node
try trie.put(gpa, .{
.name = "_main",
.vmaddr_offset = 0,
.export_flags = 0,
});
try testing.expect(trie.root.?.edges.items.len == 1);
const nextEdge = &trie.root.?.edges.items[0];
try testing.expect(mem.eql(u8, nextEdge.label, "_"));
try testing.expect(nextEdge.to.edges.items.len == 2);
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st"));
try testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main"));
const nextNextEdge = &nextEdge.to.edges.items[0];
try testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art"));
}
}
fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void { fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
assert(expected.len > 0); assert(expected.len > 0);
@ -502,7 +349,7 @@ fn expectEqualHexStrings(expected: []const u8, given: []const u8) !void {
} }
test "write Trie to a byte stream" { test "write Trie to a byte stream" {
var gpa = testing.allocator; const gpa = testing.allocator;
var trie: Trie = .{}; var trie: Trie = .{};
defer trie.deinit(gpa); defer trie.deinit(gpa);
try trie.init(gpa); try trie.init(gpa);
@ -519,7 +366,6 @@ test "write Trie to a byte stream" {
}); });
try trie.finalize(gpa); try trie.finalize(gpa);
try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes.
const exp_buffer = [_]u8{ const exp_buffer = [_]u8{
0x0, 0x1, // node root 0x0, 0x1, // node root
@ -531,51 +377,7 @@ test "write Trie to a byte stream" {
0x2, 0x0, 0x0, 0x0, // terminal node 0x2, 0x0, 0x0, 0x0, // terminal node
0x3, 0x0, 0x80, 0x20, 0x0, // terminal node 0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
}; };
try expectEqualHexStrings(&exp_buffer, trie.buffer.items);
const buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
{
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
{
// Writing finalized trie again should yield the same result.
try stream.seekTo(0);
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
}
}
test "parse Trie from byte stream" {
const gpa = testing.allocator;
const in_buffer = [_]u8{
0x0, 0x1, // node root
0x5f, 0x0, 0x5, // edge '_'
0x0, 0x2, // non-terminal node
0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, // edge '_mh_execute_header'
0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x0, 0x21, // edge '_mh_execute_header'
0x6d, 0x61, 0x69, 0x6e, 0x0, 0x25, // edge 'main'
0x2, 0x0, 0x0, 0x0, // terminal node
0x3, 0x0, 0x80, 0x20, 0x0, // terminal node
};
var in_stream = std.io.fixedBufferStream(&in_buffer);
var trie: Trie = .{};
defer trie.deinit(gpa);
try trie.init(gpa);
const nread = try trie.read(gpa, in_stream.reader());
try testing.expect(nread == in_buffer.len);
try trie.finalize(gpa);
const out_buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(out_buffer);
var out_stream = std.io.fixedBufferStream(out_buffer);
_ = try trie.write(out_stream.writer());
try expectEqualHexStrings(&in_buffer, out_buffer);
} }
test "ordering bug" { test "ordering bug" {
@ -602,11 +404,18 @@ test "ordering bug" {
0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72, 0x88, 0x80, 0x02, 0x01, 0x73, 0x53, 0x74, 0x72,
0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00, 0x00, 0x12, 0x03, 0x00, 0xD8, 0x0A, 0x00,
}; };
try expectEqualHexStrings(&exp_buffer, trie.buffer.items);
const buffer = try gpa.alloc(u8, trie.size);
defer gpa.free(buffer);
var stream = std.io.fixedBufferStream(buffer);
// Writing finalized trie again should yield the same result.
_ = try trie.write(stream.writer());
try expectEqualHexStrings(&exp_buffer, buffer);
} }
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.macho);
const macho = std.macho;
const mem = std.mem;
const std = @import("std");
const testing = std.testing;
const trace = @import("../../../tracy.zig").trace;
const Allocator = mem.Allocator;
const MachO = @import("../../MachO.zig");
const Trie = @This();

View File

@ -1,28 +1,19 @@
const std = @import("std");
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.link_dyld_info);
const macho = std.macho;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const MachO = @import("../../MachO.zig");
const Symbol = @import("../Symbol.zig");
pub const Entry = struct { pub const Entry = struct {
target: Symbol.Index, target: MachO.Ref,
offset: u64, offset: u64,
segment_id: u8, segment_id: u8,
addend: i64, addend: i64,
pub fn lessThan(ctx: *MachO, entry: Entry, other: Entry) bool { pub fn lessThan(ctx: *MachO, entry: Entry, other: Entry) bool {
_ = ctx;
if (entry.segment_id == other.segment_id) { if (entry.segment_id == other.segment_id) {
if (entry.target == other.target) { if (entry.target.eql(other.target)) {
return entry.offset < other.offset; return entry.offset < other.offset;
} }
const entry_name = ctx.getSymbol(entry.target).getName(ctx); if (entry.target.file == other.target.file) {
const other_name = ctx.getSymbol(other.target).getName(ctx); return entry.target.index < other.target.index;
return std.mem.lessThan(u8, entry_name, other_name); }
return entry.target.file < other.target.file;
} }
return entry.segment_id < other.segment_id; return entry.segment_id < other.segment_id;
} }
@ -39,11 +30,109 @@ pub const Bind = struct {
self.buffer.deinit(gpa); self.buffer.deinit(gpa);
} }
pub fn size(self: Self) u64 { pub fn updateSize(self: *Self, macho_file: *MachO) !void {
return @intCast(self.buffer.items.len); const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const cpu_arch = macho_file.getTarget().cpu.arch;
var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 2);
defer objects.deinit();
objects.appendSliceAssumeCapacity(macho_file.objects.items);
if (macho_file.getZigObject()) |obj| objects.appendAssumeCapacity(obj.index);
if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
for (objects.items) |index| {
const file = macho_file.getFile(index).?;
for (file.getAtoms()) |atom_index| {
const atom = file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
if (atom.getInputSection(macho_file).isZerofill()) continue;
const atom_addr = atom.getAddress(macho_file);
const relocs = atom.getRelocs(macho_file);
const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
const seg = macho_file.segments.items[seg_id];
for (relocs) |rel| {
if (rel.type != .unsigned or rel.meta.length != 3 or rel.tag != .@"extern") continue;
const rel_offset = rel.offset - atom.off;
const addend = rel.addend + rel.getRelocAddend(cpu_arch);
const sym = rel.getTargetSymbol(atom.*, macho_file);
if (sym.isTlvInit(macho_file)) continue;
const entry = Entry{
.target = rel.getTargetSymbolRef(atom.*, macho_file),
.offset = atom_addr + rel_offset - seg.vmaddr,
.segment_id = seg_id,
.addend = addend,
};
if (sym.flags.import or (!(sym.flags.@"export" and sym.flags.weak) and sym.flags.interposable)) {
try self.entries.append(gpa, entry);
}
}
}
} }
pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void { if (macho_file.got_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.got.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
const entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import or (sym.flags.@"export" and sym.flags.interposable and !sym.flags.weak)) {
try self.entries.append(gpa, entry);
}
}
}
if (macho_file.la_symbol_ptr_sect_index) |sid| {
const sect = macho_file.sections.items(.header)[sid];
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = sect.addr + idx * @sizeOf(u64);
const bind_entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import and sym.flags.weak) {
try self.entries.append(gpa, bind_entry);
}
}
}
if (macho_file.tlv_ptr_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
const entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import or (sym.flags.@"export" and sym.flags.interposable and !sym.flags.weak)) {
try self.entries.append(gpa, entry);
}
}
}
try self.finalize(gpa, macho_file);
macho_file.dyld_info_cmd.bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
}
fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
if (self.entries.items.len == 0) return; if (self.entries.items.len == 0) return;
const writer = self.buffer.writer(gpa); const writer = self.buffer.writer(gpa);
@ -75,7 +164,7 @@ pub const Bind = struct {
var addend: i64 = 0; var addend: i64 = 0;
var count: usize = 0; var count: usize = 0;
var skip: u64 = 0; var skip: u64 = 0;
var target: ?Symbol.Index = null; var target: ?MachO.Ref = null;
var state: enum { var state: enum {
start, start,
@ -86,7 +175,7 @@ pub const Bind = struct {
var i: usize = 0; var i: usize = 0;
while (i < entries.len) : (i += 1) { while (i < entries.len) : (i += 1) {
const current = entries[i]; const current = entries[i];
if (target == null or target.? != current.target) { if (target == null or !target.?.eql(current.target)) {
switch (state) { switch (state) {
.start => {}, .start => {},
.bind_single => try doBind(writer), .bind_single => try doBind(writer),
@ -95,7 +184,7 @@ pub const Bind = struct {
state = .start; state = .start;
target = current.target; target = current.target;
const sym = ctx.getSymbol(current.target); const sym = current.target.getSymbol(ctx).?;
const name = sym.getName(ctx); const name = sym.getName(ctx);
const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
const ordinal: i16 = ord: { const ordinal: i16 = ord: {
@ -178,7 +267,6 @@ pub const Bind = struct {
} }
pub fn write(self: Self, writer: anytype) !void { pub fn write(self: Self, writer: anytype) !void {
if (self.size() == 0) return;
try writer.writeAll(self.buffer.items); try writer.writeAll(self.buffer.items);
} }
}; };
@ -194,11 +282,110 @@ pub const WeakBind = struct {
self.buffer.deinit(gpa); self.buffer.deinit(gpa);
} }
pub fn size(self: Self) u64 { pub fn updateSize(self: *Self, macho_file: *MachO) !void {
return @intCast(self.buffer.items.len); const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const cpu_arch = macho_file.getTarget().cpu.arch;
var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 2);
defer objects.deinit();
objects.appendSliceAssumeCapacity(macho_file.objects.items);
if (macho_file.getZigObject()) |obj| objects.appendAssumeCapacity(obj.index);
if (macho_file.getInternalObject()) |obj| objects.appendAssumeCapacity(obj.index);
for (objects.items) |index| {
const file = macho_file.getFile(index).?;
for (file.getAtoms()) |atom_index| {
const atom = file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
if (atom.getInputSection(macho_file).isZerofill()) continue;
const atom_addr = atom.getAddress(macho_file);
const relocs = atom.getRelocs(macho_file);
const seg_id = macho_file.sections.items(.segment_id)[atom.out_n_sect];
const seg = macho_file.segments.items[seg_id];
for (relocs) |rel| {
if (rel.type != .unsigned or rel.meta.length != 3 or rel.tag != .@"extern") continue;
const rel_offset = rel.offset - atom.off;
const addend = rel.addend + rel.getRelocAddend(cpu_arch);
const sym = rel.getTargetSymbol(atom.*, macho_file);
if (sym.isTlvInit(macho_file)) continue;
const entry = Entry{
.target = rel.getTargetSymbolRef(atom.*, macho_file),
.offset = atom_addr + rel_offset - seg.vmaddr,
.segment_id = seg_id,
.addend = addend,
};
if (!sym.isLocal() and sym.flags.weak) {
try self.entries.append(gpa, entry);
}
}
}
} }
pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void { if (macho_file.got_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.got.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = macho_file.got.getAddress(@intCast(idx), macho_file);
const entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.weak) {
try self.entries.append(gpa, entry);
}
}
}
if (macho_file.la_symbol_ptr_sect_index) |sid| {
const sect = macho_file.sections.items(.header)[sid];
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = sect.addr + idx * @sizeOf(u64);
const bind_entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.weak) {
try self.entries.append(gpa, bind_entry);
}
}
}
if (macho_file.tlv_ptr_sect_index) |sid| {
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.tlv_ptr.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = macho_file.tlv_ptr.getAddress(@intCast(idx), macho_file);
const entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.weak) {
try self.entries.append(gpa, entry);
}
}
}
try self.finalize(gpa, macho_file);
macho_file.dyld_info_cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
}
fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
if (self.entries.items.len == 0) return; if (self.entries.items.len == 0) return;
const writer = self.buffer.writer(gpa); const writer = self.buffer.writer(gpa);
@ -230,7 +417,7 @@ pub const WeakBind = struct {
var addend: i64 = 0; var addend: i64 = 0;
var count: usize = 0; var count: usize = 0;
var skip: u64 = 0; var skip: u64 = 0;
var target: ?Symbol.Index = null; var target: ?MachO.Ref = null;
var state: enum { var state: enum {
start, start,
@ -241,7 +428,7 @@ pub const WeakBind = struct {
var i: usize = 0; var i: usize = 0;
while (i < entries.len) : (i += 1) { while (i < entries.len) : (i += 1) {
const current = entries[i]; const current = entries[i];
if (target == null or target.? != current.target) { if (target == null or !target.?.eql(current.target)) {
switch (state) { switch (state) {
.start => {}, .start => {},
.bind_single => try doBind(writer), .bind_single => try doBind(writer),
@ -250,7 +437,7 @@ pub const WeakBind = struct {
state = .start; state = .start;
target = current.target; target = current.target;
const sym = ctx.getSymbol(current.target); const sym = current.target.getSymbol(ctx).?;
const name = sym.getName(ctx); const name = sym.getName(ctx);
const flags: u8 = 0; // TODO NON_WEAK_DEFINITION const flags: u8 = 0; // TODO NON_WEAK_DEFINITION
@ -322,7 +509,6 @@ pub const WeakBind = struct {
} }
pub fn write(self: Self, writer: anytype) !void { pub fn write(self: Self, writer: anytype) !void {
if (self.size() == 0) return;
try writer.writeAll(self.buffer.items); try writer.writeAll(self.buffer.items);
} }
}; };
@ -340,11 +526,36 @@ pub const LazyBind = struct {
self.offsets.deinit(gpa); self.offsets.deinit(gpa);
} }
pub fn size(self: Self) u64 { pub fn updateSize(self: *Self, macho_file: *MachO) !void {
return @intCast(self.buffer.items.len); const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const sid = macho_file.la_symbol_ptr_sect_index.?;
const sect = macho_file.sections.items(.header)[sid];
const seg_id = macho_file.sections.items(.segment_id)[sid];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = sect.addr + idx * @sizeOf(u64);
const bind_entry = Entry{
.target = ref,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if ((sym.flags.import and !sym.flags.weak) or (sym.flags.interposable and !sym.flags.weak)) {
try self.entries.append(gpa, bind_entry);
}
} }
pub fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void { try self.finalize(gpa, macho_file);
macho_file.dyld_info_cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.buffer.items.len), @alignOf(u64));
}
fn finalize(self: *Self, gpa: Allocator, ctx: *MachO) !void {
try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len); try self.offsets.ensureTotalCapacityPrecise(gpa, self.entries.items.len);
const writer = self.buffer.writer(gpa); const writer = self.buffer.writer(gpa);
@ -356,7 +567,7 @@ pub const LazyBind = struct {
for (self.entries.items) |entry| { for (self.entries.items) |entry| {
self.offsets.appendAssumeCapacity(@intCast(self.buffer.items.len)); self.offsets.appendAssumeCapacity(@intCast(self.buffer.items.len));
const sym = ctx.getSymbol(entry.target); const sym = entry.target.getSymbol(ctx).?;
const name = sym.getName(ctx); const name = sym.getName(ctx);
const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0; const flags: u8 = if (sym.weakRef(ctx)) macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT else 0;
const ordinal: i16 = ord: { const ordinal: i16 = ord: {
@ -474,3 +685,17 @@ fn done(writer: anytype) !void {
log.debug(">>> done", .{}); log.debug(">>> done", .{});
try writer.writeByte(macho.BIND_OPCODE_DONE); try writer.writeByte(macho.BIND_OPCODE_DONE);
} }
const assert = std.debug.assert;
const leb = std.leb;
const log = std.log.scoped(.link_dyld_info);
const macho = std.macho;
const mem = std.mem;
const testing = std.testing;
const trace = @import("../../../tracy.zig").trace;
const std = @import("std");
const Allocator = mem.Allocator;
const File = @import("../file.zig").File;
const MachO = @import("../../MachO.zig");
const Symbol = @import("../Symbol.zig");

View File

@ -68,7 +68,8 @@ pub const Cie = struct {
pub fn getPersonality(cie: Cie, macho_file: *MachO) ?*Symbol { pub fn getPersonality(cie: Cie, macho_file: *MachO) ?*Symbol {
const personality = cie.personality orelse return null; const personality = cie.personality orelse return null;
return macho_file.getSymbol(personality.index); const object = cie.getObject(macho_file);
return object.getSymbolRef(personality.index, macho_file).getSymbol(macho_file);
} }
pub fn eql(cie: Cie, other: Cie, macho_file: *MachO) bool { pub fn eql(cie: Cie, other: Cie, macho_file: *MachO) bool {
@ -223,11 +224,11 @@ pub const Fde = struct {
} }
pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom { pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom {
return macho_file.getAtom(fde.atom).?; return fde.getObject(macho_file).getAtom(fde.atom).?;
} }
pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom { pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom {
return macho_file.getAtom(fde.lsda); return fde.getObject(macho_file).getAtom(fde.lsda);
} }
pub fn format( pub fn format(
@ -448,7 +449,7 @@ pub fn write(macho_file: *MachO, buffer: []u8) void {
} }
} }
pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.relocation_info)) error{Overflow}!void { pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: []macho.relocation_info) error{Overflow}!void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
@ -459,6 +460,7 @@ pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.
else => 0, else => 0,
}; };
var i: usize = 0;
for (macho_file.objects.items) |index| { for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object; const object = macho_file.getFile(index).?.object;
for (object.cies.items) |cie| { for (object.cies.items) |cie| {
@ -469,7 +471,7 @@ pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.
if (cie.getPersonality(macho_file)) |sym| { if (cie.getPersonality(macho_file)) |sym| {
const r_address = math.cast(i32, cie.out_offset + cie.personality.?.offset) orelse return error.Overflow; const r_address = math.cast(i32, cie.out_offset + cie.personality.?.offset) orelse return error.Overflow;
const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow; const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
relocs.appendAssumeCapacity(.{ relocs[i] = .{
.r_address = r_address, .r_address = r_address,
.r_symbolnum = r_symbolnum, .r_symbolnum = r_symbolnum,
.r_length = 2, .r_length = 2,
@ -480,7 +482,8 @@ pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.
.x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_GOT), .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_GOT),
else => unreachable, else => unreachable,
}, },
}); };
i += 1;
} }
} }
} }
@ -531,6 +534,8 @@ pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho.
} }
} }
} }
assert(relocs.len == i);
} }
pub const EH_PE = struct { pub const EH_PE = struct {

View File

@ -24,114 +24,23 @@ pub const File = union(enum) {
_ = options; _ = options;
switch (file) { switch (file) {
.zig_object => |x| try writer.writeAll(x.path), .zig_object => |x| try writer.writeAll(x.path),
.internal => try writer.writeAll(""), .internal => try writer.writeAll("internal"),
.object => |x| try writer.print("{}", .{x.fmtPath()}), .object => |x| try writer.print("{}", .{x.fmtPath()}),
.dylib => |x| try writer.writeAll(x.path), .dylib => |x| try writer.writeAll(x.path),
} }
} }
pub fn resolveSymbols(file: File, macho_file: *MachO) void { pub fn resolveSymbols(file: File, macho_file: *MachO) !void {
switch (file) { return switch (file) {
.internal => unreachable,
inline else => |x| x.resolveSymbols(macho_file), inline else => |x| x.resolveSymbols(macho_file),
} };
} }
pub fn resetGlobals(file: File, macho_file: *MachO) void { pub fn scanRelocs(file: File, macho_file: *MachO) !void {
switch (file) { switch (file) {
.internal => unreachable, .dylib => unreachable,
inline else => |x| x.resetGlobals(macho_file), .internal => |x| x.scanRelocs(macho_file),
} inline else => |x| x.scanRelocs(macho_file),
}
pub fn claimUnresolved(file: File, macho_file: *MachO) error{OutOfMemory}!void {
assert(file == .object or file == .zig_object);
for (file.getSymbols(), 0..) |sym_index, i| {
const nlist_idx = @as(Symbol.Index, @intCast(i));
const nlist = switch (file) {
.object => |x| x.symtab.items(.nlist)[nlist_idx],
.zig_object => |x| x.symtab.items(.nlist)[nlist_idx],
else => unreachable,
};
if (!nlist.ext()) continue;
if (!nlist.undf()) continue;
const sym = macho_file.getSymbol(sym_index);
if (sym.getFile(macho_file) != null) continue;
const is_import = switch (macho_file.undefined_treatment) {
.@"error" => false,
.warn, .suppress => nlist.weakRef(),
.dynamic_lookup => true,
};
if (is_import) {
sym.value = 0;
sym.atom = 0;
sym.nlist_idx = 0;
sym.file = macho_file.internal_object.?;
sym.flags.weak = false;
sym.flags.weak_ref = nlist.weakRef();
sym.flags.import = is_import;
sym.visibility = .global;
try macho_file.getInternalObject().?.symbols.append(macho_file.base.comp.gpa, sym_index);
}
}
}
pub fn claimUnresolvedRelocatable(file: File, macho_file: *MachO) void {
assert(file == .object or file == .zig_object);
for (file.getSymbols(), 0..) |sym_index, i| {
const nlist_idx = @as(Symbol.Index, @intCast(i));
const nlist = switch (file) {
.object => |x| x.symtab.items(.nlist)[nlist_idx],
.zig_object => |x| x.symtab.items(.nlist)[nlist_idx],
else => unreachable,
};
if (!nlist.ext()) continue;
if (!nlist.undf()) continue;
const sym = macho_file.getSymbol(sym_index);
if (sym.getFile(macho_file) != null) continue;
sym.value = 0;
sym.atom = 0;
sym.nlist_idx = nlist_idx;
sym.file = file.getIndex();
sym.flags.weak_ref = nlist.weakRef();
sym.flags.import = true;
sym.visibility = .global;
}
}
pub fn markImportsExports(file: File, macho_file: *MachO) void {
assert(file == .object or file == .zig_object);
for (file.getSymbols()) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
const other_file = sym.getFile(macho_file) orelse continue;
if (sym.visibility != .global) continue;
if (other_file == .dylib and !sym.flags.abs) {
sym.flags.import = true;
continue;
}
if (other_file.getIndex() == file.getIndex()) {
sym.flags.@"export" = true;
}
}
}
pub fn markExportsRelocatable(file: File, macho_file: *MachO) void {
assert(file == .object or file == .zig_object);
for (file.getSymbols()) |sym_index| {
const sym = macho_file.getSymbol(sym_index);
const other_file = sym.getFile(macho_file) orelse continue;
if (sym.visibility != .global) continue;
if (other_file.getIndex() == file.getIndex()) {
sym.flags.@"export" = true;
}
} }
} }
@ -148,7 +57,7 @@ pub const File = union(enum) {
weak: bool = false, weak: bool = false,
tentative: bool = false, tentative: bool = false,
}) u32 { }) u32 {
if (file == .object and !args.archive) { if (file != .dylib and !args.archive) {
const base: u32 = blk: { const base: u32 = blk: {
if (args.tentative) break :blk 3; if (args.tentative) break :blk 3;
break :blk if (args.weak) 2 else 1; break :blk if (args.weak) 2 else 1;
@ -162,16 +71,244 @@ pub const File = union(enum) {
return base + (file.getIndex() << 24); return base + (file.getIndex() << 24);
} }
pub fn getSymbols(file: File) []const Symbol.Index { pub fn getAtom(file: File, atom_index: Atom.Index) ?*Atom {
return switch (file) { return switch (file) {
inline else => |x| x.symbols.items, .dylib => unreachable,
inline else => |x| x.getAtom(atom_index),
}; };
} }
pub fn getAtoms(file: File) []const Atom.Index { pub fn getAtoms(file: File) []const Atom.Index {
return switch (file) { return switch (file) {
.dylib => unreachable, .dylib => unreachable,
inline else => |x| x.atoms.items, inline else => |x| x.getAtoms(),
};
}
pub fn addAtomExtra(file: File, allocator: Allocator, extra: Atom.Extra) !u32 {
return switch (file) {
.dylib => unreachable,
inline else => |x| x.addAtomExtra(allocator, extra),
};
}
pub fn getAtomExtra(file: File, index: u32) Atom.Extra {
return switch (file) {
.dylib => unreachable,
inline else => |x| x.getAtomExtra(index),
};
}
pub fn setAtomExtra(file: File, index: u32, extra: Atom.Extra) void {
return switch (file) {
.dylib => unreachable,
inline else => |x| x.setAtomExtra(index, extra),
};
}
pub fn getSymbols(file: File) []Symbol {
return switch (file) {
inline else => |x| x.symbols.items,
};
}
pub fn getSymbolRef(file: File, sym_index: Symbol.Index, macho_file: *MachO) MachO.Ref {
return switch (file) {
inline else => |x| x.getSymbolRef(sym_index, macho_file),
};
}
pub fn getNlists(file: File) []macho.nlist_64 {
return switch (file) {
.dylib => unreachable,
.internal => |x| x.symtab.items,
inline else => |x| x.symtab.items(.nlist),
};
}
pub fn getGlobals(file: File) []MachO.SymbolResolver.Index {
return switch (file) {
inline else => |x| x.globals.items,
};
}
pub fn markImportsExports(file: File, macho_file: *MachO) void {
const tracy = trace(@src());
defer tracy.end();
const nsyms = switch (file) {
.dylib => unreachable,
inline else => |x| x.symbols.items.len,
};
for (0..nsyms) |i| {
const ref = file.getSymbolRef(@intCast(i), macho_file);
if (ref.getFile(macho_file) == null) continue;
const sym = ref.getSymbol(macho_file).?;
if (sym.visibility != .global) continue;
if (sym.getFile(macho_file).? == .dylib and !sym.flags.abs) {
sym.flags.import = true;
continue;
}
if (file.getIndex() == ref.file) {
sym.flags.@"export" = true;
}
}
}
pub fn markExportsRelocatable(file: File, macho_file: *MachO) void {
const tracy = trace(@src());
defer tracy.end();
assert(file == .object or file == .zig_object);
for (file.getSymbols(), 0..) |*sym, i| {
const ref = file.getSymbolRef(@intCast(i), macho_file);
const other_file = ref.getFile(macho_file) orelse continue;
if (other_file.getIndex() != file.getIndex()) continue;
if (sym.visibility != .global) continue;
sym.flags.@"export" = true;
}
}
pub fn createSymbolIndirection(file: File, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const nsyms = switch (file) {
inline else => |x| x.symbols.items.len,
};
for (0..nsyms) |i| {
const ref = file.getSymbolRef(@intCast(i), macho_file);
if (ref.getFile(macho_file) == null) continue;
if (ref.file != file.getIndex()) continue;
const sym = ref.getSymbol(macho_file).?;
if (sym.flags.needs_got) {
log.debug("'{s}' needs GOT", .{sym.getName(macho_file)});
try macho_file.got.addSymbol(ref, macho_file);
}
if (sym.flags.stubs) {
log.debug("'{s}' needs STUBS", .{sym.getName(macho_file)});
try macho_file.stubs.addSymbol(ref, macho_file);
}
if (sym.flags.tlv_ptr) {
log.debug("'{s}' needs TLV pointer", .{sym.getName(macho_file)});
try macho_file.tlv_ptr.addSymbol(ref, macho_file);
}
if (sym.flags.objc_stubs) {
log.debug("'{s}' needs OBJC STUBS", .{sym.getName(macho_file)});
try macho_file.objc_stubs.addSymbol(ref, macho_file);
}
}
}
pub fn claimUnresolved(file: File, macho_file: *MachO) void {
const tracy = trace(@src());
defer tracy.end();
assert(file == .object or file == .zig_object);
for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| {
if (!nlist.ext()) continue;
if (!nlist.undf()) continue;
if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
const is_import = switch (macho_file.undefined_treatment) {
.@"error" => false,
.warn, .suppress => nlist.weakRef(),
.dynamic_lookup => true,
};
if (is_import) {
sym.value = 0;
sym.atom_ref = .{ .index = 0, .file = 0 };
sym.flags.weak = false;
sym.flags.weak_ref = nlist.weakRef();
sym.flags.import = is_import;
sym.visibility = .global;
const idx = file.getGlobals()[i];
macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = file.getIndex() };
}
}
}
pub fn claimUnresolvedRelocatable(file: File, macho_file: *MachO) void {
const tracy = trace(@src());
defer tracy.end();
assert(file == .object or file == .zig_object);
for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| {
if (!nlist.ext()) continue;
if (!nlist.undf()) continue;
if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue;
sym.value = 0;
sym.atom_ref = .{ .index = 0, .file = 0 };
sym.flags.weak_ref = nlist.weakRef();
sym.flags.import = true;
sym.visibility = .global;
const idx = file.getGlobals()[i];
macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = file.getIndex() };
}
}
pub fn checkDuplicates(file: File, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
for (file.getSymbols(), file.getNlists(), 0..) |sym, nlist, i| {
if (sym.visibility != .global) continue;
if (sym.flags.weak) continue;
if (nlist.undf()) continue;
const ref = file.getSymbolRef(@intCast(i), macho_file);
const ref_file = ref.getFile(macho_file) orelse continue;
if (ref_file.getIndex() == file.getIndex()) continue;
const gop = try macho_file.dupes.getOrPut(gpa, file.getGlobals()[i]);
if (!gop.found_existing) {
gop.value_ptr.* = .{};
}
try gop.value_ptr.append(gpa, file.getIndex());
}
}
pub fn initOutputSections(file: File, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
for (file.getAtoms()) |atom_index| {
const atom = file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file);
}
}
pub fn dedupLiterals(file: File, lp: MachO.LiteralPool, macho_file: *MachO) void {
return switch (file) {
.dylib => unreachable,
inline else => |x| x.dedupLiterals(lp, macho_file),
};
}
pub fn writeAtoms(file: File, macho_file: *MachO) !void {
return switch (file) {
.dylib, .zig_object => unreachable,
inline else => |x| x.writeAtoms(macho_file),
};
}
pub fn calcSymtabSize(file: File, macho_file: *MachO) void {
return switch (file) {
inline else => |x| x.calcSymtabSize(macho_file),
};
}
pub fn writeSymtab(file: File, macho_file: *MachO, ctx: anytype) void {
return switch (file) {
inline else => |x| x.writeSymtab(macho_file, ctx),
}; };
} }
@ -198,18 +335,6 @@ pub const File = union(enum) {
}; };
} }
pub fn calcSymtabSize(file: File, macho_file: *MachO) !void {
return switch (file) {
inline else => |x| x.calcSymtabSize(macho_file),
};
}
pub fn writeSymtab(file: File, macho_file: *MachO, ctx: anytype) !void {
return switch (file) {
inline else => |x| x.writeSymtab(macho_file, ctx),
};
}
pub const Index = u32; pub const Index = u32;
pub const Entry = union(enum) { pub const Entry = union(enum) {
@ -225,8 +350,10 @@ pub const File = union(enum) {
}; };
const assert = std.debug.assert; const assert = std.debug.assert;
const log = std.log.scoped(.link);
const macho = std.macho; const macho = std.macho;
const std = @import("std"); const std = @import("std");
const trace = @import("../../tracy.zig").trace;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Archive = @import("Archive.zig"); const Archive = @import("Archive.zig");

View File

@ -44,9 +44,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c
if (comp.link_errors.items.len > 0) return error.FlushFailure; if (comp.link_errors.items.len > 0) return error.FlushFailure;
try macho_file.addUndefinedGlobals();
try macho_file.resolveSymbols(); try macho_file.resolveSymbols();
try macho_file.parseDebugInfo();
try macho_file.dedupLiterals(); try macho_file.dedupLiterals();
markExports(macho_file); markExports(macho_file);
claimUnresolved(macho_file); claimUnresolved(macho_file);
@ -59,28 +57,13 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c
try allocateSections(macho_file); try allocateSections(macho_file);
allocateSegment(macho_file); allocateSegment(macho_file);
var off = off: {
const seg = macho_file.segments.items[0];
const off = math.cast(u32, seg.fileoff + seg.filesize) orelse return error.Overflow;
break :off mem.alignForward(u32, off, @alignOf(macho.relocation_info));
};
off = allocateSectionsRelocs(macho_file, off);
if (build_options.enable_logging) { if (build_options.enable_logging) {
state_log.debug("{}", .{macho_file.dumpState()}); state_log.debug("{}", .{macho_file.dumpState()});
} }
try macho_file.calcSymtabSize(); try writeSections(macho_file);
try writeAtoms(macho_file); sortRelocs(macho_file);
try writeCompactUnwind(macho_file); try writeSectionsToFile(macho_file);
try writeEhFrame(macho_file);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeDataInCode(0, off);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeSymtab(off);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeStrtab(off);
// In order to please Apple ld (and possibly other MachO linkers in the wild), // In order to please Apple ld (and possibly other MachO linkers in the wild),
// we will now sanitize segment names of Zig-specific segments. // we will now sanitize segment names of Zig-specific segments.
@ -129,7 +112,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
// First, we flush relocatable object file generated with our backends. // First, we flush relocatable object file generated with our backends.
if (macho_file.getZigObject()) |zo| { if (macho_file.getZigObject()) |zo| {
zo.resolveSymbols(macho_file); try zo.resolveSymbols(macho_file);
zo.asFile().markExportsRelocatable(macho_file); zo.asFile().markExportsRelocatable(macho_file);
zo.asFile().claimUnresolvedRelocatable(macho_file); zo.asFile().claimUnresolvedRelocatable(macho_file);
try macho_file.sortSections(); try macho_file.sortSections();
@ -139,26 +122,13 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
try allocateSections(macho_file); try allocateSections(macho_file);
allocateSegment(macho_file); allocateSegment(macho_file);
var off = off: {
const seg = macho_file.segments.items[0];
const off = math.cast(u32, seg.fileoff + seg.filesize) orelse return error.Overflow;
break :off mem.alignForward(u32, off, @alignOf(macho.relocation_info));
};
off = allocateSectionsRelocs(macho_file, off);
if (build_options.enable_logging) { if (build_options.enable_logging) {
state_log.debug("{}", .{macho_file.dumpState()}); state_log.debug("{}", .{macho_file.dumpState()});
} }
try macho_file.calcSymtabSize(); try writeSections(macho_file);
try writeAtoms(macho_file); sortRelocs(macho_file);
try writeSectionsToFile(macho_file);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeDataInCode(0, off);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeSymtab(off);
off = mem.alignForward(u32, off, @alignOf(u64));
off = try macho_file.writeStrtab(off);
// In order to please Apple ld (and possibly other MachO linkers in the wild), // In order to please Apple ld (and possibly other MachO linkers in the wild),
// we will now sanitize segment names of Zig-specific segments. // we will now sanitize segment names of Zig-specific segments.
@ -169,7 +139,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
// TODO we can avoid reading in the file contents we just wrote if we give the linker // TODO we can avoid reading in the file contents we just wrote if we give the linker
// ability to write directly to a buffer. // ability to write directly to a buffer.
try zo.readFileContents(off, macho_file); try zo.readFileContents(macho_file);
} }
var files = std.ArrayList(File.Index).init(gpa); var files = std.ArrayList(File.Index).init(gpa);
@ -286,12 +256,15 @@ fn parseObject(macho_file: *MachO, path: []const u8) MachO.ParseError!void {
break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000)));
}; };
const index = @as(File.Index, @intCast(try macho_file.files.addOne(gpa))); const index = @as(File.Index, @intCast(try macho_file.files.addOne(gpa)));
macho_file.files.set(index, .{ .object = .{ macho_file.files.set(index, .{
.object = .{
.offset = 0, // TODO FAT objects
.path = try gpa.dupe(u8, path), .path = try gpa.dupe(u8, path),
.file_handle = handle, .file_handle = handle,
.mtime = mtime, .mtime = mtime,
.index = index, .index = index,
} }); },
});
try macho_file.objects.append(gpa, index); try macho_file.objects.append(gpa, index);
const object = macho_file.getFile(index).?.object; const object = macho_file.getFile(index).?.object;
@ -347,9 +320,9 @@ pub fn claimUnresolved(macho_file: *MachO) void {
fn initOutputSections(macho_file: *MachO) !void { fn initOutputSections(macho_file: *MachO) !void {
for (macho_file.objects.items) |index| { for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object; const file = macho_file.getFile(index).?;
for (object.atoms.items) |atom_index| { for (file.getAtoms()) |atom_index| {
const atom = macho_file.getAtom(atom_index) orelse continue; const atom = file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue; if (!atom.flags.alive) continue;
atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file); atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file);
} }
@ -377,69 +350,147 @@ fn calcSectionSizes(macho_file: *MachO) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const slice = macho_file.sections.slice(); for (macho_file.sections.items(.atoms), 0..) |atoms, i| {
for (slice.items(.header), slice.items(.atoms)) |*header, atoms| {
if (atoms.items.len == 0) continue; if (atoms.items.len == 0) continue;
for (atoms.items) |atom_index| { calcSectionSize(macho_file, @intCast(i));
const atom = macho_file.getAtom(atom_index).?; }
if (macho_file.getZigObject()) |zo| {
// TODO this will create a race
zo.calcNumRelocs(macho_file);
zo.calcSymtabSize(macho_file);
}
if (macho_file.eh_frame_sect_index) |_| {
try calcEhFrameSize(macho_file);
}
for (macho_file.objects.items) |index| {
if (macho_file.unwind_info_sect_index) |_| {
macho_file.getFile(index).?.object.calcCompactUnwindSizeRelocatable(macho_file);
}
macho_file.getFile(index).?.calcSymtabSize(macho_file);
}
try macho_file.data_in_code.updateSize(macho_file);
if (macho_file.unwind_info_sect_index) |_| {
calcCompactUnwindSize(macho_file);
}
try calcSymtabSize(macho_file);
}
fn calcSectionSize(macho_file: *MachO, sect_id: u8) void {
const tracy = trace(@src());
defer tracy.end();
const slice = macho_file.sections.slice();
const header = &slice.items(.header)[sect_id];
const atoms = slice.items(.atoms)[sect_id].items;
for (atoms) |ref| {
const atom = ref.getAtom(macho_file).?;
const atom_alignment = atom.alignment.toByteUnits() orelse 1; const atom_alignment = atom.alignment.toByteUnits() orelse 1;
const offset = mem.alignForward(u64, header.size, atom_alignment); const offset = mem.alignForward(u64, header.size, atom_alignment);
const padding = offset - header.size; const padding = offset - header.size;
atom.value = offset; atom.value = offset;
header.size += padding + atom.size; header.size += padding + atom.size;
header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
header.nreloc += atom.calcNumRelocs(macho_file); const nreloc = atom.calcNumRelocs(macho_file);
atom.addExtra(.{ .rel_out_index = header.nreloc, .rel_out_count = nreloc }, macho_file);
header.nreloc += nreloc;
} }
} }
if (macho_file.unwind_info_sect_index) |index| { fn calcEhFrameSize(macho_file: *MachO) !void {
calcCompactUnwindSize(macho_file, index); const tracy = trace(@src());
defer tracy.end();
const header = &macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
header.size = try eh_frame.calcSize(macho_file);
header.@"align" = 3;
header.nreloc = eh_frame.calcNumRelocs(macho_file);
} }
if (macho_file.eh_frame_sect_index) |index| { fn calcCompactUnwindSize(macho_file: *MachO) void {
const sect = &macho_file.sections.items(.header)[index]; const tracy = trace(@src());
sect.size = try eh_frame.calcSize(macho_file); defer tracy.end();
sect.@"align" = 3;
sect.nreloc = eh_frame.calcNumRelocs(macho_file);
}
if (macho_file.getZigObject()) |zo| { var nrec: u32 = 0;
for (zo.atoms.items) |atom_index| {
const atom = macho_file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
const header = &macho_file.sections.items(.header)[atom.out_n_sect];
if (!macho_file.isZigSection(atom.out_n_sect) and !macho_file.isDebugSection(atom.out_n_sect)) continue;
header.nreloc += atom.calcNumRelocs(macho_file);
}
}
}
fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void {
var size: u32 = 0;
var nreloc: u32 = 0; var nreloc: u32 = 0;
for (macho_file.objects.items) |index| { for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object; const ctx = &macho_file.getFile(index).?.object.compact_unwind_ctx;
for (object.unwind_records.items) |irec| { ctx.rec_index = nrec;
const rec = macho_file.getUnwindRecord(irec); ctx.reloc_index = nreloc;
if (!rec.alive) continue; nrec += ctx.rec_count;
size += @sizeOf(macho.compact_unwind_entry); nreloc += ctx.reloc_count;
nreloc += 1;
if (rec.getPersonality(macho_file)) |_| {
nreloc += 1;
}
if (rec.getLsdaAtom(macho_file)) |_| {
nreloc += 1;
}
}
} }
const sect = &macho_file.sections.items(.header)[sect_index]; const sect = &macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?];
sect.size = size; sect.size = nrec * @sizeOf(macho.compact_unwind_entry);
sect.nreloc = nreloc; sect.nreloc = nreloc;
sect.@"align" = 3; sect.@"align" = 3;
} }
fn calcSymtabSize(macho_file: *MachO) error{OutOfMemory}!void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
var nlocals: u32 = 0;
var nstabs: u32 = 0;
var nexports: u32 = 0;
var nimports: u32 = 0;
var strsize: u32 = 1;
var objects = try std.ArrayList(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
defer objects.deinit();
if (macho_file.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
objects.appendSliceAssumeCapacity(macho_file.objects.items);
for (objects.items) |index| {
const ctx = switch (macho_file.getFile(index).?) {
inline else => |x| &x.output_symtab_ctx,
};
ctx.ilocal = nlocals;
ctx.istab = nstabs;
ctx.iexport = nexports;
ctx.iimport = nimports;
ctx.stroff = strsize;
nlocals += ctx.nlocals;
nstabs += ctx.nstabs;
nexports += ctx.nexports;
nimports += ctx.nimports;
strsize += ctx.strsize;
}
for (objects.items) |index| {
const ctx = switch (macho_file.getFile(index).?) {
inline else => |x| &x.output_symtab_ctx,
};
ctx.istab += nlocals;
ctx.iexport += nlocals + nstabs;
ctx.iimport += nlocals + nstabs + nexports;
}
{
const cmd = &macho_file.symtab_cmd;
cmd.nsyms = nlocals + nstabs + nexports + nimports;
cmd.strsize = strsize;
}
{
const cmd = &macho_file.dysymtab_cmd;
cmd.ilocalsym = 0;
cmd.nlocalsym = nlocals + nstabs;
cmd.iextdefsym = nlocals + nstabs;
cmd.nextdefsym = nexports;
cmd.iundefsym = nlocals + nstabs + nexports;
cmd.nundefsym = nimports;
}
}
fn allocateSections(macho_file: *MachO) !void { fn allocateSections(macho_file: *MachO) !void {
const slice = macho_file.sections.slice(); const slice = macho_file.sections.slice();
for (slice.items(.header)) |*header| { for (slice.items(.header)) |*header| {
@ -457,6 +508,37 @@ fn allocateSections(macho_file: *MachO) !void {
} }
header.size = needed_size; header.size = needed_size;
} }
var fileoff: u32 = 0;
for (slice.items(.header)) |header| {
fileoff = @max(fileoff, header.offset + @as(u32, @intCast(header.size)));
}
for (slice.items(.header)) |*header| {
if (header.nreloc == 0) continue;
header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info));
fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info);
}
// In -r mode, there is no LINKEDIT segment and so we allocate required LINKEDIT commands
// as if they were detached or part of the single segment.
// DATA_IN_CODE
{
const cmd = &macho_file.data_in_code_cmd;
cmd.dataoff = fileoff;
fileoff += cmd.datasize;
fileoff = mem.alignForward(u32, fileoff, @alignOf(u64));
}
// SYMTAB
{
const cmd = &macho_file.symtab_cmd;
cmd.symoff = fileoff;
fileoff += cmd.nsyms * @sizeOf(macho.nlist_64);
fileoff = mem.alignForward(u32, fileoff, @alignOf(u32));
cmd.stroff = fileoff;
}
} }
/// Renames segment names in Zig sections to standard MachO segment names such as /// Renames segment names in Zig sections to standard MachO segment names such as
@ -519,234 +601,88 @@ fn allocateSegment(macho_file: *MachO) void {
seg.filesize = fileoff - seg.fileoff; seg.filesize = fileoff - seg.fileoff;
} }
fn allocateSectionsRelocs(macho_file: *MachO, off: u32) u32 {
var fileoff = off;
const slice = macho_file.sections.slice();
for (slice.items(.header)) |*header| {
if (header.nreloc == 0) continue;
header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info));
fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info);
}
return fileoff;
}
// We need to sort relocations in descending order to be compatible with Apple's linker. // We need to sort relocations in descending order to be compatible with Apple's linker.
fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {
_ = ctx; _ = ctx;
return lhs.r_address > rhs.r_address; return lhs.r_address > rhs.r_address;
} }
fn writeAtoms(macho_file: *MachO) !void { fn sortRelocs(macho_file: *MachO) void {
const tracy = trace(@src());
defer tracy.end();
for (macho_file.sections.items(.relocs)) |*relocs| {
mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
}
}
fn writeSections(macho_file: *MachO) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const cpu_arch = macho_file.getTarget().cpu.arch; const cpu_arch = macho_file.getTarget().cpu.arch;
const slice = macho_file.sections.slice(); const slice = macho_file.sections.slice();
for (slice.items(.header), slice.items(.out), slice.items(.relocs), 0..) |header, *out, *relocs, n_sect| {
var relocs = std.ArrayList(macho.relocation_info).init(gpa);
defer relocs.deinit();
for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| {
if (atoms.items.len == 0) continue;
if (header.isZerofill()) continue; if (header.isZerofill()) continue;
if (macho_file.isZigSection(@intCast(i)) or macho_file.isDebugSection(@intCast(i))) continue; if (!macho_file.isZigSection(@intCast(n_sect))) { // TODO this is wrong; what about debug sections?
const size = math.cast(usize, header.size) orelse return error.Overflow; const size = math.cast(usize, header.size) orelse return error.Overflow;
const code = try gpa.alloc(u8, size); try out.resize(gpa, size);
defer gpa.free(code);
const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0; const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
@memset(code, padding_byte); @memset(out.items, padding_byte);
}
try relocs.ensureTotalCapacity(header.nreloc); try relocs.resize(gpa, header.nreloc);
for (atoms.items) |atom_index| {
const atom = macho_file.getAtom(atom_index).?;
assert(atom.flags.alive);
const off = math.cast(usize, atom.value) orelse return error.Overflow;
const atom_size = math.cast(usize, atom.size) orelse return error.Overflow;
try atom.getData(macho_file, code[off..][0..atom_size]);
try atom.writeRelocs(macho_file, code[off..][0..atom_size], &relocs);
} }
assert(relocs.items.len == header.nreloc); const cmd = macho_file.symtab_cmd;
try macho_file.symtab.resize(gpa, cmd.nsyms);
try macho_file.strtab.resize(gpa, cmd.strsize);
macho_file.strtab.items[0] = 0;
mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); for (macho_file.objects.items) |index| {
try macho_file.getFile(index).?.object.writeAtomsRelocatable(macho_file);
// TODO scattered writes? macho_file.getFile(index).?.writeSymtab(macho_file, macho_file);
try macho_file.base.file.?.pwriteAll(code, header.offset);
try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
relocs.clearRetainingCapacity();
} }
if (macho_file.getZigObject()) |zo| { if (macho_file.getZigObject()) |zo| {
// TODO: this is ugly; perhaps we should aggregrate before? try zo.writeRelocs(macho_file);
var zo_relocs = std.AutoArrayHashMap(u8, std.ArrayList(macho.relocation_info)).init(gpa); try zo.writeAtomsRelocatable(macho_file);
defer { zo.writeSymtab(macho_file, macho_file);
for (zo_relocs.values()) |*list| {
list.deinit();
}
zo_relocs.deinit();
} }
for (macho_file.sections.items(.header), 0..) |header, n_sect| { if (macho_file.eh_frame_sect_index) |_| {
if (header.isZerofill()) continue; try writeEhFrame(macho_file);
if (!macho_file.isZigSection(@intCast(n_sect)) and !macho_file.isDebugSection(@intCast(n_sect))) continue;
const gop = try zo_relocs.getOrPut(@intCast(n_sect));
if (gop.found_existing) continue;
gop.value_ptr.* = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
} }
for (zo.atoms.items) |atom_index| { if (macho_file.unwind_info_sect_index) |_| {
const atom = macho_file.getAtom(atom_index) orelse continue;
if (!atom.flags.alive) continue;
const header = macho_file.sections.items(.header)[atom.out_n_sect];
if (header.isZerofill()) continue;
if (!macho_file.isZigSection(atom.out_n_sect) and !macho_file.isDebugSection(atom.out_n_sect)) continue;
if (atom.getRelocs(macho_file).len == 0) continue;
const atom_size = math.cast(usize, atom.size) orelse return error.Overflow;
const code = try gpa.alloc(u8, atom_size);
defer gpa.free(code);
atom.getData(macho_file, code) catch |err| switch (err) {
error.InputOutput => {
try macho_file.reportUnexpectedError("fetching code for '{s}' failed", .{
atom.getName(macho_file),
});
return error.FlushFailure;
},
else => |e| {
try macho_file.reportUnexpectedError("unexpected error while fetching code for '{s}': {s}", .{
atom.getName(macho_file),
@errorName(e),
});
return error.FlushFailure;
},
};
const file_offset = header.offset + atom.value;
const rels = zo_relocs.getPtr(atom.out_n_sect).?;
try atom.writeRelocs(macho_file, code, rels);
try macho_file.base.file.?.pwriteAll(code, file_offset);
}
for (zo_relocs.keys(), zo_relocs.values()) |sect_id, rels| {
const header = macho_file.sections.items(.header)[sect_id];
assert(rels.items.len == header.nreloc);
mem.sort(macho.relocation_info, rels.items, {}, sortReloc);
try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(rels.items), header.reloff);
}
}
}
fn writeCompactUnwind(macho_file: *MachO) !void {
const sect_index = macho_file.unwind_info_sect_index orelse return;
const gpa = macho_file.base.comp.gpa;
const header = macho_file.sections.items(.header)[sect_index];
const nrecs = math.cast(usize, @divExact(header.size, @sizeOf(macho.compact_unwind_entry))) orelse return error.Overflow;
var entries = try std.ArrayList(macho.compact_unwind_entry).initCapacity(gpa, nrecs);
defer entries.deinit();
var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
defer relocs.deinit();
const addReloc = struct {
fn addReloc(offset: i32, cpu_arch: std.Target.Cpu.Arch) macho.relocation_info {
return .{
.r_address = offset,
.r_symbolnum = 0,
.r_pcrel = 0,
.r_length = 3,
.r_extern = 0,
.r_type = switch (cpu_arch) {
.aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED),
.x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED),
else => unreachable,
},
};
}
}.addReloc;
var offset: i32 = 0;
for (macho_file.objects.items) |index| { for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object; try macho_file.getFile(index).?.object.writeCompactUnwindRelocatable(macho_file);
for (object.unwind_records.items) |irec| {
const rec = macho_file.getUnwindRecord(irec);
if (!rec.alive) continue;
var out: macho.compact_unwind_entry = .{
.rangeStart = 0,
.rangeLength = rec.length,
.compactUnwindEncoding = rec.enc.enc,
.personalityFunction = 0,
.lsda = 0,
};
{
// Function address
const atom = rec.getAtom(macho_file);
const addr = rec.getAtomAddress(macho_file);
out.rangeStart = addr;
var reloc = addReloc(offset, macho_file.getTarget().cpu.arch);
reloc.r_symbolnum = atom.out_n_sect + 1;
relocs.appendAssumeCapacity(reloc);
}
// Personality function
if (rec.getPersonality(macho_file)) |sym| {
const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
var reloc = addReloc(offset + 16, macho_file.getTarget().cpu.arch);
reloc.r_symbolnum = r_symbolnum;
reloc.r_extern = 1;
relocs.appendAssumeCapacity(reloc);
}
// LSDA address
if (rec.getLsdaAtom(macho_file)) |atom| {
const addr = rec.getLsdaAddress(macho_file);
out.lsda = addr;
var reloc = addReloc(offset + 24, macho_file.getTarget().cpu.arch);
reloc.r_symbolnum = atom.out_n_sect + 1;
relocs.appendAssumeCapacity(reloc);
}
entries.appendAssumeCapacity(out);
offset += @sizeOf(macho.compact_unwind_entry);
} }
} }
assert(entries.items.len == nrecs);
assert(relocs.items.len == header.nreloc);
mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
// TODO scattered writes?
try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(entries.items), header.offset);
try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
} }
fn writeEhFrame(macho_file: *MachO) !void { fn writeEhFrame(macho_file: *MachO) !void {
const sect_index = macho_file.eh_frame_sect_index orelse return; const sect_index = macho_file.eh_frame_sect_index.?;
const gpa = macho_file.base.comp.gpa; const buffer = macho_file.sections.items(.out)[sect_index];
const header = macho_file.sections.items(.header)[sect_index]; const relocs = macho_file.sections.items(.relocs)[sect_index];
const size = math.cast(usize, header.size) orelse return error.Overflow; try eh_frame.writeRelocs(macho_file, buffer.items, relocs.items);
}
const code = try gpa.alloc(u8, size); fn writeSectionsToFile(macho_file: *MachO) !void {
defer gpa.free(code); const tracy = trace(@src());
defer tracy.end();
var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); const slice = macho_file.sections.slice();
defer relocs.deinit(); for (slice.items(.header), slice.items(.out), slice.items(.relocs)) |header, out, relocs| {
try macho_file.base.file.?.pwriteAll(out.items, header.offset);
try eh_frame.writeRelocs(macho_file, code, &relocs);
assert(relocs.items.len == header.nreloc);
mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
// TODO scattered writes?
try macho_file.base.file.?.pwriteAll(code, header.offset);
try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
} }
try macho_file.writeDataInCode();
try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(macho_file.symtab.items), macho_file.symtab_cmd.symoff);
try macho_file.base.file.?.pwriteAll(macho_file.strtab.items, macho_file.symtab_cmd.stroff);
}
fn writeLoadCommands(macho_file: *MachO) !struct { usize, usize } { fn writeLoadCommands(macho_file: *MachO) !struct { usize, usize } {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file); const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file);

View File

@ -18,15 +18,15 @@ pub const ZigGotSection = struct {
} }
pub fn addSymbol(zig_got: *ZigGotSection, sym_index: Symbol.Index, macho_file: *MachO) !Index { pub fn addSymbol(zig_got: *ZigGotSection, sym_index: Symbol.Index, macho_file: *MachO) !Index {
const comp = macho_file.base.comp; const gpa = macho_file.base.comp.gpa;
const gpa = comp.gpa; const zo = macho_file.getZigObject().?;
const index = try zig_got.allocateEntry(gpa); const index = try zig_got.allocateEntry(gpa);
const entry = &zig_got.entries.items[index]; const entry = &zig_got.entries.items[index];
entry.* = sym_index; entry.* = sym_index;
const symbol = macho_file.getSymbol(sym_index); const symbol = &zo.symbols.items[sym_index];
assert(symbol.flags.needs_zig_got); assert(symbol.flags.needs_zig_got);
symbol.flags.has_zig_got = true; symbol.flags.has_zig_got = true;
try symbol.addExtra(.{ .zig_got = index }, macho_file); symbol.addExtra(.{ .zig_got = index }, macho_file);
return index; return index;
} }
@ -53,9 +53,10 @@ pub const ZigGotSection = struct {
try macho_file.growSection(macho_file.zig_got_sect_index.?, needed_size); try macho_file.growSection(macho_file.zig_got_sect_index.?, needed_size);
zig_got.dirty = false; zig_got.dirty = false;
} }
const zo = macho_file.getZigObject().?;
const off = zig_got.entryOffset(index, macho_file); const off = zig_got.entryOffset(index, macho_file);
const entry = zig_got.entries.items[index]; const entry = zig_got.entries.items[index];
const value = macho_file.getSymbol(entry).getAddress(.{ .stubs = false }, macho_file); const value = zo.symbols.items[entry].getAddress(.{ .stubs = false }, macho_file);
var buf: [8]u8 = undefined; var buf: [8]u8 = undefined;
std.mem.writeInt(u64, &buf, value, .little); std.mem.writeInt(u64, &buf, value, .little);
@ -63,29 +64,14 @@ pub const ZigGotSection = struct {
} }
pub fn writeAll(zig_got: ZigGotSection, macho_file: *MachO, writer: anytype) !void { pub fn writeAll(zig_got: ZigGotSection, macho_file: *MachO, writer: anytype) !void {
const zo = macho_file.getZigObject().?;
for (zig_got.entries.items) |entry| { for (zig_got.entries.items) |entry| {
const symbol = macho_file.getSymbol(entry); const symbol = zo.symbols.items[entry];
const value = symbol.address(.{ .stubs = false }, macho_file); const value = symbol.address(.{ .stubs = false }, macho_file);
try writer.writeInt(u64, value, .little); try writer.writeInt(u64, value, .little);
} }
} }
pub fn addDyldRelocs(zig_got: ZigGotSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const seg_id = macho_file.sections.items(.segment_id)[macho_file.zig_got_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (0..zig_got.entries.items.len) |idx| {
const addr = zig_got.entryAddress(@intCast(idx), macho_file);
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
}
}
const FormatCtx = struct { const FormatCtx = struct {
zig_got: ZigGotSection, zig_got: ZigGotSection,
macho_file: *MachO, macho_file: *MachO,
@ -103,22 +89,25 @@ pub const ZigGotSection = struct {
) !void { ) !void {
_ = options; _ = options;
_ = unused_fmt_string; _ = unused_fmt_string;
const zig_got = ctx.zig_got;
const macho_file = ctx.macho_file;
try writer.writeAll("__zig_got\n"); try writer.writeAll("__zig_got\n");
for (ctx.zig_got.entries.items, 0..) |entry, index| { for (zig_got.entries.items, 0..) |entry, index| {
const symbol = ctx.macho_file.getSymbol(entry); const zo = macho_file.getZigObject().?;
const symbol = zo.symbols.items[entry];
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
index, index,
ctx.zig_got.entryAddress(@intCast(index), ctx.macho_file), zig_got.entryAddress(@intCast(index), macho_file),
entry, entry,
symbol.getAddress(.{}, ctx.macho_file), symbol.getAddress(.{}, macho_file),
symbol.getName(ctx.macho_file), symbol.getName(macho_file),
}); });
} }
} }
}; };
pub const GotSection = struct { pub const GotSection = struct {
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, symbols: std.ArrayListUnmanaged(MachO.Ref) = .{},
pub const Index = u32; pub const Index = u32;
@ -126,14 +115,14 @@ pub const GotSection = struct {
got.symbols.deinit(allocator); got.symbols.deinit(allocator);
} }
pub fn addSymbol(got: *GotSection, sym_index: Symbol.Index, macho_file: *MachO) !void { pub fn addSymbol(got: *GotSection, ref: MachO.Ref, macho_file: *MachO) !void {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const index = @as(Index, @intCast(got.symbols.items.len)); const index = @as(Index, @intCast(got.symbols.items.len));
const entry = try got.symbols.addOne(gpa); const entry = try got.symbols.addOne(gpa);
entry.* = sym_index; entry.* = ref;
const symbol = macho_file.getSymbol(sym_index); const symbol = ref.getSymbol(macho_file).?;
symbol.flags.has_got = true; symbol.flags.has_got = true;
try symbol.addExtra(.{ .got = index }, macho_file); symbol.addExtra(.{ .got = index }, macho_file);
} }
pub fn getAddress(got: GotSection, index: Index, macho_file: *MachO) u64 { pub fn getAddress(got: GotSection, index: Index, macho_file: *MachO) u64 {
@ -146,46 +135,11 @@ pub const GotSection = struct {
return got.symbols.items.len * @sizeOf(u64); return got.symbols.items.len * @sizeOf(u64);
} }
pub fn addDyldRelocs(got: GotSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const seg_id = macho_file.sections.items(.segment_id)[macho_file.got_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (got.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = got.getAddress(@intCast(idx), macho_file);
const entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
try macho_file.bind.entries.append(gpa, entry);
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
}
} else {
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
} else if (sym.flags.interposable) {
try macho_file.bind.entries.append(gpa, entry);
}
}
}
}
pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void { pub fn write(got: GotSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
for (got.symbols.items) |sym_index| { for (got.symbols.items) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
const value = if (sym.flags.import) @as(u64, 0) else sym.getAddress(.{}, macho_file); const value = if (sym.flags.import) @as(u64, 0) else sym.getAddress(.{}, macho_file);
try writer.writeInt(u64, value, .little); try writer.writeInt(u64, value, .little);
} }
@ -208,12 +162,12 @@ pub const GotSection = struct {
) !void { ) !void {
_ = options; _ = options;
_ = unused_fmt_string; _ = unused_fmt_string;
for (ctx.got.symbols.items, 0..) |entry, i| { for (ctx.got.symbols.items, 0..) |ref, i| {
const symbol = ctx.macho_file.getSymbol(entry); const symbol = ref.getSymbol(ctx.macho_file).?;
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
i, i,
symbol.getGotAddress(ctx.macho_file), symbol.getGotAddress(ctx.macho_file),
entry, ref,
symbol.getAddress(.{}, ctx.macho_file), symbol.getAddress(.{}, ctx.macho_file),
symbol.getName(ctx.macho_file), symbol.getName(ctx.macho_file),
}); });
@ -222,7 +176,7 @@ pub const GotSection = struct {
}; };
pub const StubsSection = struct { pub const StubsSection = struct {
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, symbols: std.ArrayListUnmanaged(MachO.Ref) = .{},
pub const Index = u32; pub const Index = u32;
@ -230,13 +184,13 @@ pub const StubsSection = struct {
stubs.symbols.deinit(allocator); stubs.symbols.deinit(allocator);
} }
pub fn addSymbol(stubs: *StubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void { pub fn addSymbol(stubs: *StubsSection, ref: MachO.Ref, macho_file: *MachO) !void {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const index = @as(Index, @intCast(stubs.symbols.items.len)); const index = @as(Index, @intCast(stubs.symbols.items.len));
const entry = try stubs.symbols.addOne(gpa); const entry = try stubs.symbols.addOne(gpa);
entry.* = sym_index; entry.* = ref;
const symbol = macho_file.getSymbol(sym_index); const symbol = ref.getSymbol(macho_file).?;
try symbol.addExtra(.{ .stubs = index }, macho_file); symbol.addExtra(.{ .stubs = index }, macho_file);
} }
pub fn getAddress(stubs: StubsSection, index: Index, macho_file: *MachO) u64 { pub fn getAddress(stubs: StubsSection, index: Index, macho_file: *MachO) u64 {
@ -256,8 +210,8 @@ pub const StubsSection = struct {
const cpu_arch = macho_file.getTarget().cpu.arch; const cpu_arch = macho_file.getTarget().cpu.arch;
const laptr_sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?]; const laptr_sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
for (stubs.symbols.items, 0..) |sym_index, idx| { for (stubs.symbols.items, 0..) |ref, idx| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
const source = sym.getAddress(.{ .stubs = true }, macho_file); const source = sym.getAddress(.{ .stubs = true }, macho_file);
const target = laptr_sect.addr + idx * @sizeOf(u64); const target = laptr_sect.addr + idx * @sizeOf(u64);
switch (cpu_arch) { switch (cpu_arch) {
@ -299,12 +253,12 @@ pub const StubsSection = struct {
) !void { ) !void {
_ = options; _ = options;
_ = unused_fmt_string; _ = unused_fmt_string;
for (ctx.stubs.symbols.items, 0..) |entry, i| { for (ctx.stubs.symbols.items, 0..) |ref, i| {
const symbol = ctx.macho_file.getSymbol(entry); const symbol = ref.getSymbol(ctx.macho_file).?;
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
i, i,
symbol.getStubsAddress(ctx.macho_file), symbol.getStubsAddress(ctx.macho_file),
entry, ref,
symbol.getAddress(.{}, ctx.macho_file), symbol.getAddress(.{}, ctx.macho_file),
symbol.getName(ctx.macho_file), symbol.getName(ctx.macho_file),
}); });
@ -335,8 +289,8 @@ pub const StubsHelperSection = struct {
_ = stubs_helper; _ = stubs_helper;
const cpu_arch = macho_file.getTarget().cpu.arch; const cpu_arch = macho_file.getTarget().cpu.arch;
var s: usize = preambleSize(cpu_arch); var s: usize = preambleSize(cpu_arch);
for (macho_file.stubs.symbols.items) |sym_index| { for (macho_file.stubs.symbols.items) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
if (sym.flags.weak) continue; if (sym.flags.weak) continue;
s += entrySize(cpu_arch); s += entrySize(cpu_arch);
} }
@ -355,8 +309,8 @@ pub const StubsHelperSection = struct {
const entry_size = entrySize(cpu_arch); const entry_size = entrySize(cpu_arch);
var idx: usize = 0; var idx: usize = 0;
for (macho_file.stubs.symbols.items) |sym_index| { for (macho_file.stubs.symbols.items) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
if (sym.flags.weak) continue; if (sym.flags.weak) continue;
const offset = macho_file.lazy_bind.offsets.items[idx]; const offset = macho_file.lazy_bind.offsets.items[idx];
const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx); const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx);
@ -390,14 +344,15 @@ pub const StubsHelperSection = struct {
fn writePreamble(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void { fn writePreamble(stubs_helper: StubsHelperSection, macho_file: *MachO, writer: anytype) !void {
_ = stubs_helper; _ = stubs_helper;
const obj = macho_file.getInternalObject().?;
const cpu_arch = macho_file.getTarget().cpu.arch; const cpu_arch = macho_file.getTarget().cpu.arch;
const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?]; const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
const dyld_private_addr = target: { const dyld_private_addr = target: {
const sym = macho_file.getSymbol(macho_file.dyld_private_index.?); const sym = obj.getDyldPrivateRef(macho_file).?.getSymbol(macho_file).?;
break :target sym.getAddress(.{}, macho_file); break :target sym.getAddress(.{}, macho_file);
}; };
const dyld_stub_binder_addr = target: { const dyld_stub_binder_addr = target: {
const sym = macho_file.getSymbol(macho_file.dyld_stub_binder_index.?); const sym = obj.getDyldStubBinderRef(macho_file).?.getSymbol(macho_file).?;
break :target sym.getGotAddress(macho_file); break :target sym.getGotAddress(macho_file);
}; };
switch (cpu_arch) { switch (cpu_arch) {
@ -446,49 +401,6 @@ pub const LaSymbolPtrSection = struct {
return macho_file.stubs.symbols.items.len * @sizeOf(u64); return macho_file.stubs.symbols.items.len * @sizeOf(u64);
} }
pub fn addDyldRelocs(laptr: LaSymbolPtrSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
_ = laptr;
const gpa = macho_file.base.comp.gpa;
const sect = macho_file.sections.items(.header)[macho_file.la_symbol_ptr_sect_index.?];
const seg_id = macho_file.sections.items(.segment_id)[macho_file.la_symbol_ptr_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (macho_file.stubs.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = sect.addr + idx * @sizeOf(u64);
const rebase_entry = Rebase.Entry{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
};
const bind_entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
if (sym.flags.weak) {
try macho_file.bind.entries.append(gpa, bind_entry);
try macho_file.weak_bind.entries.append(gpa, bind_entry);
} else {
try macho_file.lazy_bind.entries.append(gpa, bind_entry);
try macho_file.rebase.entries.append(gpa, rebase_entry);
}
} else {
if (sym.flags.weak) {
try macho_file.rebase.entries.append(gpa, rebase_entry);
try macho_file.weak_bind.entries.append(gpa, bind_entry);
} else if (sym.flags.interposable) {
try macho_file.lazy_bind.entries.append(gpa, bind_entry);
try macho_file.rebase.entries.append(gpa, rebase_entry);
}
}
}
}
pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void { pub fn write(laptr: LaSymbolPtrSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
@ -496,8 +408,8 @@ pub const LaSymbolPtrSection = struct {
const cpu_arch = macho_file.getTarget().cpu.arch; const cpu_arch = macho_file.getTarget().cpu.arch;
const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?]; const sect = macho_file.sections.items(.header)[macho_file.stubs_helper_sect_index.?];
var stub_helper_idx: u32 = 0; var stub_helper_idx: u32 = 0;
for (macho_file.stubs.symbols.items) |sym_index| { for (macho_file.stubs.symbols.items) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
if (sym.flags.weak) { if (sym.flags.weak) {
const value = sym.getAddress(.{ .stubs = false }, macho_file); const value = sym.getAddress(.{ .stubs = false }, macho_file);
try writer.writeInt(u64, @intCast(value), .little); try writer.writeInt(u64, @intCast(value), .little);
@ -512,7 +424,7 @@ pub const LaSymbolPtrSection = struct {
}; };
pub const TlvPtrSection = struct { pub const TlvPtrSection = struct {
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, symbols: std.ArrayListUnmanaged(MachO.Ref) = .{},
pub const Index = u32; pub const Index = u32;
@ -520,13 +432,13 @@ pub const TlvPtrSection = struct {
tlv.symbols.deinit(allocator); tlv.symbols.deinit(allocator);
} }
pub fn addSymbol(tlv: *TlvPtrSection, sym_index: Symbol.Index, macho_file: *MachO) !void { pub fn addSymbol(tlv: *TlvPtrSection, ref: MachO.Ref, macho_file: *MachO) !void {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const index = @as(Index, @intCast(tlv.symbols.items.len)); const index = @as(Index, @intCast(tlv.symbols.items.len));
const entry = try tlv.symbols.addOne(gpa); const entry = try tlv.symbols.addOne(gpa);
entry.* = sym_index; entry.* = ref;
const symbol = macho_file.getSymbol(sym_index); const symbol = ref.getSymbol(macho_file).?;
try symbol.addExtra(.{ .tlv_ptr = index }, macho_file); symbol.addExtra(.{ .tlv_ptr = index }, macho_file);
} }
pub fn getAddress(tlv: TlvPtrSection, index: Index, macho_file: *MachO) u64 { pub fn getAddress(tlv: TlvPtrSection, index: Index, macho_file: *MachO) u64 {
@ -539,47 +451,12 @@ pub const TlvPtrSection = struct {
return tlv.symbols.items.len * @sizeOf(u64); return tlv.symbols.items.len * @sizeOf(u64);
} }
pub fn addDyldRelocs(tlv: TlvPtrSection, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const gpa = macho_file.base.comp.gpa;
const seg_id = macho_file.sections.items(.segment_id)[macho_file.tlv_ptr_sect_index.?];
const seg = macho_file.segments.items[seg_id];
for (tlv.symbols.items, 0..) |sym_index, idx| {
const sym = macho_file.getSymbol(sym_index);
const addr = tlv.getAddress(@intCast(idx), macho_file);
const entry = bind.Entry{
.target = sym_index,
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
.addend = 0,
};
if (sym.flags.import) {
try macho_file.bind.entries.append(gpa, entry);
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
}
} else {
try macho_file.rebase.entries.append(gpa, .{
.offset = addr - seg.vmaddr,
.segment_id = seg_id,
});
if (sym.flags.weak) {
try macho_file.weak_bind.entries.append(gpa, entry);
} else if (sym.flags.interposable) {
try macho_file.bind.entries.append(gpa, entry);
}
}
}
}
pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void { pub fn write(tlv: TlvPtrSection, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
for (tlv.symbols.items) |sym_index| { for (tlv.symbols.items) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
if (sym.flags.import) { if (sym.flags.import) {
try writer.writeInt(u64, 0, .little); try writer.writeInt(u64, 0, .little);
} else { } else {
@ -605,12 +482,12 @@ pub const TlvPtrSection = struct {
) !void { ) !void {
_ = options; _ = options;
_ = unused_fmt_string; _ = unused_fmt_string;
for (ctx.tlv.symbols.items, 0..) |entry, i| { for (ctx.tlv.symbols.items, 0..) |ref, i| {
const symbol = ctx.macho_file.getSymbol(entry); const symbol = ref.getSymbol(ctx.macho_file).?;
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
i, i,
symbol.getTlvPtrAddress(ctx.macho_file), symbol.getTlvPtrAddress(ctx.macho_file),
entry, ref,
symbol.getAddress(.{}, ctx.macho_file), symbol.getAddress(.{}, ctx.macho_file),
symbol.getName(ctx.macho_file), symbol.getName(ctx.macho_file),
}); });
@ -619,7 +496,7 @@ pub const TlvPtrSection = struct {
}; };
pub const ObjcStubsSection = struct { pub const ObjcStubsSection = struct {
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, symbols: std.ArrayListUnmanaged(MachO.Ref) = .{},
pub fn deinit(objc: *ObjcStubsSection, allocator: Allocator) void { pub fn deinit(objc: *ObjcStubsSection, allocator: Allocator) void {
objc.symbols.deinit(allocator); objc.symbols.deinit(allocator);
@ -633,13 +510,13 @@ pub const ObjcStubsSection = struct {
}; };
} }
pub fn addSymbol(objc: *ObjcStubsSection, sym_index: Symbol.Index, macho_file: *MachO) !void { pub fn addSymbol(objc: *ObjcStubsSection, ref: MachO.Ref, macho_file: *MachO) !void {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const index = @as(Index, @intCast(objc.symbols.items.len)); const index = @as(Index, @intCast(objc.symbols.items.len));
const entry = try objc.symbols.addOne(gpa); const entry = try objc.symbols.addOne(gpa);
entry.* = sym_index; entry.* = ref;
const symbol = macho_file.getSymbol(sym_index); const symbol = ref.getSymbol(macho_file).?;
try symbol.addExtra(.{ .objc_stubs = index }, macho_file); symbol.addExtra(.{ .objc_stubs = index }, macho_file);
} }
pub fn getAddress(objc: ObjcStubsSection, index: Index, macho_file: *MachO) u64 { pub fn getAddress(objc: ObjcStubsSection, index: Index, macho_file: *MachO) u64 {
@ -656,8 +533,10 @@ pub const ObjcStubsSection = struct {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
for (objc.symbols.items, 0..) |sym_index, idx| { const obj = macho_file.getInternalObject().?;
const sym = macho_file.getSymbol(sym_index);
for (objc.symbols.items, 0..) |ref, idx| {
const sym = ref.getSymbol(macho_file).?;
const addr = objc.getAddress(@intCast(idx), macho_file); const addr = objc.getAddress(@intCast(idx), macho_file);
switch (macho_file.getTarget().cpu.arch) { switch (macho_file.getTarget().cpu.arch) {
.x86_64 => { .x86_64 => {
@ -669,7 +548,7 @@ pub const ObjcStubsSection = struct {
} }
try writer.writeAll(&.{ 0xff, 0x25 }); try writer.writeAll(&.{ 0xff, 0x25 });
{ {
const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?); const target_sym = obj.getObjcMsgSendRef(macho_file).?.getSymbol(macho_file).?;
const target = target_sym.getGotAddress(macho_file); const target = target_sym.getGotAddress(macho_file);
const source = addr + 7; const source = addr + 7;
try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little); try writer.writeInt(i32, @intCast(target - source - 2 - 4), .little);
@ -689,7 +568,7 @@ pub const ObjcStubsSection = struct {
); );
} }
{ {
const target_sym = macho_file.getSymbol(macho_file.objc_msg_send_index.?); const target_sym = obj.getObjcMsgSendRef(macho_file).?.getSymbol(macho_file).?;
const target = target_sym.getGotAddress(macho_file); const target = target_sym.getGotAddress(macho_file);
const source = addr + 2 * @sizeOf(u32); const source = addr + 2 * @sizeOf(u32);
const pages = try aarch64.calcNumberOfPages(@intCast(source), @intCast(target)); const pages = try aarch64.calcNumberOfPages(@intCast(source), @intCast(target));
@ -728,12 +607,12 @@ pub const ObjcStubsSection = struct {
) !void { ) !void {
_ = options; _ = options;
_ = unused_fmt_string; _ = unused_fmt_string;
for (ctx.objc.symbols.items, 0..) |entry, i| { for (ctx.objc.symbols.items, 0..) |ref, i| {
const symbol = ctx.macho_file.getSymbol(entry); const symbol = ref.getSymbol(ctx.macho_file).?;
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{ try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
i, i,
symbol.getObjcStubsAddress(ctx.macho_file), symbol.getObjcStubsAddress(ctx.macho_file),
entry, ref,
symbol.getAddress(.{}, ctx.macho_file), symbol.getAddress(.{}, ctx.macho_file),
symbol.getName(ctx.macho_file), symbol.getName(ctx.macho_file),
}); });
@ -749,44 +628,112 @@ pub const Indsymtab = struct {
return @intCast(macho_file.stubs.symbols.items.len * 2 + macho_file.got.symbols.items.len); return @intCast(macho_file.stubs.symbols.items.len * 2 + macho_file.got.symbols.items.len);
} }
pub fn updateSize(ind: *Indsymtab, macho_file: *MachO) !void {
macho_file.dysymtab_cmd.nindirectsyms = ind.nsyms(macho_file);
}
pub fn write(ind: Indsymtab, macho_file: *MachO, writer: anytype) !void { pub fn write(ind: Indsymtab, macho_file: *MachO, writer: anytype) !void {
const tracy = trace(@src()); const tracy = trace(@src());
defer tracy.end(); defer tracy.end();
_ = ind; _ = ind;
for (macho_file.stubs.symbols.items) |sym_index| { for (macho_file.stubs.symbols.items) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little); try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
} }
for (macho_file.got.symbols.items) |sym_index| { for (macho_file.got.symbols.items) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little); try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
} }
for (macho_file.stubs.symbols.items) |sym_index| { for (macho_file.stubs.symbols.items) |ref| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little); try writer.writeInt(u32, sym.getOutputSymtabIndex(macho_file).?, .little);
} }
} }
}; };
pub const RebaseSection = Rebase; pub const DataInCode = struct {
pub const BindSection = bind.Bind; entries: std.ArrayListUnmanaged(Entry) = .{},
pub const WeakBindSection = bind.WeakBind;
pub const LazyBindSection = bind.LazyBind; pub fn deinit(dice: *DataInCode, allocator: Allocator) void {
pub const ExportTrieSection = Trie; dice.entries.deinit(allocator);
}
pub fn size(dice: DataInCode) usize {
return dice.entries.items.len * @sizeOf(macho.data_in_code_entry);
}
pub fn updateSize(dice: *DataInCode, macho_file: *MachO) !void {
const gpa = macho_file.base.comp.gpa;
for (macho_file.objects.items) |index| {
const object = macho_file.getFile(index).?.object;
const dices = object.getDataInCode();
try dice.entries.ensureUnusedCapacity(gpa, dices.len);
var next_dice: usize = 0;
for (object.getAtoms()) |atom_index| {
if (next_dice >= dices.len) break;
const atom = object.getAtom(atom_index) orelse continue;
const start_off = atom.getInputAddress(macho_file);
const end_off = start_off + atom.size;
const start_dice = next_dice;
if (end_off < dices[next_dice].offset) continue;
while (next_dice < dices.len and
dices[next_dice].offset < end_off) : (next_dice += 1)
{}
if (atom.flags.alive) for (dices[start_dice..next_dice]) |d| {
dice.entries.appendAssumeCapacity(.{
.atom_ref = .{ .index = atom_index, .file = index },
.offset = @intCast(d.offset - start_off),
.length = d.length,
.kind = d.kind,
});
};
}
}
macho_file.data_in_code_cmd.datasize = math.cast(u32, dice.size()) orelse return error.Overflow;
}
pub fn write(dice: DataInCode, macho_file: *MachO, writer: anytype) !void {
const base_address = if (!macho_file.base.isRelocatable())
macho_file.getTextSegment().vmaddr
else
0;
for (dice.entries.items) |entry| {
const atom_address = entry.atom_ref.getAtom(macho_file).?.getAddress(macho_file);
const offset = atom_address + entry.offset - base_address;
try writer.writeStruct(macho.data_in_code_entry{
.offset = @intCast(offset),
.length = entry.length,
.kind = entry.kind,
});
}
}
const Entry = struct {
atom_ref: MachO.Ref,
offset: u32,
length: u16,
kind: u16,
};
};
const aarch64 = @import("../aarch64.zig"); const aarch64 = @import("../aarch64.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const bind = @import("dyld_info/bind.zig"); const macho = std.macho;
const math = std.math; const math = std.math;
const std = @import("std"); const std = @import("std");
const trace = @import("../../tracy.zig").trace; const trace = @import("../../tracy.zig").trace;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const MachO = @import("../MachO.zig"); const MachO = @import("../MachO.zig");
const Rebase = @import("dyld_info/Rebase.zig");
const Symbol = @import("Symbol.zig"); const Symbol = @import("Symbol.zig");
const Trie = @import("dyld_info/Trie.zig");

View File

@ -5,55 +5,45 @@ pub fn createThunks(sect_id: u8, macho_file: *MachO) !void {
const gpa = macho_file.base.comp.gpa; const gpa = macho_file.base.comp.gpa;
const slice = macho_file.sections.slice(); const slice = macho_file.sections.slice();
const header = &slice.items(.header)[sect_id]; const header = &slice.items(.header)[sect_id];
const thnks = &slice.items(.thunks)[sect_id];
const atoms = slice.items(.atoms)[sect_id].items; const atoms = slice.items(.atoms)[sect_id].items;
assert(atoms.len > 0); assert(atoms.len > 0);
for (atoms) |atom_index| { for (atoms) |ref| {
macho_file.getAtom(atom_index).?.value = @bitCast(@as(i64, -1)); ref.getAtom(macho_file).?.value = @bitCast(@as(i64, -1));
} }
var i: usize = 0; var i: usize = 0;
while (i < atoms.len) { while (i < atoms.len) {
const start = i; const start = i;
const start_atom = macho_file.getAtom(atoms[start]).?; const start_atom = atoms[start].getAtom(macho_file).?;
assert(start_atom.flags.alive); assert(start_atom.flags.alive);
start_atom.value = try advance(header, start_atom.size, start_atom.alignment); start_atom.value = advance(header, start_atom.size, start_atom.alignment);
i += 1; i += 1;
while (i < atoms.len and while (i < atoms.len and
header.size - start_atom.value < max_allowed_distance) : (i += 1) header.size - start_atom.value < max_allowed_distance) : (i += 1)
{ {
const atom_index = atoms[i]; const atom = atoms[i].getAtom(macho_file).?;
const atom = macho_file.getAtom(atom_index).?;
assert(atom.flags.alive); assert(atom.flags.alive);
atom.value = try advance(header, atom.size, atom.alignment); atom.value = advance(header, atom.size, atom.alignment);
} }
// Insert a thunk at the group end // Insert a thunk at the group end
const thunk_index = try macho_file.addThunk(); const thunk_index = try macho_file.addThunk();
const thunk = macho_file.getThunk(thunk_index); const thunk = macho_file.getThunk(thunk_index);
thunk.out_n_sect = sect_id; thunk.out_n_sect = sect_id;
try thnks.append(gpa, thunk_index);
// Scan relocs in the group and create trampolines for any unreachable callsite // Scan relocs in the group and create trampolines for any unreachable callsite
for (atoms[start..i]) |atom_index| { try scanRelocs(thunk_index, gpa, atoms[start..i], macho_file);
const atom = macho_file.getAtom(atom_index).?; thunk.value = advance(header, thunk.size(), .@"4");
log.debug("atom({d}) {s}", .{ atom_index, atom.getName(macho_file) });
for (atom.getRelocs(macho_file)) |rel| {
if (rel.type != .branch) continue;
if (isReachable(atom, rel, macho_file)) continue;
try thunk.symbols.put(gpa, rel.target, {});
}
try atom.addExtra(.{ .thunk = thunk_index }, macho_file);
atom.flags.thunk = true;
}
thunk.value = try advance(header, thunk.size(), .@"4");
log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) }); log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) });
} }
} }
fn advance(sect: *macho.section_64, size: u64, alignment: Atom.Alignment) !u64 { fn advance(sect: *macho.section_64, size: u64, alignment: Atom.Alignment) u64 {
const offset = alignment.forward(sect.size); const offset = alignment.forward(sect.size);
const padding = offset - sect.size; const padding = offset - sect.size;
sect.size += padding + size; sect.size += padding + size;
@ -61,14 +51,32 @@ fn advance(sect: *macho.section_64, size: u64, alignment: Atom.Alignment) !u64 {
return offset; return offset;
} }
fn scanRelocs(thunk_index: Thunk.Index, gpa: Allocator, atoms: []const MachO.Ref, macho_file: *MachO) !void {
const tracy = trace(@src());
defer tracy.end();
const thunk = macho_file.getThunk(thunk_index);
for (atoms) |ref| {
const atom = ref.getAtom(macho_file).?;
log.debug("atom({d}) {s}", .{ atom.atom_index, atom.getName(macho_file) });
for (atom.getRelocs(macho_file)) |rel| {
if (rel.type != .branch) continue;
if (isReachable(atom, rel, macho_file)) continue;
try thunk.symbols.put(gpa, rel.getTargetSymbolRef(atom.*, macho_file), {});
}
atom.addExtra(.{ .thunk = thunk_index }, macho_file);
}
}
fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
const target = rel.getTargetSymbol(macho_file); const target = rel.getTargetSymbol(atom.*, macho_file);
if (target.flags.stubs or target.flags.objc_stubs) return false; if (target.flags.stubs or target.flags.objc_stubs) return false;
if (atom.out_n_sect != target.out_n_sect) return false; if (atom.out_n_sect != target.getOutputSectionIndex(macho_file)) return false;
const target_atom = target.getAtom(macho_file).?; const target_atom = target.getAtom(macho_file).?;
if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false;
const saddr = @as(i64, @intCast(atom.getAddress(macho_file))) + @as(i64, @intCast(rel.offset - atom.off)); const saddr = @as(i64, @intCast(atom.getAddress(macho_file))) + @as(i64, @intCast(rel.offset - atom.off));
const taddr: i64 = @intCast(rel.getTargetAddress(macho_file)); const taddr: i64 = @intCast(rel.getTargetAddress(atom.*, macho_file));
_ = math.cast(i28, taddr + rel.addend - saddr) orelse return false; _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false;
return true; return true;
} }
@ -76,7 +84,7 @@ fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
pub const Thunk = struct { pub const Thunk = struct {
value: u64 = 0, value: u64 = 0,
out_n_sect: u8 = 0, out_n_sect: u8 = 0,
symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{}, symbols: std.AutoArrayHashMapUnmanaged(MachO.Ref, void) = .{},
pub fn deinit(thunk: *Thunk, allocator: Allocator) void { pub fn deinit(thunk: *Thunk, allocator: Allocator) void {
thunk.symbols.deinit(allocator); thunk.symbols.deinit(allocator);
@ -91,13 +99,13 @@ pub const Thunk = struct {
return header.addr + thunk.value; return header.addr + thunk.value;
} }
pub fn getTargetAddress(thunk: Thunk, sym_index: Symbol.Index, macho_file: *MachO) u64 { pub fn getTargetAddress(thunk: Thunk, ref: MachO.Ref, macho_file: *MachO) u64 {
return thunk.getAddress(macho_file) + thunk.symbols.getIndex(sym_index).? * trampoline_size; return thunk.getAddress(macho_file) + thunk.symbols.getIndex(ref).? * trampoline_size;
} }
pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void { pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void {
for (thunk.symbols.keys(), 0..) |sym_index, i| { for (thunk.symbols.keys(), 0..) |ref, i| {
const sym = macho_file.getSymbol(sym_index); const sym = ref.getSymbol(macho_file).?;
const saddr = thunk.getAddress(macho_file) + i * trampoline_size; const saddr = thunk.getAddress(macho_file) + i * trampoline_size;
const taddr = sym.getAddress(.{}, macho_file); const taddr = sym.getAddress(.{}, macho_file);
const pages = try aarch64.calcNumberOfPages(@intCast(saddr), @intCast(taddr)); const pages = try aarch64.calcNumberOfPages(@intCast(saddr), @intCast(taddr));
@ -144,9 +152,9 @@ pub const Thunk = struct {
const thunk = ctx.thunk; const thunk = ctx.thunk;
const macho_file = ctx.macho_file; const macho_file = ctx.macho_file;
try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() }); try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() });
for (thunk.symbols.keys()) |index| { for (thunk.symbols.keys()) |ref| {
const sym = macho_file.getSymbol(index); const sym = ref.getSymbol(macho_file).?;
try writer.print(" %{d} : {s} : @{x}\n", .{ index, sym.getName(macho_file), sym.value }); try writer.print(" {} : {s} : @{x}\n", .{ ref, sym.getName(macho_file), sym.value });
} }
} }

View File

@ -74,8 +74,9 @@ fn addCompileStep(
.target = base.target, .target = base.target,
.optimize = base.optimize, .optimize = base.optimize,
.root_source_file = rsf: { .root_source_file = rsf: {
const name = b.fmt("{s}.zig", .{overlay.name});
const bytes = overlay.zig_source_bytes orelse break :rsf null; const bytes = overlay.zig_source_bytes orelse break :rsf null;
break :rsf b.addWriteFiles().add("a.zig", bytes); break :rsf b.addWriteFiles().add(name, bytes);
}, },
.pic = overlay.pic, .pic = overlay.pic,
.strip = if (base.strip) |s| s else overlay.strip, .strip = if (base.strip) |s| s else overlay.strip,

View File

@ -25,9 +25,12 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
macho_step.dependOn(testLinkingStaticLib(b, .{ .use_llvm = false, .target = x86_64_target })); macho_step.dependOn(testLinkingStaticLib(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testReexportsZig(b, .{ .use_llvm = false, .target = x86_64_target })); macho_step.dependOn(testReexportsZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testRelocatableZig(b, .{ .use_llvm = false, .target = x86_64_target })); macho_step.dependOn(testRelocatableZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testTlsZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testUnresolvedError(b, .{ .use_llvm = false, .target = x86_64_target }));
// Exercise linker with LLVM backend // Exercise linker with LLVM backend
macho_step.dependOn(testDeadStrip(b, .{ .target = default_target })); macho_step.dependOn(testDeadStrip(b, .{ .target = default_target }));
macho_step.dependOn(testDuplicateDefinitions(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyObject(b, .{ .target = default_target })); macho_step.dependOn(testEmptyObject(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyZig(b, .{ .target = default_target })); macho_step.dependOn(testEmptyZig(b, .{ .target = default_target }));
macho_step.dependOn(testEntryPoint(b, .{ .target = default_target })); macho_step.dependOn(testEntryPoint(b, .{ .target = default_target }));
@ -56,7 +59,9 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
macho_step.dependOn(testTentative(b, .{ .target = default_target })); macho_step.dependOn(testTentative(b, .{ .target = default_target }));
macho_step.dependOn(testThunks(b, .{ .target = aarch64_target })); macho_step.dependOn(testThunks(b, .{ .target = aarch64_target }));
macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target })); macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target }));
macho_step.dependOn(testTlsZig(b, .{ .target = default_target }));
macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target })); macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target }));
macho_step.dependOn(testUnresolvedError(b, .{ .target = default_target }));
macho_step.dependOn(testUnwindInfo(b, .{ .target = default_target })); macho_step.dependOn(testUnwindInfo(b, .{ .target = default_target }));
macho_step.dependOn(testUnwindInfoNoSubsectionsX64(b, .{ .target = x86_64_target })); macho_step.dependOn(testUnwindInfoNoSubsectionsX64(b, .{ .target = x86_64_target }));
macho_step.dependOn(testUnwindInfoNoSubsectionsArm64(b, .{ .target = aarch64_target })); macho_step.dependOn(testUnwindInfoNoSubsectionsArm64(b, .{ .target = aarch64_target }));
@ -178,6 +183,37 @@ fn testDeadStrip(b: *Build, opts: Options) *Step {
return test_step; return test_step;
} }
fn testDuplicateDefinitions(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "duplicate-definitions", opts);
const obj = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\var x: usize = 1;
\\export fn strong() void { x += 1; }
\\export fn weak() void { x += 1; }
});
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\var x: usize = 1;
\\export fn strong() void { x += 1; }
\\comptime { @export(weakImpl, .{ .name = "weak", .linkage = .weak }); }
\\fn weakImpl() callconv(.C) void { x += 1; }
\\extern fn weak() void;
\\pub fn main() void {
\\ weak();
\\ strong();
\\}
});
exe.addObject(obj);
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: duplicate symbol definition: _strong",
"note: defined by /?/a.o",
"note: defined by /?/main.o",
} });
return test_step;
}
fn testDeadStripDylibs(b: *Build, opts: Options) *Step { fn testDeadStripDylibs(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dead-strip-dylibs", opts); const test_step = addTestStep(b, "dead-strip-dylibs", opts);
@ -912,7 +948,7 @@ fn testLinksection(b: *Build, opts: Options) *Step {
if (opts.optimize == .Debug) { if (opts.optimize == .Debug) {
check.checkInSymtab(); check.checkInSymtab();
check.checkContains("(__TEXT,__TestGenFnA) _a.testGenericFn__anon_"); check.checkContains("(__TEXT,__TestGenFnA) _main.testGenericFn__anon_");
} }
test_step.dependOn(&check.step); test_step.dependOn(&check.step);
@ -2274,6 +2310,32 @@ fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
return test_step; return test_step;
} }
fn testTlsZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-zig", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\threadlocal var x: i32 = 0;
\\threadlocal var y: i32 = -1;
\\pub fn main() void {
\\ std.io.getStdOut().writer().print("{d} {d}\n", .{x, y}) catch unreachable;
\\ x -= 1;
\\ y += 1;
\\ std.io.getStdOut().writer().print("{d} {d}\n", .{x, y}) catch unreachable;
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\0 -1
\\-1 0
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testTwoLevelNamespace(b: *Build, opts: Options) *Step { fn testTwoLevelNamespace(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "two-level-namespace", opts); const test_step = addTestStep(b, "two-level-namespace", opts);
@ -2471,6 +2533,42 @@ fn testUndefinedFlag(b: *Build, opts: Options) *Step {
return test_step; return test_step;
} }
fn testUnresolvedError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error", opts);
const obj = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\extern fn foo() i32;
\\export fn bar() i32 { return foo() + 1; }
});
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() i32;
\\extern fn bar() i32;
\\pub fn main() void {
\\ std.debug.print("foo() + bar() = {d}", .{foo() + bar()});
\\}
});
exe.addObject(obj);
// TODO order should match across backends if possible
if (opts.use_llvm) {
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: _foo",
"note: referenced by /?/a.o:_bar",
"note: referenced by /?/main.o:_main.main",
} });
} else {
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: _foo",
"note: referenced by /?/main.o:_main.main",
"note: referenced by /?/a.o:__TEXT$__text_zig",
} });
}
return test_step;
}
fn testUnwindInfo(b: *Build, opts: Options) *Step { fn testUnwindInfo(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unwind-info", opts); const test_step = addTestStep(b, "unwind-info", opts);