| ... | ... | @@ -0,0 +1,277 @@ |
| 1 | const Relocation = @This(); |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const aarch64 = @import("../../arch/aarch64/bits.zig"); |
| 5 | const assert = std.debug.assert; |
| 6 | const log = std.log.scoped(.link); |
| 7 | const macho = std.macho; |
| 8 | const math = std.math; |
| 9 | const mem = std.mem; |
| 10 | const meta = std.meta; |
| 11 | |
| 12 | const Atom = @import("Atom.zig"); |
| 13 | const MachO = @import("../MachO.zig"); |
| 14 | const SymbolWithLoc = MachO.SymbolWithLoc; |
| 15 | |
| 16 | pub const Table = std.AutoHashMapUnmanaged(*Atom, std.ArrayListUnmanaged(Relocation)); |
| 17 | |
| 18 | /// Offset within the atom's code buffer. |
| 19 | /// Note relocation size can be inferred by relocation's kind. |
| 20 | offset: u32, |
| 21 | target: SymbolWithLoc, |
| 22 | addend: i64, |
| 23 | pcrel: bool, |
| 24 | length: u2, |
| 25 | @"type": u4, |
| 26 | dirty: bool = true, |
| 27 | |
| 28 | pub fn getTargetAtom(self: Relocation, macho_file: *MachO) ?*Atom { |
| 29 | switch (macho_file.base.options.target.cpu.arch) { |
| 30 | .aarch64 => switch (@intToEnum(macho.reloc_type_arm64, self.@"type")) { |
| 31 | .ARM64_RELOC_GOT_LOAD_PAGE21, |
| 32 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12, |
| 33 | .ARM64_RELOC_POINTER_TO_GOT, |
| 34 | => return macho_file.getGotAtomForSymbol(self.target).?, |
| 35 | else => {}, |
| 36 | }, |
| 37 | .x86_64 => switch (@intToEnum(macho.reloc_type_x86_64, self.@"type")) { |
| 38 | .X86_64_RELOC_GOT, |
| 39 | .X86_64_RELOC_GOT_LOAD, |
| 40 | => return macho_file.getGotAtomForSymbol(self.target).?, |
| 41 | else => {}, |
| 42 | }, |
| 43 | else => unreachable, |
| 44 | } |
| 45 | if (macho_file.getStubsAtomForSymbol(self.target)) |stubs_atom| return stubs_atom; |
| 46 | if (macho_file.getTlvPtrAtomForSymbol(self.target)) |tlv_ptr_atom| return tlv_ptr_atom; |
| 47 | return macho_file.getAtomForSymbol(self.target); |
| 48 | } |
| 49 | |
| 50 | pub fn resolve(self: Relocation, atom: *Atom, macho_file: *MachO, code: []u8) !void { |
| 51 | const arch = macho_file.base.options.target.cpu.arch; |
| 52 | const source_sym = atom.getSymbol(macho_file); |
| 53 | const source_addr = source_sym.n_value + self.offset; |
| 54 | |
| 55 | const target_atom = self.getTargetAtom(macho_file) orelse return; |
| 56 | const target_addr = target_atom.getSymbol(macho_file).n_value + self.addend; |
| 57 | |
| 58 | log.debug(" ({x}: [() => 0x{x} ({s})) ({s})", .{ |
| 59 | source_addr, |
| 60 | target_addr, |
| 61 | macho_file.getSymbolName(self.target), |
| 62 | switch (arch) { |
| 63 | .aarch64 => @tagName(@intToEnum(macho.reloc_type_arm64, self.@"type")), |
| 64 | .x86_64 => @tagName(@intToEnum(macho.reloc_type_x86_64, self.@"type")), |
| 65 | else => unreachable, |
| 66 | }, |
| 67 | }); |
| 68 | |
| 69 | switch (arch) { |
| 70 | .aarch64 => return self.resolveAarch64(source_addr, target_addr, macho_file, code), |
| 71 | .x86_64 => return self.resolveX8664(source_addr, target_addr, code), |
| 72 | else => unreachable, |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | fn resolveAarch64(self: Relocation, source_addr: u64, target_addr: u64, macho_file: *MachO, code: []u8) !void { |
| 77 | const rel_type = @intToEnum(macho.reloc_type_arm64, self.@"type"); |
| 78 | switch (rel_type) { |
| 79 | .ARM64_RELOC_BRANCH26 => { |
| 80 | const displacement = math.cast(i28, @intCast(i64, target_addr) - @intCast(i64, source_addr)) orelse { |
| 81 | log.err("jump too big to encode as i28 displacement value", .{}); |
| 82 | log.err(" (target - source) = displacement => 0x{x} - 0x{x} = 0x{x}", .{ |
| 83 | target_addr, |
| 84 | source_addr, |
| 85 | @intCast(i64, target_addr) - @intCast(i64, source_addr), |
| 86 | }); |
| 87 | log.err(" TODO implement branch islands to extend jump distance for arm64", .{}); |
| 88 | return error.TODOImplementBranchIslands; |
| 89 | }; |
| 90 | var inst = aarch64.Instruction{ |
| 91 | .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload( |
| 92 | aarch64.Instruction, |
| 93 | aarch64.Instruction.unconditional_branch_immediate, |
| 94 | ), code), |
| 95 | }; |
| 96 | inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2)); |
| 97 | mem.writeIntLittle(u32, code, inst.toU32()); |
| 98 | }, |
| 99 | .ARM64_RELOC_PAGE21, |
| 100 | .ARM64_RELOC_GOT_LOAD_PAGE21, |
| 101 | .ARM64_RELOC_TLVP_LOAD_PAGE21, |
| 102 | => { |
| 103 | const source_page = @intCast(i32, source_addr >> 12); |
| 104 | const target_page = @intCast(i32, target_addr >> 12); |
| 105 | const pages = @bitCast(u21, @intCast(i21, target_page - source_page)); |
| 106 | var inst = aarch64.Instruction{ |
| 107 | .pc_relative_address = mem.bytesToValue(meta.TagPayload( |
| 108 | aarch64.Instruction, |
| 109 | aarch64.Instruction.pc_relative_address, |
| 110 | ), code), |
| 111 | }; |
| 112 | inst.pc_relative_address.immhi = @truncate(u19, pages >> 2); |
| 113 | inst.pc_relative_address.immlo = @truncate(u2, pages); |
| 114 | mem.writeIntLittle(u32, code, inst.toU32()); |
| 115 | }, |
| 116 | .ARM64_RELOC_PAGEOFF12 => { |
| 117 | const narrowed = @truncate(u12, @intCast(u64, target_addr)); |
| 118 | if (isArithmeticOp(code)) { |
| 119 | var inst = aarch64.Instruction{ |
| 120 | .add_subtract_immediate = mem.bytesToValue(meta.TagPayload( |
| 121 | aarch64.Instruction, |
| 122 | aarch64.Instruction.add_subtract_immediate, |
| 123 | ), code), |
| 124 | }; |
| 125 | inst.add_subtract_immediate.imm12 = narrowed; |
| 126 | mem.writeIntLittle(u32, code, inst.toU32()); |
| 127 | } else { |
| 128 | var inst = aarch64.Instruction{ |
| 129 | .load_store_register = mem.bytesToValue(meta.TagPayload( |
| 130 | aarch64.Instruction, |
| 131 | aarch64.Instruction.load_store_register, |
| 132 | ), code), |
| 133 | }; |
| 134 | const offset: u12 = blk: { |
| 135 | if (inst.load_store_register.size == 0) { |
| 136 | if (inst.load_store_register.v == 1) { |
| 137 | // 128-bit SIMD is scaled by 16. |
| 138 | break :blk try math.divExact(u12, narrowed, 16); |
| 139 | } |
| 140 | // Otherwise, 8-bit SIMD or ldrb. |
| 141 | break :blk narrowed; |
| 142 | } else { |
| 143 | const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size); |
| 144 | break :blk try math.divExact(u12, narrowed, denom); |
| 145 | } |
| 146 | }; |
| 147 | inst.load_store_register.offset = offset; |
| 148 | mem.writeIntLittle(u32, code, inst.toU32()); |
| 149 | } |
| 150 | }, |
| 151 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => { |
| 152 | const narrowed = @truncate(u12, @intCast(u64, target_addr)); |
| 153 | var inst: aarch64.Instruction = .{ |
| 154 | .load_store_register = mem.bytesToValue(meta.TagPayload( |
| 155 | aarch64.Instruction, |
| 156 | aarch64.Instruction.load_store_register, |
| 157 | ), code), |
| 158 | }; |
| 159 | const offset = try math.divExact(u12, narrowed, 8); |
| 160 | inst.load_store_register.offset = offset; |
| 161 | mem.writeIntLittle(u32, code, inst.toU32()); |
| 162 | }, |
| 163 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { |
| 164 | const RegInfo = struct { |
| 165 | rd: u5, |
| 166 | rn: u5, |
| 167 | size: u2, |
| 168 | }; |
| 169 | const reg_info: RegInfo = blk: { |
| 170 | if (isArithmeticOp(code)) { |
| 171 | const inst = mem.bytesToValue(meta.TagPayload( |
| 172 | aarch64.Instruction, |
| 173 | aarch64.Instruction.add_subtract_immediate, |
| 174 | ), code); |
| 175 | break :blk .{ |
| 176 | .rd = inst.rd, |
| 177 | .rn = inst.rn, |
| 178 | .size = inst.sf, |
| 179 | }; |
| 180 | } else { |
| 181 | const inst = mem.bytesToValue(meta.TagPayload( |
| 182 | aarch64.Instruction, |
| 183 | aarch64.Instruction.load_store_register, |
| 184 | ), code); |
| 185 | break :blk .{ |
| 186 | .rd = inst.rt, |
| 187 | .rn = inst.rn, |
| 188 | .size = inst.size, |
| 189 | }; |
| 190 | } |
| 191 | }; |
| 192 | const narrowed = @truncate(u12, @intCast(u64, target_addr)); |
| 193 | var inst = if (macho_file.tlv_ptr_entries_table.contains(self.target)) blk: { |
| 194 | const offset = try math.divExact(u12, narrowed, 8); |
| 195 | break :blk aarch64.Instruction{ |
| 196 | .load_store_register = .{ |
| 197 | .rt = reg_info.rd, |
| 198 | .rn = reg_info.rn, |
| 199 | .offset = offset, |
| 200 | .opc = 0b01, |
| 201 | .op1 = 0b01, |
| 202 | .v = 0, |
| 203 | .size = reg_info.size, |
| 204 | }, |
| 205 | }; |
| 206 | } else aarch64.Instruction{ |
| 207 | .add_subtract_immediate = .{ |
| 208 | .rd = reg_info.rd, |
| 209 | .rn = reg_info.rn, |
| 210 | .imm12 = narrowed, |
| 211 | .sh = 0, |
| 212 | .s = 0, |
| 213 | .op = 0, |
| 214 | .sf = @truncate(u1, reg_info.size), |
| 215 | }, |
| 216 | }; |
| 217 | mem.writeIntLittle(u32, code, inst.toU32()); |
| 218 | }, |
| 219 | .ARM64_RELOC_POINTER_TO_GOT => { |
| 220 | const result = math.cast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr)) orelse |
| 221 | return error.Overflow; |
| 222 | mem.writeIntLittle(u32, code, @bitCast(u32, result)); |
| 223 | }, |
| 224 | .ARM64_RELOC_UNSIGNED => { |
| 225 | switch (self.length) { |
| 226 | 2 => mem.writeIntLittle(u32, code, @truncate(u32, @bitCast(u64, target_addr))), |
| 227 | 3 => mem.writeIntLittle(u64, code, target_addr), |
| 228 | else => unreachable, |
| 229 | } |
| 230 | }, |
| 231 | .ARM64_RELOC_SUBTRACTOR => unreachable, |
| 232 | .ARM64_RELOC_ADDEND => unreachable, |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | fn resolveX8664(self: Relocation, source_addr: u64, target_addr: u64, code: []u8) !void { |
| 237 | const rel_type = @intToEnum(macho.reloc_type_x86_64, self.@"type"); |
| 238 | switch (rel_type) { |
| 239 | .X86_64_RELOC_BRANCH, |
| 240 | .X86_64_RELOC_GOT, |
| 241 | .X86_64_RELOC_GOT_LOAD, |
| 242 | .X86_64_RELOC_TLV, |
| 243 | => { |
| 244 | const displacement = math.cast(i32, @intCast(i64, target_addr) - @intCast(i64, source_addr) - 4) orelse |
| 245 | return error.Overflow; |
| 246 | mem.writeIntLittle(u32, code, @bitCast(u32, displacement)); |
| 247 | }, |
| 248 | .X86_64_RELOC_SIGNED, |
| 249 | .X86_64_RELOC_SIGNED_1, |
| 250 | .X86_64_RELOC_SIGNED_2, |
| 251 | .X86_64_RELOC_SIGNED_4, |
| 252 | => { |
| 253 | const correction: u3 = switch (rel_type) { |
| 254 | .X86_64_RELOC_SIGNED => 0, |
| 255 | .X86_64_RELOC_SIGNED_1 => 1, |
| 256 | .X86_64_RELOC_SIGNED_2 => 2, |
| 257 | .X86_64_RELOC_SIGNED_4 => 4, |
| 258 | else => unreachable, |
| 259 | }; |
| 260 | const displacement = math.cast(i32, target_addr - @intCast(i64, source_addr + correction + 4)) orelse |
| 261 | return error.Overflow; |
| 262 | mem.writeIntLittle(u32, code, @bitCast(u32, displacement)); |
| 263 | }, |
| 264 | .X86_64_RELOC_UNSIGNED => { |
| 265 | switch (self.length) { |
| 266 | 2 => mem.writeIntLittle(u32, code, @truncate(u32, @bitCast(u64, target_addr))), |
| 267 | 3 => mem.writeIntLittle(u64, code, target_addr), |
| 268 | } |
| 269 | }, |
| 270 | .X86_64_RELOC_SUBTRACTOR => unreachable, |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | inline fn isArithmeticOp(inst: *const [4]u8) bool { |
| 275 | const group_decode = @truncate(u5, inst[3]); |
| 276 | return ((group_decode >> 2) == 4); |
| 277 | } |