authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-09-10 13:47:05+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-09-18 10:00:03+02:00
log26af8d254af5277e36cc78e1ab99241dee199c37
treeeffe8f6b663962ea5e2a06d389a4e93b5c97b71c
parent54854e2ab87e667751f2eae86f41b9d41bcfda9d

macho: separate in structure and logic incremental relocs and file relocs


3 files changed, 408 insertions(+), 1 deletions(-)

src/link/MachO.zig+85-1
......@@ -38,6 +38,8 @@ const LibStub = @import("tapi.zig").LibStub;
3838const Liveness = @import("../Liveness.zig");
3939const LlvmObject = @import("../codegen/llvm.zig").Object;
4040const Module = @import("../Module.zig");
41const Relocation = @import("MachO/Relocation.zig");
42const RelocationTable = Relocation.Table;
4143const StringTable = @import("strtab.zig").StringTable;
4244const Trie = @import("MachO/Trie.zig");
4345const Type = @import("../type.zig").Type;
......@@ -193,6 +195,11 @@ atom_by_index_table: std.AutoHashMapUnmanaged(u32, *Atom) = .{},
193195/// with `Decl` `main`, and lives as long as that `Decl`.
194196unnamed_const_atoms: UnnamedConstTable = .{},
195197
198/// A table of relocations indexed by the owning them `Atom`.
199/// Note that once we refactor `Atom`'s lifetime and ownership rules,
200/// this will be a table indexed by index into the list of Atoms.
201relocs: RelocationTable = .{},
202
196203/// Table of Decls that are currently alive.
197204/// We store them here so that we can properly dispose of any allocated
198205/// memory within the atom in the incremental linker.
......@@ -1854,11 +1861,80 @@ pub fn writeAtom(self: *MachO, atom: *Atom, sect_id: u8) !void {
18541861 const section = self.sections.get(sect_id);
18551862 const sym = atom.getSymbol(self);
18561863 const file_offset = section.header.offset + sym.n_value - section.header.addr;
1857 try atom.resolveRelocs(self);
18581864 log.debug("writing atom for symbol {s} at file offset 0x{x}", .{ atom.getName(self), file_offset });
18591865 try self.base.file.?.pwriteAll(atom.code.items, file_offset);
18601866}
18611867
1868// fn markRelocsDirtyByTarget(self: *MachO, target: SymbolWithLoc) void {
1869// // TODO: reverse-lookup might come in handy here
1870// var it = self.relocs.valueIterator();
1871// while (it.next()) |relocs| {
1872// for (relocs.items) |*reloc| {
1873// if (!reloc.target.eql(target)) continue;
1874// reloc.dirty = true;
1875// }
1876// }
1877// }
1878
1879// fn markRelocsDirtyByAddress(self: *MachO, addr: u32) void {
1880// var it = self.relocs.valueIterator();
1881// while (it.next()) |relocs| {
1882// for (relocs.items) |*reloc| {
1883// const target_atom = reloc.getTargetAtom(self) orelse continue;
1884// const target_sym = target_atom.getSymbol(self);
1885// if (target_sym.value < addr) continue;
1886// reloc.dirty = true;
1887// }
1888// }
1889// }
1890
1891// fn resolveRelocs(self: *MachO, atom: *Atom) !void {
1892// const relocs = self.relocs.get(atom) orelse return;
1893// const source_sym = atom.getSymbol(self);
1894// const source_section = self.sections.get(@enumToInt(source_sym.section_number) - 1).header;
1895// const file_offset = section.offset + source_sym.n_value - section.addr;
1896
1897// log.debug("relocating '{s}'", .{atom.getName(self)});
1898
1899// for (relocs.items) |*reloc| {
1900// if (!reloc.dirty) continue;
1901
1902// const target_atom = reloc.getTargetAtom(self) orelse continue;
1903// const target_vaddr = target_atom.getSymbol(self).value;
1904// const target_vaddr_with_addend = target_vaddr + reloc.addend;
1905
1906// log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) (in file at 0x{x})", .{
1907// source_sym.value + reloc.offset,
1908// target_vaddr_with_addend,
1909// self.getSymbolName(reloc.target),
1910// @tagName(reloc.@"type"),
1911// file_offset + reloc.offset,
1912// });
1913
1914// reloc.dirty = false;
1915
1916// if (reloc.pcrel) {
1917// const source_vaddr = source_sym.value + reloc.offset;
1918// const disp =
1919// @intCast(i32, target_vaddr_with_addend) - @intCast(i32, source_vaddr) - 4;
1920// try self.base.file.?.pwriteAll(mem.asBytes(&disp), file_offset + reloc.offset);
1921// continue;
1922// }
1923
1924// switch (reloc.length) {
1925// 2 => try self.base.file.?.pwriteAll(
1926// mem.asBytes(&@truncate(u32, target_vaddr_with_addend)),
1927// file_offset + reloc.offset,
1928// ),
1929// 3 => try self.base.file.?.pwriteAll(
1930// mem.asBytes(&(target_vaddr_with_addend)),
1931// file_offset + reloc.offset,
1932// ),
1933// else => unreachable,
1934// }
1935// }
1936// }
1937
18621938fn allocateSymbols(self: *MachO) !void {
18631939 const slice = self.sections.slice();
18641940 for (slice.items(.last_atom)) |last_atom, sect_id| {
......@@ -3069,6 +3145,14 @@ pub fn deinit(self: *MachO) void {
30693145 }
30703146
30713147 self.atom_by_index_table.deinit(gpa);
3148
3149 {
3150 var it = self.relocs.valueIterator();
3151 while (it.next()) |relocs| {
3152 relocs.deinit(gpa);
3153 }
3154 self.relocs.deinit(gpa);
3155 }
30723156}
30733157
30743158fn freeAtom(self: *MachO, atom: *Atom, sect_id: u8, owns_atom: bool) void {
src/link/MachO/Atom.zig+46
......@@ -16,6 +16,7 @@ const Arch = std.Target.Cpu.Arch;
1616const Dwarf = @import("../Dwarf.zig");
1717const MachO = @import("../MachO.zig");
1818const Object = @import("Object.zig");
19const RelocationIncr = @import("Relocation.zig"); // temporary name until we clean up object-file relocation scanning
1920const SymbolWithLoc = MachO.SymbolWithLoc;
2021
2122/// Each decl always gets a local symbol with the fully qualified name.
......@@ -894,3 +895,48 @@ inline fn isArithmeticOp(inst: *const [4]u8) bool {
894895 const group_decode = @truncate(u5, inst[3]);
895896 return ((group_decode >> 2) == 4);
896897}
898
899pub fn addRelocation(self: *Atom, macho_file: *MachO, reloc: RelocationIncr) !void {
900 const gpa = macho_file.base.allocator;
901 log.debug(" (adding reloc of type {s} to target %{d})", .{ @tagName(reloc.@"type"), reloc.target.sym_index });
902 const gop = try macho_file.relocs.getOrPut(gpa, self);
903 if (!gop.found_existing) {
904 gop.value_ptr.* = .{};
905 }
906 try gop.value_ptr.append(gpa, reloc);
907}
908
909pub fn resolveRelocationsInCodeBuffer(self: *Atom, macho_file: *MachO, code: []u8) !void {
910 const relocs = macho_file.relocs.get(self) orelse return;
911
912 log.debug("relocating '{s}'", .{self.getName(macho_file)});
913
914 for (relocs.items) |*reloc| {
915 // We don't check for dirty relocation as we resolve in memory so it's effectively free.
916 try reloc.resolve(self, macho_file, code);
917 reloc.dirty = false;
918 }
919}
920
921pub fn resolveRelocationsInFile(self: *Atom, macho_file: *MachO) !void {
922 const relocs = macho_file.relocs.get(self) orelse return;
923 const gpa = macho_file.base.allocator;
924
925 // No code available in a buffer; we need to read it in from the binary.
926 const source_sym = self.getSymbol(macho_file);
927 const source_section = macho_file.sections.get(source_sym.n_sect - 1).header;
928 const file_offset = source_section.offset + source_sym.value - source_section.addr;
929 const code = try gpa.alloc(u8, self.size);
930 try self.base.file.?.preadAll(code, file_offset);
931 defer gpa.free(code);
932
933 log.debug("relocating '{s}'", .{self.getName(macho_file)});
934
935 for (relocs.items) |*reloc| {
936 if (!reloc.dirty) continue;
937 try reloc.resolve(self, macho_file, code);
938 reloc.dirty = false;
939 }
940
941 try self.base.file.?.pwriteAll(code, file_offset);
942}
src/link/MachO/Relocation.zig created+277
......@@ -0,0 +1,277 @@
1const Relocation = @This();
2
3const std = @import("std");
4const aarch64 = @import("../../arch/aarch64/bits.zig");
5const assert = std.debug.assert;
6const log = std.log.scoped(.link);
7const macho = std.macho;
8const math = std.math;
9const mem = std.mem;
10const meta = std.meta;
11
12const Atom = @import("Atom.zig");
13const MachO = @import("../MachO.zig");
14const SymbolWithLoc = MachO.SymbolWithLoc;
15
16pub const Table = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Relocation));
17
18/// Offset within the atom's code buffer.
19/// Note relocation size can be inferred by relocation's kind.
20offset: u32,
21target: SymbolWithLoc,
22addend: i64,
23pcrel: bool,
24length: u2,
25@"type": u4,
26dirty: bool = true,
27
28pub fn getTargetAtom(self: Relocation, macho_file: *MachO) ?*Atom {
29 switch (macho_file.base.options.target.cpu.arch) {
30 .aarch64 => switch (@intToEnum(macho.reloc_type_arm64, self.@"type")) {
31 .ARM64_RELOC_GOT_LOAD_PAGE21,
32 .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
33 .ARM64_RELOC_POINTER_TO_GOT,
34 => return macho_file.getGotAtomForSymbol(self.target).?,
35 else => {},
36 },
37 .x86_64 => switch (@intToEnum(macho.reloc_type_x86_64, self.@"type")) {
38 .X86_64_RELOC_GOT,
39 .X86_64_RELOC_GOT_LOAD,
40 => return macho_file.getGotAtomForSymbol(self.target).?,
41 else => {},
42 },
43 else => unreachable,
44 }
45 if (macho_file.getStubsAtomForSymbol(self.target)) |stubs_atom| return stubs_atom;
46 if (macho_file.getTlvPtrAtomForSymbol(self.target)) |tlv_ptr_atom| return tlv_ptr_atom;
47 return macho_file.getAtomForSymbol(self.target);
48}
49
50pub fn resolve(self: Relocation, atom: *Atom, macho_file: *MachO, code: []u8) !void {
51 const arch = macho_file.base.options.target.cpu.arch;
52 const source_sym = atom.getSymbol(macho_file);
53 const source_addr = source_sym.n_value + self.offset;
54
55 const target_atom = self.getTargetAtom(macho_file) orelse return;
56 const target_addr = target_atom.getSymbol(macho_file).n_value + self.addend;
57
58 log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{
59 source_addr,
60 target_addr,
61 macho_file.getSymbolName(self.target),
62 switch (arch) {
63 .aarch64 => @tagName(@intToEnum(macho.reloc_type_arm64, self.@"type")),
64 .x86_64 => @tagName(@intToEnum(macho.reloc_type_x86_64, self.@"type")),
65 else => unreachable,
66 },
67 });
68
69 switch (arch) {
70 .aarch64 => return self.resolveAarch64(source_addr, target_addr, macho_file, code),
71 .x86_64 => return self.resolveX8664(source_addr, target_addr, code),
72 else => unreachable,
73 }
74}
75
76fn resolveAarch64(self: Relocation, source_addr: u64, target_addr: u64, macho_file: *MachO, code: []u8) !void {
77 const rel_type = @intToEnum(macho.reloc_type_arm64, self.@"type");
78 switch (rel_type) {
79 .ARM64_RELOC_BRANCH26 => {
80 const displacement = math.cast(i28, @intCast(i64, target_addr) - @intCast(i64, source_addr)) orelse {
81 log.err("jump too big to encode as i28 displacement value", .{});
82 log.err(" (target - source) = displacement => 0x{x} - 0x{x} = 0x{x}", .{
83 target_addr,
84 source_addr,
85 @intCast(i64, target_addr) - @intCast(i64, source_addr),
86 });
87 log.err(" TODO implement branch islands to extend jump distance for arm64", .{});
88 return error.TODOImplementBranchIslands;
89 };
90 var inst = aarch64.Instruction{
91 .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload(
92 aarch64.Instruction,
93 aarch64.Instruction.unconditional_branch_immediate,
94 ), code),
95 };
96 inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2));
97 mem.writeIntLittle(u32, code, inst.toU32());
98 },
99 .ARM64_RELOC_PAGE21,
100 .ARM64_RELOC_GOT_LOAD_PAGE21,
101 .ARM64_RELOC_TLVP_LOAD_PAGE21,
102 => {
103 const source_page = @intCast(i32, source_addr >> 12);
104 const target_page = @intCast(i32, target_addr >> 12);
105 const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
106 var inst = aarch64.Instruction{
107 .pc_relative_address = mem.bytesToValue(meta.TagPayload(
108 aarch64.Instruction,
109 aarch64.Instruction.pc_relative_address,
110 ), code),
111 };
112 inst.pc_relative_address.immhi = @truncate(u19, pages >> 2);
113 inst.pc_relative_address.immlo = @truncate(u2, pages);
114 mem.writeIntLittle(u32, code, inst.toU32());
115 },
116 .ARM64_RELOC_PAGEOFF12 => {
117 const narrowed = @truncate(u12, @intCast(u64, target_addr));
118 if (isArithmeticOp(code)) {
119 var inst = aarch64.Instruction{
120 .add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
121 aarch64.Instruction,
122 aarch64.Instruction.add_subtract_immediate,
123 ), code),
124 };
125 inst.add_subtract_immediate.imm12 = narrowed;
126 mem.writeIntLittle(u32, code, inst.toU32());
127 } else {
128 var inst = aarch64.Instruction{
129 .load_store_register = mem.bytesToValue(meta.TagPayload(
130 aarch64.Instruction,
131 aarch64.Instruction.load_store_register,
132 ), code),
133 };
134 const offset: u12 = blk: {
135 if (inst.load_store_register.size == 0) {
136 if (inst.load_store_register.v == 1) {
137 // 128-bit SIMD is scaled by 16.
138 break :blk try math.divExact(u12, narrowed, 16);
139 }
140 // Otherwise, 8-bit SIMD or ldrb.
141 break :blk narrowed;
142 } else {
143 const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size);
144 break :blk try math.divExact(u12, narrowed, denom);
145 }
146 };
147 inst.load_store_register.offset = offset;
148 mem.writeIntLittle(u32, code, inst.toU32());
149 }
150 },
151 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => {
152 const narrowed = @truncate(u12, @intCast(u64, target_addr));
153 var inst: aarch64.Instruction = .{
154 .load_store_register = mem.bytesToValue(meta.TagPayload(
155 aarch64.Instruction,
156 aarch64.Instruction.load_store_register,
157 ), code),
158 };
159 const offset = try math.divExact(u12, narrowed, 8);
160 inst.load_store_register.offset = offset;
161 mem.writeIntLittle(u32, code, inst.toU32());
162 },
163 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
164 const RegInfo = struct {
165 rd: u5,
166 rn: u5,
167 size: u2,
168 };
169 const reg_info: RegInfo = blk: {
170 if (isArithmeticOp(code)) {
171 const inst = mem.bytesToValue(meta.TagPayload(
172 aarch64.Instruction,
173 aarch64.Instruction.add_subtract_immediate,
174 ), code);
175 break :blk .{
176 .rd = inst.rd,
177 .rn = inst.rn,
178 .size = inst.sf,
179 };
180 } else {
181 const inst = mem.bytesToValue(meta.TagPayload(
182 aarch64.Instruction,
183 aarch64.Instruction.load_store_register,
184 ), code);
185 break :blk .{
186 .rd = inst.rt,
187 .rn = inst.rn,
188 .size = inst.size,
189 };
190 }
191 };
192 const narrowed = @truncate(u12, @intCast(u64, target_addr));
193 var inst = if (macho_file.tlv_ptr_entries_table.contains(self.target)) blk: {
194 const offset = try math.divExact(u12, narrowed, 8);
195 break :blk aarch64.Instruction{
196 .load_store_register = .{
197 .rt = reg_info.rd,
198 .rn = reg_info.rn,
199 .offset = offset,
200 .opc = 0b01,
201 .op1 = 0b01,
202 .v = 0,
203 .size = reg_info.size,
204 },
205 };
206 } else aarch64.Instruction{
207 .add_subtract_immediate = .{
208 .rd = reg_info.rd,
209 .rn = reg_info.rn,
210 .imm12 = narrowed,
211 .sh = 0,
212 .s = 0,
213 .op = 0,
214 .sf = @truncate(u1, reg_info.size),
215 },
216 };
217 mem.writeIntLittle(u32, code, inst.toU32());
218 },
219 .ARM64_RELOC_POINTER_TO_GOT => {
220 const result = math.cast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr)) orelse
221 return error.Overflow;
222 mem.writeIntLittle(u32, code, @bitCast(u32, result));
223 },
224 .ARM64_RELOC_UNSIGNED => {
225 switch (self.length) {
226 2 => mem.writeIntLittle(u32, code, @truncate(u32, @bitCast(u64, target_addr))),
227 3 => mem.writeIntLittle(u64, code, target_addr),
228 else => unreachable,
229 }
230 },
231 .ARM64_RELOC_SUBTRACTOR => unreachable,
232 .ARM64_RELOC_ADDEND => unreachable,
233 }
234}
235
236fn resolveX8664(self: Relocation, source_addr: u64, target_addr: u64, code: []u8) !void {
237 const rel_type = @intToEnum(macho.reloc_type_x86_64, self.@"type");
238 switch (rel_type) {
239 .X86_64_RELOC_BRANCH,
240 .X86_64_RELOC_GOT,
241 .X86_64_RELOC_GOT_LOAD,
242 .X86_64_RELOC_TLV,
243 => {
244 const displacement = math.cast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr) - 4) orelse
245 return error.Overflow;
246 mem.writeIntLittle(u32, code, @bitCast(u32, displacement));
247 },
248 .X86_64_RELOC_SIGNED,
249 .X86_64_RELOC_SIGNED_1,
250 .X86_64_RELOC_SIGNED_2,
251 .X86_64_RELOC_SIGNED_4,
252 => {
253 const correction: u3 = switch (rel_type) {
254 .X86_64_RELOC_SIGNED => 0,
255 .X86_64_RELOC_SIGNED_1 => 1,
256 .X86_64_RELOC_SIGNED_2 => 2,
257 .X86_64_RELOC_SIGNED_4 => 4,
258 else => unreachable,
259 };
260 const displacement = math.cast(i32, target_addr - @intCast(i64, source_addr + correction + 4)) orelse
261 return error.Overflow;
262 mem.writeIntLittle(u32, code, @bitCast(u32, displacement));
263 },
264 .X86_64_RELOC_UNSIGNED => {
265 switch (self.length) {
266 2 => mem.writeIntLittle(u32, code, @truncate(u32, @bitCast(u64, target_addr))),
267 3 => mem.writeIntLittle(u64, code, target_addr),
268 }
269 },
270 .X86_64_RELOC_SUBTRACTOR => unreachable,
271 }
272}
273
274inline fn isArithmeticOp(inst: *const [4]u8) bool {
275 const group_decode = @truncate(u5, inst[3]);
276 return ((group_decode >> 2) == 4);
277}