diff --git a/src/MachO.zig b/src/MachO.zig index 8e92a7a0..d957ba3a 100644 --- a/src/MachO.zig +++ b/src/MachO.zig @@ -71,6 +71,7 @@ export_trie: ExportTrieSection = .{}, unwind_info: UnwindInfo = .{}, atoms: std.ArrayListUnmanaged(Atom) = .{}, +thunks: std.ArrayListUnmanaged(Thunk) = .{}, unwind_records: std.ArrayListUnmanaged(UnwindInfo.Record) = .{}, has_tlv: bool = false, @@ -132,6 +133,7 @@ pub fn deinit(self: *MachO) void { self.segments.deinit(gpa); self.sections.deinit(gpa); self.atoms.deinit(gpa); + self.thunks.deinit(gpa); self.symtab.deinit(gpa); self.strtab.deinit(gpa); @@ -349,7 +351,6 @@ pub fn flush(self: *MachO) !void { try self.allocateSections(); self.allocateSegments(); self.allocateAtoms(); - self.allocateSymbols(); self.allocateSyntheticSymbols(); state_log.debug("{}", .{self.dumpState()}); @@ -1577,12 +1578,20 @@ fn sortSections(self: *MachO) !void { fn addAtomsToSections(self: *MachO) !void { for (self.objects.items) |index| { - for (self.getFile(index).?.object.atoms.items) |atom_index| { + const object = self.getFile(index).?.object; + for (object.atoms.items) |atom_index| { const atom = self.getAtom(atom_index) orelse continue; if (!atom.flags.alive) continue; const atoms = &self.sections.items(.atoms)[atom.out_n_sect]; try atoms.append(self.base.allocator, atom_index); } + for (object.symbols.items) |sym_index| { + const sym = self.getSymbol(sym_index); + const atom = sym.getAtom(self) orelse continue; + if (!atom.flags.alive) continue; + if (sym.getFile(self).?.getIndex() != index) continue; + sym.out_n_sect = atom.out_n_sect; + } } if (self.getInternalObject()) |object| { for (object.atoms.items) |atom_index| { @@ -1591,6 +1600,13 @@ fn addAtomsToSections(self: *MachO) !void { const atoms = &self.sections.items(.atoms)[atom.out_n_sect]; try atoms.append(self.base.allocator, atom_index); } + for (object.symbols.items) |sym_index| { + const sym = self.getSymbol(sym_index); + const atom = sym.getAtom(self) orelse continue; + if (!atom.flags.alive) continue; + if (sym.getFile(self).?.getIndex() != object.index) continue; + sym.out_n_sect = atom.out_n_sect; + } } } @@ -1623,11 +1639,7 @@ fn calcSectionSizes(self: *MachO) !void { const slice = self.sections.slice(); for (slice.items(.header), slice.items(.atoms)) |*header, atoms| { if (atoms.items.len == 0) continue; - - // TODO - // if (self.requiresThunks()) { - // if (header.isCode()) continue; - // } + if (self.requiresThunks() and header.isCode()) continue; for (atoms.items) |atom_index| { const atom = self.getAtom(atom_index).?; @@ -1640,16 +1652,15 @@ fn calcSectionSizes(self: *MachO) !void { } } - // TODO - // if (self.requiresThunks()) { - // for (slice.items(.header), slice.items(.atoms)) |header,atoms| { - // if (!header.isCode()) continue; - // if (atoms.items.len == 0) continue; + if (self.requiresThunks()) { + for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| { + if (!header.isCode()) continue; + if (atoms.items.len == 0) continue; - // // Create jump/branch range extenders if needed. - // try thunks.createThunks(self, @as(u8, @intCast(sect_id))); - // } - // } + // Create jump/branch range extenders if needed. + try thunks.createThunks(@intCast(i), self); + } + } if (self.got_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; @@ -1874,19 +1885,10 @@ fn allocateAtoms(self: *MachO) void { atom.value += header.addr; } } -} -fn allocateSymbols(self: *MachO) void { - for (self.objects.items) |index| { - for (self.getFile(index).?.getSymbols()) |sym_index| { - const sym = self.getSymbol(sym_index); - const atom = sym.getAtom(self) orelse continue; - if (!atom.flags.alive) continue; - if (sym.getFile(self).?.getIndex() != index) continue; - - sym.value += atom.value; - sym.out_n_sect = atom.out_n_sect; - } + for (self.thunks.items) |*thunk| { + const header = self.sections.items(.header)[thunk.out_n_sect]; + thunk.value += header.addr; } } @@ -2059,6 +2061,16 @@ fn writeAtoms(self: *MachO) !void { try self.base.file.pwriteAll(buffer, header.offset); } + for (self.thunks.items) |thunk| { + const header = slice.items(.header)[thunk.out_n_sect]; + const offset = thunk.value - header.addr + header.offset; + const buffer = try gpa.alloc(u8, thunk.size()); + defer gpa.free(buffer); + var stream = std.io.fixedBufferStream(buffer); + try thunk.write(self, stream.writer()); + try self.base.file.pwriteAll(buffer, offset); + } + if (has_resolve_error) return error.ResolveFailed; } @@ -2817,6 +2829,18 @@ pub fn getUnwindRecord(self: *MachO, index: UnwindInfo.Record.Index) *UnwindInfo return &self.unwind_records.items[index]; } +pub fn addThunk(self: *MachO) !Thunk.Index { + const index = @as(Thunk.Index, @intCast(self.thunks.items.len)); + const thunk = try self.thunks.addOne(self.base.allocator); + thunk.* = .{}; + return index; +} + +pub fn getThunk(self: *MachO, index: Thunk.Index) *Thunk { + assert(index < self.thunks.items.len); + return &self.thunks.items[index]; +} + pub fn eatPrefix(path: []const u8, prefix: []const u8) ?[]const u8 { if (mem.startsWith(u8, path, prefix)) return path[prefix.len..]; return null; @@ -2867,6 +2891,10 @@ fn fmtDumpState( try writer.print("internal({d}) : internal\n", .{internal.index}); try writer.print("{}{}\n", .{ internal.fmtAtoms(self), internal.fmtSymtab(self) }); } + try writer.writeAll("thunks\n"); + for (self.thunks.items, 0..) |thunk, index| { + try writer.print("thunk({d}) : {}\n", .{ index, thunk.fmt(self) }); + } try writer.print("stubs\n{}\n", .{self.stubs.fmt(self)}); try writer.print("objc_stubs\n{}\n", .{self.objc_stubs.fmt(self)}); try writer.print("got\n{}\n", .{self.got.fmt(self)}); @@ -3082,6 +3110,7 @@ const Symbol = @import("MachO/Symbol.zig"); const StringTable = @import("strtab.zig").StringTable; const StubsSection = synthetic.StubsSection; const StubsHelperSection = synthetic.StubsHelperSection; +const Thunk = thunks.Thunk; const ThreadPool = std.Thread.Pool; const TlvPtrSection = synthetic.TlvPtrSection; const UnwindInfo = @import("MachO/UnwindInfo.zig"); diff --git a/src/MachO/Atom.zig b/src/MachO/Atom.zig index 6066f4bb..6065d9f9 100644 --- a/src/MachO/Atom.zig +++ b/src/MachO/Atom.zig @@ -29,6 +29,9 @@ relocs: Loc = .{}, /// Index of this atom in the linker's atoms table. atom_index: Index = 0, +/// Index of the thunk for this atom. +thunk_index: Thunk.Index = 0, + /// Unwind records associated with this atom. unwind_records: Loc = .{}, @@ -93,6 +96,10 @@ pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void { } } +pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk { + return macho_file.getThunk(self.thunk_index); +} + pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 { const segname, const sectname, const flags = blk: { if (sect.isCode()) break :blk .{ @@ -410,12 +417,16 @@ fn resolveRelocInner( .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 => { - // TODO thunk indirection - const disp = math.cast(i28, S + A - P) orelse return error.Overflow; + const disp: i28 = math.cast(i28, S + A - P) orelse blk: { + const thunk = self.getThunk(macho_file); + const S_: i64 = @intCast(thunk.getAddress(rel.target)); + break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow; + }; var inst = aarch64.Instruction{ .unconditional_branch_immediate = mem.bytesToValue(std.meta.TagPayload( aarch64.Instruction, @@ -666,7 +677,7 @@ pub fn format( _ = unused_fmt_string; _ = options; _ = writer; - @compileError("do not format symbols directly"); + @compileError("do not format Atom directly"); } pub fn fmt(atom: Atom, macho_file: *MachO) std.fmt.Formatter(format2) { @@ -691,9 +702,10 @@ fn format2( _ = unused_fmt_string; const atom = ctx.atom; const macho_file = ctx.macho_file; - try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x})", .{ - atom.atom_index, atom.getName(macho_file), atom.value, - atom.out_n_sect, atom.alignment, atom.size, + try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : thunk({d})", .{ + atom.atom_index, atom.getName(macho_file), atom.value, + atom.out_n_sect, atom.alignment, atom.size, + atom.thunk_index, }); if (!atom.flags.alive) try writer.writeAll(" : [*]"); if (atom.unwind_records.len > 0) { @@ -744,4 +756,5 @@ const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); const Relocation = @import("Relocation.zig"); const Symbol = @import("Symbol.zig"); +const Thunk = @import("thunks.zig").Thunk; const UnwindInfo = @import("UnwindInfo.zig"); diff --git a/src/MachO/Symbol.zig b/src/MachO/Symbol.zig index 272003b3..d1d2e638 100644 --- a/src/MachO/Symbol.zig +++ b/src/MachO/Symbol.zig @@ -105,6 +105,7 @@ pub fn getAddress(symbol: Symbol, opts: struct { return symbol.getObjcStubsAddress(macho_file); } } + if (symbol.getAtom(macho_file)) |atom| return atom.value + symbol.value; return symbol.value; } @@ -272,7 +273,11 @@ fn format2( _ = options; _ = unused_fmt_string; const symbol = ctx.symbol; - try writer.print("%{d} : {s} : @{x}", .{ symbol.nlist_idx, symbol.getName(ctx.macho_file), symbol.value }); + try writer.print("%{d} : {s} : @{x}", .{ + symbol.nlist_idx, + symbol.getName(ctx.macho_file), + symbol.getAddress(.{}, ctx.macho_file), + }); if (symbol.getFile(ctx.macho_file)) |file| { if (symbol.out_n_sect != 0) { try writer.print(" : sect({d})", .{symbol.out_n_sect}); diff --git a/src/MachO/thunks.zig b/src/MachO/thunks.zig index 1e09be5c..89120934 100644 --- a/src/MachO/thunks.zig +++ b/src/MachO/thunks.zig @@ -1,364 +1,172 @@ -const std = @import("std"); -const assert = std.debug.assert; -const log = std.log.scoped(.thunks); -const macho = std.macho; -const math = std.math; -const mem = std.mem; - -const aarch64 = @import("../aarch64.zig"); - -const Allocator = mem.Allocator; -const Atom = @import("Atom.zig"); -const AtomIndex = MachO.AtomIndex; -const MachO = @import("../MachO.zig"); -const SymbolWithLoc = MachO.SymbolWithLoc; - -pub const ThunkIndex = u32; - -/// Branch instruction has 26 bits immediate but 4 byte aligned. -const jump_bits = @bitSizeOf(i28); - -const max_distance = (1 << (jump_bits - 1)); - -/// A branch will need an extender if its target is larger than -/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number. -/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold -/// and assume margin to be 5MiB. -const max_allowed_distance = max_distance - 0x500_000; - -pub const Thunk = struct { - start_index: AtomIndex, - len: u32, - - lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, AtomIndex) = .{}, - - pub fn deinit(self: *Thunk, gpa: Allocator) void { - self.lookup.deinit(gpa); - } - - pub fn getStartAtomIndex(self: Thunk) AtomIndex { - assert(self.len != 0); - return self.start_index; - } - - pub fn getEndAtomIndex(self: Thunk) AtomIndex { - assert(self.len != 0); - return self.start_index + self.len - 1; - } - - pub fn getSize(self: Thunk) u64 { - return 12 * self.len; - } - - pub fn getAlignment() u32 { - return @alignOf(u32); - } - - pub fn getTrampolineForSymbol(self: Thunk, macho_file: *MachO, target: SymbolWithLoc) ?SymbolWithLoc { - const atom_index = self.lookup.get(target) orelse return null; - const atom = macho_file.getAtom(atom_index); - return atom.getSymbolWithLoc(); - } -}; - -pub fn createThunks(macho_file: *MachO, sect_id: u8) !void { - const header = &macho_file.sections.items(.header)[sect_id]; - if (header.size == 0) return; - +pub fn createThunks(sect_id: u8, macho_file: *MachO) !void { const gpa = macho_file.base.allocator; - const first_atom_index = macho_file.sections.items(.first_atom_index)[sect_id]; - assert(first_atom_index != 0); - - header.size = 0; - header.@"align" = 0; - - var atom_count: u32 = 0; - - { - var atom_index = first_atom_index; - while (true) { - const atom = macho_file.getAtom(atom_index); - const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc()); - sym.n_value = 0; - atom_count += 1; + const slice = macho_file.sections.slice(); + const header = &slice.items(.header)[sect_id]; + const atoms = slice.items(.atoms)[sect_id].items; + assert(atoms.len > 0); - if (atom.next_index) |next_index| { - atom_index = next_index; - } else break; - } + for (atoms) |atom_index| { + macho_file.getAtom(atom_index).?.value = @bitCast(@as(i64, -1)); } - var allocated = std.AutoHashMap(AtomIndex, void).init(gpa); - defer allocated.deinit(); - try allocated.ensureTotalCapacity(atom_count); - - var group_start = first_atom_index; - var group_end = first_atom_index; - var offset: u64 = 0; - - while (true) { - const group_start_atom = macho_file.getAtom(group_start); - - while (true) { - const atom = macho_file.getAtom(group_end); - offset = mem.alignForward(u64, offset, try math.powi(u32, 2, atom.alignment)); - - const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc()); - sym.n_value = offset; - offset += atom.size; - - macho_file.logAtom(group_end, log); - - header.@"align" = @max(header.@"align", atom.alignment); - - allocated.putAssumeCapacityNoClobber(group_end, {}); - - const group_start_sym = macho_file.getSymbol(group_start_atom.getSymbolWithLoc()); - if (offset - group_start_sym.n_value >= max_allowed_distance) break; - - if (atom.next_index) |next_index| { - group_end = next_index; - } else break; + var i: usize = 0; + while (i < atoms.len) { + const start = i; + const start_atom = macho_file.getAtom(atoms[start]).?; + assert(start_atom.flags.alive); + start_atom.value = try advance(header, start_atom.size, start_atom.alignment); + i += 1; + + while (i < atoms.len and + header.size - start_atom.value < max_allowed_distance) : (i += 1) + { + const atom_index = atoms[i]; + const atom = macho_file.getAtom(atom_index).?; + assert(atom.flags.alive); + atom.value = try advance(header, atom.size, atom.alignment); } - // Insert thunk at group_end - const thunk_index = @as(u32, @intCast(macho_file.thunks.items.len)); - try macho_file.thunks.append(gpa, .{ .start_index = undefined, .len = 0 }); - - // Scan relocs in the group and create trampolines for any unreachable callsite. - var atom_index = group_start; - while (true) { - const atom = macho_file.getAtom(atom_index); - try scanRelocs( - macho_file, - atom_index, - allocated, - thunk_index, - group_end, - ); - - if (atom_index == group_end) break; - - if (atom.next_index) |next_index| { - atom_index = next_index; - } else break; + // Insert a thunk at the group end + const thunk_index = try macho_file.addThunk(); + const thunk = macho_file.getThunk(thunk_index); + thunk.out_n_sect = sect_id; + + // Scan relocs in the group and create trampolines for any unreachable callsite + for (atoms[start..i]) |atom_index| { + const atom = macho_file.getAtom(atom_index).?; + 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, {}); + } + atom.thunk_index = thunk_index; } - offset = mem.alignForward(u64, offset, Thunk.getAlignment()); - allocateThunk(macho_file, thunk_index, offset, header); - offset += macho_file.thunks.items[thunk_index].getSize(); + thunk.value = try advance(header, thunk.size(), 2); - const thunk = macho_file.thunks.items[thunk_index]; - if (thunk.len == 0) { - const group_end_atom = macho_file.getAtom(group_end); - if (group_end_atom.next_index) |next_index| { - group_start = next_index; - group_end = next_index; - } else break; - } else { - const thunk_end_atom_index = thunk.getEndAtomIndex(); - const thunk_end_atom = macho_file.getAtom(thunk_end_atom_index); - if (thunk_end_atom.next_index) |next_index| { - group_start = next_index; - group_end = next_index; - } else break; - } + log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) }); } - - header.size = @as(u32, @intCast(offset)); } -fn allocateThunk( - macho_file: *MachO, - thunk_index: ThunkIndex, - base_offset: u64, - header: *macho.section_64, -) void { - const thunk = macho_file.thunks.items[thunk_index]; - if (thunk.len == 0) return; - - const first_atom_index = thunk.getStartAtomIndex(); - const end_atom_index = thunk.getEndAtomIndex(); - - var atom_index = first_atom_index; - var offset = base_offset; - while (true) { - const atom = macho_file.getAtom(atom_index); - offset = mem.alignForward(u64, offset, Thunk.getAlignment()); - - const sym = macho_file.getSymbolPtr(atom.getSymbolWithLoc()); - sym.n_value = offset; - offset += atom.size; - - macho_file.logAtom(atom_index, log); - - header.@"align" = @max(header.@"align", atom.alignment); - - if (end_atom_index == atom_index) break; - - if (atom.next_index) |next_index| { - atom_index = next_index; - } else break; - } +fn advance(sect: *macho.section_64, size: u64, pow2_align: u32) !u64 { + const alignment = try math.powi(u32, 2, pow2_align); + const offset = mem.alignForward(u64, sect.size, alignment); + const padding = offset - sect.size; + sect.size += padding + size; + sect.@"align" = @max(sect.@"align", pow2_align); + return offset; } -fn scanRelocs( - macho_file: *MachO, - atom_index: AtomIndex, - allocated: std.AutoHashMap(AtomIndex, void), - thunk_index: ThunkIndex, - group_end: AtomIndex, -) !void { - const atom = macho_file.getAtom(atom_index); - const object = macho_file.objects.items[atom.getFile().?]; - - const base_offset = if (object.getSourceSymbol(atom.sym_index)) |source_sym| blk: { - const source_sect = object.getSourceSection(source_sym.n_sect - 1); - break :blk @as(i32, @intCast(source_sym.n_value - source_sect.addr)); - } else 0; - - const code = Atom.getAtomCode(macho_file, atom_index); - const relocs = Atom.getAtomRelocs(macho_file, atom_index); - const ctx = Atom.getRelocContext(macho_file, atom_index); - - for (relocs) |rel| { - if (!relocNeedsThunk(rel)) continue; - - const target = Atom.parseRelocTarget(macho_file, .{ - .object_id = atom.getFile().?, - .rel = rel, - .code = code, - .base_offset = ctx.base_offset, - .base_addr = ctx.base_addr, - }); - if (isReachable(macho_file, atom_index, rel, base_offset, target, allocated)) continue; - - log.debug("{x}: source = {s}@{x}, target = {s}@{x} unreachable", .{ - rel.r_address - base_offset, - macho_file.getSymbolName(atom.getSymbolWithLoc()), - macho_file.getSymbol(atom.getSymbolWithLoc()).n_value, - macho_file.getSymbolName(target), - macho_file.getSymbol(target).n_value, - }); - - const gpa = macho_file.base.allocator; - const target_sym = macho_file.getSymbol(target); - - const actual_target: SymbolWithLoc = if (target_sym.undf()) blk: { - const stub_atom_index = macho_file.getStubsAtomIndexForSymbol(target).?; - break :blk .{ .sym_index = macho_file.getAtom(stub_atom_index).sym_index }; - } else target; - - const thunk = &macho_file.thunks.items[thunk_index]; - const gop = try thunk.lookup.getOrPut(gpa, actual_target); - if (!gop.found_existing) { - const thunk_atom_index = try createThunkAtom(macho_file); - gop.value_ptr.* = thunk_atom_index; +fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { + const target = rel.getTargetSymbol(macho_file); + if (target.flags.stubs or target.flags.objc_stubs) return false; + if (atom.out_n_sect != target.out_n_sect) return false; + const target_atom = target.getAtom(macho_file).?; + if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; + const saddr = @as(i64, @intCast(atom.value)) + @as(i64, @intCast(rel.offset - atom.off)); + const taddr: i64 = @intCast(rel.getTargetAddress(macho_file)); + _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false; + return true; +} - const thunk_atom = macho_file.getAtomPtr(thunk_atom_index); - const end_atom_index = if (thunk.len == 0) group_end else thunk.getEndAtomIndex(); - const end_atom = macho_file.getAtomPtr(end_atom_index); +pub const Thunk = struct { + value: u64 = 0, + out_n_sect: u8 = 0, + symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{}, - if (end_atom.next_index) |first_after_index| { - const first_after_atom = macho_file.getAtomPtr(first_after_index); - first_after_atom.prev_index = thunk_atom_index; - thunk_atom.next_index = first_after_index; - } + pub fn deinit(thunk: *Thunk, allocator: Allocator) void { + thunk.symbols.deinit(allocator); + } - end_atom.next_index = thunk_atom_index; - thunk_atom.prev_index = end_atom_index; + pub fn size(thunk: Thunk) usize { + return thunk.symbols.keys().len * trampoline_size; + } - if (thunk.len == 0) { - thunk.start_index = thunk_atom_index; - } + pub fn getAddress(thunk: Thunk, sym_index: Symbol.Index) u64 { + return thunk.value + thunk.symbols.getIndex(sym_index).? * trampoline_size; + } - thunk.len += 1; + pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void { + for (thunk.symbols.keys(), 0..) |sym_index, i| { + const sym = macho_file.getSymbol(sym_index); + const saddr = thunk.value + i * trampoline_size; + const taddr = sym.getAddress(.{}, macho_file); + const pages = try Relocation.calcNumberOfPages(saddr, taddr); + try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); + const off = try Relocation.calcPageOffset(taddr, .arithmetic); + try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little); + try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); } - - try macho_file.thunk_table.put(gpa, atom_index, thunk_index); } -} - -inline fn relocNeedsThunk(rel: macho.relocation_info) bool { - const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); - return rel_type == .ARM64_RELOC_BRANCH26; -} - -fn isReachable( - macho_file: *MachO, - atom_index: AtomIndex, - rel: macho.relocation_info, - base_offset: i32, - target: SymbolWithLoc, - allocated: std.AutoHashMap(AtomIndex, void), -) bool { - if (macho_file.getStubsAtomIndexForSymbol(target)) |_| return false; - - const source_atom = macho_file.getAtom(atom_index); - const source_sym = macho_file.getSymbol(source_atom.getSymbolWithLoc()); - - const target_object = macho_file.objects.items[target.getFile().?]; - const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?; - const target_atom = macho_file.getAtom(target_atom_index); - const target_sym = macho_file.getSymbol(target_atom.getSymbolWithLoc()); - - if (source_sym.n_sect != target_sym.n_sect) return false; - if (!allocated.contains(target_atom_index)) return false; - - const source_addr = source_sym.n_value + @as(u32, @intCast(rel.r_address - base_offset)); - const is_via_got = Atom.relocRequiresGot(macho_file, rel); - const target_addr = Atom.getRelocTargetAddress(macho_file, target, is_via_got, false) catch unreachable; - _ = Atom.calcPcRelativeDisplacementArm64(source_addr, target_addr) catch - return false; + pub fn format( + thunk: Thunk, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = thunk; + _ = unused_fmt_string; + _ = options; + _ = writer; + @compileError("do not format Thunk directly"); + } - return true; -} + pub fn fmt(thunk: Thunk, macho_file: *MachO) std.fmt.Formatter(format2) { + return .{ .data = .{ + .thunk = thunk, + .macho_file = macho_file, + } }; + } -fn createThunkAtom(macho_file: *MachO) !AtomIndex { - const sym_index = try macho_file.allocateSymbol(); - const atom_index = try macho_file.createEmptyAtom(sym_index, @sizeOf(u32) * 3, 2); - const sym = macho_file.getSymbolPtr(.{ .sym_index = sym_index }); - sym.n_type = macho.N_SECT; + const FormatContext = struct { + thunk: Thunk, + macho_file: *MachO, + }; + + fn format2( + ctx: FormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = options; + _ = unused_fmt_string; + const thunk = ctx.thunk; + const macho_file = ctx.macho_file; + try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() }); + for (thunk.symbols.keys()) |index| { + const sym = macho_file.getSymbol(index); + try writer.print(" %{d} : {s} : @{x}\n", .{ index, sym.getName(macho_file), sym.value }); + } + } - const sect_id = macho_file.getSectionByName("__TEXT", "__text") orelse unreachable; - sym.n_sect = sect_id + 1; + const trampoline_size = 3 * @sizeOf(u32); - return atom_index; -} - -fn getThunkIndex(macho_file: *MachO, atom_index: AtomIndex) ?ThunkIndex { - const atom = macho_file.getAtom(atom_index); - const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); - for (macho_file.thunks.items, 0..) |thunk, i| { - if (thunk.len == 0) continue; + pub const Index = u32; +}; - const thunk_atom_index = thunk.getStartAtomIndex(); - const thunk_atom = macho_file.getAtom(thunk_atom_index); - const thunk_sym = macho_file.getSymbol(thunk_atom.getSymbolWithLoc()); - const start_addr = thunk_sym.n_value; - const end_addr = start_addr + thunk.getSize(); +/// Branch instruction has 26 bits immediate but is 4 byte aligned. +const jump_bits = @bitSizeOf(i28); +const max_distance = (1 << (jump_bits - 1)); - if (start_addr <= sym.n_value and sym.n_value < end_addr) { - return @as(u32, @intCast(i)); - } - } - return null; -} +/// A branch will need an extender if its target is larger than +/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number. +/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold +/// and assume margin to be 5MiB. +const max_allowed_distance = max_distance - 0x500_000; -pub fn writeThunkCode(macho_file: *MachO, atom_index: AtomIndex, writer: anytype) !void { - const atom = macho_file.getAtom(atom_index); - const sym = macho_file.getSymbol(atom.getSymbolWithLoc()); - const source_addr = sym.n_value; - const thunk = macho_file.thunks.items[getThunkIndex(macho_file, atom_index).?]; - const target_addr = for (thunk.lookup.keys()) |target| { - const target_atom_index = thunk.lookup.get(target).?; - if (atom_index == target_atom_index) break macho_file.getSymbol(target).n_value; - } else unreachable; +const aarch64 = @import("../aarch64.zig"); +const assert = std.debug.assert; +const log = std.log.scoped(.link); +const macho = std.macho; +const math = std.math; +const mem = std.mem; +const std = @import("std"); - const pages = Atom.calcNumberOfPages(source_addr, target_addr); - try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); - const off = try Atom.calcPageOffset(target_addr, .arithmetic); - try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little); - try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); -} +const Allocator = mem.Allocator; +const Atom = @import("Atom.zig"); +const MachO = @import("../MachO.zig"); +const Relocation = @import("Relocation.zig"); +const Symbol = @import("Symbol.zig"); diff --git a/test/macho.zig b/test/macho.zig index 4bc8b33e..6103f6f4 100644 --- a/test/macho.zig +++ b/test/macho.zig @@ -61,6 +61,7 @@ pub fn addMachOTests(b: *Build, options: common.Options) *Step { macho_step.dependOn(testSymbolStabs(b, opts)); macho_step.dependOn(testTbdv3(b, opts)); macho_step.dependOn(testTentative(b, opts)); + macho_step.dependOn(testThunks(b, opts)); macho_step.dependOn(testTls(b, opts)); macho_step.dependOn(testTlsLargeTbss(b, opts)); macho_step.dependOn(testTwoLevelNamespace(b, opts)); @@ -2394,6 +2395,38 @@ fn testTentative(b: *Build, opts: Options) *Step { return test_step; } +fn testThunks(b: *Build, opts: Options) *Step { + const test_step = b.step("test-macho-thunks", ""); + + if (builtin.target.cpu.arch != .aarch64) return skipTestStep(test_step); + + const exe = cc(b, opts); + exe.addCSource( + \\#include + \\__attribute__((aligned(0x8000000))) int bar() { + \\ return 42; + \\} + \\int foobar(); + \\int foo() { + \\ return bar() - foobar(); + \\} + \\__attribute__((aligned(0x8000000))) int foobar() { + \\ return 42; + \\} + \\int main() { + \\ printf("bar=%d, foo=%d, foobar=%d", bar(), foo(), foobar()); + \\ return foo(); + \\} + ); + + const run = exe.run(); + run.expectStdOutEqual("bar=42, foo=0, foobar=42"); + run.expectExitCode(0); + test_step.dependOn(run.step()); + + return test_step; +} + fn testTls(b: *Build, opts: Options) *Step { const test_step = b.step("test-macho-tls", "");