/// Address offset allocated for this Atom wrt to its section start address. value: u64 = 0, /// Name of this Atom. name: MachO.String = .{}, /// Index into linker's input file table. file: File.Index = 0, /// Size of this atom size: u64 = 0, /// Alignment of this atom as a power of two. alignment: Alignment = .@"1", /// Index of the input section. n_sect: u32 = 0, /// Index of the output section. out_n_sect: u8 = 0, /// Offset within the parent section pointed to by n_sect. /// off + size <= parent section size. off: u64 = 0, /// Index of this atom in the linker's atoms table. atom_index: Index = 0, /// Specifies whether this atom is alive or has been garbage collected. alive: AtomicBool = AtomicBool.init(true), /// Specifies if this atom has been visited during garbage collection. visited: AtomicBool = AtomicBool.init(false), /// Points to the previous and next neighbors, based on the `text_offset`. /// This can be used to find, for example, the capacity of this `TextBlock`. prev_index: Index = 0, next_index: Index = 0, extra: u32 = 0, pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 { return switch (self.getFile(macho_file)) { .dylib => unreachable, inline else => |x| x.getString(self.name), }; } pub fn getFile(self: Atom, macho_file: *MachO) File { return macho_file.getFile(self.file).?; } pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation { return switch (self.getFile(macho_file)) { .dylib => unreachable, inline else => |x| x.getAtomRelocs(self, macho_file), }; } pub fn getInputSection(self: Atom, macho_file: *MachO) macho.section_64 { return switch (self.getFile(macho_file)) { .dylib => unreachable, .zig_object => |x| x.getInputSection(self, macho_file), .object => |x| x.sections.items(.header)[self.n_sect], .internal => |x| x.sections.items(.header)[self.n_sect], }; } pub fn getInputAddress(self: Atom, macho_file: *MachO) u64 { return self.getInputSection(macho_file).addr + self.off; } pub fn getAddress(self: Atom, macho_file: *MachO) u64 { const header = macho_file.sections.items(.header)[self.out_n_sect]; return header.addr + self.value; } pub fn getPriority(self: Atom, macho_file: *MachO) u64 { const file = self.getFile(macho_file); return (@as(u64, @intCast(file.getIndex())) << 32) | @as(u64, @intCast(self.n_sect)); } pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index { const extra = self.getExtra(macho_file); return switch (self.getFile(macho_file)) { .dylib => unreachable, .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 { const object = self.getFile(macho_file).object; for (self.getUnwindRecords(macho_file)) |cu_index| { const cu = object.getUnwindRecord(cu_index); cu.alive = false; if (cu.getFdePtr(macho_file)) |fde| { fde.alive = false; } } } pub fn isAlive(self: Atom) bool { return self.alive.load(.seq_cst); } pub fn setAlive(self: *Atom, alive: bool) void { _ = self.alive.swap(alive, .seq_cst); } pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk { const extra = self.getExtra(macho_file); return macho_file.getThunk(extra.thunk); } const AddExtraOpts = struct { thunk: ?u32 = null, rel_index: ?u32 = null, rel_count: ?u32 = null, rel_out_index: ?u32 = null, rel_out_count: ?u32 = null, unwind_index: ?u32 = null, unwind_count: ?u32 = null, literal_pool_index: ?u32 = null, literal_symbol_index: ?u32 = null, }; pub fn addExtra(atom: *Atom, opts: AddExtraOpts, macho_file: *MachO) void { const file = atom.getFile(macho_file); var extra = file.getAtomExtra(atom.extra); inline for (@typeInfo(@TypeOf(opts)).@"struct".fields) |field| { if (@field(opts, field.name)) |x| { @field(extra, field.name) = x; } } file.setAtomExtra(atom.extra, extra); } pub inline fn getExtra(atom: Atom, macho_file: *MachO) Extra { return atom.getFile(macho_file).getAtomExtra(atom.extra); } pub inline fn setExtra(atom: Atom, extra: Extra, macho_file: *MachO) void { atom.getFile(macho_file).setAtomExtra(atom.extra, extra); } pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { if (macho_file.base.isRelocatable()) { const osec = macho_file.getSectionByName(sect.segName(), sect.sectName()) orelse try macho_file.addSection( sect.segName(), sect.sectName(), .{ .flags = sect.flags }, ); return osec; } const segname, const sectname, const flags = blk: { if (sect.isCode()) break :blk .{ "__TEXT", "__text", macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, }; switch (sect.type()) { macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS, => break :blk .{ "__TEXT", "__const", macho.S_REGULAR }, macho.S_CSTRING_LITERALS => { if (mem.startsWith(u8, sect.sectName(), "__objc")) break :blk .{ sect.segName(), sect.sectName(), macho.S_REGULAR, }; break :blk .{ "__TEXT", "__cstring", macho.S_CSTRING_LITERALS }; }, macho.S_MOD_INIT_FUNC_POINTERS, macho.S_MOD_TERM_FUNC_POINTERS, => break :blk .{ "__DATA_CONST", sect.sectName(), sect.flags }, macho.S_LITERAL_POINTERS, macho.S_ZEROFILL, macho.S_GB_ZEROFILL, macho.S_THREAD_LOCAL_VARIABLES, macho.S_THREAD_LOCAL_VARIABLE_POINTERS, macho.S_THREAD_LOCAL_REGULAR, macho.S_THREAD_LOCAL_ZEROFILL, => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, macho.S_COALESCED => break :blk .{ sect.segName(), sect.sectName(), macho.S_REGULAR, }, macho.S_REGULAR => { const segname = sect.segName(); const sectname = sect.sectName(); if (mem.eql(u8, segname, "__DATA")) { if (mem.eql(u8, sectname, "__cfstring") or mem.eql(u8, sectname, "__objc_classlist") or mem.eql(u8, sectname, "__objc_imageinfo")) break :blk .{ "__DATA_CONST", sectname, macho.S_REGULAR, }; } break :blk .{ segname, sectname, sect.flags }; }, else => break :blk .{ sect.segName(), sect.sectName(), sect.flags }, } }; return macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection( segname, sectname, .{ .flags = flags }, ); } /// Returns how much room there is to grow in virtual address space. /// File offset relocation happens transparently, so it is not included in /// this calculation. pub fn capacity(self: Atom, macho_file: *MachO) u64 { const zo = macho_file.getZigObject().?; const next_addr = if (zo.getAtom(self.next_index)) |next| next.getAddress(macho_file) else std.math.maxInt(u32); return next_addr - self.getAddress(macho_file); } pub fn freeListEligible(self: Atom, macho_file: *MachO) bool { // No need to keep a free list node for the last block. 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 ideal_cap = MachO.padToIdeal(self.size); if (cap <= ideal_cap) return false; const surplus = cap - ideal_cap; return surplus >= MachO.min_text_capacity; } 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 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 new_atom_ideal_capacity = MachO.padToIdeal(self.size); // We use these to indicate our intention to update metadata, placing the new atom, // and possibly removing a free list node. // It would be simpler to do it inside the for loop below, but that would cause a // problem if an error was returned later in the function. So this action // is actually carried out at the end of the function, when errors are no longer possible. var atom_placement: ?Atom.Index = null; var free_list_removal: ?usize = null; // First we look for an appropriately sized free list node. // The list is unordered. We'll just take the first thing that works. self.value = blk: { var i: usize = free_list.items.len; while (i < free_list.items.len) { const big_atom_index = free_list.items[i]; const big_atom = zo.getAtom(big_atom_index).?; // We now have a pointer to a live atom that has too much capacity. // Is it enough that we could fit this new atom? const cap = big_atom.capacity(macho_file); const ideal_capacity = MachO.padToIdeal(cap); const ideal_capacity_end_vaddr = std.math.add(u64, big_atom.value, ideal_capacity) catch ideal_capacity; const capacity_end_vaddr = big_atom.value + cap; const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity; const new_start_vaddr = self.alignment.backward(new_start_vaddr_unaligned); if (new_start_vaddr < ideal_capacity_end_vaddr) { // Additional bookkeeping here to notice if this free list node // should be deleted because the block that it points to has grown to take up // more of the extra capacity. if (!big_atom.freeListEligible(macho_file)) { _ = free_list.swapRemove(i); } else { i += 1; } continue; } // At this point we know that we will place the new block here. But the // remaining question is whether there is still yet enough capacity left // over for there to still be a free list node. const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; const keep_free_list_node = remaining_capacity >= MachO.min_text_capacity; // Set up the metadata to be updated, after errors are no longer possible. atom_placement = big_atom_index; if (!keep_free_list_node) { free_list_removal = i; } break :blk new_start_vaddr; } else if (zo.getAtom(last_atom_index.*)) |last| { const ideal_capacity = MachO.padToIdeal(last.size); const ideal_capacity_end_vaddr = last.value + ideal_capacity; const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr); // Set up the metadata to be updated, after errors are no longer possible. atom_placement = last.atom_index; break :blk new_start_vaddr; } else { break :blk 0; } }; log.debug("allocated atom({d}) : '{s}' at 0x{x} to 0x{x}", .{ self.atom_index, self.getName(macho_file), self.getAddress(macho_file), self.getAddress(macho_file) + self.size, }); const expand_section = if (atom_placement) |placement_index| zo.getAtom(placement_index).?.next_index == 0 else true; if (expand_section) { const needed_size = self.value + self.size; try macho_file.growSection(self.out_n_sect, needed_size); last_atom_index.* = self.atom_index; // const zig_object = macho_file_file.getZigObject().?; // if (zig_object.dwarf) |_| { // // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address // // range of the compilation unit. When we expand the text section, this range changes, // // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty. // zig_object.debug_info_header_dirty = true; // // This becomes dirty for the same reason. We could potentially make this more // // fine-grained with the addition of support for more compilation units. It is planned to // // model each package as a different compilation unit. // zig_object.debug_aranges_section_dirty = true; // } } sect.@"align" = @max(sect.@"align", self.alignment.toLog2Units()); // This function can also reallocate an atom. // In this case we need to "unplug" it from its previous location before // plugging it in to its new location. if (zo.getAtom(self.prev_index)) |prev| { prev.next_index = self.next_index; } if (zo.getAtom(self.next_index)) |next| { next.prev_index = self.prev_index; } if (atom_placement) |big_atom_index| { const big_atom = zo.getAtom(big_atom_index).?; self.prev_index = big_atom_index; self.next_index = big_atom.next_index; big_atom.next_index = self.atom_index; } else { self.prev_index = 0; self.next_index = 0; } if (free_list_removal) |i| { _ = free_list.swapRemove(i); } self.setAlive(true); } pub fn shrink(self: *Atom, macho_file: *MachO) void { _ = self; _ = macho_file; } pub fn grow(self: *Atom, macho_file: *MachO) !void { if (!self.alignment.check(self.value) or self.size > self.capacity(macho_file)) try self.allocate(macho_file); } pub fn free(self: *Atom, macho_file: *MachO) void { log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.getName(macho_file) }); const comp = macho_file.base.comp; const gpa = comp.gpa; const zo = macho_file.getZigObject().?; 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]; var already_have_free_list_node = false; { var i: usize = 0; // TODO turn free_list into a hash map while (i < free_list.items.len) { if (free_list.items[i] == self.atom_index) { _ = free_list.swapRemove(i); continue; } if (free_list.items[i] == self.prev_index) { already_have_free_list_node = true; } i += 1; } } if (zo.getAtom(last_atom_index.*)) |last_atom| { if (last_atom.atom_index == self.atom_index) { if (zo.getAtom(self.prev_index)) |_| { // TODO shrink the section size here last_atom_index.* = self.prev_index; } else { last_atom_index.* = 0; } } } if (zo.getAtom(self.prev_index)) |prev| { prev.next_index = self.next_index; 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 // ignore the OOM here. free_list.append(gpa, prev.atom_index) catch {}; } } else { self.prev_index = 0; } if (zo.getAtom(self.next_index)) |next| { next.prev_index = self.prev_index; } else { self.next_index = 0; } // TODO create relocs free list self.freeRelocs(macho_file); // TODO figure out how to free input section mappind in ZigModule // const zig_object = macho_file.zigObjectPtr().? // assert(zig_object.atoms.swapRemove(self.atom_index)); self.* = .{}; } pub fn addReloc(self: *Atom, macho_file: *MachO, reloc: Relocation) !void { const gpa = macho_file.base.comp.gpa; const file = self.getFile(macho_file); assert(file == .zig_object); var extra = self.getExtra(macho_file); const rels = &file.zig_object.relocs.items[extra.rel_index]; try rels.append(gpa, reloc); extra.rel_count += 1; self.setExtra(extra, macho_file); } pub fn freeRelocs(self: *Atom, macho_file: *MachO) void { self.getFile(macho_file).zig_object.freeAtomRelocs(self.*, macho_file); var extra = self.getExtra(macho_file); extra.rel_count = 0; self.setExtra(extra, macho_file); } pub fn scanRelocs(self: Atom, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); assert(self.isAlive()); const relocs = self.getRelocs(macho_file); for (relocs) |rel| { if (try self.reportUndefSymbol(rel, macho_file)) continue; switch (rel.type) { .branch => { const symbol = rel.getTargetSymbol(self, macho_file); if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) { symbol.setSectionFlags(.{ .stubs = true }); if (symbol.flags.weak) { macho_file.binds_to_weak.store(true, .seq_cst); } } else if (mem.startsWith(u8, symbol.getName(macho_file), "_objc_msgSend$")) { symbol.setSectionFlags(.{ .objc_stubs = true }); } }, .got_load, .got_load_page, .got_load_pageoff, => { const symbol = rel.getTargetSymbol(self, macho_file); if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable or macho_file.getTarget().cpu.arch == .aarch64) // TODO relax on arm64 { symbol.setSectionFlags(.{ .needs_got = true }); if (symbol.flags.weak) { macho_file.binds_to_weak.store(true, .seq_cst); } } }, .got => { rel.getTargetSymbol(self, macho_file).setSectionFlags(.{ .needs_got = true }); }, .tlv, .tlvp_page, .tlvp_pageoff, => { const symbol = rel.getTargetSymbol(self, macho_file); if (!symbol.flags.tlv) { try macho_file.reportParseError2( self.getFile(macho_file).getIndex(), "{s}: illegal thread-local variable reference to regular symbol {s}", .{ self.getName(macho_file), symbol.getName(macho_file) }, ); } if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) { symbol.setSectionFlags(.{ .tlv_ptr = true }); if (symbol.flags.weak) { macho_file.binds_to_weak.store(true, .seq_cst); } } }, .unsigned => { if (rel.meta.length == 3) { // TODO this really should check if this is pointer width if (rel.tag == .@"extern") { const symbol = rel.getTargetSymbol(self, macho_file); if (symbol.isTlvInit(macho_file)) { macho_file.has_tlv.store(true, .seq_cst); continue; } if (symbol.flags.import) { if (symbol.flags.weak) { macho_file.binds_to_weak.store(true, .seq_cst); } continue; } if (symbol.flags.@"export" and symbol.flags.weak) { macho_file.binds_to_weak.store(true, .seq_cst); } } } }, .signed, .signed1, .signed2, .signed4, .page, .pageoff, .subtractor, => {}, } } } fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool { if (rel.tag == .local) return false; const file = self.getFile(macho_file); const ref = file.getSymbolRef(rel.target, macho_file); if (ref.getFile(macho_file) == null) { macho_file.undefs_mutex.lock(); defer macho_file.undefs_mutex.unlock(); const gpa = macho_file.base.comp.gpa; const gop = try macho_file.undefs.getOrPut(gpa, file.getGlobals()[rel.target]); if (!gop.found_existing) { gop.value_ptr.* = .{ .refs = .{} }; } try gop.value_ptr.refs.append(gpa, .{ .index = self.atom_index, .file = self.file }); return true; } return false; } pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void { const tracy = trace(@src()); defer tracy.end(); assert(!self.getInputSection(macho_file).isZerofill()); const file = self.getFile(macho_file); const name = self.getName(macho_file); const relocs = self.getRelocs(macho_file); relocs_log.debug("{x}: {s}", .{ self.value, name }); var has_error = false; var stream: Writer = .fixed(buffer); var i: usize = 0; while (i < relocs.len) : (i += 1) { const rel = relocs[i]; const rel_offset: usize = @intCast(rel.offset - self.off); const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null; if (rel.tag == .@"extern") { if (rel.getTargetSymbol(self, macho_file).getFile(macho_file) == null) continue; } stream.end = rel_offset; self.resolveRelocInner(rel, subtractor, buffer, macho_file, &stream) catch |err| { switch (err) { error.RelaxFail => { const target = switch (rel.tag) { .@"extern" => rel.getTargetSymbol(self, macho_file).getName(macho_file), .local => rel.getTargetAtom(self, macho_file).getName(macho_file), }; try macho_file.reportParseError2( file.getIndex(), "{s}: 0x{x}: 0x{x}: failed to relax relocation: type {f}, target {s}", .{ name, self.getAddress(macho_file), rel.offset, rel.fmtPretty(macho_file.getTarget().cpu.arch), target, }, ); has_error = true; }, error.RelaxFailUnexpectedInstruction => has_error = true, else => |e| return e, } }; } if (has_error) return error.ResolveFailed; } const ResolveError = error{ RelaxFail, RelaxFailUnexpectedInstruction, NoSpaceLeft, DivisionByZero, UnexpectedRemainder, Overflow, OutOfMemory, WriteFailed, }; fn resolveRelocInner( self: Atom, rel: Relocation, subtractor: ?Relocation, code: []u8, macho_file: *MachO, writer: *Writer, ) ResolveError!void { const t = &macho_file.base.comp.root_mod.resolved_target.result; const cpu_arch = t.cpu.arch; const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow; const P = @as(i64, @intCast(self.getAddress(macho_file))) + @as(i64, @intCast(rel_offset)); const A = rel.addend + rel.getRelocAddend(cpu_arch); const S: i64 = @intCast(rel.getTargetAddress(self, macho_file)); const G: i64 = @intCast(rel.getGotTargetAddress(self, macho_file)); const TLS = @as(i64, @intCast(macho_file.getTlsAddress())); const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(self, macho_file))) else 0; const divExact = struct { fn divExact(atom: Atom, r: Relocation, num: u12, den: u12, ctx: *MachO) !u12 { return math.divExact(u12, num, den) catch { try ctx.reportParseError2(atom.getFile(ctx).getIndex(), "{s}: unexpected remainder when resolving {f} at offset 0x{x}", .{ atom.getName(ctx), r.fmtPretty(ctx.getTarget().cpu.arch), r.offset, }); return error.UnexpectedRemainder; }; } }.divExact; switch (rel.tag) { .local => relocs_log.debug(" {x}<+{d}>: {f}: [=> {x}] atom({d})", .{ P, rel_offset, rel.fmtPretty(cpu_arch), S + A - SUB, rel.getTargetAtom(self, macho_file).atom_index, }), .@"extern" => relocs_log.debug(" {x}<+{d}>: {f}: [=> {x}] G({x}) ({s})", .{ P, rel_offset, rel.fmtPretty(cpu_arch), S + A - SUB, G + A, rel.getTargetSymbol(self, macho_file).getName(macho_file), }), } switch (rel.type) { .subtractor => {}, .unsigned => { assert(!rel.meta.pcrel); if (rel.meta.length == 3) { if (rel.tag == .@"extern") { const sym = rel.getTargetSymbol(self, macho_file); if (sym.isTlvInit(macho_file)) { try writer.writeInt(u64, @intCast(S - TLS), .little); return; } if (sym.flags.import) return; } try writer.writeInt(u64, @bitCast(S + A - SUB), .little); } else if (rel.meta.length == 2) { try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little); } else unreachable; }, .got => { assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); assert(rel.meta.pcrel); try writer.writeInt(i32, @intCast(G + A - P), .little); }, .branch => { assert(rel.meta.length == 2); assert(rel.meta.pcrel); assert(rel.tag == .@"extern"); switch (cpu_arch) { .x86_64 => try writer.writeInt(i32, @intCast(S + A - P), .little), .aarch64 => { const disp: i28 = math.cast(i28, S + A - P) orelse blk: { const thunk = self.getThunk(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; }; aarch64.writeBranchImm(disp, code[rel_offset..][0..4]); }, else => unreachable, } }, .got_load => { assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); assert(rel.meta.pcrel); if (rel.getTargetSymbol(self, macho_file).getSectionFlags().has_got) { try writer.writeInt(i32, @intCast(G + A - P), .little); } else { try x86_64.relaxGotLoad(self, code[rel_offset - 3 ..], rel, macho_file); try writer.writeInt(i32, @intCast(S + A - P), .little); } }, .tlv => { assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); assert(rel.meta.pcrel); const sym = rel.getTargetSymbol(self, macho_file); if (sym.getSectionFlags().tlv_ptr) { const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); try writer.writeInt(i32, @intCast(S_ + A - P), .little); } else { try x86_64.relaxTlv(code[rel_offset - 3 ..], t); try writer.writeInt(i32, @intCast(S + A - P), .little); } }, .signed, .signed1, .signed2, .signed4 => { assert(rel.meta.length == 2); assert(rel.meta.pcrel); try writer.writeInt(i32, @intCast(S + A - P), .little); }, .page, .got_load_page, .tlvp_page, => { assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); assert(rel.meta.pcrel); const sym = rel.getTargetSymbol(self, macho_file); const source = math.cast(u64, P) orelse return error.Overflow; const target = target: { const target = switch (rel.type) { .page => S + A, .got_load_page => G + A, .tlvp_page => if (sym.getSectionFlags().tlv_ptr) blk: { const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); break :blk S_ + A; } else S + A, else => unreachable, }; break :target math.cast(u64, target) orelse return error.Overflow; }; aarch64.writeAdrInst(try aarch64.calcNumberOfPages(@intCast(source), @intCast(target)), code[rel_offset..][0..aarch64.encoding.Instruction.size]); }, .pageoff => { assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); assert(!rel.meta.pcrel); const target = math.cast(u64, S + A) orelse return error.Overflow; const inst_code = code[rel_offset..][0..aarch64.encoding.Instruction.size]; var inst: aarch64.encoding.Instruction = .read(inst_code); switch (inst.decode()) { else => unreachable, .data_processing_immediate => aarch64.writeAddImmInst(@truncate(target), inst_code), .load_store => |load_store| { inst.load_store.register_unsigned_immediate.group.imm12 = switch (load_store.register_unsigned_immediate.decode()) { .integer => |integer| try divExact(self, rel, @truncate(target), @as(u4, 1) << @intFromEnum(integer.group.size), macho_file), .vector => |vector| try divExact(self, rel, @truncate(target), @as(u5, 1) << @intFromEnum(vector.group.opc1.decode(vector.group.size)), macho_file), }; try writer.writeInt(u32, @bitCast(inst), .little); }, } }, .got_load_pageoff => { assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); assert(!rel.meta.pcrel); const target = math.cast(u64, G + A) orelse return error.Overflow; aarch64.writeLoadStoreRegInst(try divExact(self, rel, @truncate(target), 8, macho_file), code[rel_offset..][0..4]); }, .tlvp_pageoff => { assert(rel.tag == .@"extern"); assert(rel.meta.length == 2); assert(!rel.meta.pcrel); const sym = rel.getTargetSymbol(self, macho_file); const target = target: { const target = if (sym.getSectionFlags().tlv_ptr) blk: { const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file)); break :blk S_ + A; } else S + A; break :target math.cast(u64, target) orelse return error.Overflow; }; const inst_code = code[rel_offset..][0..4]; const rd, const rn = switch (aarch64.encoding.Instruction.read(inst_code).decode()) { else => unreachable, .data_processing_immediate => |decoded| .{ decoded.add_subtract_immediate.group.Rd.decode(.{ .sp = true }), decoded.add_subtract_immediate.group.Rn.decode(.{ .sp = true }), }, .load_store => |decoded| .{ decoded.register_unsigned_immediate.integer.group.Rt.decode(.{}), decoded.register_unsigned_immediate.group.Rn.decode(.{ .sp = true }), }, }; try writer.writeInt(u32, @bitCast(@as( aarch64.encoding.Instruction, if (sym.getSectionFlags().tlv_ptr) .ldr(rd.x(), .{ .unsigned_offset = .{ .base = rn.x(), .offset = try divExact(self, rel, @truncate(target), 8, macho_file) * 8, } }) else .add(rd.x(), rn.x(), .{ .immediate = @truncate(target) }), )), .little); }, } } const x86_64 = struct { fn relaxGotLoad(self: Atom, code: []u8, rel: Relocation, macho_file: *MachO) ResolveError!void { dev.check(.x86_64_backend); const t = &macho_file.base.comp.root_mod.resolved_target.result; const diags = &macho_file.base.comp.link_diags; const old_inst = disassemble(code) orelse return error.RelaxFail; switch (old_inst.encoding.mnemonic) { .mov => { const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops, t) catch return error.RelaxFail; relocs_log.debug(" relaxing {f} => {f}", .{ old_inst.encoding, inst.encoding }); encode(&.{inst}, code) catch return error.RelaxFail; }, else => |x| { var err = try diags.addErrorWithNotes(2); try err.addMsg("{s}: 0x{x}: 0x{x}: failed to relax relocation of type {f}", .{ self.getName(macho_file), self.getAddress(macho_file), rel.offset, rel.fmtPretty(.x86_64), }); err.addNote("expected .mov instruction but found .{s}", .{@tagName(x)}); err.addNote("while parsing {f}", .{self.getFile(macho_file).fmtPath()}); return error.RelaxFailUnexpectedInstruction; }, } } fn relaxTlv(code: []u8, t: *const std.Target) error{RelaxFail}!void { dev.check(.x86_64_backend); const old_inst = disassemble(code) orelse return error.RelaxFail; switch (old_inst.encoding.mnemonic) { .mov => { const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops, t) catch return error.RelaxFail; relocs_log.debug(" relaxing {f} => {f}", .{ old_inst.encoding, inst.encoding }); encode(&.{inst}, code) catch return error.RelaxFail; }, else => return error.RelaxFail, } } fn disassemble(code: []const u8) ?Instruction { var disas = Disassembler.init(code); const inst = disas.next() catch return null; return inst; } fn encode(insts: []const Instruction, code: []u8) !void { var stream: Writer = .fixed(code); for (insts) |inst| try inst.encode(&stream, .{}); } const bits = @import("../../codegen/x86_64/bits.zig"); const encoder = @import("../../codegen/x86_64/encoder.zig"); const Disassembler = @import("../../codegen/x86_64/Disassembler.zig"); const Immediate = bits.Immediate; const Instruction = encoder.Instruction; }; pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 { const relocs = self.getRelocs(macho_file); switch (macho_file.getTarget().cpu.arch) { .aarch64 => { var nreloc: u32 = 0; for (relocs) |rel| { nreloc += 1; switch (rel.type) { .page, .pageoff => if (rel.addend > 0) { nreloc += 1; }, else => {}, } } return nreloc; }, .x86_64 => return @intCast(relocs.len), else => unreachable, } } pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: []macho.relocation_info) error{ LinkFailure, OutOfMemory }!void { const tracy = trace(@src()); defer tracy.end(); relocs_log.debug("{x}: {s}", .{ self.getAddress(macho_file), self.getName(macho_file) }); const cpu_arch = macho_file.getTarget().cpu.arch; const relocs = self.getRelocs(macho_file); var i: usize = 0; for (relocs) |rel| { defer i += 1; const rel_offset = try macho_file.cast(usize, rel.offset - self.off); const r_address: i32 = try macho_file.cast(i32, self.value + rel_offset); assert(r_address >= 0); const r_symbolnum = r_symbolnum: { const r_symbolnum: u32 = switch (rel.tag) { .local => rel.getTargetAtom(self, macho_file).out_n_sect + 1, .@"extern" => rel.getTargetSymbol(self, macho_file).getOutputSymtabIndex(macho_file).?, }; break :r_symbolnum try macho_file.cast(u24, r_symbolnum); }; const r_extern = rel.tag == .@"extern"; var addend = rel.addend + rel.getRelocAddend(cpu_arch); if (rel.tag == .local) { const target: i64 = @intCast(rel.getTargetAddress(self, macho_file)); addend += target; } switch (rel.tag) { .local => relocs_log.debug(" {f}: [{x} => {d}({s},{s})] + {x}", .{ rel.fmtPretty(cpu_arch), r_address, r_symbolnum, macho_file.sections.items(.header)[r_symbolnum - 1].segName(), macho_file.sections.items(.header)[r_symbolnum - 1].sectName(), addend, }), .@"extern" => relocs_log.debug(" {f}: [{x} => {d}({s})] + {x}", .{ rel.fmtPretty(cpu_arch), r_address, r_symbolnum, rel.getTargetSymbol(self, macho_file).getName(macho_file), addend, }), } switch (cpu_arch) { .aarch64 => { if (rel.type == .unsigned) switch (rel.meta.length) { 0, 1 => unreachable, 2 => mem.writeInt(i32, code[rel_offset..][0..4], @truncate(addend), .little), 3 => mem.writeInt(i64, code[rel_offset..][0..8], addend, .little), } else if (addend > 0) { buffer[i] = .{ .r_address = r_address, .r_symbolnum = @bitCast(try macho_file.cast(i24, addend)), .r_pcrel = 0, .r_length = 2, .r_extern = 0, .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND), }; i += 1; } const r_type: macho.reloc_type_arm64 = switch (rel.type) { .page => .ARM64_RELOC_PAGE21, .pageoff => .ARM64_RELOC_PAGEOFF12, .got_load_page => .ARM64_RELOC_GOT_LOAD_PAGE21, .got_load_pageoff => .ARM64_RELOC_GOT_LOAD_PAGEOFF12, .tlvp_page => .ARM64_RELOC_TLVP_LOAD_PAGE21, .tlvp_pageoff => .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, .branch => .ARM64_RELOC_BRANCH26, .got => .ARM64_RELOC_POINTER_TO_GOT, .subtractor => .ARM64_RELOC_SUBTRACTOR, .unsigned => .ARM64_RELOC_UNSIGNED, .signed, .signed1, .signed2, .signed4, .got_load, .tlv, => unreachable, }; buffer[i] = .{ .r_address = r_address, .r_symbolnum = r_symbolnum, .r_pcrel = @intFromBool(rel.meta.pcrel), .r_extern = @intFromBool(r_extern), .r_length = rel.meta.length, .r_type = @intFromEnum(r_type), }; }, .x86_64 => { if (rel.meta.pcrel) { if (rel.tag == .local) { addend -= @as(i64, @intCast(self.getAddress(macho_file) + rel_offset)); } else { addend += 4; } } switch (rel.meta.length) { 0, 1 => unreachable, 2 => mem.writeInt(i32, code[rel_offset..][0..4], @truncate(addend), .little), 3 => mem.writeInt(i64, code[rel_offset..][0..8], addend, .little), } const r_type: macho.reloc_type_x86_64 = switch (rel.type) { .signed => .X86_64_RELOC_SIGNED, .signed1 => .X86_64_RELOC_SIGNED_1, .signed2 => .X86_64_RELOC_SIGNED_2, .signed4 => .X86_64_RELOC_SIGNED_4, .got_load => .X86_64_RELOC_GOT_LOAD, .tlv => .X86_64_RELOC_TLV, .branch => .X86_64_RELOC_BRANCH, .got => .X86_64_RELOC_GOT, .subtractor => .X86_64_RELOC_SUBTRACTOR, .unsigned => .X86_64_RELOC_UNSIGNED, .page, .pageoff, .got_load_page, .got_load_pageoff, .tlvp_page, .tlvp_pageoff, => unreachable, }; buffer[i] = .{ .r_address = r_address, .r_symbolnum = r_symbolnum, .r_pcrel = @intFromBool(rel.meta.pcrel), .r_extern = @intFromBool(r_extern), .r_length = rel.meta.length, .r_type = @intFromEnum(r_type), }; }, else => unreachable, } } assert(i == buffer.len); } pub fn fmt(atom: Atom, macho_file: *MachO) std.fmt.Alt(Format, Format.print) { return .{ .data = .{ .atom = atom, .macho_file = macho_file, } }; } const Format = struct { atom: Atom, macho_file: *MachO, fn print(f: Format, w: *Writer) Writer.Error!void { const atom = f.atom; const macho_file = f.macho_file; const file = atom.getFile(macho_file); try w.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.out_n_sect, atom.alignment, atom.size, atom.getRelocs(macho_file).len, atom.getExtra(macho_file).thunk, }); if (!atom.isAlive()) try w.writeAll(" : [*]"); if (atom.getUnwindRecords(macho_file).len > 0) { try w.writeAll(" : unwind{ "); const extra = atom.getExtra(macho_file); for (atom.getUnwindRecords(macho_file), extra.unwind_index..) |index, i| { const rec = file.object.getUnwindRecord(index); try w.print("{d}", .{index}); if (!rec.alive) try w.writeAll("([*])"); if (i < extra.unwind_index + extra.unwind_count - 1) try w.writeAll(", "); } try w.writeAll(" }"); } } }; pub const Index = u32; pub const Extra = struct { /// Index of the range extension thunk of this atom. thunk: u32 = 0, /// Start index of relocations belonging to this atom. rel_index: u32 = 0, /// Count of relocations belonging to this atom. 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. unwind_index: u32 = 0, /// Count of relocations belonging to this atom. unwind_count: u32 = 0, /// Index into LiteralPool entry for this atom. 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; const std = @import("std"); const assert = std.debug.assert; const macho = std.macho; const math = std.math; const mem = std.mem; const log = std.log.scoped(.link); const relocs_log = std.log.scoped(.link_relocs); const Writer = std.Io.Writer; const Allocator = mem.Allocator; const AtomicBool = std.atomic.Value(bool); const aarch64 = @import("../aarch64.zig"); const trace = @import("../../tracy.zig").trace; const Atom = @This(); const File = @import("file.zig").File; const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); const Relocation = @import("Relocation.zig"); const Symbol = @import("Symbol.zig"); const Thunk = @import("Thunk.zig"); const UnwindInfo = @import("UnwindInfo.zig"); const dev = @import("../../dev.zig");