| author | |
| committer | |
| log | 1119970d22f67112159682ab103c767088cbf4b8 |
| tree | 8c120994ad987f0d62de5a58b86a70a4f82f07f2 |
| parent | b1f9db75f3380c9a563972a197c5d1c363c6f215 |
6 files changed, 1056 insertions(+), 658 deletions(-)
CMakeLists.txt+2| ... | ... | @@ -575,6 +575,8 @@ set(ZIG_STAGE2_SOURCES |
| 575 | 575 | "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" |
| 576 | 576 | "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig" |
| 577 | 577 | "${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" | |
| 578 | 580 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" |
| 579 | 581 | "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" |
| 580 | 582 | "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" |
src/link/MachO/Object.zig+1| ... | ... | @@ -310,6 +310,7 @@ pub fn parseSections(self: *Object) !void { |
| 310 | 310 | |
| 311 | 311 | break :relocs try reloc.parse( |
| 312 | 312 | self.allocator, |
| 313 | self.arch.?, | |
| 313 | 314 | section.code, |
| 314 | 315 | mem.bytesAsSlice(macho.relocation_info, raw_relocs), |
| 315 | 316 | ); |
src/link/MachO/Zld.zig+21-12| ... | ... | @@ -10,12 +10,12 @@ const macho = std.macho; |
| 10 | 10 | const math = std.math; |
| 11 | 11 | const log = std.log.scoped(.zld); |
| 12 | 12 | const aarch64 = @import("../../codegen/aarch64.zig"); |
| 13 | const reloc = @import("reloc.zig"); | |
| 13 | 14 | |
| 14 | 15 | const Allocator = mem.Allocator; |
| 15 | 16 | const Archive = @import("Archive.zig"); |
| 16 | 17 | const CodeSignature = @import("CodeSignature.zig"); |
| 17 | 18 | const Object = @import("Object.zig"); |
| 18 | const Relocation = @import("reloc.zig").Relocation; | |
| 19 | 19 | const Symbol = @import("Symbol.zig"); |
| 20 | 20 | const Trie = @import("Trie.zig"); |
| 21 | 21 | |
| ... | ... | @@ -1360,11 +1360,11 @@ fn resolveStubsAndGotEntries(self: *Zld) !void { |
| 1360 | 1360 | |
| 1361 | 1361 | for (object.sections.items) |sect| { |
| 1362 | 1362 | const relocs = sect.relocs orelse continue; |
| 1363 | for (relocs) |reloc| { | |
| 1364 | switch (reloc.@"type") { | |
| 1363 | for (relocs) |rel| { | |
| 1364 | switch (rel.@"type") { | |
| 1365 | 1365 | .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]; | |
| 1368 | 1368 | const sym_name = object.getString(sym.n_strx); |
| 1369 | 1369 | |
| 1370 | 1370 | if (self.got_entries.contains(sym_name)) continue; |
| ... | ... | @@ -1383,7 +1383,9 @@ fn resolveStubsAndGotEntries(self: *Zld) !void { |
| 1383 | 1383 | log.debug(" | found GOT entry {s}: {}", .{ sym_name, self.got_entries.get(sym_name) }); |
| 1384 | 1384 | }, |
| 1385 | 1385 | 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]; | |
| 1387 | 1389 | const sym_name = object.getString(sym.n_strx); |
| 1388 | 1390 | |
| 1389 | 1391 | if (!Symbol.isUndef(sym)) continue; |
| ... | ... | @@ -1442,20 +1444,23 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void { |
| 1442 | 1444 | for (relocs) |rel| { |
| 1443 | 1445 | const source_addr = target_sect_addr + rel.offset; |
| 1444 | 1446 | |
| 1445 | var args: Relocation.ResolveArgs = .{ | |
| 1447 | var args: reloc.Relocation.ResolveArgs = .{ | |
| 1446 | 1448 | .source_addr = source_addr, |
| 1447 | 1449 | .target_addr = undefined, |
| 1448 | .subtractor = null, | |
| 1449 | 1450 | }; |
| 1450 | 1451 | |
| 1451 | 1452 | switch (rel.@"type") { |
| 1452 | 1453 | .unsigned => { |
| 1453 | 1454 | args.target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel.target); |
| 1454 | 1455 | |
| 1455 | const unsigned = rel.cast(Relocation.Unsigned) orelse unreachable; | |
| 1456 | const unsigned = rel.cast(reloc.Unsigned) orelse unreachable; | |
| 1456 | 1457 | if (unsigned.subtractor) |subtractor| { |
| 1457 | 1458 | args.subtractor = try self.relocTargetAddr(@intCast(u16, object_id), subtractor); |
| 1458 | 1459 | } |
| 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 | } | |
| 1459 | 1464 | |
| 1460 | 1465 | rebases: { |
| 1461 | 1466 | var hit: bool = false; |
| ... | ... | @@ -1500,7 +1505,7 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void { |
| 1500 | 1505 | try self.threadlocal_offsets.append(self.allocator, args.target_addr - base_addr); |
| 1501 | 1506 | } |
| 1502 | 1507 | }, |
| 1503 | .got_page, .got_page_off => { | |
| 1508 | .got_page, .got_page_off, .got_load, .got => { | |
| 1504 | 1509 | const dc_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; |
| 1505 | 1510 | const got = dc_seg.sections.items[self.got_section_index.?]; |
| 1506 | 1511 | const sym = object.symtab.items[rel.target.symbol]; |
| ... | ... | @@ -1508,7 +1513,11 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void { |
| 1508 | 1513 | const entry = self.got_entries.get(sym_name) orelse unreachable; |
| 1509 | 1514 | args.target_addr = got.addr + entry.index * @sizeOf(u64); |
| 1510 | 1515 | }, |
| 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 | } | |
| 1512 | 1521 | args.target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel.target); |
| 1513 | 1522 | }, |
| 1514 | 1523 | } |
| ... | ... | @@ -1547,7 +1556,7 @@ fn resolveRelocsAndWriteSections(self: *Zld) !void { |
| 1547 | 1556 | } |
| 1548 | 1557 | } |
| 1549 | 1558 | |
| 1550 | fn relocTargetAddr(self: *Zld, object_id: u16, target: Relocation.Target) !u64 { | |
| 1559 | fn relocTargetAddr(self: *Zld, object_id: u16, target: reloc.Relocation.Target) !u64 { | |
| 1551 | 1560 | const object = self.objects.items[object_id]; |
| 1552 | 1561 | const target_addr = blk: { |
| 1553 | 1562 | switch (target) { |
src/link/MachO/reloc.zig+102-646| ... | ... | @@ -1,5 +1,4 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const aarch64 = @import("../../codegen/aarch64.zig"); | |
| 3 | 2 | const assert = std.debug.assert; |
| 4 | 3 | const log = std.log.scoped(.reloc); |
| 5 | 4 | const macho = std.macho; |
| ... | ... | @@ -7,6 +6,9 @@ const math = std.math; |
| 7 | 6 | const mem = std.mem; |
| 8 | 7 | const meta = std.meta; |
| 9 | 8 | |
| 9 | const aarch64 = @import("reloc/aarch64.zig"); | |
| 10 | const x86_64 = @import("reloc/x86_64.zig"); | |
| 11 | ||
| 10 | 12 | const Allocator = mem.Allocator; |
| 11 | 13 | |
| 12 | 14 | pub const Relocation = struct { |
| ... | ... | @@ -25,7 +27,8 @@ pub const Relocation = struct { |
| 25 | 27 | pub const ResolveArgs = struct { |
| 26 | 28 | source_addr: u64, |
| 27 | 29 | target_addr: u64, |
| 28 | subtractor: ?u64, | |
| 30 | subtractor: ?u64 = null, | |
| 31 | source_sect_addr: ?u64 = null, | |
| 29 | 32 | }; |
| 30 | 33 | |
| 31 | 34 | pub fn resolve(base: *Relocation, args: ResolveArgs) !void { |
| ... | ... | @@ -35,21 +38,28 @@ pub const Relocation = struct { |
| 35 | 38 | log.debug(" | target address 0x{x}", .{args.target_addr}); |
| 36 | 39 | if (args.subtractor) |sub| |
| 37 | 40 | log.debug(" | subtractor address 0x{x}", .{sub}); |
| 41 | if (args.source_sect_addr) |addr| | |
| 42 | log.debug(" | source section address 0x{x}", .{addr}); | |
| 38 | 43 | |
| 39 | 44 | 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), | |
| 48 | 58 | }; |
| 49 | 59 | } |
| 50 | 60 | |
| 51 | 61 | pub const Type = enum { |
| 52 | branch, | |
| 62 | branch_aarch64, | |
| 53 | 63 | unsigned, |
| 54 | 64 | page, |
| 55 | 65 | page_off, |
| ... | ... | @@ -57,6 +67,11 @@ pub const Relocation = struct { |
| 57 | 67 | got_page_off, |
| 58 | 68 | tlvp_page, |
| 59 | 69 | tlvp_page_off, |
| 70 | branch_x86_64, | |
| 71 | signed, | |
| 72 | got_load, | |
| 73 | got, | |
| 74 | tlv, | |
| 60 | 75 | }; |
| 61 | 76 | |
| 62 | 77 | pub const Target = union(enum) { |
| ... | ... | @@ -71,236 +86,91 @@ pub const Relocation = struct { |
| 71 | 86 | }; |
| 72 | 87 | } |
| 73 | 88 | }; |
| 89 | }; | |
| 74 | 90 | |
| 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); | |
| 91 | pub 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; | |
| 275 | 109 | |
| 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; | |
| 277 | 114 | |
| 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}); | |
| 280 | 117 | |
| 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 | ); | |
| 282 | 130 | } |
| 283 | }; | |
| 131 | } | |
| 284 | 132 | }; |
| 285 | 133 | |
| 286 | pub fn parse(allocator: *Allocator, code: []u8, relocs: []const macho.relocation_info) ![]*Relocation { | |
| 134 | pub fn parse( | |
| 135 | allocator: *Allocator, | |
| 136 | arch: std.Target.Cpu.Arch, | |
| 137 | code: []u8, | |
| 138 | relocs: []const macho.relocation_info, | |
| 139 | ) ![]*Relocation { | |
| 287 | 140 | var it = RelocIterator{ |
| 288 | 141 | .buffer = relocs, |
| 289 | 142 | }; |
| 290 | 143 | |
| 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(); | |
| 299 | 166 | |
| 300 | return parser.parsed.toOwnedSlice(); | |
| 167 | return parser.parsed.toOwnedSlice(); | |
| 168 | }, | |
| 169 | else => unreachable, | |
| 170 | } | |
| 301 | 171 | } |
| 302 | 172 | |
| 303 | const RelocIterator = struct { | |
| 173 | pub const RelocIterator = struct { | |
| 304 | 174 | buffer: []const macho.relocation_info, |
| 305 | 175 | index: i64 = -1, |
| 306 | 176 | |
| ... | ... | @@ -308,7 +178,8 @@ const RelocIterator = struct { |
| 308 | 178 | self.index += 1; |
| 309 | 179 | if (self.index < self.buffer.len) { |
| 310 | 180 | 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}); | |
| 312 | 183 | log.debug(" | offset = {}", .{reloc.r_address}); |
| 313 | 184 | log.debug(" | PC = {}", .{reloc.r_pcrel == 1}); |
| 314 | 185 | log.debug(" | length = {}", .{reloc.r_length}); |
| ... | ... | @@ -319,423 +190,8 @@ const RelocIterator = struct { |
| 319 | 190 | return null; |
| 320 | 191 | } |
| 321 | 192 | |
| 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)]; | |
| 329 | 196 | } |
| 330 | 197 | }; |
| 331 | ||
| 332 | const 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 | ||
| 738 | fn 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 @@ |
| 1 | const std = @import("std"); | |
| 2 | const aarch64 = @import("../../../codegen/aarch64.zig"); | |
| 3 | const assert = std.debug.assert; | |
| 4 | const log = std.log.scoped(.reloc); | |
| 5 | const macho = std.macho; | |
| 6 | const math = std.math; | |
| 7 | const mem = std.mem; | |
| 8 | const meta = std.meta; | |
| 9 | const reloc = @import("../reloc.zig"); | |
| 10 | ||
| 11 | const Allocator = mem.Allocator; | |
| 12 | const Relocation = reloc.Relocation; | |
| 13 | ||
| 14 | pub 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 | ||
| 32 | pub 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 | ||
| 57 | pub 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 | ||
| 101 | pub 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 | ||
| 123 | pub 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 | ||
| 143 | pub 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 | ||
| 165 | pub 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 | ||
| 186 | pub 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 | ||
| 584 | fn 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 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const log = std.log.scoped(.reloc); | |
| 4 | const macho = std.macho; | |
| 5 | const math = std.math; | |
| 6 | const mem = std.mem; | |
| 7 | const meta = std.meta; | |
| 8 | const reloc = @import("../reloc.zig"); | |
| 9 | ||
| 10 | const Allocator = mem.Allocator; | |
| 11 | const Relocation = reloc.Relocation; | |
| 12 | ||
| 13 | pub 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 | ||
| 25 | pub 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 | ||
| 51 | pub 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 | ||
| 64 | pub 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 | ||
| 76 | pub 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 | ||
| 94 | pub 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 | }; |