authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-04-11 12:54:57+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-04-13 10:56:03+02:00
log1119970d22f67112159682ab103c767088cbf4b8
tree8c120994ad987f0d62de5a58b86a70a4f82f07f2
parentb1f9db75f3380c9a563972a197c5d1c363c6f215

zld: add x86_64 relocs


6 files changed, 1056 insertions(+), 658 deletions(-)

CMakeLists.txt+2
......@@ -575,6 +575,8 @@ set(ZIG_STAGE2_SOURCES
575575 "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig"
576576 "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig"
577577 "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc.zig"
578 "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/aarch64.zig"
579 "${CMAKE_SOURCE_DIR}/src/link/MachO/reloc/x86_64.zig"
578580 "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"
579581 "${CMAKE_SOURCE_DIR}/src/link/C/zig.h"
580582 "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin"
src/link/MachO/Object.zig+1
......@@ -310,6 +310,7 @@ pub fn parseSections(self: *Object) !void {
310310
311311 break :relocs try reloc.parse(
312312 self.allocator,
313 self.arch.?,
313314 section.code,
314315 mem.bytesAsSlice(macho.relocation_info, raw_relocs),
315316 );
src/link/MachO/Zld.zig+21-12
......@@ -10,12 +10,12 @@ const macho = std.macho;
1010const math = std.math;
1111const log = std.log.scoped(.zld);
1212const aarch64 = @import("../../codegen/aarch64.zig");
13const reloc = @import("reloc.zig");
1314
1415const Allocator = mem.Allocator;
1516const Archive = @import("Archive.zig");
1617const CodeSignature = @import("CodeSignature.zig");
1718const Object = @import("Object.zig");
18const Relocation = @import("reloc.zig").Relocation;
1919const Symbol = @import("Symbol.zig");
2020const Trie = @import("Trie.zig");
2121
......@@ -1360,11 +1360,11 @@ fn resolveStubsAndGotEntries(self: *Zld) !void {
13601360
13611361 for (object.sections.items) |sect| {
13621362 const relocs = sect.relocs orelse continue;
1363 for (relocs) |reloc| {
1364 switch (reloc.@"type") {
1363 for (relocs) |rel| {
1364 switch (rel.@"type") {
13651365 .unsigned => continue,
1366 .got_page, .got_page_off => {
1367 const sym = object.symtab.items[reloc.target.symbol];
1366 .got_page, .got_page_off, .got_load, .got => {
1367 const sym = object.symtab.items[rel.target.symbol];
13681368 const sym_name = object.getString(sym.n_strx);
13691369
13701370 if (self.got_entries.contains(sym_name)) continue;
......@@ -1383,7 +1383,9 @@ fn resolveStubsAndGotEntries(self: *Zld) !void {
13831383 log.debug(" | found GOT entry {s}: {}", .{ sym_name, self.got_entries.get(sym_name) });
13841384 },
13851385 else => {
1386 const sym = object.symtab.items[reloc.target.symbol];
1386 if (rel.target != .symbol) continue;
1387
1388 const sym = object.symtab.items[rel.target.symbol];
13871389 const sym_name = object.getString(sym.n_strx);
13881390
13891391 if (!Symbol.isUndef(sym)) continue;
......@@ -1442,20 +1444,23 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void {
14421444 for (relocs) |rel| {
14431445 const source_addr = target_sect_addr + rel.offset;
14441446
1445 var args: Relocation.ResolveArgs = .{
1447 var args: reloc.Relocation.ResolveArgs = .{
14461448 .source_addr = source_addr,
14471449 .target_addr = undefined,
1448 .subtractor = null,
14491450 };
14501451
14511452 switch (rel.@"type") {
14521453 .unsigned => {
14531454 args.target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel.target);
14541455
1455 const unsigned = rel.cast(Relocation.Unsigned) orelse unreachable;
1456 const unsigned = rel.cast(reloc.Unsigned) orelse unreachable;
14561457 if (unsigned.subtractor) |subtractor| {
14571458 args.subtractor = try self.relocTargetAddr(@intCast(u16, object_id), subtractor);
14581459 }
1460 if (rel.target == .section) {
1461 const source_sect = object.sections.items[rel.target.section];
1462 args.source_sect_addr = source_sect.inner.addr;
1463 }
14591464
14601465 rebases: {
14611466 var hit: bool = false;
......@@ -1500,7 +1505,7 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void {
15001505 try self.threadlocal_offsets.append(self.allocator, args.target_addr - base_addr);
15011506 }
15021507 },
1503 .got_page, .got_page_off => {
1508 .got_page, .got_page_off, .got_load, .got => {
15041509 const dc_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment;
15051510 const got = dc_seg.sections.items[self.got_section_index.?];
15061511 const sym = object.symtab.items[rel.target.symbol];
......@@ -1508,7 +1513,11 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void {
15081513 const entry = self.got_entries.get(sym_name) orelse unreachable;
15091514 args.target_addr = got.addr + entry.index * @sizeOf(u64);
15101515 },
1511 else => {
1516 else => |tt| {
1517 if (tt == .signed and rel.target == .section) {
1518 const source_sect = object.sections.items[rel.target.section];
1519 args.source_sect_addr = source_sect.inner.addr;
1520 }
15121521 args.target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel.target);
15131522 },
15141523 }
......@@ -1547,7 +1556,7 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void {
15471556 }
15481557}
15491558
1550fn relocTargetAddr(self: *Zld, object_id: u16, target: Relocation.Target) !u64 {
1559fn relocTargetAddr(self: *Zld, object_id: u16, target: reloc.Relocation.Target) !u64 {
15511560 const object = self.objects.items[object_id];
15521561 const target_addr = blk: {
15531562 switch (target) {
src/link/MachO/reloc.zig+102-646
......@@ -1,5 +1,4 @@
11const std = @import("std");
2const aarch64 = @import("../../codegen/aarch64.zig");
32const assert = std.debug.assert;
43const log = std.log.scoped(.reloc);
54const macho = std.macho;
......@@ -7,6 +6,9 @@ const math = std.math;
76const mem = std.mem;
87const meta = std.meta;
98
9const aarch64 = @import("reloc/aarch64.zig");
10const x86_64 = @import("reloc/x86_64.zig");
11
1012const Allocator = mem.Allocator;
1113
1214pub const Relocation = struct {
......@@ -25,7 +27,8 @@ pub const Relocation = struct {
2527 pub const ResolveArgs = struct {
2628 source_addr: u64,
2729 target_addr: u64,
28 subtractor: ?u64,
30 subtractor: ?u64 = null,
31 source_sect_addr: ?u64 = null,
2932 };
3033
3134 pub fn resolve(base: *Relocation, args: ResolveArgs) !void {
......@@ -35,21 +38,28 @@ pub const Relocation = struct {
3538 log.debug(" | target address 0x{x}", .{args.target_addr});
3639 if (args.subtractor) |sub|
3740 log.debug(" | subtractor address 0x{x}", .{sub});
41 if (args.source_sect_addr) |addr|
42 log.debug(" | source section address 0x{x}", .{addr});
3843
3944 return switch (base.@"type") {
40 .branch => @fieldParentPtr(Branch, "base", base).resolve(args.source_addr, args.target_addr),
41 .unsigned => @fieldParentPtr(Unsigned, "base", base).resolve(args.target_addr, args.subtractor),
42 .page => @fieldParentPtr(Page, "base", base).resolve(args.source_addr, args.target_addr),
43 .page_off => @fieldParentPtr(PageOff, "base", base).resolve(args.target_addr),
44 .got_page => @fieldParentPtr(GotPage, "base", base).resolve(args.source_addr, args.target_addr),
45 .got_page_off => @fieldParentPtr(GotPageOff, "base", base).resolve(args.target_addr),
46 .tlvp_page => @fieldParentPtr(TlvpPage, "base", base).resolve(args.source_addr, args.target_addr),
47 .tlvp_page_off => @fieldParentPtr(TlvpPageOff, "base", base).resolve(args.target_addr),
45 .unsigned => @fieldParentPtr(Unsigned, "base", base).resolve(args),
46 .branch_aarch64 => @fieldParentPtr(aarch64.Branch, "base", base).resolve(args),
47 .page => @fieldParentPtr(aarch64.Page, "base", base).resolve(args),
48 .page_off => @fieldParentPtr(aarch64.PageOff, "base", base).resolve(args),
49 .got_page => @fieldParentPtr(aarch64.GotPage, "base", base).resolve(args),
50 .got_page_off => @fieldParentPtr(aarch64.GotPageOff, "base", base).resolve(args),
51 .tlvp_page => @fieldParentPtr(aarch64.TlvpPage, "base", base).resolve(args),
52 .tlvp_page_off => @fieldParentPtr(aarch64.TlvpPageOff, "base", base).resolve(args),
53 .branch_x86_64 => @fieldParentPtr(x86_64.Branch, "base", base).resolve(args),
54 .signed => @fieldParentPtr(x86_64.Signed, "base", base).resolve(args),
55 .got_load => @fieldParentPtr(x86_64.GotLoad, "base", base).resolve(args),
56 .got => @fieldParentPtr(x86_64.Got, "base", base).resolve(args),
57 .tlv => @fieldParentPtr(x86_64.Tlv, "base", base).resolve(args),
4858 };
4959 }
5060
5161 pub const Type = enum {
52 branch,
62 branch_aarch64,
5363 unsigned,
5464 page,
5565 page_off,
......@@ -57,6 +67,11 @@ pub const Relocation = struct {
5767 got_page_off,
5868 tlvp_page,
5969 tlvp_page_off,
70 branch_x86_64,
71 signed,
72 got_load,
73 got,
74 tlv,
6075 };
6176
6277 pub const Target = union(enum) {
......@@ -71,236 +86,91 @@ pub const Relocation = struct {
7186 };
7287 }
7388 };
89};
7490
75 pub const Branch = struct {
76 base: Relocation,
77 /// Always .UnconditionalBranchImmediate
78 inst: aarch64.Instruction,
79
80 pub const base_type: Relocation.Type = .branch;
81
82 pub fn resolve(branch: Branch, source_addr: u64, target_addr: u64) !void {
83 const displacement = try math.cast(i28, @intCast(i64, target_addr) - @intCast(i64, source_addr));
84
85 log.debug(" | displacement 0x{x}", .{displacement});
86
87 var inst = branch.inst;
88 inst.UnconditionalBranchImmediate.imm26 = @truncate(u26, @bitCast(u28, displacement) >> 2);
89 mem.writeIntLittle(u32, branch.base.code[0..4], inst.toU32());
90 }
91 };
92
93 pub const Unsigned = struct {
94 base: Relocation,
95 subtractor: ?Target = null,
96 /// Addend embedded directly in the relocation slot
97 addend: i64,
98 /// Extracted from r_length:
99 /// => 3 implies true
100 /// => 2 implies false
101 /// => * is unreachable
102 is_64bit: bool,
103
104 pub const base_type: Relocation.Type = .unsigned;
105
106 pub fn resolve(unsigned: Unsigned, target_addr: u64, subtractor: ?u64) !void {
107 const result = if (subtractor) |sub|
108 @intCast(i64, target_addr) - @intCast(i64, sub) + unsigned.addend
109 else
110 @intCast(i64, target_addr) + unsigned.addend;
111
112 log.debug(" | calculated addend 0x{x}", .{unsigned.addend});
113 log.debug(" | calculated unsigned value 0x{x}", .{result});
114
115 if (unsigned.is_64bit) {
116 mem.writeIntLittle(
117 u64,
118 unsigned.base.code[0..8],
119 @bitCast(u64, result),
120 );
121 } else {
122 mem.writeIntLittle(
123 u32,
124 unsigned.base.code[0..4],
125 @truncate(u32, @bitCast(u64, result)),
126 );
127 }
128 }
129 };
130
131 pub const Page = struct {
132 base: Relocation,
133 addend: ?u32 = null,
134 /// Always .PCRelativeAddress
135 inst: aarch64.Instruction,
136
137 pub const base_type: Relocation.Type = .page;
138
139 pub fn resolve(page: Page, source_addr: u64, target_addr: u64) !void {
140 const ta = if (page.addend) |a| target_addr + a else target_addr;
141 const source_page = @intCast(i32, source_addr >> 12);
142 const target_page = @intCast(i32, ta >> 12);
143 const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
144
145 log.debug(" | calculated addend 0x{x}", .{page.addend});
146 log.debug(" | moving by {} pages", .{pages});
147
148 var inst = page.inst;
149 inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2);
150 inst.PCRelativeAddress.immlo = @truncate(u2, pages);
151
152 mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
153 }
154 };
155
156 pub const PageOff = struct {
157 base: Relocation,
158 addend: ?u32 = null,
159 op_kind: OpKind,
160 inst: aarch64.Instruction,
161
162 pub const base_type: Relocation.Type = .page_off;
163
164 pub const OpKind = enum {
165 arithmetic,
166 load_store,
167 };
168
169 pub fn resolve(page_off: PageOff, target_addr: u64) !void {
170 const ta = if (page_off.addend) |a| target_addr + a else target_addr;
171 const narrowed = @truncate(u12, ta);
172
173 log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
174 log.debug(" | {s} opcode", .{page_off.op_kind});
175
176 var inst = page_off.inst;
177 if (page_off.op_kind == .arithmetic) {
178 inst.AddSubtractImmediate.imm12 = narrowed;
179 } else {
180 const offset: u12 = blk: {
181 if (inst.LoadStoreRegister.size == 0) {
182 if (inst.LoadStoreRegister.v == 1) {
183 // 128-bit SIMD is scaled by 16.
184 break :blk try math.divExact(u12, narrowed, 16);
185 }
186 // Otherwise, 8-bit SIMD or ldrb.
187 break :blk narrowed;
188 } else {
189 const denom: u4 = try math.powi(u4, 2, inst.LoadStoreRegister.size);
190 break :blk try math.divExact(u12, narrowed, denom);
191 }
192 };
193 inst.LoadStoreRegister.offset = offset;
194 }
195
196 mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
197 }
198 };
199
200 pub const GotPage = struct {
201 base: Relocation,
202 /// Always .PCRelativeAddress
203 inst: aarch64.Instruction,
204
205 pub const base_type: Relocation.Type = .got_page;
206
207 pub fn resolve(page: GotPage, source_addr: u64, target_addr: u64) !void {
208 const source_page = @intCast(i32, source_addr >> 12);
209 const target_page = @intCast(i32, target_addr >> 12);
210 const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
211
212 log.debug(" | moving by {} pages", .{pages});
213
214 var inst = page.inst;
215 inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2);
216 inst.PCRelativeAddress.immlo = @truncate(u2, pages);
217
218 mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
219 }
220 };
221
222 pub const GotPageOff = struct {
223 base: Relocation,
224 /// Always .LoadStoreRegister with size = 3 for GOT indirection
225 inst: aarch64.Instruction,
226
227 pub const base_type: Relocation.Type = .got_page_off;
228
229 pub fn resolve(page_off: GotPageOff, target_addr: u64) !void {
230 const narrowed = @truncate(u12, target_addr);
231
232 log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
233
234 var inst = page_off.inst;
235 const offset = try math.divExact(u12, narrowed, 8);
236 inst.LoadStoreRegister.offset = offset;
237
238 mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
239 }
240 };
241
242 pub const TlvpPage = struct {
243 base: Relocation,
244 /// Always .PCRelativeAddress
245 inst: aarch64.Instruction,
246
247 pub const base_type: Relocation.Type = .tlvp_page;
248
249 pub fn resolve(page: TlvpPage, source_addr: u64, target_addr: u64) !void {
250 const source_page = @intCast(i32, source_addr >> 12);
251 const target_page = @intCast(i32, target_addr >> 12);
252 const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
253
254 log.debug(" | moving by {} pages", .{pages});
255
256 var inst = page.inst;
257 inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2);
258 inst.PCRelativeAddress.immlo = @truncate(u2, pages);
259
260 mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
261 }
262 };
263
264 pub const TlvpPageOff = struct {
265 base: Relocation,
266 /// Always .AddSubtractImmediate regardless of the source instruction.
267 /// This means, we always rewrite the instruction to add even if the
268 /// source instruction was an ldr.
269 inst: aarch64.Instruction,
270
271 pub const base_type: Relocation.Type = .tlvp_page_off;
272
273 pub fn resolve(page_off: TlvpPageOff, target_addr: u64) !void {
274 const narrowed = @truncate(u12, target_addr);
91pub const Unsigned = struct {
92 base: Relocation,
93 subtractor: ?Relocation.Target = null,
94 /// Addend embedded directly in the relocation slot
95 addend: i64,
96 /// Extracted from r_length:
97 /// => 3 implies true
98 /// => 2 implies false
99 /// => * is unreachable
100 is_64bit: bool,
101
102 pub const base_type: Relocation.Type = .unsigned;
103
104 pub fn resolve(unsigned: Unsigned, args: Relocation.ResolveArgs) !void {
105 const addend = if (unsigned.base.target == .section)
106 unsigned.addend - @intCast(i64, args.source_sect_addr.?)
107 else
108 unsigned.addend;
275109
276 log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
110 const result = if (args.subtractor) |subtractor|
111 @intCast(i64, args.target_addr) - @intCast(i64, subtractor) + addend
112 else
113 @intCast(i64, args.target_addr) + addend;
277114
278 var inst = page_off.inst;
279 inst.AddSubtractImmediate.imm12 = narrowed;
115 log.debug(" | calculated addend 0x{x}", .{addend});
116 log.debug(" | calculated unsigned value 0x{x}", .{result});
280117
281 mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
118 if (unsigned.is_64bit) {
119 mem.writeIntLittle(
120 u64,
121 unsigned.base.code[0..8],
122 @bitCast(u64, result),
123 );
124 } else {
125 mem.writeIntLittle(
126 u32,
127 unsigned.base.code[0..4],
128 @truncate(u32, @bitCast(u64, result)),
129 );
282130 }
283 };
131 }
284132};
285133
286pub fn parse(allocator: *Allocator, code: []u8, relocs: []const macho.relocation_info) ![]*Relocation {
134pub fn parse(
135 allocator: *Allocator,
136 arch: std.Target.Cpu.Arch,
137 code: []u8,
138 relocs: []const macho.relocation_info,
139) ![]*Relocation {
287140 var it = RelocIterator{
288141 .buffer = relocs,
289142 };
290143
291 var parser = Parser{
292 .allocator = allocator,
293 .it = &it,
294 .code = code,
295 .parsed = std.ArrayList(*Relocation).init(allocator),
296 };
297 defer parser.deinit();
298 try parser.parse();
144 switch (arch) {
145 .aarch64 => {
146 var parser = aarch64.Parser{
147 .allocator = allocator,
148 .it = &it,
149 .code = code,
150 .parsed = std.ArrayList(*Relocation).init(allocator),
151 };
152 defer parser.deinit();
153 try parser.parse();
154
155 return parser.parsed.toOwnedSlice();
156 },
157 .x86_64 => {
158 var parser = x86_64.Parser{
159 .allocator = allocator,
160 .it = &it,
161 .code = code,
162 .parsed = std.ArrayList(*Relocation).init(allocator),
163 };
164 defer parser.deinit();
165 try parser.parse();
299166
300 return parser.parsed.toOwnedSlice();
167 return parser.parsed.toOwnedSlice();
168 },
169 else => unreachable,
170 }
301171}
302172
303const RelocIterator = struct {
173pub const RelocIterator = struct {
304174 buffer: []const macho.relocation_info,
305175 index: i64 = -1,
306176
......@@ -308,7 +178,8 @@ const RelocIterator = struct {
308178 self.index += 1;
309179 if (self.index < self.buffer.len) {
310180 const reloc = self.buffer[@intCast(u64, self.index)];
311 log.debug("{s}", .{@intToEnum(macho.reloc_type_arm64, reloc.r_type)});
181 log.debug("relocation", .{});
182 log.debug(" | type = {}", .{reloc.r_type});
312183 log.debug(" | offset = {}", .{reloc.r_address});
313184 log.debug(" | PC = {}", .{reloc.r_pcrel == 1});
314185 log.debug(" | length = {}", .{reloc.r_length});
......@@ -319,423 +190,8 @@ const RelocIterator = struct {
319190 return null;
320191 }
321192
322 pub fn peek(self: *RelocIterator) ?macho.reloc_type_arm64 {
323 if (self.index + 1 < self.buffer.len) {
324 const reloc = self.buffer[@intCast(u64, self.index + 1)];
325 const tt = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
326 return tt;
327 }
328 return null;
193 pub fn peek(self: RelocIterator) macho.relocation_info {
194 assert(self.index + 1 < self.buffer.len);
195 return self.buffer[@intCast(u64, self.index + 1)];
329196 }
330197};
331
332const Parser = struct {
333 allocator: *Allocator,
334 it: *RelocIterator,
335 code: []u8,
336 parsed: std.ArrayList(*Relocation),
337 addend: ?u32 = null,
338 subtractor: ?Relocation.Target = null,
339
340 fn deinit(parser: *Parser) void {
341 parser.parsed.deinit();
342 }
343
344 fn parse(parser: *Parser) !void {
345 while (parser.it.next()) |reloc| {
346 switch (@intToEnum(macho.reloc_type_arm64, reloc.r_type)) {
347 .ARM64_RELOC_BRANCH26 => {
348 try parser.parseBranch(reloc);
349 },
350 .ARM64_RELOC_SUBTRACTOR => {
351 try parser.parseSubtractor(reloc);
352 },
353 .ARM64_RELOC_UNSIGNED => {
354 try parser.parseUnsigned(reloc);
355 },
356 .ARM64_RELOC_ADDEND => {
357 try parser.parseAddend(reloc);
358 },
359 .ARM64_RELOC_PAGE21,
360 .ARM64_RELOC_GOT_LOAD_PAGE21,
361 .ARM64_RELOC_TLVP_LOAD_PAGE21,
362 => {
363 try parser.parsePage(reloc);
364 },
365 .ARM64_RELOC_PAGEOFF12 => {
366 try parser.parsePageOff(reloc);
367 },
368 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => {
369 try parser.parseGotLoadPageOff(reloc);
370 },
371 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
372 try parser.parseTlvpLoadPageOff(reloc);
373 },
374 .ARM64_RELOC_POINTER_TO_GOT => {
375 return error.ToDoRelocPointerToGot;
376 },
377 }
378 }
379 }
380
381 fn parseAddend(parser: *Parser, reloc: macho.relocation_info) !void {
382 const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
383 assert(reloc_type == .ARM64_RELOC_ADDEND);
384 assert(reloc.r_pcrel == 0);
385 assert(reloc.r_extern == 0);
386 assert(parser.addend == null);
387
388 parser.addend = reloc.r_symbolnum;
389
390 // Verify ADDEND is followed by a load.
391 if (parser.it.peek()) |tt| {
392 switch (tt) {
393 .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
394 else => |other| {
395 log.err("unexpected relocation type: expected PAGE21 or PAGEOFF12, found {s}", .{other});
396 return error.UnexpectedRelocationType;
397 },
398 }
399 } else {
400 log.err("unexpected end of stream", .{});
401 return error.UnexpectedEndOfStream;
402 }
403 }
404
405 fn parseBranch(parser: *Parser, reloc: macho.relocation_info) !void {
406 const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
407 assert(reloc_type == .ARM64_RELOC_BRANCH26);
408 assert(reloc.r_pcrel == 1);
409 assert(reloc.r_length == 2);
410
411 const offset = @intCast(u32, reloc.r_address);
412 const inst = parser.code[offset..][0..4];
413 const parsed_inst = aarch64.Instruction{ .UnconditionalBranchImmediate = mem.bytesToValue(
414 meta.TagPayload(
415 aarch64.Instruction,
416 aarch64.Instruction.UnconditionalBranchImmediate,
417 ),
418 inst,
419 ) };
420
421 var branch = try parser.allocator.create(Relocation.Branch);
422 errdefer parser.allocator.destroy(branch);
423
424 const target = Relocation.Target.from_reloc(reloc);
425
426 branch.* = .{
427 .base = .{
428 .@"type" = .branch,
429 .code = inst,
430 .offset = @intCast(u32, reloc.r_address),
431 .target = target,
432 },
433 .inst = parsed_inst,
434 };
435
436 log.debug(" | emitting {}", .{branch});
437 try parser.parsed.append(&branch.base);
438 }
439
440 fn parsePage(parser: *Parser, reloc: macho.relocation_info) !void {
441 assert(reloc.r_pcrel == 1);
442 assert(reloc.r_length == 2);
443
444 const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
445 const target = Relocation.Target.from_reloc(reloc);
446
447 const offset = @intCast(u32, reloc.r_address);
448 const inst = parser.code[offset..][0..4];
449 const parsed_inst = aarch64.Instruction{ .PCRelativeAddress = mem.bytesToValue(meta.TagPayload(
450 aarch64.Instruction,
451 aarch64.Instruction.PCRelativeAddress,
452 ), inst) };
453
454 const ptr: *Relocation = ptr: {
455 switch (reloc_type) {
456 .ARM64_RELOC_PAGE21 => {
457 defer {
458 // Reset parser's addend state
459 parser.addend = null;
460 }
461 var page = try parser.allocator.create(Relocation.Page);
462 errdefer parser.allocator.destroy(page);
463
464 page.* = .{
465 .base = .{
466 .@"type" = .page,
467 .code = inst,
468 .offset = offset,
469 .target = target,
470 },
471 .addend = parser.addend,
472 .inst = parsed_inst,
473 };
474
475 log.debug(" | emitting {}", .{page});
476
477 break :ptr &page.base;
478 },
479 .ARM64_RELOC_GOT_LOAD_PAGE21 => {
480 var page = try parser.allocator.create(Relocation.GotPage);
481 errdefer parser.allocator.destroy(page);
482
483 page.* = .{
484 .base = .{
485 .@"type" = .got_page,
486 .code = inst,
487 .offset = offset,
488 .target = target,
489 },
490 .inst = parsed_inst,
491 };
492
493 log.debug(" | emitting {}", .{page});
494
495 break :ptr &page.base;
496 },
497 .ARM64_RELOC_TLVP_LOAD_PAGE21 => {
498 var page = try parser.allocator.create(Relocation.TlvpPage);
499 errdefer parser.allocator.destroy(page);
500
501 page.* = .{
502 .base = .{
503 .@"type" = .tlvp_page,
504 .code = inst,
505 .offset = offset,
506 .target = target,
507 },
508 .inst = parsed_inst,
509 };
510
511 log.debug(" | emitting {}", .{page});
512
513 break :ptr &page.base;
514 },
515 else => unreachable,
516 }
517 };
518
519 try parser.parsed.append(ptr);
520 }
521
522 fn parsePageOff(parser: *Parser, reloc: macho.relocation_info) !void {
523 defer {
524 // Reset parser's addend state
525 parser.addend = null;
526 }
527
528 const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
529 assert(reloc_type == .ARM64_RELOC_PAGEOFF12);
530 assert(reloc.r_pcrel == 0);
531 assert(reloc.r_length == 2);
532
533 const offset = @intCast(u32, reloc.r_address);
534 const inst = parser.code[offset..][0..4];
535
536 var op_kind: Relocation.PageOff.OpKind = undefined;
537 var parsed_inst: aarch64.Instruction = undefined;
538 if (isArithmeticOp(inst)) {
539 op_kind = .arithmetic;
540 parsed_inst = .{ .AddSubtractImmediate = mem.bytesToValue(meta.TagPayload(
541 aarch64.Instruction,
542 aarch64.Instruction.AddSubtractImmediate,
543 ), inst) };
544 } else {
545 op_kind = .load_store;
546 parsed_inst = .{ .LoadStoreRegister = mem.bytesToValue(meta.TagPayload(
547 aarch64.Instruction,
548 aarch64.Instruction.LoadStoreRegister,
549 ), inst) };
550 }
551 const target = Relocation.Target.from_reloc(reloc);
552
553 var page_off = try parser.allocator.create(Relocation.PageOff);
554 errdefer parser.allocator.destroy(page_off);
555
556 page_off.* = .{
557 .base = .{
558 .@"type" = .page_off,
559 .code = inst,
560 .offset = offset,
561 .target = target,
562 },
563 .op_kind = op_kind,
564 .inst = parsed_inst,
565 .addend = parser.addend,
566 };
567
568 log.debug(" | emitting {}", .{page_off});
569 try parser.parsed.append(&page_off.base);
570 }
571
572 fn parseGotLoadPageOff(parser: *Parser, reloc: macho.relocation_info) !void {
573 const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
574 assert(reloc_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12);
575 assert(reloc.r_pcrel == 0);
576 assert(reloc.r_length == 2);
577
578 const offset = @intCast(u32, reloc.r_address);
579 const inst = parser.code[offset..][0..4];
580 assert(!isArithmeticOp(inst));
581
582 const parsed_inst = mem.bytesToValue(meta.TagPayload(
583 aarch64.Instruction,
584 aarch64.Instruction.LoadStoreRegister,
585 ), inst);
586 assert(parsed_inst.size == 3);
587
588 const target = Relocation.Target.from_reloc(reloc);
589
590 var page_off = try parser.allocator.create(Relocation.GotPageOff);
591 errdefer parser.allocator.destroy(page_off);
592
593 page_off.* = .{
594 .base = .{
595 .@"type" = .got_page_off,
596 .code = inst,
597 .offset = offset,
598 .target = target,
599 },
600 .inst = .{
601 .LoadStoreRegister = parsed_inst,
602 },
603 };
604
605 log.debug(" | emitting {}", .{page_off});
606 try parser.parsed.append(&page_off.base);
607 }
608
609 fn parseTlvpLoadPageOff(parser: *Parser, reloc: macho.relocation_info) !void {
610 const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
611 assert(reloc_type == .ARM64_RELOC_TLVP_LOAD_PAGEOFF12);
612 assert(reloc.r_pcrel == 0);
613 assert(reloc.r_length == 2);
614
615 const RegInfo = struct {
616 rd: u5,
617 rn: u5,
618 size: u1,
619 };
620
621 const offset = @intCast(u32, reloc.r_address);
622 const inst = parser.code[offset..][0..4];
623 const parsed: RegInfo = parsed: {
624 if (isArithmeticOp(inst)) {
625 const parsed_inst = mem.bytesAsValue(meta.TagPayload(
626 aarch64.Instruction,
627 aarch64.Instruction.AddSubtractImmediate,
628 ), inst);
629 break :parsed .{
630 .rd = parsed_inst.rd,
631 .rn = parsed_inst.rn,
632 .size = parsed_inst.sf,
633 };
634 } else {
635 const parsed_inst = mem.bytesAsValue(meta.TagPayload(
636 aarch64.Instruction,
637 aarch64.Instruction.LoadStoreRegister,
638 ), inst);
639 break :parsed .{
640 .rd = parsed_inst.rt,
641 .rn = parsed_inst.rn,
642 .size = @truncate(u1, parsed_inst.size),
643 };
644 }
645 };
646
647 const target = Relocation.Target.from_reloc(reloc);
648
649 var page_off = try parser.allocator.create(Relocation.TlvpPageOff);
650 errdefer parser.allocator.destroy(page_off);
651
652 page_off.* = .{
653 .base = .{
654 .@"type" = .tlvp_page_off,
655 .code = inst,
656 .offset = @intCast(u32, reloc.r_address),
657 .target = target,
658 },
659 .inst = .{
660 .AddSubtractImmediate = .{
661 .rd = parsed.rd,
662 .rn = parsed.rn,
663 .imm12 = 0, // This will be filled when target addresses are known.
664 .sh = 0,
665 .s = 0,
666 .op = 0,
667 .sf = parsed.size,
668 },
669 },
670 };
671
672 log.debug(" | emitting {}", .{page_off});
673 try parser.parsed.append(&page_off.base);
674 }
675
676 fn parseSubtractor(parser: *Parser, reloc: macho.relocation_info) !void {
677 const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
678 assert(reloc_type == .ARM64_RELOC_SUBTRACTOR);
679 assert(reloc.r_pcrel == 0);
680 assert(parser.subtractor == null);
681
682 parser.subtractor = Relocation.Target.from_reloc(reloc);
683
684 // Verify SUBTRACTOR is followed by UNSIGNED.
685 if (parser.it.peek()) |tt| {
686 if (tt != .ARM64_RELOC_UNSIGNED) {
687 log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{tt});
688 return error.UnexpectedRelocationType;
689 }
690 } else {
691 log.err("unexpected end of stream", .{});
692 return error.UnexpectedEndOfStream;
693 }
694 }
695
696 fn parseUnsigned(parser: *Parser, reloc: macho.relocation_info) !void {
697 defer {
698 // Reset parser's subtractor state
699 parser.subtractor = null;
700 }
701
702 const reloc_type = @intToEnum(macho.reloc_type_arm64, reloc.r_type);
703 assert(reloc_type == .ARM64_RELOC_UNSIGNED);
704 assert(reloc.r_pcrel == 0);
705
706 var unsigned = try parser.allocator.create(Relocation.Unsigned);
707 errdefer parser.allocator.destroy(unsigned);
708
709 const target = Relocation.Target.from_reloc(reloc);
710 const is_64bit: bool = switch (reloc.r_length) {
711 3 => true,
712 2 => false,
713 else => unreachable,
714 };
715 const offset = @intCast(u32, reloc.r_address);
716 const addend: i64 = if (is_64bit)
717 mem.readIntLittle(i64, parser.code[offset..][0..8])
718 else
719 mem.readIntLittle(i32, parser.code[offset..][0..4]);
720
721 unsigned.* = .{
722 .base = .{
723 .@"type" = .unsigned,
724 .code = if (is_64bit) parser.code[offset..][0..8] else parser.code[offset..][0..4],
725 .offset = offset,
726 .target = target,
727 },
728 .subtractor = parser.subtractor,
729 .is_64bit = is_64bit,
730 .addend = addend,
731 };
732
733 log.debug(" | emitting {}", .{unsigned});
734 try parser.parsed.append(&unsigned.base);
735 }
736};
737
738fn isArithmeticOp(inst: *const [4]u8) callconv(.Inline) bool {
739 const group_decode = @truncate(u5, inst[3]);
740 return ((group_decode >> 2) == 4);
741}
src/link/MachO/reloc/aarch64.zig created+587
......@@ -0,0 +1,587 @@
1const std = @import("std");
2const aarch64 = @import("../../../codegen/aarch64.zig");
3const assert = std.debug.assert;
4const log = std.log.scoped(.reloc);
5const macho = std.macho;
6const math = std.math;
7const mem = std.mem;
8const meta = std.meta;
9const reloc = @import("../reloc.zig");
10
11const Allocator = mem.Allocator;
12const Relocation = reloc.Relocation;
13
14pub const Branch = struct {
15 base: Relocation,
16 /// Always .UnconditionalBranchImmediate
17 inst: aarch64.Instruction,
18
19 pub const base_type: Relocation.Type = .branch_aarch64;
20
21 pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
22 const displacement = try math.cast(i28, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr));
23
24 log.debug(" | displacement 0x{x}", .{displacement});
25
26 var inst = branch.inst;
27 inst.UnconditionalBranchImmediate.imm26 = @truncate(u26, @bitCast(u28, displacement) >> 2);
28 mem.writeIntLittle(u32, branch.base.code[0..4], inst.toU32());
29 }
30};
31
32pub const Page = struct {
33 base: Relocation,
34 addend: ?u32 = null,
35 /// Always .PCRelativeAddress
36 inst: aarch64.Instruction,
37
38 pub const base_type: Relocation.Type = .page;
39
40 pub fn resolve(page: Page, args: Relocation.ResolveArgs) !void {
41 const target_addr = if (page.addend) |addend| args.target_addr + addend else args.target_addr;
42 const source_page = @intCast(i32, args.source_addr >> 12);
43 const target_page = @intCast(i32, target_addr >> 12);
44 const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
45
46 log.debug(" | calculated addend 0x{x}", .{page.addend});
47 log.debug(" | moving by {} pages", .{pages});
48
49 var inst = page.inst;
50 inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2);
51 inst.PCRelativeAddress.immlo = @truncate(u2, pages);
52
53 mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
54 }
55};
56
57pub const PageOff = struct {
58 base: Relocation,
59 addend: ?u32 = null,
60 op_kind: OpKind,
61 inst: aarch64.Instruction,
62
63 pub const base_type: Relocation.Type = .page_off;
64
65 pub const OpKind = enum {
66 arithmetic,
67 load_store,
68 };
69
70 pub fn resolve(page_off: PageOff, args: Relocation.ResolveArgs) !void {
71 const target_addr = if (page_off.addend) |addend| args.target_addr + addend else args.target_addr;
72 const narrowed = @truncate(u12, target_addr);
73
74 log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
75 log.debug(" | {s} opcode", .{page_off.op_kind});
76
77 var inst = page_off.inst;
78 if (page_off.op_kind == .arithmetic) {
79 inst.AddSubtractImmediate.imm12 = narrowed;
80 } else {
81 const offset: u12 = blk: {
82 if (inst.LoadStoreRegister.size == 0) {
83 if (inst.LoadStoreRegister.v == 1) {
84 // 128-bit SIMD is scaled by 16.
85 break :blk try math.divExact(u12, narrowed, 16);
86 }
87 // Otherwise, 8-bit SIMD or ldrb.
88 break :blk narrowed;
89 } else {
90 const denom: u4 = try math.powi(u4, 2, inst.LoadStoreRegister.size);
91 break :blk try math.divExact(u12, narrowed, denom);
92 }
93 };
94 inst.LoadStoreRegister.offset = offset;
95 }
96
97 mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
98 }
99};
100
101pub const GotPage = struct {
102 base: Relocation,
103 /// Always .PCRelativeAddress
104 inst: aarch64.Instruction,
105
106 pub const base_type: Relocation.Type = .got_page;
107
108 pub fn resolve(page: GotPage, args: Relocation.ResolveArgs) !void {
109 const source_page = @intCast(i32, args.source_addr >> 12);
110 const target_page = @intCast(i32, args.target_addr >> 12);
111 const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
112
113 log.debug(" | moving by {} pages", .{pages});
114
115 var inst = page.inst;
116 inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2);
117 inst.PCRelativeAddress.immlo = @truncate(u2, pages);
118
119 mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
120 }
121};
122
123pub const GotPageOff = struct {
124 base: Relocation,
125 /// Always .LoadStoreRegister with size = 3 for GOT indirection
126 inst: aarch64.Instruction,
127
128 pub const base_type: Relocation.Type = .got_page_off;
129
130 pub fn resolve(page_off: GotPageOff, args: Relocation.ResolveArgs) !void {
131 const narrowed = @truncate(u12, args.target_addr);
132
133 log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
134
135 var inst = page_off.inst;
136 const offset = try math.divExact(u12, narrowed, 8);
137 inst.LoadStoreRegister.offset = offset;
138
139 mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
140 }
141};
142
143pub const TlvpPage = struct {
144 base: Relocation,
145 /// Always .PCRelativeAddress
146 inst: aarch64.Instruction,
147
148 pub const base_type: Relocation.Type = .tlvp_page;
149
150 pub fn resolve(page: TlvpPage, args: Relocation.ResolveArgs) !void {
151 const source_page = @intCast(i32, args.source_addr >> 12);
152 const target_page = @intCast(i32, args.target_addr >> 12);
153 const pages = @bitCast(u21, @intCast(i21, target_page - source_page));
154
155 log.debug(" | moving by {} pages", .{pages});
156
157 var inst = page.inst;
158 inst.PCRelativeAddress.immhi = @truncate(u19, pages >> 2);
159 inst.PCRelativeAddress.immlo = @truncate(u2, pages);
160
161 mem.writeIntLittle(u32, page.base.code[0..4], inst.toU32());
162 }
163};
164
165pub const TlvpPageOff = struct {
166 base: Relocation,
167 /// Always .AddSubtractImmediate regardless of the source instruction.
168 /// This means, we always rewrite the instruction to add even if the
169 /// source instruction was an ldr.
170 inst: aarch64.Instruction,
171
172 pub const base_type: Relocation.Type = .tlvp_page_off;
173
174 pub fn resolve(page_off: TlvpPageOff, args: Relocation.ResolveArgs) !void {
175 const narrowed = @truncate(u12, args.target_addr);
176
177 log.debug(" | narrowed address within the page 0x{x}", .{narrowed});
178
179 var inst = page_off.inst;
180 inst.AddSubtractImmediate.imm12 = narrowed;
181
182 mem.writeIntLittle(u32, page_off.base.code[0..4], inst.toU32());
183 }
184};
185
186pub const Parser = struct {
187 allocator: *Allocator,
188 it: *reloc.RelocIterator,
189 code: []u8,
190 parsed: std.ArrayList(*Relocation),
191 addend: ?u32 = null,
192 subtractor: ?Relocation.Target = null,
193
194 pub fn deinit(parser: *Parser) void {
195 parser.parsed.deinit();
196 }
197
198 pub fn parse(parser: *Parser) !void {
199 while (parser.it.next()) |rel| {
200 switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) {
201 .ARM64_RELOC_BRANCH26 => {
202 try parser.parseBranch(rel);
203 },
204 .ARM64_RELOC_SUBTRACTOR => {
205 try parser.parseSubtractor(rel);
206 },
207 .ARM64_RELOC_UNSIGNED => {
208 try parser.parseUnsigned(rel);
209 },
210 .ARM64_RELOC_ADDEND => {
211 try parser.parseAddend(rel);
212 },
213 .ARM64_RELOC_PAGE21,
214 .ARM64_RELOC_GOT_LOAD_PAGE21,
215 .ARM64_RELOC_TLVP_LOAD_PAGE21,
216 => {
217 try parser.parsePage(rel);
218 },
219 .ARM64_RELOC_PAGEOFF12 => {
220 try parser.parsePageOff(rel);
221 },
222 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => {
223 try parser.parseGotLoadPageOff(rel);
224 },
225 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
226 try parser.parseTlvpLoadPageOff(rel);
227 },
228 .ARM64_RELOC_POINTER_TO_GOT => {
229 return error.ToDoRelocPointerToGot;
230 },
231 }
232 }
233 }
234
235 fn parseAddend(parser: *Parser, rel: macho.relocation_info) !void {
236 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
237 assert(rel_type == .ARM64_RELOC_ADDEND);
238 assert(rel.r_pcrel == 0);
239 assert(rel.r_extern == 0);
240 assert(parser.addend == null);
241
242 parser.addend = rel.r_symbolnum;
243
244 // Verify ADDEND is followed by a load.
245 const next = @intToEnum(macho.reloc_type_arm64, parser.it.peek().r_type);
246 switch (next) {
247 .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
248 else => {
249 log.err("unexpected relocation type: expected PAGE21 or PAGEOFF12, found {s}", .{next});
250 return error.UnexpectedRelocationType;
251 },
252 }
253 }
254
255 fn parseBranch(parser: *Parser, rel: macho.relocation_info) !void {
256 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
257 assert(rel_type == .ARM64_RELOC_BRANCH26);
258 assert(rel.r_pcrel == 1);
259 assert(rel.r_length == 2);
260
261 const offset = @intCast(u32, rel.r_address);
262 const inst = parser.code[offset..][0..4];
263 const parsed_inst = aarch64.Instruction{ .UnconditionalBranchImmediate = mem.bytesToValue(
264 meta.TagPayload(
265 aarch64.Instruction,
266 aarch64.Instruction.UnconditionalBranchImmediate,
267 ),
268 inst,
269 ) };
270
271 var branch = try parser.allocator.create(Branch);
272 errdefer parser.allocator.destroy(branch);
273
274 const target = Relocation.Target.from_reloc(rel);
275
276 branch.* = .{
277 .base = .{
278 .@"type" = .branch_aarch64,
279 .code = inst,
280 .offset = offset,
281 .target = target,
282 },
283 .inst = parsed_inst,
284 };
285
286 log.debug(" | emitting {}", .{branch});
287 try parser.parsed.append(&branch.base);
288 }
289
290 fn parsePage(parser: *Parser, rel: macho.relocation_info) !void {
291 assert(rel.r_pcrel == 1);
292 assert(rel.r_length == 2);
293
294 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
295 const target = Relocation.Target.from_reloc(rel);
296
297 const offset = @intCast(u32, rel.r_address);
298 const inst = parser.code[offset..][0..4];
299 const parsed_inst = aarch64.Instruction{ .PCRelativeAddress = mem.bytesToValue(meta.TagPayload(
300 aarch64.Instruction,
301 aarch64.Instruction.PCRelativeAddress,
302 ), inst) };
303
304 const ptr: *Relocation = ptr: {
305 switch (rel_type) {
306 .ARM64_RELOC_PAGE21 => {
307 defer {
308 // Reset parser's addend state
309 parser.addend = null;
310 }
311 var page = try parser.allocator.create(Page);
312 errdefer parser.allocator.destroy(page);
313
314 page.* = .{
315 .base = .{
316 .@"type" = .page,
317 .code = inst,
318 .offset = offset,
319 .target = target,
320 },
321 .addend = parser.addend,
322 .inst = parsed_inst,
323 };
324
325 log.debug(" | emitting {}", .{page});
326
327 break :ptr &page.base;
328 },
329 .ARM64_RELOC_GOT_LOAD_PAGE21 => {
330 var page = try parser.allocator.create(GotPage);
331 errdefer parser.allocator.destroy(page);
332
333 page.* = .{
334 .base = .{
335 .@"type" = .got_page,
336 .code = inst,
337 .offset = offset,
338 .target = target,
339 },
340 .inst = parsed_inst,
341 };
342
343 log.debug(" | emitting {}", .{page});
344
345 break :ptr &page.base;
346 },
347 .ARM64_RELOC_TLVP_LOAD_PAGE21 => {
348 var page = try parser.allocator.create(TlvpPage);
349 errdefer parser.allocator.destroy(page);
350
351 page.* = .{
352 .base = .{
353 .@"type" = .tlvp_page,
354 .code = inst,
355 .offset = offset,
356 .target = target,
357 },
358 .inst = parsed_inst,
359 };
360
361 log.debug(" | emitting {}", .{page});
362
363 break :ptr &page.base;
364 },
365 else => unreachable,
366 }
367 };
368
369 try parser.parsed.append(ptr);
370 }
371
372 fn parsePageOff(parser: *Parser, rel: macho.relocation_info) !void {
373 defer {
374 // Reset parser's addend state
375 parser.addend = null;
376 }
377
378 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
379 assert(rel_type == .ARM64_RELOC_PAGEOFF12);
380 assert(rel.r_pcrel == 0);
381 assert(rel.r_length == 2);
382
383 const offset = @intCast(u32, rel.r_address);
384 const inst = parser.code[offset..][0..4];
385
386 var op_kind: PageOff.OpKind = undefined;
387 var parsed_inst: aarch64.Instruction = undefined;
388 if (isArithmeticOp(inst)) {
389 op_kind = .arithmetic;
390 parsed_inst = .{ .AddSubtractImmediate = mem.bytesToValue(meta.TagPayload(
391 aarch64.Instruction,
392 aarch64.Instruction.AddSubtractImmediate,
393 ), inst) };
394 } else {
395 op_kind = .load_store;
396 parsed_inst = .{ .LoadStoreRegister = mem.bytesToValue(meta.TagPayload(
397 aarch64.Instruction,
398 aarch64.Instruction.LoadStoreRegister,
399 ), inst) };
400 }
401 const target = Relocation.Target.from_reloc(rel);
402
403 var page_off = try parser.allocator.create(PageOff);
404 errdefer parser.allocator.destroy(page_off);
405
406 page_off.* = .{
407 .base = .{
408 .@"type" = .page_off,
409 .code = inst,
410 .offset = offset,
411 .target = target,
412 },
413 .op_kind = op_kind,
414 .inst = parsed_inst,
415 .addend = parser.addend,
416 };
417
418 log.debug(" | emitting {}", .{page_off});
419 try parser.parsed.append(&page_off.base);
420 }
421
422 fn parseGotLoadPageOff(parser: *Parser, rel: macho.relocation_info) !void {
423 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
424 assert(rel_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12);
425 assert(rel.r_pcrel == 0);
426 assert(rel.r_length == 2);
427
428 const offset = @intCast(u32, rel.r_address);
429 const inst = parser.code[offset..][0..4];
430 assert(!isArithmeticOp(inst));
431
432 const parsed_inst = mem.bytesToValue(meta.TagPayload(
433 aarch64.Instruction,
434 aarch64.Instruction.LoadStoreRegister,
435 ), inst);
436 assert(parsed_inst.size == 3);
437
438 const target = Relocation.Target.from_reloc(rel);
439
440 var page_off = try parser.allocator.create(GotPageOff);
441 errdefer parser.allocator.destroy(page_off);
442
443 page_off.* = .{
444 .base = .{
445 .@"type" = .got_page_off,
446 .code = inst,
447 .offset = offset,
448 .target = target,
449 },
450 .inst = .{
451 .LoadStoreRegister = parsed_inst,
452 },
453 };
454
455 log.debug(" | emitting {}", .{page_off});
456 try parser.parsed.append(&page_off.base);
457 }
458
459 fn parseTlvpLoadPageOff(parser: *Parser, rel: macho.relocation_info) !void {
460 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
461 assert(rel_type == .ARM64_RELOC_TLVP_LOAD_PAGEOFF12);
462 assert(rel.r_pcrel == 0);
463 assert(rel.r_length == 2);
464
465 const RegInfo = struct {
466 rd: u5,
467 rn: u5,
468 size: u1,
469 };
470
471 const offset = @intCast(u32, rel.r_address);
472 const inst = parser.code[offset..][0..4];
473 const parsed: RegInfo = parsed: {
474 if (isArithmeticOp(inst)) {
475 const parsed_inst = mem.bytesAsValue(meta.TagPayload(
476 aarch64.Instruction,
477 aarch64.Instruction.AddSubtractImmediate,
478 ), inst);
479 break :parsed .{
480 .rd = parsed_inst.rd,
481 .rn = parsed_inst.rn,
482 .size = parsed_inst.sf,
483 };
484 } else {
485 const parsed_inst = mem.bytesAsValue(meta.TagPayload(
486 aarch64.Instruction,
487 aarch64.Instruction.LoadStoreRegister,
488 ), inst);
489 break :parsed .{
490 .rd = parsed_inst.rt,
491 .rn = parsed_inst.rn,
492 .size = @truncate(u1, parsed_inst.size),
493 };
494 }
495 };
496
497 const target = Relocation.Target.from_reloc(rel);
498
499 var page_off = try parser.allocator.create(TlvpPageOff);
500 errdefer parser.allocator.destroy(page_off);
501
502 page_off.* = .{
503 .base = .{
504 .@"type" = .tlvp_page_off,
505 .code = inst,
506 .offset = offset,
507 .target = target,
508 },
509 .inst = .{
510 .AddSubtractImmediate = .{
511 .rd = parsed.rd,
512 .rn = parsed.rn,
513 .imm12 = 0, // This will be filled when target addresses are known.
514 .sh = 0,
515 .s = 0,
516 .op = 0,
517 .sf = parsed.size,
518 },
519 },
520 };
521
522 log.debug(" | emitting {}", .{page_off});
523 try parser.parsed.append(&page_off.base);
524 }
525
526 fn parseSubtractor(parser: *Parser, rel: macho.relocation_info) !void {
527 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
528 assert(rel_type == .ARM64_RELOC_SUBTRACTOR);
529 assert(rel.r_pcrel == 0);
530 assert(parser.subtractor == null);
531
532 parser.subtractor = Relocation.Target.from_reloc(rel);
533
534 // Verify SUBTRACTOR is followed by UNSIGNED.
535 const next = @intToEnum(macho.reloc_type_arm64, parser.it.peek().r_type);
536 if (next != .ARM64_RELOC_UNSIGNED) {
537 log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
538 return error.UnexpectedRelocationType;
539 }
540 }
541
542 fn parseUnsigned(parser: *Parser, rel: macho.relocation_info) !void {
543 defer {
544 // Reset parser's subtractor state
545 parser.subtractor = null;
546 }
547
548 const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type);
549 assert(rel_type == .ARM64_RELOC_UNSIGNED);
550 assert(rel.r_pcrel == 0);
551
552 var unsigned = try parser.allocator.create(reloc.Unsigned);
553 errdefer parser.allocator.destroy(unsigned);
554
555 const target = Relocation.Target.from_reloc(rel);
556 const is_64bit: bool = switch (rel.r_length) {
557 3 => true,
558 2 => false,
559 else => unreachable,
560 };
561 const offset = @intCast(u32, rel.r_address);
562 const addend: i64 = if (is_64bit)
563 mem.readIntLittle(i64, parser.code[offset..][0..8])
564 else
565 mem.readIntLittle(i32, parser.code[offset..][0..4]);
566
567 unsigned.* = .{
568 .base = .{
569 .@"type" = .unsigned,
570 .code = if (is_64bit) parser.code[offset..][0..8] else parser.code[offset..][0..4],
571 .offset = offset,
572 .target = target,
573 },
574 .subtractor = parser.subtractor,
575 .is_64bit = is_64bit,
576 .addend = addend,
577 };
578
579 log.debug(" | emitting {}", .{unsigned});
580 try parser.parsed.append(&unsigned.base);
581 }
582};
583
584fn isArithmeticOp(inst: *const [4]u8) callconv(.Inline) bool {
585 const group_decode = @truncate(u5, inst[3]);
586 return ((group_decode >> 2) == 4);
587}
src/link/MachO/reloc/x86_64.zig created+343
......@@ -0,0 +1,343 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const log = std.log.scoped(.reloc);
4const macho = std.macho;
5const math = std.math;
6const mem = std.mem;
7const meta = std.meta;
8const reloc = @import("../reloc.zig");
9
10const Allocator = mem.Allocator;
11const Relocation = reloc.Relocation;
12
13pub const Branch = struct {
14 base: Relocation,
15
16 pub const base_type: Relocation.Type = .branch_x86_64;
17
18 pub fn resolve(branch: Branch, args: Relocation.ResolveArgs) !void {
19 const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
20 log.debug(" | displacement 0x{x}", .{displacement});
21 mem.writeIntLittle(u32, branch.base.code[0..4], @bitCast(u32, displacement));
22 }
23};
24
25pub const Signed = struct {
26 base: Relocation,
27 addend: i32,
28 correction: i4,
29
30 pub const base_type: Relocation.Type = .signed;
31
32 pub fn resolve(signed: Signed, args: Relocation.ResolveArgs) !void {
33 const target_addr = target_addr: {
34 if (signed.base.target == .section) {
35 const source_target = @intCast(i64, signed.base.offset) + signed.addend + 4 + signed.correction;
36 const source_disp = source_target - @intCast(i64, args.source_sect_addr.?);
37 break :target_addr @intCast(i64, args.target_addr) + source_disp;
38 }
39 break :target_addr @intCast(i64, args.target_addr) + signed.addend;
40 };
41 const displacement = try math.cast(i32, target_addr - @intCast(i64, args.source_addr) - signed.correction - 4);
42
43 log.debug(" | calculated addend 0x{x}", .{signed.addend});
44 log.debug(" | calculated correction 0x{x}", .{signed.correction});
45 log.debug(" | displacement 0x{x}", .{displacement});
46
47 mem.writeIntLittle(u32, signed.base.code[0..4], @bitCast(u32, displacement));
48 }
49};
50
51pub const GotLoad = struct {
52 base: Relocation,
53 op: *u8,
54
55 pub const base_type: Relocation.Type = .got_load;
56
57 pub fn resolve(got_load: GotLoad, args: Relocation.ResolveArgs) !void {
58 const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
59 log.debug(" | displacement 0x{x}", .{displacement});
60 mem.writeIntLittle(u32, got_load.base.code[0..4], @bitCast(u32, displacement));
61 }
62};
63
64pub const Got = struct {
65 base: Relocation,
66
67 pub const base_type: Relocation.Type = .got;
68
69 pub fn resolve(got: Got, args: Relocation.ResolveArgs) !void {
70 const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
71 log.debug(" | displacement 0x{x}", .{displacement});
72 mem.writeIntLittle(u32, got.base.code[0..4], @bitCast(u32, displacement));
73 }
74};
75
76pub const Tlv = struct {
77 base: Relocation,
78 op: *u8,
79
80 pub const base_type: Relocation.Type = .tlv;
81
82 pub fn resolve(tlv: Tlv, args: Relocation.ResolveArgs) !void {
83 // We need to rewrite the opcode from movq to leaq.
84 tlv.op.* = 0x8d;
85 log.debug(" | rewriting op to leaq", .{});
86
87 const displacement = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4);
88 log.debug(" | displacement 0x{x}", .{displacement});
89
90 mem.writeIntLittle(u32, tlv.base.code[0..4], @bitCast(u32, displacement));
91 }
92};
93
94pub const Parser = struct {
95 allocator: *Allocator,
96 it: *reloc.RelocIterator,
97 code: []u8,
98 parsed: std.ArrayList(*Relocation),
99 subtractor: ?Relocation.Target = null,
100
101 pub fn deinit(parser: *Parser) void {
102 parser.parsed.deinit();
103 }
104
105 pub fn parse(parser: *Parser) !void {
106 while (parser.it.next()) |rel| {
107 switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) {
108 .X86_64_RELOC_BRANCH => {
109 try parser.parseBranch(rel);
110 },
111 .X86_64_RELOC_SUBTRACTOR => {
112 try parser.parseSubtractor(rel);
113 },
114 .X86_64_RELOC_UNSIGNED => {
115 try parser.parseUnsigned(rel);
116 },
117 .X86_64_RELOC_SIGNED,
118 .X86_64_RELOC_SIGNED_1,
119 .X86_64_RELOC_SIGNED_2,
120 .X86_64_RELOC_SIGNED_4,
121 => {
122 try parser.parseSigned(rel);
123 },
124 .X86_64_RELOC_GOT_LOAD => {
125 try parser.parseGotLoad(rel);
126 },
127 .X86_64_RELOC_GOT => {
128 try parser.parseGot(rel);
129 },
130 .X86_64_RELOC_TLV => {
131 try parser.parseTlv(rel);
132 },
133 }
134 }
135 }
136
137 fn parseBranch(parser: *Parser, rel: macho.relocation_info) !void {
138 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
139 assert(rel_type == .X86_64_RELOC_BRANCH);
140 assert(rel.r_pcrel == 1);
141 assert(rel.r_length == 2);
142
143 const offset = @intCast(u32, rel.r_address);
144 const inst = parser.code[offset..][0..4];
145
146 var branch = try parser.allocator.create(Branch);
147 errdefer parser.allocator.destroy(branch);
148
149 const target = Relocation.Target.from_reloc(rel);
150
151 branch.* = .{
152 .base = .{
153 .@"type" = .branch_x86_64,
154 .code = inst,
155 .offset = offset,
156 .target = target,
157 },
158 };
159
160 log.debug(" | emitting {}", .{branch});
161 try parser.parsed.append(&branch.base);
162 }
163
164 fn parseSigned(parser: *Parser, rel: macho.relocation_info) !void {
165 assert(rel.r_pcrel == 1);
166 assert(rel.r_length == 2);
167
168 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
169 const target = Relocation.Target.from_reloc(rel);
170 const is_extern = rel.r_extern == 1;
171
172 const offset = @intCast(u32, rel.r_address);
173 const inst = parser.code[offset..][0..4];
174 const addend = mem.readIntLittle(i32, inst);
175
176 const correction: i4 = correction: {
177 if (is_extern) break :correction 0;
178
179 const corr: i4 = switch (rel_type) {
180 .X86_64_RELOC_SIGNED => 0,
181 .X86_64_RELOC_SIGNED_1 => 1,
182 .X86_64_RELOC_SIGNED_2 => 2,
183 .X86_64_RELOC_SIGNED_4 => 4,
184 else => unreachable,
185 };
186 break :correction corr;
187 };
188
189 var signed = try parser.allocator.create(Signed);
190 errdefer parser.allocator.destroy(signed);
191
192 signed.* = .{
193 .base = .{
194 .@"type" = .signed,
195 .code = inst,
196 .offset = offset,
197 .target = target,
198 },
199 .addend = addend,
200 .correction = correction,
201 };
202
203 log.debug(" | emitting {}", .{signed});
204 try parser.parsed.append(&signed.base);
205 }
206
207 fn parseGotLoad(parser: *Parser, rel: macho.relocation_info) !void {
208 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
209 assert(rel_type == .X86_64_RELOC_GOT_LOAD);
210 assert(rel.r_pcrel == 1);
211 assert(rel.r_length == 2);
212
213 const offset = @intCast(u32, rel.r_address);
214 const inst = parser.code[offset..][0..4];
215 const target = Relocation.Target.from_reloc(rel);
216
217 var got_load = try parser.allocator.create(GotLoad);
218 errdefer parser.allocator.destroy(got_load);
219
220 got_load.* = .{
221 .base = .{
222 .@"type" = .got_load,
223 .code = inst,
224 .offset = offset,
225 .target = target,
226 },
227 .op = &parser.code[offset - 2],
228 };
229
230 log.debug(" | emitting {}", .{got_load});
231 try parser.parsed.append(&got_load.base);
232 }
233
234 fn parseGot(parser: *Parser, rel: macho.relocation_info) !void {
235 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
236 assert(rel_type == .X86_64_RELOC_GOT);
237 assert(rel.r_pcrel == 1);
238 assert(rel.r_length == 2);
239
240 const offset = @intCast(u32, rel.r_address);
241 const inst = parser.code[offset..][0..4];
242 const target = Relocation.Target.from_reloc(rel);
243
244 var got = try parser.allocator.create(Got);
245 errdefer parser.allocator.destroy(got);
246
247 got.* = .{
248 .base = .{
249 .@"type" = .got,
250 .code = inst,
251 .offset = offset,
252 .target = target,
253 },
254 };
255
256 log.debug(" | emitting {}", .{got});
257 try parser.parsed.append(&got.base);
258 }
259
260 fn parseTlv(parser: *Parser, rel: macho.relocation_info) !void {
261 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
262 assert(rel_type == .X86_64_RELOC_TLV);
263 assert(rel.r_pcrel == 1);
264 assert(rel.r_length == 2);
265
266 const offset = @intCast(u32, rel.r_address);
267 const inst = parser.code[offset..][0..4];
268 const target = Relocation.Target.from_reloc(rel);
269
270 var tlv = try parser.allocator.create(Tlv);
271 errdefer parser.allocator.destroy(tlv);
272
273 tlv.* = .{
274 .base = .{
275 .@"type" = .tlv,
276 .code = inst,
277 .offset = offset,
278 .target = target,
279 },
280 .op = &parser.code[offset - 2],
281 };
282
283 log.debug(" | emitting {}", .{tlv});
284 try parser.parsed.append(&tlv.base);
285 }
286
287 fn parseSubtractor(parser: *Parser, rel: macho.relocation_info) !void {
288 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
289 assert(rel_type == .X86_64_RELOC_SUBTRACTOR);
290 assert(rel.r_pcrel == 0);
291 assert(parser.subtractor == null);
292
293 parser.subtractor = Relocation.Target.from_reloc(rel);
294
295 // Verify SUBTRACTOR is followed by UNSIGNED.
296 const next = @intToEnum(macho.reloc_type_x86_64, parser.it.peek().r_type);
297 if (next != .X86_64_RELOC_UNSIGNED) {
298 log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next});
299 return error.UnexpectedRelocationType;
300 }
301 }
302
303 fn parseUnsigned(parser: *Parser, rel: macho.relocation_info) !void {
304 defer {
305 // Reset parser's subtractor state
306 parser.subtractor = null;
307 }
308
309 const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type);
310 assert(rel_type == .X86_64_RELOC_UNSIGNED);
311 assert(rel.r_pcrel == 0);
312
313 var unsigned = try parser.allocator.create(reloc.Unsigned);
314 errdefer parser.allocator.destroy(unsigned);
315
316 const target = Relocation.Target.from_reloc(rel);
317 const is_64bit: bool = switch (rel.r_length) {
318 3 => true,
319 2 => false,
320 else => unreachable,
321 };
322 const offset = @intCast(u32, rel.r_address);
323 const addend: i64 = if (is_64bit)
324 mem.readIntLittle(i64, parser.code[offset..][0..8])
325 else
326 mem.readIntLittle(i32, parser.code[offset..][0..4]);
327
328 unsigned.* = .{
329 .base = .{
330 .@"type" = .unsigned,
331 .code = if (is_64bit) parser.code[offset..][0..8] else parser.code[offset..][0..4],
332 .offset = offset,
333 .target = target,
334 },
335 .subtractor = parser.subtractor,
336 .is_64bit = is_64bit,
337 .addend = addend,
338 };
339
340 log.debug(" | emitting {}", .{unsigned});
341 try parser.parsed.append(&unsigned.base);
342 }
343};