| author | |
| committer | |
| log | aaacfc0d0a23918c6712272e10bb1cdca1daaf04 |
| tree | bb5bdeb8dd441f1024d7236fa7b55e35c64fe149 |
| parent | 56fdada577d5d7f871bed8e5ae74e395291d4140 |
Initially, internally within the linker.5 files changed, 1312 insertions(+), 1304 deletions(-)
CMakeLists.txt+1-1| ... | @@ -577,11 +577,11 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -577,11 +577,11 @@ set(ZIG_STAGE2_SOURCES |
| 577 | "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" | 577 | "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" |
| 578 | "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" | 578 | "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" |
| 579 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" | 579 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" |
| 580 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Atom.zig" | ||
| 580 | "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig" | 581 | "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig" |
| 581 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" | 582 | "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" |
| 582 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" | 583 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" |
| 583 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" | 584 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" |
| 584 | "${CMAKE_SOURCE_DIR}/src/link/MachO/TextBlock.zig" | ||
| 585 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" | 585 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" |
| 586 | "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" | 586 | "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" |
| 587 | "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig" | 587 | "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig" |
src/link/MachO.zig+3-1| ... | @@ -24,6 +24,7 @@ const trace = @import("../tracy.zig").trace; | ... | @@ -24,6 +24,7 @@ const trace = @import("../tracy.zig").trace; |
| 24 | const Air = @import("../Air.zig"); | 24 | const Air = @import("../Air.zig"); |
| 25 | const Allocator = mem.Allocator; | 25 | const Allocator = mem.Allocator; |
| 26 | const Archive = @import("MachO/Archive.zig"); | 26 | const Archive = @import("MachO/Archive.zig"); |
| 27 | const Atom = @import("MachO/Atom.zig"); | ||
| 27 | const Cache = @import("../Cache.zig"); | 28 | const Cache = @import("../Cache.zig"); |
| 28 | const CodeSignature = @import("MachO/CodeSignature.zig"); | 29 | const CodeSignature = @import("MachO/CodeSignature.zig"); |
| 29 | const Compilation = @import("../Compilation.zig"); | 30 | const Compilation = @import("../Compilation.zig"); |
| ... | @@ -37,9 +38,10 @@ const LlvmObject = @import("../codegen/llvm.zig").Object; | ... | @@ -37,9 +38,10 @@ const LlvmObject = @import("../codegen/llvm.zig").Object; |
| 37 | const LoadCommand = commands.LoadCommand; | 38 | const LoadCommand = commands.LoadCommand; |
| 38 | const Module = @import("../Module.zig"); | 39 | const Module = @import("../Module.zig"); |
| 39 | const SegmentCommand = commands.SegmentCommand; | 40 | const SegmentCommand = commands.SegmentCommand; |
| 40 | pub const TextBlock = @import("MachO/TextBlock.zig"); | ||
| 41 | const Trie = @import("MachO/Trie.zig"); | 41 | const Trie = @import("MachO/Trie.zig"); |
| 42 | 42 | ||
| 43 | pub const TextBlock = Atom; | ||
| 44 | |||
| 43 | pub const base_tag: File.Tag = File.Tag.macho; | 45 | pub const base_tag: File.Tag = File.Tag.macho; |
| 44 | 46 | ||
| 45 | base: File, | 47 | base: File, |
src/link/MachO/Atom.zig created+1305| ... | @@ -0,0 +1,1305 @@ | ||
| 1 | const Atom = @This(); | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const build_options = @import("build_options"); | ||
| 5 | const aarch64 = @import("../../codegen/aarch64.zig"); | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | const commands = @import("commands.zig"); | ||
| 8 | const log = std.log.scoped(.text_block); | ||
| 9 | const macho = std.macho; | ||
| 10 | const math = std.math; | ||
| 11 | const mem = std.mem; | ||
| 12 | const meta = std.meta; | ||
| 13 | |||
| 14 | const Allocator = mem.Allocator; | ||
| 15 | const Arch = std.Target.Cpu.Arch; | ||
| 16 | const MachO = @import("../MachO.zig"); | ||
| 17 | const Object = @import("Object.zig"); | ||
| 18 | |||
| 19 | /// Each decl always gets a local symbol with the fully qualified name. | ||
| 20 | /// The vaddr and size are found here directly. | ||
| 21 | /// The file offset is found by computing the vaddr offset from the section vaddr | ||
| 22 | /// the symbol references, and adding that to the file offset of the section. | ||
| 23 | /// If this field is 0, it means the codegen size = 0 and there is no symbol or | ||
| 24 | /// offset table entry. | ||
| 25 | local_sym_index: u32, | ||
| 26 | |||
| 27 | /// List of symbol aliases pointing to the same atom via different nlists | ||
| 28 | aliases: std.ArrayListUnmanaged(u32) = .{}, | ||
| 29 | |||
| 30 | /// List of symbols contained within this atom | ||
| 31 | contained: std.ArrayListUnmanaged(SymbolAtOffset) = .{}, | ||
| 32 | |||
| 33 | /// Code (may be non-relocated) this atom represents | ||
| 34 | code: std.ArrayListUnmanaged(u8) = .{}, | ||
| 35 | |||
| 36 | /// Size and alignment of this atom | ||
| 37 | /// Unlike in Elf, we need to store the size of this symbol as part of | ||
| 38 | /// the atom since macho.nlist_64 lacks this information. | ||
| 39 | size: u64, | ||
| 40 | |||
| 41 | /// Alignment of this atom as a power of 2. | ||
| 42 | /// For instance, aligmment of 0 should be read as 2^0 = 1 byte aligned. | ||
| 43 | alignment: u32, | ||
| 44 | |||
| 45 | /// List of relocations belonging to this atom. | ||
| 46 | relocs: std.ArrayListUnmanaged(Relocation) = .{}, | ||
| 47 | |||
| 48 | /// List of offsets contained within this atom that need rebasing by the dynamic | ||
| 49 | /// loader in presence of ASLR. | ||
| 50 | rebases: std.ArrayListUnmanaged(u64) = .{}, | ||
| 51 | |||
| 52 | /// List of offsets contained within this atom that will be dynamically bound | ||
| 53 | /// by the dynamic loader and contain pointers to resolved (at load time) extern | ||
| 54 | /// symbols (aka proxies aka imports) | ||
| 55 | bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{}, | ||
| 56 | |||
| 57 | /// List of lazy bindings | ||
| 58 | lazy_bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{}, | ||
| 59 | |||
| 60 | /// List of data-in-code entries. This is currently specific to x86_64 only. | ||
| 61 | dices: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, | ||
| 62 | |||
| 63 | /// Stab entry for this atom. This is currently specific to a binary created | ||
| 64 | /// by linking object files in a traditional sense - in incremental sense, we | ||
| 65 | /// bypass stabs altogether to produce dSYM bundle directly with fully relocated | ||
| 66 | /// DWARF sections. | ||
| 67 | stab: ?Stab = null, | ||
| 68 | |||
| 69 | /// Points to the previous and next neighbours | ||
| 70 | next: ?*Atom, | ||
| 71 | prev: ?*Atom, | ||
| 72 | |||
| 73 | /// Previous/next linked list pointers. | ||
| 74 | /// This is the linked list node for this Decl's corresponding .debug_info tag. | ||
| 75 | dbg_info_prev: ?*Atom, | ||
| 76 | dbg_info_next: ?*Atom, | ||
| 77 | /// Offset into .debug_info pointing to the tag for this Decl. | ||
| 78 | dbg_info_off: u32, | ||
| 79 | /// Size of the .debug_info tag for this Decl, not including padding. | ||
| 80 | dbg_info_len: u32, | ||
| 81 | |||
| 82 | dirty: bool = true, | ||
| 83 | |||
| 84 | pub const SymbolAtOffset = struct { | ||
| 85 | local_sym_index: u32, | ||
| 86 | offset: u64, | ||
| 87 | stab: ?Stab = null, | ||
| 88 | |||
| 89 | pub fn format( | ||
| 90 | self: SymbolAtOffset, | ||
| 91 | comptime fmt: []const u8, | ||
| 92 | options: std.fmt.FormatOptions, | ||
| 93 | writer: anytype, | ||
| 94 | ) !void { | ||
| 95 | _ = fmt; | ||
| 96 | _ = options; | ||
| 97 | try std.fmt.format(writer, "{{ {d}: .offset = {d}", .{ self.local_sym_index, self.offset }); | ||
| 98 | if (self.stab) |stab| { | ||
| 99 | try std.fmt.format(writer, ", .stab = {any}", .{stab}); | ||
| 100 | } | ||
| 101 | try std.fmt.format(writer, " }}", .{}); | ||
| 102 | } | ||
| 103 | }; | ||
| 104 | |||
| 105 | pub const Stab = union(enum) { | ||
| 106 | function: u64, | ||
| 107 | static, | ||
| 108 | global, | ||
| 109 | |||
| 110 | pub fn asNlists(stab: Stab, local_sym_index: u32, macho_file: anytype) ![]macho.nlist_64 { | ||
| 111 | var nlists = std.ArrayList(macho.nlist_64).init(macho_file.base.allocator); | ||
| 112 | defer nlists.deinit(); | ||
| 113 | |||
| 114 | const sym = macho_file.locals.items[local_sym_index]; | ||
| 115 | switch (stab) { | ||
| 116 | .function => |size| { | ||
| 117 | try nlists.ensureUnusedCapacity(4); | ||
| 118 | nlists.appendAssumeCapacity(.{ | ||
| 119 | .n_strx = 0, | ||
| 120 | .n_type = macho.N_BNSYM, | ||
| 121 | .n_sect = sym.n_sect, | ||
| 122 | .n_desc = 0, | ||
| 123 | .n_value = sym.n_value, | ||
| 124 | }); | ||
| 125 | nlists.appendAssumeCapacity(.{ | ||
| 126 | .n_strx = sym.n_strx, | ||
| 127 | .n_type = macho.N_FUN, | ||
| 128 | .n_sect = sym.n_sect, | ||
| 129 | .n_desc = 0, | ||
| 130 | .n_value = sym.n_value, | ||
| 131 | }); | ||
| 132 | nlists.appendAssumeCapacity(.{ | ||
| 133 | .n_strx = 0, | ||
| 134 | .n_type = macho.N_FUN, | ||
| 135 | .n_sect = 0, | ||
| 136 | .n_desc = 0, | ||
| 137 | .n_value = size, | ||
| 138 | }); | ||
| 139 | nlists.appendAssumeCapacity(.{ | ||
| 140 | .n_strx = 0, | ||
| 141 | .n_type = macho.N_ENSYM, | ||
| 142 | .n_sect = sym.n_sect, | ||
| 143 | .n_desc = 0, | ||
| 144 | .n_value = size, | ||
| 145 | }); | ||
| 146 | }, | ||
| 147 | .global => { | ||
| 148 | try nlists.append(.{ | ||
| 149 | .n_strx = sym.n_strx, | ||
| 150 | .n_type = macho.N_GSYM, | ||
| 151 | .n_sect = 0, | ||
| 152 | .n_desc = 0, | ||
| 153 | .n_value = 0, | ||
| 154 | }); | ||
| 155 | }, | ||
| 156 | .static => { | ||
| 157 | try nlists.append(.{ | ||
| 158 | .n_strx = sym.n_strx, | ||
| 159 | .n_type = macho.N_STSYM, | ||
| 160 | .n_sect = sym.n_sect, | ||
| 161 | .n_desc = 0, | ||
| 162 | .n_value = sym.n_value, | ||
| 163 | }); | ||
| 164 | }, | ||
| 165 | } | ||
| 166 | |||
| 167 | return nlists.toOwnedSlice(); | ||
| 168 | } | ||
| 169 | }; | ||
| 170 | |||
| 171 | pub const Relocation = struct { | ||
| 172 | /// Offset within the atom's code buffer. | ||
| 173 | /// Note relocation size can be inferred by relocation's kind. | ||
| 174 | offset: u32, | ||
| 175 | |||
| 176 | where: enum { | ||
| 177 | local, | ||
| 178 | undef, | ||
| 179 | }, | ||
| 180 | |||
| 181 | where_index: u32, | ||
| 182 | |||
| 183 | payload: union(enum) { | ||
| 184 | unsigned: Unsigned, | ||
| 185 | branch: Branch, | ||
| 186 | page: Page, | ||
| 187 | page_off: PageOff, | ||
| 188 | pointer_to_got: PointerToGot, | ||
| 189 | signed: Signed, | ||
| 190 | load: Load, | ||
| 191 | }, | ||
| 192 | |||
| 193 | const ResolveArgs = struct { | ||
| 194 | block: *Atom, | ||
| 195 | offset: u32, | ||
| 196 | source_addr: u64, | ||
| 197 | target_addr: u64, | ||
| 198 | macho_file: *MachO, | ||
| 199 | }; | ||
| 200 | |||
| 201 | pub const Unsigned = struct { | ||
| 202 | subtractor: ?u32, | ||
| 203 | |||
| 204 | /// Addend embedded directly in the relocation slot | ||
| 205 | addend: i64, | ||
| 206 | |||
| 207 | /// Extracted from r_length: | ||
| 208 | /// => 3 implies true | ||
| 209 | /// => 2 implies false | ||
| 210 | /// => * is unreachable | ||
| 211 | is_64bit: bool, | ||
| 212 | |||
| 213 | pub fn resolve(self: Unsigned, args: ResolveArgs) !void { | ||
| 214 | const result = blk: { | ||
| 215 | if (self.subtractor) |subtractor| { | ||
| 216 | const sym = args.macho_file.locals.items[subtractor]; | ||
| 217 | break :blk @intCast(i64, args.target_addr) - @intCast(i64, sym.n_value) + self.addend; | ||
| 218 | } else { | ||
| 219 | break :blk @intCast(i64, args.target_addr) + self.addend; | ||
| 220 | } | ||
| 221 | }; | ||
| 222 | |||
| 223 | if (self.is_64bit) { | ||
| 224 | mem.writeIntLittle(u64, args.block.code.items[args.offset..][0..8], @bitCast(u64, result)); | ||
| 225 | } else { | ||
| 226 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @truncate(u32, @bitCast(u64, result))); | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | pub fn format(self: Unsigned, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 231 | _ = fmt; | ||
| 232 | _ = options; | ||
| 233 | try std.fmt.format(writer, "Unsigned {{ ", .{}); | ||
| 234 | if (self.subtractor) |sub| { | ||
| 235 | try std.fmt.format(writer, ".subtractor = {}, ", .{sub}); | ||
| 236 | } | ||
| 237 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 238 | const length: usize = if (self.is_64bit) 8 else 4; | ||
| 239 | try std.fmt.format(writer, ".length = {}, ", .{length}); | ||
| 240 | try std.fmt.format(writer, "}}", .{}); | ||
| 241 | } | ||
| 242 | }; | ||
| 243 | |||
| 244 | pub const Branch = struct { | ||
| 245 | arch: Arch, | ||
| 246 | |||
| 247 | pub fn resolve(self: Branch, args: ResolveArgs) !void { | ||
| 248 | switch (self.arch) { | ||
| 249 | .aarch64 => { | ||
| 250 | const displacement = math.cast( | ||
| 251 | i28, | ||
| 252 | @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr), | ||
| 253 | ) catch |err| switch (err) { | ||
| 254 | error.Overflow => { | ||
| 255 | log.err("jump too big to encode as i28 displacement value", .{}); | ||
| 256 | log.err(" (target - source) = displacement => 0x{x} - 0x{x} = 0x{x}", .{ | ||
| 257 | args.target_addr, | ||
| 258 | args.source_addr, | ||
| 259 | @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr), | ||
| 260 | }); | ||
| 261 | log.err(" TODO implement branch islands to extend jump distance for arm64", .{}); | ||
| 262 | return error.TODOImplementBranchIslands; | ||
| 263 | }, | ||
| 264 | }; | ||
| 265 | const code = args.block.code.items[args.offset..][0..4]; | ||
| 266 | var inst = aarch64.Instruction{ | ||
| 267 | .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload( | ||
| 268 | aarch64.Instruction, | ||
| 269 | aarch64.Instruction.unconditional_branch_immediate, | ||
| 270 | ), code), | ||
| 271 | }; | ||
| 272 | inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2)); | ||
| 273 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 274 | }, | ||
| 275 | .x86_64 => { | ||
| 276 | const displacement = try math.cast( | ||
| 277 | i32, | ||
| 278 | @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4, | ||
| 279 | ); | ||
| 280 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @bitCast(u32, displacement)); | ||
| 281 | }, | ||
| 282 | else => return error.UnsupportedCpuArchitecture, | ||
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | pub fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 287 | _ = self; | ||
| 288 | _ = fmt; | ||
| 289 | _ = options; | ||
| 290 | try std.fmt.format(writer, "Branch {{}}", .{}); | ||
| 291 | } | ||
| 292 | }; | ||
| 293 | |||
| 294 | pub const Page = struct { | ||
| 295 | kind: enum { | ||
| 296 | page, | ||
| 297 | got, | ||
| 298 | tlvp, | ||
| 299 | }, | ||
| 300 | addend: u32 = 0, | ||
| 301 | |||
| 302 | pub fn resolve(self: Page, args: ResolveArgs) !void { | ||
| 303 | const target_addr = args.target_addr + self.addend; | ||
| 304 | const source_page = @intCast(i32, args.source_addr >> 12); | ||
| 305 | const target_page = @intCast(i32, target_addr >> 12); | ||
| 306 | const pages = @bitCast(u21, @intCast(i21, target_page - source_page)); | ||
| 307 | |||
| 308 | const code = args.block.code.items[args.offset..][0..4]; | ||
| 309 | var inst = aarch64.Instruction{ | ||
| 310 | .pc_relative_address = mem.bytesToValue(meta.TagPayload( | ||
| 311 | aarch64.Instruction, | ||
| 312 | aarch64.Instruction.pc_relative_address, | ||
| 313 | ), code), | ||
| 314 | }; | ||
| 315 | inst.pc_relative_address.immhi = @truncate(u19, pages >> 2); | ||
| 316 | inst.pc_relative_address.immlo = @truncate(u2, pages); | ||
| 317 | |||
| 318 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 319 | } | ||
| 320 | |||
| 321 | pub fn format(self: Page, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 322 | _ = fmt; | ||
| 323 | _ = options; | ||
| 324 | try std.fmt.format(writer, "Page {{ ", .{}); | ||
| 325 | switch (self.kind) { | ||
| 326 | .page => {}, | ||
| 327 | .got => { | ||
| 328 | try std.fmt.format(writer, ".got, ", .{}); | ||
| 329 | }, | ||
| 330 | .tlvp => { | ||
| 331 | try std.fmt.format(writer, ".tlvp", .{}); | ||
| 332 | }, | ||
| 333 | } | ||
| 334 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 335 | try std.fmt.format(writer, "}}", .{}); | ||
| 336 | } | ||
| 337 | }; | ||
| 338 | |||
| 339 | pub const PageOff = struct { | ||
| 340 | kind: enum { | ||
| 341 | page, | ||
| 342 | got, | ||
| 343 | tlvp, | ||
| 344 | }, | ||
| 345 | addend: u32 = 0, | ||
| 346 | op_kind: ?OpKind = null, | ||
| 347 | |||
| 348 | pub const OpKind = enum { | ||
| 349 | arithmetic, | ||
| 350 | load, | ||
| 351 | }; | ||
| 352 | |||
| 353 | pub fn resolve(self: PageOff, args: ResolveArgs) !void { | ||
| 354 | const code = args.block.code.items[args.offset..][0..4]; | ||
| 355 | |||
| 356 | switch (self.kind) { | ||
| 357 | .page => { | ||
| 358 | const target_addr = args.target_addr + self.addend; | ||
| 359 | const narrowed = @truncate(u12, target_addr); | ||
| 360 | |||
| 361 | const op_kind = self.op_kind orelse unreachable; | ||
| 362 | var inst: aarch64.Instruction = blk: { | ||
| 363 | switch (op_kind) { | ||
| 364 | .arithmetic => { | ||
| 365 | break :blk .{ | ||
| 366 | .add_subtract_immediate = mem.bytesToValue(meta.TagPayload( | ||
| 367 | aarch64.Instruction, | ||
| 368 | aarch64.Instruction.add_subtract_immediate, | ||
| 369 | ), code), | ||
| 370 | }; | ||
| 371 | }, | ||
| 372 | .load => { | ||
| 373 | break :blk .{ | ||
| 374 | .load_store_register = mem.bytesToValue(meta.TagPayload( | ||
| 375 | aarch64.Instruction, | ||
| 376 | aarch64.Instruction.load_store_register, | ||
| 377 | ), code), | ||
| 378 | }; | ||
| 379 | }, | ||
| 380 | } | ||
| 381 | }; | ||
| 382 | |||
| 383 | if (op_kind == .arithmetic) { | ||
| 384 | inst.add_subtract_immediate.imm12 = narrowed; | ||
| 385 | } else { | ||
| 386 | const offset: u12 = blk: { | ||
| 387 | if (inst.load_store_register.size == 0) { | ||
| 388 | if (inst.load_store_register.v == 1) { | ||
| 389 | // 128-bit SIMD is scaled by 16. | ||
| 390 | break :blk try math.divExact(u12, narrowed, 16); | ||
| 391 | } | ||
| 392 | // Otherwise, 8-bit SIMD or ldrb. | ||
| 393 | break :blk narrowed; | ||
| 394 | } else { | ||
| 395 | const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size); | ||
| 396 | break :blk try math.divExact(u12, narrowed, denom); | ||
| 397 | } | ||
| 398 | }; | ||
| 399 | inst.load_store_register.offset = offset; | ||
| 400 | } | ||
| 401 | |||
| 402 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 403 | }, | ||
| 404 | .got => { | ||
| 405 | const narrowed = @truncate(u12, args.target_addr); | ||
| 406 | var inst: aarch64.Instruction = .{ | ||
| 407 | .load_store_register = mem.bytesToValue(meta.TagPayload( | ||
| 408 | aarch64.Instruction, | ||
| 409 | aarch64.Instruction.load_store_register, | ||
| 410 | ), code), | ||
| 411 | }; | ||
| 412 | const offset = try math.divExact(u12, narrowed, 8); | ||
| 413 | inst.load_store_register.offset = offset; | ||
| 414 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 415 | }, | ||
| 416 | .tlvp => { | ||
| 417 | const RegInfo = struct { | ||
| 418 | rd: u5, | ||
| 419 | rn: u5, | ||
| 420 | size: u1, | ||
| 421 | }; | ||
| 422 | const reg_info: RegInfo = blk: { | ||
| 423 | if (isArithmeticOp(code)) { | ||
| 424 | const inst = mem.bytesToValue(meta.TagPayload( | ||
| 425 | aarch64.Instruction, | ||
| 426 | aarch64.Instruction.add_subtract_immediate, | ||
| 427 | ), code); | ||
| 428 | break :blk .{ | ||
| 429 | .rd = inst.rd, | ||
| 430 | .rn = inst.rn, | ||
| 431 | .size = inst.sf, | ||
| 432 | }; | ||
| 433 | } else { | ||
| 434 | const inst = mem.bytesToValue(meta.TagPayload( | ||
| 435 | aarch64.Instruction, | ||
| 436 | aarch64.Instruction.load_store_register, | ||
| 437 | ), code); | ||
| 438 | break :blk .{ | ||
| 439 | .rd = inst.rt, | ||
| 440 | .rn = inst.rn, | ||
| 441 | .size = @truncate(u1, inst.size), | ||
| 442 | }; | ||
| 443 | } | ||
| 444 | }; | ||
| 445 | const narrowed = @truncate(u12, args.target_addr); | ||
| 446 | var inst = aarch64.Instruction{ | ||
| 447 | .add_subtract_immediate = .{ | ||
| 448 | .rd = reg_info.rd, | ||
| 449 | .rn = reg_info.rn, | ||
| 450 | .imm12 = narrowed, | ||
| 451 | .sh = 0, | ||
| 452 | .s = 0, | ||
| 453 | .op = 0, | ||
| 454 | .sf = reg_info.size, | ||
| 455 | }, | ||
| 456 | }; | ||
| 457 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 458 | }, | ||
| 459 | } | ||
| 460 | } | ||
| 461 | |||
| 462 | pub fn format(self: PageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 463 | _ = fmt; | ||
| 464 | _ = options; | ||
| 465 | try std.fmt.format(writer, "PageOff {{ ", .{}); | ||
| 466 | switch (self.kind) { | ||
| 467 | .page => {}, | ||
| 468 | .got => { | ||
| 469 | try std.fmt.format(writer, ".got, ", .{}); | ||
| 470 | }, | ||
| 471 | .tlvp => { | ||
| 472 | try std.fmt.format(writer, ".tlvp, ", .{}); | ||
| 473 | }, | ||
| 474 | } | ||
| 475 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 476 | try std.fmt.format(writer, ".op_kind = {s}, ", .{self.op_kind}); | ||
| 477 | try std.fmt.format(writer, "}}", .{}); | ||
| 478 | } | ||
| 479 | }; | ||
| 480 | |||
| 481 | pub const PointerToGot = struct { | ||
| 482 | pub fn resolve(_: PointerToGot, args: ResolveArgs) !void { | ||
| 483 | const result = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr)); | ||
| 484 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @bitCast(u32, result)); | ||
| 485 | } | ||
| 486 | |||
| 487 | pub fn format(self: PointerToGot, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 488 | _ = self; | ||
| 489 | _ = fmt; | ||
| 490 | _ = options; | ||
| 491 | try std.fmt.format(writer, "PointerToGot {{}}", .{}); | ||
| 492 | } | ||
| 493 | }; | ||
| 494 | |||
| 495 | pub const Signed = struct { | ||
| 496 | addend: i64, | ||
| 497 | correction: u3, | ||
| 498 | |||
| 499 | pub fn resolve(self: Signed, args: ResolveArgs) !void { | ||
| 500 | const target_addr = @intCast(i64, args.target_addr) + self.addend; | ||
| 501 | const displacement = try math.cast( | ||
| 502 | i32, | ||
| 503 | target_addr - @intCast(i64, args.source_addr + self.correction + 4), | ||
| 504 | ); | ||
| 505 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @bitCast(u32, displacement)); | ||
| 506 | } | ||
| 507 | |||
| 508 | pub fn format(self: Signed, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 509 | _ = fmt; | ||
| 510 | _ = options; | ||
| 511 | try std.fmt.format(writer, "Signed {{ ", .{}); | ||
| 512 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 513 | try std.fmt.format(writer, ".correction = {}, ", .{self.correction}); | ||
| 514 | try std.fmt.format(writer, "}}", .{}); | ||
| 515 | } | ||
| 516 | }; | ||
| 517 | |||
| 518 | pub const Load = struct { | ||
| 519 | kind: enum { | ||
| 520 | got, | ||
| 521 | tlvp, | ||
| 522 | }, | ||
| 523 | addend: i32 = 0, | ||
| 524 | |||
| 525 | pub fn resolve(self: Load, args: ResolveArgs) !void { | ||
| 526 | if (self.kind == .tlvp) { | ||
| 527 | // We need to rewrite the opcode from movq to leaq. | ||
| 528 | args.block.code.items[args.offset - 2] = 0x8d; | ||
| 529 | } | ||
| 530 | const displacement = try math.cast( | ||
| 531 | i32, | ||
| 532 | @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4 + self.addend, | ||
| 533 | ); | ||
| 534 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @bitCast(u32, displacement)); | ||
| 535 | } | ||
| 536 | |||
| 537 | pub fn format(self: Load, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 538 | _ = fmt; | ||
| 539 | _ = options; | ||
| 540 | try std.fmt.format(writer, "Load {{ ", .{}); | ||
| 541 | try std.fmt.format(writer, "{s}, ", .{self.kind}); | ||
| 542 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 543 | try std.fmt.format(writer, "}}", .{}); | ||
| 544 | } | ||
| 545 | }; | ||
| 546 | |||
| 547 | pub fn resolve(self: Relocation, args: ResolveArgs) !void { | ||
| 548 | switch (self.payload) { | ||
| 549 | .unsigned => |unsigned| try unsigned.resolve(args), | ||
| 550 | .branch => |branch| try branch.resolve(args), | ||
| 551 | .page => |page| try page.resolve(args), | ||
| 552 | .page_off => |page_off| try page_off.resolve(args), | ||
| 553 | .pointer_to_got => |pointer_to_got| try pointer_to_got.resolve(args), | ||
| 554 | .signed => |signed| try signed.resolve(args), | ||
| 555 | .load => |load| try load.resolve(args), | ||
| 556 | } | ||
| 557 | } | ||
| 558 | |||
| 559 | pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 560 | try std.fmt.format(writer, "Relocation {{ ", .{}); | ||
| 561 | try std.fmt.format(writer, ".offset = {}, ", .{self.offset}); | ||
| 562 | try std.fmt.format(writer, ".where = {}, ", .{self.where}); | ||
| 563 | try std.fmt.format(writer, ".where_index = {d}, ", .{self.where_index}); | ||
| 564 | |||
| 565 | switch (self.payload) { | ||
| 566 | .unsigned => |unsigned| try unsigned.format(fmt, options, writer), | ||
| 567 | .branch => |branch| try branch.format(fmt, options, writer), | ||
| 568 | .page => |page| try page.format(fmt, options, writer), | ||
| 569 | .page_off => |page_off| try page_off.format(fmt, options, writer), | ||
| 570 | .pointer_to_got => |pointer_to_got| try pointer_to_got.format(fmt, options, writer), | ||
| 571 | .signed => |signed| try signed.format(fmt, options, writer), | ||
| 572 | .load => |load| try load.format(fmt, options, writer), | ||
| 573 | } | ||
| 574 | |||
| 575 | try std.fmt.format(writer, "}}", .{}); | ||
| 576 | } | ||
| 577 | }; | ||
| 578 | |||
| 579 | pub const empty = Atom{ | ||
| 580 | .local_sym_index = 0, | ||
| 581 | .size = 0, | ||
| 582 | .alignment = 0, | ||
| 583 | .prev = null, | ||
| 584 | .next = null, | ||
| 585 | .dbg_info_prev = null, | ||
| 586 | .dbg_info_next = null, | ||
| 587 | .dbg_info_off = undefined, | ||
| 588 | .dbg_info_len = undefined, | ||
| 589 | }; | ||
| 590 | |||
| 591 | pub fn deinit(self: *Atom, allocator: *Allocator) void { | ||
| 592 | self.dices.deinit(allocator); | ||
| 593 | self.lazy_bindings.deinit(allocator); | ||
| 594 | self.bindings.deinit(allocator); | ||
| 595 | self.rebases.deinit(allocator); | ||
| 596 | self.relocs.deinit(allocator); | ||
| 597 | self.contained.deinit(allocator); | ||
| 598 | self.aliases.deinit(allocator); | ||
| 599 | self.code.deinit(allocator); | ||
| 600 | } | ||
| 601 | |||
| 602 | /// Returns how much room there is to grow in virtual address space. | ||
| 603 | /// File offset relocation happens transparently, so it is not included in | ||
| 604 | /// this calculation. | ||
| 605 | pub fn capacity(self: Atom, macho_file: MachO) u64 { | ||
| 606 | const self_sym = macho_file.locals.items[self.local_sym_index]; | ||
| 607 | if (self.next) |next| { | ||
| 608 | const next_sym = macho_file.locals.items[next.local_sym_index]; | ||
| 609 | return next_sym.n_value - self_sym.n_value; | ||
| 610 | } else { | ||
| 611 | // We are the last atom. | ||
| 612 | // The capacity is limited only by virtual address space. | ||
| 613 | return std.math.maxInt(u64) - self_sym.n_value; | ||
| 614 | } | ||
| 615 | } | ||
| 616 | |||
| 617 | pub fn freeListEligible(self: Atom, macho_file: MachO) bool { | ||
| 618 | // No need to keep a free list node for the last atom. | ||
| 619 | const next = self.next orelse return false; | ||
| 620 | const self_sym = macho_file.locals.items[self.local_sym_index]; | ||
| 621 | const next_sym = macho_file.locals.items[next.local_sym_index]; | ||
| 622 | const cap = next_sym.n_value - self_sym.n_value; | ||
| 623 | const ideal_cap = MachO.padToIdeal(self.size); | ||
| 624 | if (cap <= ideal_cap) return false; | ||
| 625 | const surplus = cap - ideal_cap; | ||
| 626 | return surplus >= MachO.min_text_capacity; | ||
| 627 | } | ||
| 628 | |||
| 629 | const RelocContext = struct { | ||
| 630 | base_addr: u64 = 0, | ||
| 631 | base_offset: u64 = 0, | ||
| 632 | allocator: *Allocator, | ||
| 633 | object: *Object, | ||
| 634 | macho_file: *MachO, | ||
| 635 | parsed_atoms: *Object.ParsedAtoms, | ||
| 636 | }; | ||
| 637 | |||
| 638 | fn initRelocFromObject(rel: macho.relocation_info, context: RelocContext) !Relocation { | ||
| 639 | var parsed_rel = Relocation{ | ||
| 640 | .offset = @intCast(u32, @intCast(u64, rel.r_address) - context.base_offset), | ||
| 641 | .where = undefined, | ||
| 642 | .where_index = undefined, | ||
| 643 | .payload = undefined, | ||
| 644 | }; | ||
| 645 | |||
| 646 | if (rel.r_extern == 0) { | ||
| 647 | const sect_id = @intCast(u16, rel.r_symbolnum - 1); | ||
| 648 | |||
| 649 | const local_sym_index = context.object.sections_as_symbols.get(sect_id) orelse blk: { | ||
| 650 | const seg = context.object.load_commands.items[context.object.segment_cmd_index.?].Segment; | ||
| 651 | const sect = seg.sections.items[sect_id]; | ||
| 652 | const match = (try context.macho_file.getMatchingSection(sect)) orelse unreachable; | ||
| 653 | const local_sym_index = @intCast(u32, context.macho_file.locals.items.len); | ||
| 654 | const sym_name = try std.fmt.allocPrint(context.allocator, "l_{s}_{s}_{s}", .{ | ||
| 655 | context.object.name, | ||
| 656 | commands.segmentName(sect), | ||
| 657 | commands.sectionName(sect), | ||
| 658 | }); | ||
| 659 | defer context.allocator.free(sym_name); | ||
| 660 | |||
| 661 | try context.macho_file.locals.append(context.allocator, .{ | ||
| 662 | .n_strx = try context.macho_file.makeString(sym_name), | ||
| 663 | .n_type = macho.N_SECT, | ||
| 664 | .n_sect = @intCast(u8, context.macho_file.section_ordinals.getIndex(match).? + 1), | ||
| 665 | .n_desc = 0, | ||
| 666 | .n_value = 0, | ||
| 667 | }); | ||
| 668 | try context.object.sections_as_symbols.putNoClobber(context.allocator, sect_id, local_sym_index); | ||
| 669 | break :blk local_sym_index; | ||
| 670 | }; | ||
| 671 | |||
| 672 | parsed_rel.where = .local; | ||
| 673 | parsed_rel.where_index = local_sym_index; | ||
| 674 | } else { | ||
| 675 | const sym = context.object.symtab.items[rel.r_symbolnum]; | ||
| 676 | const sym_name = context.object.getString(sym.n_strx); | ||
| 677 | |||
| 678 | if (MachO.symbolIsSect(sym) and !MachO.symbolIsExt(sym)) { | ||
| 679 | const where_index = context.object.symbol_mapping.get(rel.r_symbolnum) orelse unreachable; | ||
| 680 | parsed_rel.where = .local; | ||
| 681 | parsed_rel.where_index = where_index; | ||
| 682 | } else { | ||
| 683 | const n_strx = context.macho_file.strtab_dir.getAdapted(@as([]const u8, sym_name), MachO.StringSliceAdapter{ | ||
| 684 | .strtab = &context.macho_file.strtab, | ||
| 685 | }) orelse unreachable; | ||
| 686 | const resolv = context.macho_file.symbol_resolver.get(n_strx) orelse unreachable; | ||
| 687 | switch (resolv.where) { | ||
| 688 | .global => { | ||
| 689 | parsed_rel.where = .local; | ||
| 690 | parsed_rel.where_index = resolv.local_sym_index; | ||
| 691 | }, | ||
| 692 | .undef => { | ||
| 693 | parsed_rel.where = .undef; | ||
| 694 | parsed_rel.where_index = resolv.where_index; | ||
| 695 | }, | ||
| 696 | } | ||
| 697 | } | ||
| 698 | } | ||
| 699 | |||
| 700 | return parsed_rel; | ||
| 701 | } | ||
| 702 | |||
| 703 | pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocContext) !void { | ||
| 704 | const filtered_relocs = filterRelocs(relocs, context.base_offset, context.base_offset + self.size); | ||
| 705 | var it = RelocIterator{ | ||
| 706 | .buffer = filtered_relocs, | ||
| 707 | }; | ||
| 708 | |||
| 709 | var addend: u32 = 0; | ||
| 710 | var subtractor: ?u32 = null; | ||
| 711 | const arch = context.macho_file.base.options.target.cpu.arch; | ||
| 712 | |||
| 713 | while (it.next()) |rel| { | ||
| 714 | if (isAddend(rel, arch)) { | ||
| 715 | // Addend is not a relocation with effect on the TextBlock, so | ||
| 716 | // parse it and carry on. | ||
| 717 | assert(addend == 0); // Oh no, addend was not reset! | ||
| 718 | addend = rel.r_symbolnum; | ||
| 719 | |||
| 720 | // Verify ADDEND is followed by a PAGE21 or PAGEOFF12. | ||
| 721 | const next = @intToEnum(macho.reloc_type_arm64, it.peek().r_type); | ||
| 722 | switch (next) { | ||
| 723 | .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {}, | ||
| 724 | else => { | ||
| 725 | log.err("unexpected relocation type: expected PAGE21 or PAGEOFF12, found {s}", .{next}); | ||
| 726 | return error.UnexpectedRelocationType; | ||
| 727 | }, | ||
| 728 | } | ||
| 729 | continue; | ||
| 730 | } | ||
| 731 | |||
| 732 | if (isSubtractor(rel, arch)) { | ||
| 733 | // Subtractor is not a relocation with effect on the TextBlock, so | ||
| 734 | // parse it and carry on. | ||
| 735 | assert(subtractor == null); // Oh no, subtractor was not reset! | ||
| 736 | assert(rel.r_extern == 1); | ||
| 737 | const sym = context.object.symtab.items[rel.r_symbolnum]; | ||
| 738 | const sym_name = context.object.getString(sym.n_strx); | ||
| 739 | |||
| 740 | if (MachO.symbolIsSect(sym) and !MachO.symbolIsExt(sym)) { | ||
| 741 | const where_index = context.object.symbol_mapping.get(rel.r_symbolnum) orelse unreachable; | ||
| 742 | subtractor = where_index; | ||
| 743 | } else { | ||
| 744 | const n_strx = context.macho_file.strtab_dir.getAdapted(@as([]const u8, sym_name), MachO.StringSliceAdapter{ | ||
| 745 | .strtab = &context.macho_file.strtab, | ||
| 746 | }) orelse unreachable; | ||
| 747 | const resolv = context.macho_file.symbol_resolver.get(n_strx) orelse unreachable; | ||
| 748 | assert(resolv.where == .global); | ||
| 749 | subtractor = resolv.local_sym_index; | ||
| 750 | } | ||
| 751 | |||
| 752 | // Verify SUBTRACTOR is followed by UNSIGNED. | ||
| 753 | switch (arch) { | ||
| 754 | .aarch64 => { | ||
| 755 | const next = @intToEnum(macho.reloc_type_arm64, it.peek().r_type); | ||
| 756 | if (next != .ARM64_RELOC_UNSIGNED) { | ||
| 757 | log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next}); | ||
| 758 | return error.UnexpectedRelocationType; | ||
| 759 | } | ||
| 760 | }, | ||
| 761 | .x86_64 => { | ||
| 762 | const next = @intToEnum(macho.reloc_type_x86_64, it.peek().r_type); | ||
| 763 | if (next != .X86_64_RELOC_UNSIGNED) { | ||
| 764 | log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next}); | ||
| 765 | return error.UnexpectedRelocationType; | ||
| 766 | } | ||
| 767 | }, | ||
| 768 | else => unreachable, | ||
| 769 | } | ||
| 770 | continue; | ||
| 771 | } | ||
| 772 | |||
| 773 | var parsed_rel = try initRelocFromObject(rel, context); | ||
| 774 | |||
| 775 | switch (arch) { | ||
| 776 | .aarch64 => { | ||
| 777 | const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); | ||
| 778 | switch (rel_type) { | ||
| 779 | .ARM64_RELOC_ADDEND => unreachable, | ||
| 780 | .ARM64_RELOC_SUBTRACTOR => unreachable, | ||
| 781 | .ARM64_RELOC_BRANCH26 => { | ||
| 782 | self.parseBranch(rel, &parsed_rel, context); | ||
| 783 | }, | ||
| 784 | .ARM64_RELOC_UNSIGNED => { | ||
| 785 | self.parseUnsigned(rel, &parsed_rel, subtractor, context); | ||
| 786 | subtractor = null; | ||
| 787 | }, | ||
| 788 | .ARM64_RELOC_PAGE21, | ||
| 789 | .ARM64_RELOC_GOT_LOAD_PAGE21, | ||
| 790 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | ||
| 791 | => { | ||
| 792 | self.parsePage(rel, &parsed_rel, addend); | ||
| 793 | if (rel_type == .ARM64_RELOC_PAGE21) | ||
| 794 | addend = 0; | ||
| 795 | }, | ||
| 796 | .ARM64_RELOC_PAGEOFF12, | ||
| 797 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12, | ||
| 798 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, | ||
| 799 | => { | ||
| 800 | self.parsePageOff(rel, &parsed_rel, addend); | ||
| 801 | if (rel_type == .ARM64_RELOC_PAGEOFF12) | ||
| 802 | addend = 0; | ||
| 803 | }, | ||
| 804 | .ARM64_RELOC_POINTER_TO_GOT => { | ||
| 805 | self.parsePointerToGot(rel, &parsed_rel); | ||
| 806 | }, | ||
| 807 | } | ||
| 808 | }, | ||
| 809 | .x86_64 => { | ||
| 810 | switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) { | ||
| 811 | .X86_64_RELOC_SUBTRACTOR => unreachable, | ||
| 812 | .X86_64_RELOC_BRANCH => { | ||
| 813 | self.parseBranch(rel, &parsed_rel, context); | ||
| 814 | }, | ||
| 815 | .X86_64_RELOC_UNSIGNED => { | ||
| 816 | self.parseUnsigned(rel, &parsed_rel, subtractor, context); | ||
| 817 | subtractor = null; | ||
| 818 | }, | ||
| 819 | .X86_64_RELOC_SIGNED, | ||
| 820 | .X86_64_RELOC_SIGNED_1, | ||
| 821 | .X86_64_RELOC_SIGNED_2, | ||
| 822 | .X86_64_RELOC_SIGNED_4, | ||
| 823 | => { | ||
| 824 | self.parseSigned(rel, &parsed_rel, context); | ||
| 825 | }, | ||
| 826 | .X86_64_RELOC_GOT_LOAD, | ||
| 827 | .X86_64_RELOC_GOT, | ||
| 828 | .X86_64_RELOC_TLV, | ||
| 829 | => { | ||
| 830 | self.parseLoad(rel, &parsed_rel); | ||
| 831 | }, | ||
| 832 | } | ||
| 833 | }, | ||
| 834 | else => unreachable, | ||
| 835 | } | ||
| 836 | |||
| 837 | try self.relocs.append(context.allocator, parsed_rel); | ||
| 838 | |||
| 839 | const is_via_got = switch (parsed_rel.payload) { | ||
| 840 | .pointer_to_got => true, | ||
| 841 | .load => |load| load.kind == .got, | ||
| 842 | .page => |page| page.kind == .got, | ||
| 843 | .page_off => |page_off| page_off.kind == .got, | ||
| 844 | else => false, | ||
| 845 | }; | ||
| 846 | |||
| 847 | if (is_via_got) blk: { | ||
| 848 | const key = MachO.GotIndirectionKey{ | ||
| 849 | .where = switch (parsed_rel.where) { | ||
| 850 | .local => .local, | ||
| 851 | .undef => .undef, | ||
| 852 | }, | ||
| 853 | .where_index = parsed_rel.where_index, | ||
| 854 | }; | ||
| 855 | if (context.macho_file.got_entries_map.contains(key)) break :blk; | ||
| 856 | |||
| 857 | const atom = try context.macho_file.createGotAtom(key); | ||
| 858 | try context.macho_file.got_entries_map.putNoClobber(context.macho_file.base.allocator, key, atom); | ||
| 859 | const match = MachO.MatchingSection{ | ||
| 860 | .seg = context.macho_file.data_const_segment_cmd_index.?, | ||
| 861 | .sect = context.macho_file.got_section_index.?, | ||
| 862 | }; | ||
| 863 | |||
| 864 | if (context.parsed_atoms.getPtr(match)) |last| { | ||
| 865 | last.*.next = atom; | ||
| 866 | atom.prev = last.*; | ||
| 867 | last.* = atom; | ||
| 868 | } else { | ||
| 869 | try context.parsed_atoms.putNoClobber(match, atom); | ||
| 870 | } | ||
| 871 | } else if (parsed_rel.payload == .unsigned) { | ||
| 872 | switch (parsed_rel.where) { | ||
| 873 | .undef => { | ||
| 874 | try self.bindings.append(context.allocator, .{ | ||
| 875 | .local_sym_index = parsed_rel.where_index, | ||
| 876 | .offset = parsed_rel.offset, | ||
| 877 | }); | ||
| 878 | }, | ||
| 879 | .local => { | ||
| 880 | const source_sym = context.macho_file.locals.items[self.local_sym_index]; | ||
| 881 | const match = context.macho_file.section_ordinals.keys()[source_sym.n_sect - 1]; | ||
| 882 | const seg = context.macho_file.load_commands.items[match.seg].Segment; | ||
| 883 | const sect = seg.sections.items[match.sect]; | ||
| 884 | const sect_type = commands.sectionType(sect); | ||
| 885 | |||
| 886 | const should_rebase = rebase: { | ||
| 887 | if (!parsed_rel.payload.unsigned.is_64bit) break :rebase false; | ||
| 888 | |||
| 889 | // TODO actually, a check similar to what dyld is doing, that is, verifying | ||
| 890 | // that the segment is writable should be enough here. | ||
| 891 | const is_right_segment = blk: { | ||
| 892 | if (context.macho_file.data_segment_cmd_index) |idx| { | ||
| 893 | if (match.seg == idx) { | ||
| 894 | break :blk true; | ||
| 895 | } | ||
| 896 | } | ||
| 897 | if (context.macho_file.data_const_segment_cmd_index) |idx| { | ||
| 898 | if (match.seg == idx) { | ||
| 899 | break :blk true; | ||
| 900 | } | ||
| 901 | } | ||
| 902 | break :blk false; | ||
| 903 | }; | ||
| 904 | |||
| 905 | if (!is_right_segment) break :rebase false; | ||
| 906 | if (sect_type != macho.S_LITERAL_POINTERS and | ||
| 907 | sect_type != macho.S_REGULAR and | ||
| 908 | sect_type != macho.S_MOD_INIT_FUNC_POINTERS and | ||
| 909 | sect_type != macho.S_MOD_TERM_FUNC_POINTERS) | ||
| 910 | { | ||
| 911 | break :rebase false; | ||
| 912 | } | ||
| 913 | |||
| 914 | break :rebase true; | ||
| 915 | }; | ||
| 916 | |||
| 917 | if (should_rebase) { | ||
| 918 | try self.rebases.append(context.allocator, parsed_rel.offset); | ||
| 919 | } | ||
| 920 | }, | ||
| 921 | } | ||
| 922 | } else if (parsed_rel.payload == .branch) blk: { | ||
| 923 | if (parsed_rel.where != .undef) break :blk; | ||
| 924 | if (context.macho_file.stubs_map.contains(parsed_rel.where_index)) break :blk; | ||
| 925 | |||
| 926 | const stub_helper_atom = try context.macho_file.createStubHelperAtom(); | ||
| 927 | const laptr_atom = try context.macho_file.createLazyPointerAtom( | ||
| 928 | stub_helper_atom.local_sym_index, | ||
| 929 | parsed_rel.where_index, | ||
| 930 | ); | ||
| 931 | const stub_atom = try context.macho_file.createStubAtom(laptr_atom.local_sym_index); | ||
| 932 | try context.macho_file.stubs_map.putNoClobber(context.allocator, parsed_rel.where_index, stub_atom); | ||
| 933 | // TODO clean this up! | ||
| 934 | if (context.parsed_atoms.getPtr(.{ | ||
| 935 | .seg = context.macho_file.text_segment_cmd_index.?, | ||
| 936 | .sect = context.macho_file.stub_helper_section_index.?, | ||
| 937 | })) |last| { | ||
| 938 | last.*.next = stub_helper_atom; | ||
| 939 | stub_helper_atom.prev = last.*; | ||
| 940 | last.* = stub_helper_atom; | ||
| 941 | } else { | ||
| 942 | try context.parsed_atoms.putNoClobber(.{ | ||
| 943 | .seg = context.macho_file.text_segment_cmd_index.?, | ||
| 944 | .sect = context.macho_file.stub_helper_section_index.?, | ||
| 945 | }, stub_helper_atom); | ||
| 946 | } | ||
| 947 | if (context.parsed_atoms.getPtr(.{ | ||
| 948 | .seg = context.macho_file.text_segment_cmd_index.?, | ||
| 949 | .sect = context.macho_file.stubs_section_index.?, | ||
| 950 | })) |last| { | ||
| 951 | last.*.next = stub_atom; | ||
| 952 | stub_atom.prev = last.*; | ||
| 953 | last.* = stub_atom; | ||
| 954 | } else { | ||
| 955 | try context.parsed_atoms.putNoClobber(.{ | ||
| 956 | .seg = context.macho_file.text_segment_cmd_index.?, | ||
| 957 | .sect = context.macho_file.stubs_section_index.?, | ||
| 958 | }, stub_atom); | ||
| 959 | } | ||
| 960 | if (context.parsed_atoms.getPtr(.{ | ||
| 961 | .seg = context.macho_file.data_segment_cmd_index.?, | ||
| 962 | .sect = context.macho_file.la_symbol_ptr_section_index.?, | ||
| 963 | })) |last| { | ||
| 964 | last.*.next = laptr_atom; | ||
| 965 | laptr_atom.prev = last.*; | ||
| 966 | last.* = laptr_atom; | ||
| 967 | } else { | ||
| 968 | try context.parsed_atoms.putNoClobber(.{ | ||
| 969 | .seg = context.macho_file.data_segment_cmd_index.?, | ||
| 970 | .sect = context.macho_file.la_symbol_ptr_section_index.?, | ||
| 971 | }, laptr_atom); | ||
| 972 | } | ||
| 973 | } | ||
| 974 | } | ||
| 975 | } | ||
| 976 | |||
| 977 | fn isAddend(rel: macho.relocation_info, arch: Arch) bool { | ||
| 978 | if (arch != .aarch64) return false; | ||
| 979 | return @intToEnum(macho.reloc_type_arm64, rel.r_type) == .ARM64_RELOC_ADDEND; | ||
| 980 | } | ||
| 981 | |||
| 982 | fn isSubtractor(rel: macho.relocation_info, arch: Arch) bool { | ||
| 983 | return switch (arch) { | ||
| 984 | .aarch64 => @intToEnum(macho.reloc_type_arm64, rel.r_type) == .ARM64_RELOC_SUBTRACTOR, | ||
| 985 | .x86_64 => @intToEnum(macho.reloc_type_x86_64, rel.r_type) == .X86_64_RELOC_SUBTRACTOR, | ||
| 986 | else => unreachable, | ||
| 987 | }; | ||
| 988 | } | ||
| 989 | |||
| 990 | fn parseUnsigned( | ||
| 991 | self: Atom, | ||
| 992 | rel: macho.relocation_info, | ||
| 993 | out: *Relocation, | ||
| 994 | subtractor: ?u32, | ||
| 995 | context: RelocContext, | ||
| 996 | ) void { | ||
| 997 | assert(rel.r_pcrel == 0); | ||
| 998 | |||
| 999 | const is_64bit: bool = switch (rel.r_length) { | ||
| 1000 | 3 => true, | ||
| 1001 | 2 => false, | ||
| 1002 | else => unreachable, | ||
| 1003 | }; | ||
| 1004 | |||
| 1005 | var addend: i64 = if (is_64bit) | ||
| 1006 | mem.readIntLittle(i64, self.code.items[out.offset..][0..8]) | ||
| 1007 | else | ||
| 1008 | mem.readIntLittle(i32, self.code.items[out.offset..][0..4]); | ||
| 1009 | |||
| 1010 | if (rel.r_extern == 0) { | ||
| 1011 | const seg = context.object.load_commands.items[context.object.segment_cmd_index.?].Segment; | ||
| 1012 | const target_sect_base_addr = seg.sections.items[rel.r_symbolnum - 1].addr; | ||
| 1013 | addend -= @intCast(i64, target_sect_base_addr); | ||
| 1014 | } | ||
| 1015 | |||
| 1016 | out.payload = .{ | ||
| 1017 | .unsigned = .{ | ||
| 1018 | .subtractor = subtractor, | ||
| 1019 | .is_64bit = is_64bit, | ||
| 1020 | .addend = addend, | ||
| 1021 | }, | ||
| 1022 | }; | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | fn parseBranch(self: Atom, rel: macho.relocation_info, out: *Relocation, context: RelocContext) void { | ||
| 1026 | _ = self; | ||
| 1027 | assert(rel.r_pcrel == 1); | ||
| 1028 | assert(rel.r_length == 2); | ||
| 1029 | |||
| 1030 | out.payload = .{ | ||
| 1031 | .branch = .{ | ||
| 1032 | .arch = context.macho_file.base.options.target.cpu.arch, | ||
| 1033 | }, | ||
| 1034 | }; | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | fn parsePage(self: Atom, rel: macho.relocation_info, out: *Relocation, addend: u32) void { | ||
| 1038 | _ = self; | ||
| 1039 | assert(rel.r_pcrel == 1); | ||
| 1040 | assert(rel.r_length == 2); | ||
| 1041 | |||
| 1042 | out.payload = .{ | ||
| 1043 | .page = .{ | ||
| 1044 | .kind = switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) { | ||
| 1045 | .ARM64_RELOC_PAGE21 => .page, | ||
| 1046 | .ARM64_RELOC_GOT_LOAD_PAGE21 => .got, | ||
| 1047 | .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp, | ||
| 1048 | else => unreachable, | ||
| 1049 | }, | ||
| 1050 | .addend = addend, | ||
| 1051 | }, | ||
| 1052 | }; | ||
| 1053 | } | ||
| 1054 | |||
| 1055 | fn parsePageOff(self: Atom, rel: macho.relocation_info, out: *Relocation, addend: u32) void { | ||
| 1056 | assert(rel.r_pcrel == 0); | ||
| 1057 | assert(rel.r_length == 2); | ||
| 1058 | |||
| 1059 | const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); | ||
| 1060 | const op_kind: ?Relocation.PageOff.OpKind = blk: { | ||
| 1061 | if (rel_type != .ARM64_RELOC_PAGEOFF12) break :blk null; | ||
| 1062 | const op_kind: Relocation.PageOff.OpKind = if (isArithmeticOp(self.code.items[out.offset..][0..4])) | ||
| 1063 | .arithmetic | ||
| 1064 | else | ||
| 1065 | .load; | ||
| 1066 | break :blk op_kind; | ||
| 1067 | }; | ||
| 1068 | |||
| 1069 | out.payload = .{ | ||
| 1070 | .page_off = .{ | ||
| 1071 | .kind = switch (rel_type) { | ||
| 1072 | .ARM64_RELOC_PAGEOFF12 => .page, | ||
| 1073 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got, | ||
| 1074 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp, | ||
| 1075 | else => unreachable, | ||
| 1076 | }, | ||
| 1077 | .addend = addend, | ||
| 1078 | .op_kind = op_kind, | ||
| 1079 | }, | ||
| 1080 | }; | ||
| 1081 | } | ||
| 1082 | |||
| 1083 | fn parsePointerToGot(self: Atom, rel: macho.relocation_info, out: *Relocation) void { | ||
| 1084 | _ = self; | ||
| 1085 | assert(rel.r_pcrel == 1); | ||
| 1086 | assert(rel.r_length == 2); | ||
| 1087 | |||
| 1088 | out.payload = .{ | ||
| 1089 | .pointer_to_got = .{}, | ||
| 1090 | }; | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | fn parseSigned(self: Atom, rel: macho.relocation_info, out: *Relocation, context: RelocContext) void { | ||
| 1094 | assert(rel.r_pcrel == 1); | ||
| 1095 | assert(rel.r_length == 2); | ||
| 1096 | |||
| 1097 | const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); | ||
| 1098 | const correction: u3 = switch (rel_type) { | ||
| 1099 | .X86_64_RELOC_SIGNED => 0, | ||
| 1100 | .X86_64_RELOC_SIGNED_1 => 1, | ||
| 1101 | .X86_64_RELOC_SIGNED_2 => 2, | ||
| 1102 | .X86_64_RELOC_SIGNED_4 => 4, | ||
| 1103 | else => unreachable, | ||
| 1104 | }; | ||
| 1105 | var addend: i64 = mem.readIntLittle(i32, self.code.items[out.offset..][0..4]) + correction; | ||
| 1106 | |||
| 1107 | if (rel.r_extern == 0) { | ||
| 1108 | const seg = context.object.load_commands.items[context.object.segment_cmd_index.?].Segment; | ||
| 1109 | const target_sect_base_addr = seg.sections.items[rel.r_symbolnum - 1].addr; | ||
| 1110 | addend += @intCast(i64, context.base_addr + out.offset + correction + 4) - @intCast(i64, target_sect_base_addr); | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | out.payload = .{ | ||
| 1114 | .signed = .{ | ||
| 1115 | .correction = correction, | ||
| 1116 | .addend = addend, | ||
| 1117 | }, | ||
| 1118 | }; | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | fn parseLoad(self: Atom, rel: macho.relocation_info, out: *Relocation) void { | ||
| 1122 | assert(rel.r_pcrel == 1); | ||
| 1123 | assert(rel.r_length == 2); | ||
| 1124 | |||
| 1125 | const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); | ||
| 1126 | const addend: i32 = if (rel_type == .X86_64_RELOC_GOT) | ||
| 1127 | mem.readIntLittle(i32, self.code.items[out.offset..][0..4]) | ||
| 1128 | else | ||
| 1129 | 0; | ||
| 1130 | |||
| 1131 | out.payload = .{ | ||
| 1132 | .load = .{ | ||
| 1133 | .kind = switch (rel_type) { | ||
| 1134 | .X86_64_RELOC_GOT_LOAD, .X86_64_RELOC_GOT => .got, | ||
| 1135 | .X86_64_RELOC_TLV => .tlvp, | ||
| 1136 | else => unreachable, | ||
| 1137 | }, | ||
| 1138 | .addend = addend, | ||
| 1139 | }, | ||
| 1140 | }; | ||
| 1141 | } | ||
| 1142 | |||
| 1143 | pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void { | ||
| 1144 | for (self.relocs.items) |rel| { | ||
| 1145 | log.debug("relocating {}", .{rel}); | ||
| 1146 | |||
| 1147 | const source_addr = blk: { | ||
| 1148 | const sym = macho_file.locals.items[self.local_sym_index]; | ||
| 1149 | break :blk sym.n_value + rel.offset; | ||
| 1150 | }; | ||
| 1151 | const target_addr = blk: { | ||
| 1152 | const is_via_got = switch (rel.payload) { | ||
| 1153 | .pointer_to_got => true, | ||
| 1154 | .page => |page| page.kind == .got, | ||
| 1155 | .page_off => |page_off| page_off.kind == .got, | ||
| 1156 | .load => |load| load.kind == .got, | ||
| 1157 | else => false, | ||
| 1158 | }; | ||
| 1159 | |||
| 1160 | if (is_via_got) { | ||
| 1161 | const atom = macho_file.got_entries_map.get(.{ | ||
| 1162 | .where = switch (rel.where) { | ||
| 1163 | .local => .local, | ||
| 1164 | .undef => .undef, | ||
| 1165 | }, | ||
| 1166 | .where_index = rel.where_index, | ||
| 1167 | }) orelse { | ||
| 1168 | const sym = switch (rel.where) { | ||
| 1169 | .local => macho_file.locals.items[rel.where_index], | ||
| 1170 | .undef => macho_file.undefs.items[rel.where_index], | ||
| 1171 | }; | ||
| 1172 | log.err("expected GOT entry for symbol '{s}'", .{macho_file.getString(sym.n_strx)}); | ||
| 1173 | log.err(" this is an internal linker error", .{}); | ||
| 1174 | return error.FailedToResolveRelocationTarget; | ||
| 1175 | }; | ||
| 1176 | break :blk macho_file.locals.items[atom.local_sym_index].n_value; | ||
| 1177 | } | ||
| 1178 | |||
| 1179 | switch (rel.where) { | ||
| 1180 | .local => { | ||
| 1181 | const sym = macho_file.locals.items[rel.where_index]; | ||
| 1182 | const is_tlv = is_tlv: { | ||
| 1183 | const source_sym = macho_file.locals.items[self.local_sym_index]; | ||
| 1184 | const match = macho_file.section_ordinals.keys()[source_sym.n_sect - 1]; | ||
| 1185 | const seg = macho_file.load_commands.items[match.seg].Segment; | ||
| 1186 | const sect = seg.sections.items[match.sect]; | ||
| 1187 | break :is_tlv commands.sectionType(sect) == macho.S_THREAD_LOCAL_VARIABLES; | ||
| 1188 | }; | ||
| 1189 | if (is_tlv) { | ||
| 1190 | // For TLV relocations, the value specified as a relocation is the displacement from the | ||
| 1191 | // TLV initializer (either value in __thread_data or zero-init in __thread_bss) to the first | ||
| 1192 | // defined TLV template init section in the following order: | ||
| 1193 | // * wrt to __thread_data if defined, then | ||
| 1194 | // * wrt to __thread_bss | ||
| 1195 | const seg = macho_file.load_commands.items[macho_file.data_segment_cmd_index.?].Segment; | ||
| 1196 | const base_address = inner: { | ||
| 1197 | if (macho_file.tlv_data_section_index) |i| { | ||
| 1198 | break :inner seg.sections.items[i].addr; | ||
| 1199 | } else if (macho_file.tlv_bss_section_index) |i| { | ||
| 1200 | break :inner seg.sections.items[i].addr; | ||
| 1201 | } else { | ||
| 1202 | log.err("threadlocal variables present but no initializer sections found", .{}); | ||
| 1203 | log.err(" __thread_data not found", .{}); | ||
| 1204 | log.err(" __thread_bss not found", .{}); | ||
| 1205 | return error.FailedToResolveRelocationTarget; | ||
| 1206 | } | ||
| 1207 | }; | ||
| 1208 | break :blk sym.n_value - base_address; | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | break :blk sym.n_value; | ||
| 1212 | }, | ||
| 1213 | .undef => { | ||
| 1214 | const atom = macho_file.stubs_map.get(rel.where_index) orelse { | ||
| 1215 | // TODO this is required for incremental when we don't have every symbol | ||
| 1216 | // resolved when creating relocations. In this case, we will insert a branch | ||
| 1217 | // reloc to an undef symbol which may happen to be defined within the binary. | ||
| 1218 | // Then, the undef we point at will be a null symbol (free symbol) which we | ||
| 1219 | // should remove/repurpose. To circumvent this (for now), we check if the symbol | ||
| 1220 | // we point to is garbage, and if so we fall back to symbol resolver to find by name. | ||
| 1221 | const n_strx = macho_file.undefs.items[rel.where_index].n_strx; | ||
| 1222 | if (macho_file.symbol_resolver.get(n_strx)) |resolv| inner: { | ||
| 1223 | if (resolv.where != .global) break :inner; | ||
| 1224 | break :blk macho_file.globals.items[resolv.where_index].n_value; | ||
| 1225 | } | ||
| 1226 | |||
| 1227 | // TODO verify in TextBlock that the symbol is indeed dynamically bound. | ||
| 1228 | break :blk 0; // Dynamically bound by dyld. | ||
| 1229 | }; | ||
| 1230 | |||
| 1231 | break :blk macho_file.locals.items[atom.local_sym_index].n_value; | ||
| 1232 | }, | ||
| 1233 | } | ||
| 1234 | }; | ||
| 1235 | |||
| 1236 | log.debug(" | source_addr = 0x{x}", .{source_addr}); | ||
| 1237 | log.debug(" | target_addr = 0x{x}", .{target_addr}); | ||
| 1238 | |||
| 1239 | try rel.resolve(.{ | ||
| 1240 | .block = self, | ||
| 1241 | .offset = rel.offset, | ||
| 1242 | .source_addr = source_addr, | ||
| 1243 | .target_addr = target_addr, | ||
| 1244 | .macho_file = macho_file, | ||
| 1245 | }); | ||
| 1246 | } | ||
| 1247 | } | ||
| 1248 | |||
| 1249 | pub fn format(self: Atom, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 1250 | _ = fmt; | ||
| 1251 | _ = options; | ||
| 1252 | try std.fmt.format(writer, "TextBlock {{ ", .{}); | ||
| 1253 | try std.fmt.format(writer, ".local_sym_index = {d}, ", .{self.local_sym_index}); | ||
| 1254 | try std.fmt.format(writer, ".aliases = {any}, ", .{self.aliases.items}); | ||
| 1255 | try std.fmt.format(writer, ".contained = {any}, ", .{self.contained.items}); | ||
| 1256 | try std.fmt.format(writer, ".code = {*}, ", .{self.code.items}); | ||
| 1257 | try std.fmt.format(writer, ".size = {d}, ", .{self.size}); | ||
| 1258 | try std.fmt.format(writer, ".alignment = {d}, ", .{self.alignment}); | ||
| 1259 | try std.fmt.format(writer, ".relocs = {any}, ", .{self.relocs.items}); | ||
| 1260 | try std.fmt.format(writer, ".rebases = {any}, ", .{self.rebases.items}); | ||
| 1261 | try std.fmt.format(writer, ".bindings = {any}, ", .{self.bindings.items}); | ||
| 1262 | try std.fmt.format(writer, ".dices = {any}, ", .{self.dices.items}); | ||
| 1263 | if (self.stab) |stab| { | ||
| 1264 | try std.fmt.format(writer, ".stab = {any}, ", .{stab}); | ||
| 1265 | } | ||
| 1266 | try std.fmt.format(writer, "}}", .{}); | ||
| 1267 | } | ||
| 1268 | |||
| 1269 | const RelocIterator = struct { | ||
| 1270 | buffer: []const macho.relocation_info, | ||
| 1271 | index: i32 = -1, | ||
| 1272 | |||
| 1273 | pub fn next(self: *RelocIterator) ?macho.relocation_info { | ||
| 1274 | self.index += 1; | ||
| 1275 | if (self.index < self.buffer.len) { | ||
| 1276 | return self.buffer[@intCast(u32, self.index)]; | ||
| 1277 | } | ||
| 1278 | return null; | ||
| 1279 | } | ||
| 1280 | |||
| 1281 | pub fn peek(self: RelocIterator) macho.relocation_info { | ||
| 1282 | assert(self.index + 1 < self.buffer.len); | ||
| 1283 | return self.buffer[@intCast(u32, self.index + 1)]; | ||
| 1284 | } | ||
| 1285 | }; | ||
| 1286 | |||
| 1287 | fn filterRelocs(relocs: []macho.relocation_info, start_addr: u64, end_addr: u64) []macho.relocation_info { | ||
| 1288 | const Predicate = struct { | ||
| 1289 | addr: u64, | ||
| 1290 | |||
| 1291 | pub fn predicate(self: @This(), rel: macho.relocation_info) bool { | ||
| 1292 | return rel.r_address < self.addr; | ||
| 1293 | } | ||
| 1294 | }; | ||
| 1295 | |||
| 1296 | const start = MachO.findFirst(macho.relocation_info, relocs, 0, Predicate{ .addr = end_addr }); | ||
| 1297 | const end = MachO.findFirst(macho.relocation_info, relocs, start, Predicate{ .addr = start_addr }); | ||
| 1298 | |||
| 1299 | return relocs[start..end]; | ||
| 1300 | } | ||
| 1301 | |||
| 1302 | inline fn isArithmeticOp(inst: *const [4]u8) bool { | ||
| 1303 | const group_decode = @truncate(u5, inst[3]); | ||
| 1304 | return ((group_decode >> 2) == 4); | ||
| 1305 | } | ||
src/link/MachO/Object.zig+3-1| ... | @@ -16,9 +16,11 @@ const segmentName = commands.segmentName; | ... | @@ -16,9 +16,11 @@ const segmentName = commands.segmentName; |
| 16 | const sectionName = commands.sectionName; | 16 | const sectionName = commands.sectionName; |
| 17 | 17 | ||
| 18 | const Allocator = mem.Allocator; | 18 | const Allocator = mem.Allocator; |
| 19 | const Atom = @import("Atom.zig"); | ||
| 19 | const LoadCommand = commands.LoadCommand; | 20 | const LoadCommand = commands.LoadCommand; |
| 20 | const MachO = @import("../MachO.zig"); | 21 | const MachO = @import("../MachO.zig"); |
| 21 | const TextBlock = @import("TextBlock.zig"); | 22 | |
| 23 | const TextBlock = Atom; | ||
| 22 | 24 | ||
| 23 | file: fs.File, | 25 | file: fs.File, |
| 24 | name: []const u8, | 26 | name: []const u8, |
src/link/MachO/TextBlock.zig deleted-1301| ... | @@ -1,1301 +0,0 @@ | ||
| 1 | const TextBlock = @This(); | ||
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const build_options = @import("build_options"); | ||
| 5 | const aarch64 = @import("../../codegen/aarch64.zig"); | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | const commands = @import("commands.zig"); | ||
| 8 | const log = std.log.scoped(.text_block); | ||
| 9 | const macho = std.macho; | ||
| 10 | const math = std.math; | ||
| 11 | const mem = std.mem; | ||
| 12 | const meta = std.meta; | ||
| 13 | |||
| 14 | const Allocator = mem.Allocator; | ||
| 15 | const Arch = std.Target.Cpu.Arch; | ||
| 16 | const MachO = @import("../MachO.zig"); | ||
| 17 | const Object = @import("Object.zig"); | ||
| 18 | |||
| 19 | /// Each decl always gets a local symbol with the fully qualified name. | ||
| 20 | /// The vaddr and size are found here directly. | ||
| 21 | /// The file offset is found by computing the vaddr offset from the section vaddr | ||
| 22 | /// the symbol references, and adding that to the file offset of the section. | ||
| 23 | /// If this field is 0, it means the codegen size = 0 and there is no symbol or | ||
| 24 | /// offset table entry. | ||
| 25 | local_sym_index: u32, | ||
| 26 | |||
| 27 | /// List of symbol aliases pointing to the same block via different nlists | ||
| 28 | aliases: std.ArrayListUnmanaged(u32) = .{}, | ||
| 29 | |||
| 30 | /// List of symbols contained within this block | ||
| 31 | contained: std.ArrayListUnmanaged(SymbolAtOffset) = .{}, | ||
| 32 | |||
| 33 | /// Code (may be non-relocated) this block represents | ||
| 34 | code: std.ArrayListUnmanaged(u8) = .{}, | ||
| 35 | |||
| 36 | /// Size and alignment of this text block | ||
| 37 | /// Unlike in Elf, we need to store the size of this symbol as part of | ||
| 38 | /// the TextBlock since macho.nlist_64 lacks this information. | ||
| 39 | size: u64, | ||
| 40 | alignment: u32, | ||
| 41 | |||
| 42 | relocs: std.ArrayListUnmanaged(Relocation) = .{}, | ||
| 43 | |||
| 44 | /// List of offsets contained within this block that need rebasing by the dynamic | ||
| 45 | /// loader in presence of ASLR | ||
| 46 | rebases: std.ArrayListUnmanaged(u64) = .{}, | ||
| 47 | |||
| 48 | /// List of offsets contained within this block that will be dynamically bound | ||
| 49 | /// by the dynamic loader and contain pointers to resolved (at load time) extern | ||
| 50 | /// symbols (aka proxies aka imports) | ||
| 51 | bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{}, | ||
| 52 | |||
| 53 | /// List of lazy bindings | ||
| 54 | lazy_bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{}, | ||
| 55 | |||
| 56 | /// List of data-in-code entries. This is currently specific to x86_64 only. | ||
| 57 | dices: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, | ||
| 58 | |||
| 59 | /// Stab entry for this block. This is currently specific to a binary created | ||
| 60 | /// by linking object files in a traditional sense - in incremental sense, we | ||
| 61 | /// bypass stabs altogether to produce dSYM bundle directly with fully relocated | ||
| 62 | /// DWARF sections. | ||
| 63 | stab: ?Stab = null, | ||
| 64 | |||
| 65 | /// Points to the previous and next neighbours | ||
| 66 | next: ?*TextBlock, | ||
| 67 | prev: ?*TextBlock, | ||
| 68 | |||
| 69 | /// Previous/next linked list pointers. | ||
| 70 | /// This is the linked list node for this Decl's corresponding .debug_info tag. | ||
| 71 | dbg_info_prev: ?*TextBlock, | ||
| 72 | dbg_info_next: ?*TextBlock, | ||
| 73 | /// Offset into .debug_info pointing to the tag for this Decl. | ||
| 74 | dbg_info_off: u32, | ||
| 75 | /// Size of the .debug_info tag for this Decl, not including padding. | ||
| 76 | dbg_info_len: u32, | ||
| 77 | |||
| 78 | dirty: bool = true, | ||
| 79 | |||
| 80 | pub const SymbolAtOffset = struct { | ||
| 81 | local_sym_index: u32, | ||
| 82 | offset: u64, | ||
| 83 | stab: ?Stab = null, | ||
| 84 | |||
| 85 | pub fn format( | ||
| 86 | self: SymbolAtOffset, | ||
| 87 | comptime fmt: []const u8, | ||
| 88 | options: std.fmt.FormatOptions, | ||
| 89 | writer: anytype, | ||
| 90 | ) !void { | ||
| 91 | _ = fmt; | ||
| 92 | _ = options; | ||
| 93 | try std.fmt.format(writer, "{{ {d}: .offset = {d}", .{ self.local_sym_index, self.offset }); | ||
| 94 | if (self.stab) |stab| { | ||
| 95 | try std.fmt.format(writer, ", .stab = {any}", .{stab}); | ||
| 96 | } | ||
| 97 | try std.fmt.format(writer, " }}", .{}); | ||
| 98 | } | ||
| 99 | }; | ||
| 100 | |||
| 101 | pub const Stab = union(enum) { | ||
| 102 | function: u64, | ||
| 103 | static, | ||
| 104 | global, | ||
| 105 | |||
| 106 | pub fn asNlists(stab: Stab, local_sym_index: u32, macho_file: anytype) ![]macho.nlist_64 { | ||
| 107 | var nlists = std.ArrayList(macho.nlist_64).init(macho_file.base.allocator); | ||
| 108 | defer nlists.deinit(); | ||
| 109 | |||
| 110 | const sym = macho_file.locals.items[local_sym_index]; | ||
| 111 | switch (stab) { | ||
| 112 | .function => |size| { | ||
| 113 | try nlists.ensureUnusedCapacity(4); | ||
| 114 | nlists.appendAssumeCapacity(.{ | ||
| 115 | .n_strx = 0, | ||
| 116 | .n_type = macho.N_BNSYM, | ||
| 117 | .n_sect = sym.n_sect, | ||
| 118 | .n_desc = 0, | ||
| 119 | .n_value = sym.n_value, | ||
| 120 | }); | ||
| 121 | nlists.appendAssumeCapacity(.{ | ||
| 122 | .n_strx = sym.n_strx, | ||
| 123 | .n_type = macho.N_FUN, | ||
| 124 | .n_sect = sym.n_sect, | ||
| 125 | .n_desc = 0, | ||
| 126 | .n_value = sym.n_value, | ||
| 127 | }); | ||
| 128 | nlists.appendAssumeCapacity(.{ | ||
| 129 | .n_strx = 0, | ||
| 130 | .n_type = macho.N_FUN, | ||
| 131 | .n_sect = 0, | ||
| 132 | .n_desc = 0, | ||
| 133 | .n_value = size, | ||
| 134 | }); | ||
| 135 | nlists.appendAssumeCapacity(.{ | ||
| 136 | .n_strx = 0, | ||
| 137 | .n_type = macho.N_ENSYM, | ||
| 138 | .n_sect = sym.n_sect, | ||
| 139 | .n_desc = 0, | ||
| 140 | .n_value = size, | ||
| 141 | }); | ||
| 142 | }, | ||
| 143 | .global => { | ||
| 144 | try nlists.append(.{ | ||
| 145 | .n_strx = sym.n_strx, | ||
| 146 | .n_type = macho.N_GSYM, | ||
| 147 | .n_sect = 0, | ||
| 148 | .n_desc = 0, | ||
| 149 | .n_value = 0, | ||
| 150 | }); | ||
| 151 | }, | ||
| 152 | .static => { | ||
| 153 | try nlists.append(.{ | ||
| 154 | .n_strx = sym.n_strx, | ||
| 155 | .n_type = macho.N_STSYM, | ||
| 156 | .n_sect = sym.n_sect, | ||
| 157 | .n_desc = 0, | ||
| 158 | .n_value = sym.n_value, | ||
| 159 | }); | ||
| 160 | }, | ||
| 161 | } | ||
| 162 | |||
| 163 | return nlists.toOwnedSlice(); | ||
| 164 | } | ||
| 165 | }; | ||
| 166 | |||
| 167 | pub const Relocation = struct { | ||
| 168 | /// Offset within the `block`s code buffer. | ||
| 169 | /// Note relocation size can be inferred by relocation's kind. | ||
| 170 | offset: u32, | ||
| 171 | |||
| 172 | where: enum { | ||
| 173 | local, | ||
| 174 | undef, | ||
| 175 | }, | ||
| 176 | |||
| 177 | where_index: u32, | ||
| 178 | |||
| 179 | payload: union(enum) { | ||
| 180 | unsigned: Unsigned, | ||
| 181 | branch: Branch, | ||
| 182 | page: Page, | ||
| 183 | page_off: PageOff, | ||
| 184 | pointer_to_got: PointerToGot, | ||
| 185 | signed: Signed, | ||
| 186 | load: Load, | ||
| 187 | }, | ||
| 188 | |||
| 189 | const ResolveArgs = struct { | ||
| 190 | block: *TextBlock, | ||
| 191 | offset: u32, | ||
| 192 | source_addr: u64, | ||
| 193 | target_addr: u64, | ||
| 194 | macho_file: *MachO, | ||
| 195 | }; | ||
| 196 | |||
| 197 | pub const Unsigned = struct { | ||
| 198 | subtractor: ?u32, | ||
| 199 | |||
| 200 | /// Addend embedded directly in the relocation slot | ||
| 201 | addend: i64, | ||
| 202 | |||
| 203 | /// Extracted from r_length: | ||
| 204 | /// => 3 implies true | ||
| 205 | /// => 2 implies false | ||
| 206 | /// => * is unreachable | ||
| 207 | is_64bit: bool, | ||
| 208 | |||
| 209 | pub fn resolve(self: Unsigned, args: ResolveArgs) !void { | ||
| 210 | const result = blk: { | ||
| 211 | if (self.subtractor) |subtractor| { | ||
| 212 | const sym = args.macho_file.locals.items[subtractor]; | ||
| 213 | break :blk @intCast(i64, args.target_addr) - @intCast(i64, sym.n_value) + self.addend; | ||
| 214 | } else { | ||
| 215 | break :blk @intCast(i64, args.target_addr) + self.addend; | ||
| 216 | } | ||
| 217 | }; | ||
| 218 | |||
| 219 | if (self.is_64bit) { | ||
| 220 | mem.writeIntLittle(u64, args.block.code.items[args.offset..][0..8], @bitCast(u64, result)); | ||
| 221 | } else { | ||
| 222 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @truncate(u32, @bitCast(u64, result))); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | pub fn format(self: Unsigned, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 227 | _ = fmt; | ||
| 228 | _ = options; | ||
| 229 | try std.fmt.format(writer, "Unsigned {{ ", .{}); | ||
| 230 | if (self.subtractor) |sub| { | ||
| 231 | try std.fmt.format(writer, ".subtractor = {}, ", .{sub}); | ||
| 232 | } | ||
| 233 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 234 | const length: usize = if (self.is_64bit) 8 else 4; | ||
| 235 | try std.fmt.format(writer, ".length = {}, ", .{length}); | ||
| 236 | try std.fmt.format(writer, "}}", .{}); | ||
| 237 | } | ||
| 238 | }; | ||
| 239 | |||
| 240 | pub const Branch = struct { | ||
| 241 | arch: Arch, | ||
| 242 | |||
| 243 | pub fn resolve(self: Branch, args: ResolveArgs) !void { | ||
| 244 | switch (self.arch) { | ||
| 245 | .aarch64 => { | ||
| 246 | const displacement = math.cast( | ||
| 247 | i28, | ||
| 248 | @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr), | ||
| 249 | ) catch |err| switch (err) { | ||
| 250 | error.Overflow => { | ||
| 251 | log.err("jump too big to encode as i28 displacement value", .{}); | ||
| 252 | log.err(" (target - source) = displacement => 0x{x} - 0x{x} = 0x{x}", .{ | ||
| 253 | args.target_addr, | ||
| 254 | args.source_addr, | ||
| 255 | @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr), | ||
| 256 | }); | ||
| 257 | log.err(" TODO implement branch islands to extend jump distance for arm64", .{}); | ||
| 258 | return error.TODOImplementBranchIslands; | ||
| 259 | }, | ||
| 260 | }; | ||
| 261 | const code = args.block.code.items[args.offset..][0..4]; | ||
| 262 | var inst = aarch64.Instruction{ | ||
| 263 | .unconditional_branch_immediate = mem.bytesToValue(meta.TagPayload( | ||
| 264 | aarch64.Instruction, | ||
| 265 | aarch64.Instruction.unconditional_branch_immediate, | ||
| 266 | ), code), | ||
| 267 | }; | ||
| 268 | inst.unconditional_branch_immediate.imm26 = @truncate(u26, @bitCast(u28, displacement >> 2)); | ||
| 269 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 270 | }, | ||
| 271 | .x86_64 => { | ||
| 272 | const displacement = try math.cast( | ||
| 273 | i32, | ||
| 274 | @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4, | ||
| 275 | ); | ||
| 276 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @bitCast(u32, displacement)); | ||
| 277 | }, | ||
| 278 | else => return error.UnsupportedCpuArchitecture, | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | pub fn format(self: Branch, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 283 | _ = self; | ||
| 284 | _ = fmt; | ||
| 285 | _ = options; | ||
| 286 | try std.fmt.format(writer, "Branch {{}}", .{}); | ||
| 287 | } | ||
| 288 | }; | ||
| 289 | |||
| 290 | pub const Page = struct { | ||
| 291 | kind: enum { | ||
| 292 | page, | ||
| 293 | got, | ||
| 294 | tlvp, | ||
| 295 | }, | ||
| 296 | addend: u32 = 0, | ||
| 297 | |||
| 298 | pub fn resolve(self: Page, args: ResolveArgs) !void { | ||
| 299 | const target_addr = args.target_addr + self.addend; | ||
| 300 | const source_page = @intCast(i32, args.source_addr >> 12); | ||
| 301 | const target_page = @intCast(i32, target_addr >> 12); | ||
| 302 | const pages = @bitCast(u21, @intCast(i21, target_page - source_page)); | ||
| 303 | |||
| 304 | const code = args.block.code.items[args.offset..][0..4]; | ||
| 305 | var inst = aarch64.Instruction{ | ||
| 306 | .pc_relative_address = mem.bytesToValue(meta.TagPayload( | ||
| 307 | aarch64.Instruction, | ||
| 308 | aarch64.Instruction.pc_relative_address, | ||
| 309 | ), code), | ||
| 310 | }; | ||
| 311 | inst.pc_relative_address.immhi = @truncate(u19, pages >> 2); | ||
| 312 | inst.pc_relative_address.immlo = @truncate(u2, pages); | ||
| 313 | |||
| 314 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 315 | } | ||
| 316 | |||
| 317 | pub fn format(self: Page, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 318 | _ = fmt; | ||
| 319 | _ = options; | ||
| 320 | try std.fmt.format(writer, "Page {{ ", .{}); | ||
| 321 | switch (self.kind) { | ||
| 322 | .page => {}, | ||
| 323 | .got => { | ||
| 324 | try std.fmt.format(writer, ".got, ", .{}); | ||
| 325 | }, | ||
| 326 | .tlvp => { | ||
| 327 | try std.fmt.format(writer, ".tlvp", .{}); | ||
| 328 | }, | ||
| 329 | } | ||
| 330 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 331 | try std.fmt.format(writer, "}}", .{}); | ||
| 332 | } | ||
| 333 | }; | ||
| 334 | |||
| 335 | pub const PageOff = struct { | ||
| 336 | kind: enum { | ||
| 337 | page, | ||
| 338 | got, | ||
| 339 | tlvp, | ||
| 340 | }, | ||
| 341 | addend: u32 = 0, | ||
| 342 | op_kind: ?OpKind = null, | ||
| 343 | |||
| 344 | pub const OpKind = enum { | ||
| 345 | arithmetic, | ||
| 346 | load, | ||
| 347 | }; | ||
| 348 | |||
| 349 | pub fn resolve(self: PageOff, args: ResolveArgs) !void { | ||
| 350 | const code = args.block.code.items[args.offset..][0..4]; | ||
| 351 | |||
| 352 | switch (self.kind) { | ||
| 353 | .page => { | ||
| 354 | const target_addr = args.target_addr + self.addend; | ||
| 355 | const narrowed = @truncate(u12, target_addr); | ||
| 356 | |||
| 357 | const op_kind = self.op_kind orelse unreachable; | ||
| 358 | var inst: aarch64.Instruction = blk: { | ||
| 359 | switch (op_kind) { | ||
| 360 | .arithmetic => { | ||
| 361 | break :blk .{ | ||
| 362 | .add_subtract_immediate = mem.bytesToValue(meta.TagPayload( | ||
| 363 | aarch64.Instruction, | ||
| 364 | aarch64.Instruction.add_subtract_immediate, | ||
| 365 | ), code), | ||
| 366 | }; | ||
| 367 | }, | ||
| 368 | .load => { | ||
| 369 | break :blk .{ | ||
| 370 | .load_store_register = mem.bytesToValue(meta.TagPayload( | ||
| 371 | aarch64.Instruction, | ||
| 372 | aarch64.Instruction.load_store_register, | ||
| 373 | ), code), | ||
| 374 | }; | ||
| 375 | }, | ||
| 376 | } | ||
| 377 | }; | ||
| 378 | |||
| 379 | if (op_kind == .arithmetic) { | ||
| 380 | inst.add_subtract_immediate.imm12 = narrowed; | ||
| 381 | } else { | ||
| 382 | const offset: u12 = blk: { | ||
| 383 | if (inst.load_store_register.size == 0) { | ||
| 384 | if (inst.load_store_register.v == 1) { | ||
| 385 | // 128-bit SIMD is scaled by 16. | ||
| 386 | break :blk try math.divExact(u12, narrowed, 16); | ||
| 387 | } | ||
| 388 | // Otherwise, 8-bit SIMD or ldrb. | ||
| 389 | break :blk narrowed; | ||
| 390 | } else { | ||
| 391 | const denom: u4 = try math.powi(u4, 2, inst.load_store_register.size); | ||
| 392 | break :blk try math.divExact(u12, narrowed, denom); | ||
| 393 | } | ||
| 394 | }; | ||
| 395 | inst.load_store_register.offset = offset; | ||
| 396 | } | ||
| 397 | |||
| 398 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 399 | }, | ||
| 400 | .got => { | ||
| 401 | const narrowed = @truncate(u12, args.target_addr); | ||
| 402 | var inst: aarch64.Instruction = .{ | ||
| 403 | .load_store_register = mem.bytesToValue(meta.TagPayload( | ||
| 404 | aarch64.Instruction, | ||
| 405 | aarch64.Instruction.load_store_register, | ||
| 406 | ), code), | ||
| 407 | }; | ||
| 408 | const offset = try math.divExact(u12, narrowed, 8); | ||
| 409 | inst.load_store_register.offset = offset; | ||
| 410 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 411 | }, | ||
| 412 | .tlvp => { | ||
| 413 | const RegInfo = struct { | ||
| 414 | rd: u5, | ||
| 415 | rn: u5, | ||
| 416 | size: u1, | ||
| 417 | }; | ||
| 418 | const reg_info: RegInfo = blk: { | ||
| 419 | if (isArithmeticOp(code)) { | ||
| 420 | const inst = mem.bytesToValue(meta.TagPayload( | ||
| 421 | aarch64.Instruction, | ||
| 422 | aarch64.Instruction.add_subtract_immediate, | ||
| 423 | ), code); | ||
| 424 | break :blk .{ | ||
| 425 | .rd = inst.rd, | ||
| 426 | .rn = inst.rn, | ||
| 427 | .size = inst.sf, | ||
| 428 | }; | ||
| 429 | } else { | ||
| 430 | const inst = mem.bytesToValue(meta.TagPayload( | ||
| 431 | aarch64.Instruction, | ||
| 432 | aarch64.Instruction.load_store_register, | ||
| 433 | ), code); | ||
| 434 | break :blk .{ | ||
| 435 | .rd = inst.rt, | ||
| 436 | .rn = inst.rn, | ||
| 437 | .size = @truncate(u1, inst.size), | ||
| 438 | }; | ||
| 439 | } | ||
| 440 | }; | ||
| 441 | const narrowed = @truncate(u12, args.target_addr); | ||
| 442 | var inst = aarch64.Instruction{ | ||
| 443 | .add_subtract_immediate = .{ | ||
| 444 | .rd = reg_info.rd, | ||
| 445 | .rn = reg_info.rn, | ||
| 446 | .imm12 = narrowed, | ||
| 447 | .sh = 0, | ||
| 448 | .s = 0, | ||
| 449 | .op = 0, | ||
| 450 | .sf = reg_info.size, | ||
| 451 | }, | ||
| 452 | }; | ||
| 453 | mem.writeIntLittle(u32, code, inst.toU32()); | ||
| 454 | }, | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | pub fn format(self: PageOff, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 459 | _ = fmt; | ||
| 460 | _ = options; | ||
| 461 | try std.fmt.format(writer, "PageOff {{ ", .{}); | ||
| 462 | switch (self.kind) { | ||
| 463 | .page => {}, | ||
| 464 | .got => { | ||
| 465 | try std.fmt.format(writer, ".got, ", .{}); | ||
| 466 | }, | ||
| 467 | .tlvp => { | ||
| 468 | try std.fmt.format(writer, ".tlvp, ", .{}); | ||
| 469 | }, | ||
| 470 | } | ||
| 471 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 472 | try std.fmt.format(writer, ".op_kind = {s}, ", .{self.op_kind}); | ||
| 473 | try std.fmt.format(writer, "}}", .{}); | ||
| 474 | } | ||
| 475 | }; | ||
| 476 | |||
| 477 | pub const PointerToGot = struct { | ||
| 478 | pub fn resolve(_: PointerToGot, args: ResolveArgs) !void { | ||
| 479 | const result = try math.cast(i32, @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr)); | ||
| 480 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @bitCast(u32, result)); | ||
| 481 | } | ||
| 482 | |||
| 483 | pub fn format(self: PointerToGot, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 484 | _ = self; | ||
| 485 | _ = fmt; | ||
| 486 | _ = options; | ||
| 487 | try std.fmt.format(writer, "PointerToGot {{}}", .{}); | ||
| 488 | } | ||
| 489 | }; | ||
| 490 | |||
| 491 | pub const Signed = struct { | ||
| 492 | addend: i64, | ||
| 493 | correction: u3, | ||
| 494 | |||
| 495 | pub fn resolve(self: Signed, args: ResolveArgs) !void { | ||
| 496 | const target_addr = @intCast(i64, args.target_addr) + self.addend; | ||
| 497 | const displacement = try math.cast( | ||
| 498 | i32, | ||
| 499 | target_addr - @intCast(i64, args.source_addr + self.correction + 4), | ||
| 500 | ); | ||
| 501 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @bitCast(u32, displacement)); | ||
| 502 | } | ||
| 503 | |||
| 504 | pub fn format(self: Signed, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 505 | _ = fmt; | ||
| 506 | _ = options; | ||
| 507 | try std.fmt.format(writer, "Signed {{ ", .{}); | ||
| 508 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 509 | try std.fmt.format(writer, ".correction = {}, ", .{self.correction}); | ||
| 510 | try std.fmt.format(writer, "}}", .{}); | ||
| 511 | } | ||
| 512 | }; | ||
| 513 | |||
| 514 | pub const Load = struct { | ||
| 515 | kind: enum { | ||
| 516 | got, | ||
| 517 | tlvp, | ||
| 518 | }, | ||
| 519 | addend: i32 = 0, | ||
| 520 | |||
| 521 | pub fn resolve(self: Load, args: ResolveArgs) !void { | ||
| 522 | if (self.kind == .tlvp) { | ||
| 523 | // We need to rewrite the opcode from movq to leaq. | ||
| 524 | args.block.code.items[args.offset - 2] = 0x8d; | ||
| 525 | } | ||
| 526 | const displacement = try math.cast( | ||
| 527 | i32, | ||
| 528 | @intCast(i64, args.target_addr) - @intCast(i64, args.source_addr) - 4 + self.addend, | ||
| 529 | ); | ||
| 530 | mem.writeIntLittle(u32, args.block.code.items[args.offset..][0..4], @bitCast(u32, displacement)); | ||
| 531 | } | ||
| 532 | |||
| 533 | pub fn format(self: Load, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 534 | _ = fmt; | ||
| 535 | _ = options; | ||
| 536 | try std.fmt.format(writer, "Load {{ ", .{}); | ||
| 537 | try std.fmt.format(writer, "{s}, ", .{self.kind}); | ||
| 538 | try std.fmt.format(writer, ".addend = {}, ", .{self.addend}); | ||
| 539 | try std.fmt.format(writer, "}}", .{}); | ||
| 540 | } | ||
| 541 | }; | ||
| 542 | |||
| 543 | pub fn resolve(self: Relocation, args: ResolveArgs) !void { | ||
| 544 | switch (self.payload) { | ||
| 545 | .unsigned => |unsigned| try unsigned.resolve(args), | ||
| 546 | .branch => |branch| try branch.resolve(args), | ||
| 547 | .page => |page| try page.resolve(args), | ||
| 548 | .page_off => |page_off| try page_off.resolve(args), | ||
| 549 | .pointer_to_got => |pointer_to_got| try pointer_to_got.resolve(args), | ||
| 550 | .signed => |signed| try signed.resolve(args), | ||
| 551 | .load => |load| try load.resolve(args), | ||
| 552 | } | ||
| 553 | } | ||
| 554 | |||
| 555 | pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 556 | try std.fmt.format(writer, "Relocation {{ ", .{}); | ||
| 557 | try std.fmt.format(writer, ".offset = {}, ", .{self.offset}); | ||
| 558 | try std.fmt.format(writer, ".where = {}, ", .{self.where}); | ||
| 559 | try std.fmt.format(writer, ".where_index = {d}, ", .{self.where_index}); | ||
| 560 | |||
| 561 | switch (self.payload) { | ||
| 562 | .unsigned => |unsigned| try unsigned.format(fmt, options, writer), | ||
| 563 | .branch => |branch| try branch.format(fmt, options, writer), | ||
| 564 | .page => |page| try page.format(fmt, options, writer), | ||
| 565 | .page_off => |page_off| try page_off.format(fmt, options, writer), | ||
| 566 | .pointer_to_got => |pointer_to_got| try pointer_to_got.format(fmt, options, writer), | ||
| 567 | .signed => |signed| try signed.format(fmt, options, writer), | ||
| 568 | .load => |load| try load.format(fmt, options, writer), | ||
| 569 | } | ||
| 570 | |||
| 571 | try std.fmt.format(writer, "}}", .{}); | ||
| 572 | } | ||
| 573 | }; | ||
| 574 | |||
| 575 | pub const empty = TextBlock{ | ||
| 576 | .local_sym_index = 0, | ||
| 577 | .size = 0, | ||
| 578 | .alignment = 0, | ||
| 579 | .prev = null, | ||
| 580 | .next = null, | ||
| 581 | .dbg_info_prev = null, | ||
| 582 | .dbg_info_next = null, | ||
| 583 | .dbg_info_off = undefined, | ||
| 584 | .dbg_info_len = undefined, | ||
| 585 | }; | ||
| 586 | |||
| 587 | pub fn deinit(self: *TextBlock, allocator: *Allocator) void { | ||
| 588 | self.dices.deinit(allocator); | ||
| 589 | self.lazy_bindings.deinit(allocator); | ||
| 590 | self.bindings.deinit(allocator); | ||
| 591 | self.rebases.deinit(allocator); | ||
| 592 | self.relocs.deinit(allocator); | ||
| 593 | self.contained.deinit(allocator); | ||
| 594 | self.aliases.deinit(allocator); | ||
| 595 | self.code.deinit(allocator); | ||
| 596 | } | ||
| 597 | |||
| 598 | /// Returns how much room there is to grow in virtual address space. | ||
| 599 | /// File offset relocation happens transparently, so it is not included in | ||
| 600 | /// this calculation. | ||
| 601 | pub fn capacity(self: TextBlock, macho_file: MachO) u64 { | ||
| 602 | const self_sym = macho_file.locals.items[self.local_sym_index]; | ||
| 603 | if (self.next) |next| { | ||
| 604 | const next_sym = macho_file.locals.items[next.local_sym_index]; | ||
| 605 | return next_sym.n_value - self_sym.n_value; | ||
| 606 | } else { | ||
| 607 | // We are the last block. | ||
| 608 | // The capacity is limited only by virtual address space. | ||
| 609 | return std.math.maxInt(u64) - self_sym.n_value; | ||
| 610 | } | ||
| 611 | } | ||
| 612 | |||
| 613 | pub fn freeListEligible(self: TextBlock, macho_file: MachO) bool { | ||
| 614 | // No need to keep a free list node for the last block. | ||
| 615 | const next = self.next orelse return false; | ||
| 616 | const self_sym = macho_file.locals.items[self.local_sym_index]; | ||
| 617 | const next_sym = macho_file.locals.items[next.local_sym_index]; | ||
| 618 | const cap = next_sym.n_value - self_sym.n_value; | ||
| 619 | const ideal_cap = MachO.padToIdeal(self.size); | ||
| 620 | if (cap <= ideal_cap) return false; | ||
| 621 | const surplus = cap - ideal_cap; | ||
| 622 | return surplus >= MachO.min_text_capacity; | ||
| 623 | } | ||
| 624 | |||
| 625 | const RelocContext = struct { | ||
| 626 | base_addr: u64 = 0, | ||
| 627 | base_offset: u64 = 0, | ||
| 628 | allocator: *Allocator, | ||
| 629 | object: *Object, | ||
| 630 | macho_file: *MachO, | ||
| 631 | parsed_atoms: *Object.ParsedAtoms, | ||
| 632 | }; | ||
| 633 | |||
| 634 | fn initRelocFromObject(rel: macho.relocation_info, context: RelocContext) !Relocation { | ||
| 635 | var parsed_rel = Relocation{ | ||
| 636 | .offset = @intCast(u32, @intCast(u64, rel.r_address) - context.base_offset), | ||
| 637 | .where = undefined, | ||
| 638 | .where_index = undefined, | ||
| 639 | .payload = undefined, | ||
| 640 | }; | ||
| 641 | |||
| 642 | if (rel.r_extern == 0) { | ||
| 643 | const sect_id = @intCast(u16, rel.r_symbolnum - 1); | ||
| 644 | |||
| 645 | const local_sym_index = context.object.sections_as_symbols.get(sect_id) orelse blk: { | ||
| 646 | const seg = context.object.load_commands.items[context.object.segment_cmd_index.?].Segment; | ||
| 647 | const sect = seg.sections.items[sect_id]; | ||
| 648 | const match = (try context.macho_file.getMatchingSection(sect)) orelse unreachable; | ||
| 649 | const local_sym_index = @intCast(u32, context.macho_file.locals.items.len); | ||
| 650 | const sym_name = try std.fmt.allocPrint(context.allocator, "l_{s}_{s}_{s}", .{ | ||
| 651 | context.object.name, | ||
| 652 | commands.segmentName(sect), | ||
| 653 | commands.sectionName(sect), | ||
| 654 | }); | ||
| 655 | defer context.allocator.free(sym_name); | ||
| 656 | |||
| 657 | try context.macho_file.locals.append(context.allocator, .{ | ||
| 658 | .n_strx = try context.macho_file.makeString(sym_name), | ||
| 659 | .n_type = macho.N_SECT, | ||
| 660 | .n_sect = @intCast(u8, context.macho_file.section_ordinals.getIndex(match).? + 1), | ||
| 661 | .n_desc = 0, | ||
| 662 | .n_value = 0, | ||
| 663 | }); | ||
| 664 | try context.object.sections_as_symbols.putNoClobber(context.allocator, sect_id, local_sym_index); | ||
| 665 | break :blk local_sym_index; | ||
| 666 | }; | ||
| 667 | |||
| 668 | parsed_rel.where = .local; | ||
| 669 | parsed_rel.where_index = local_sym_index; | ||
| 670 | } else { | ||
| 671 | const sym = context.object.symtab.items[rel.r_symbolnum]; | ||
| 672 | const sym_name = context.object.getString(sym.n_strx); | ||
| 673 | |||
| 674 | if (MachO.symbolIsSect(sym) and !MachO.symbolIsExt(sym)) { | ||
| 675 | const where_index = context.object.symbol_mapping.get(rel.r_symbolnum) orelse unreachable; | ||
| 676 | parsed_rel.where = .local; | ||
| 677 | parsed_rel.where_index = where_index; | ||
| 678 | } else { | ||
| 679 | const n_strx = context.macho_file.strtab_dir.getAdapted(@as([]const u8, sym_name), MachO.StringSliceAdapter{ | ||
| 680 | .strtab = &context.macho_file.strtab, | ||
| 681 | }) orelse unreachable; | ||
| 682 | const resolv = context.macho_file.symbol_resolver.get(n_strx) orelse unreachable; | ||
| 683 | switch (resolv.where) { | ||
| 684 | .global => { | ||
| 685 | parsed_rel.where = .local; | ||
| 686 | parsed_rel.where_index = resolv.local_sym_index; | ||
| 687 | }, | ||
| 688 | .undef => { | ||
| 689 | parsed_rel.where = .undef; | ||
| 690 | parsed_rel.where_index = resolv.where_index; | ||
| 691 | }, | ||
| 692 | } | ||
| 693 | } | ||
| 694 | } | ||
| 695 | |||
| 696 | return parsed_rel; | ||
| 697 | } | ||
| 698 | |||
| 699 | pub fn parseRelocs(self: *TextBlock, relocs: []macho.relocation_info, context: RelocContext) !void { | ||
| 700 | const filtered_relocs = filterRelocs(relocs, context.base_offset, context.base_offset + self.size); | ||
| 701 | var it = RelocIterator{ | ||
| 702 | .buffer = filtered_relocs, | ||
| 703 | }; | ||
| 704 | |||
| 705 | var addend: u32 = 0; | ||
| 706 | var subtractor: ?u32 = null; | ||
| 707 | const arch = context.macho_file.base.options.target.cpu.arch; | ||
| 708 | |||
| 709 | while (it.next()) |rel| { | ||
| 710 | if (isAddend(rel, arch)) { | ||
| 711 | // Addend is not a relocation with effect on the TextBlock, so | ||
| 712 | // parse it and carry on. | ||
| 713 | assert(addend == 0); // Oh no, addend was not reset! | ||
| 714 | addend = rel.r_symbolnum; | ||
| 715 | |||
| 716 | // Verify ADDEND is followed by a PAGE21 or PAGEOFF12. | ||
| 717 | const next = @intToEnum(macho.reloc_type_arm64, it.peek().r_type); | ||
| 718 | switch (next) { | ||
| 719 | .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {}, | ||
| 720 | else => { | ||
| 721 | log.err("unexpected relocation type: expected PAGE21 or PAGEOFF12, found {s}", .{next}); | ||
| 722 | return error.UnexpectedRelocationType; | ||
| 723 | }, | ||
| 724 | } | ||
| 725 | continue; | ||
| 726 | } | ||
| 727 | |||
| 728 | if (isSubtractor(rel, arch)) { | ||
| 729 | // Subtractor is not a relocation with effect on the TextBlock, so | ||
| 730 | // parse it and carry on. | ||
| 731 | assert(subtractor == null); // Oh no, subtractor was not reset! | ||
| 732 | assert(rel.r_extern == 1); | ||
| 733 | const sym = context.object.symtab.items[rel.r_symbolnum]; | ||
| 734 | const sym_name = context.object.getString(sym.n_strx); | ||
| 735 | |||
| 736 | if (MachO.symbolIsSect(sym) and !MachO.symbolIsExt(sym)) { | ||
| 737 | const where_index = context.object.symbol_mapping.get(rel.r_symbolnum) orelse unreachable; | ||
| 738 | subtractor = where_index; | ||
| 739 | } else { | ||
| 740 | const n_strx = context.macho_file.strtab_dir.getAdapted(@as([]const u8, sym_name), MachO.StringSliceAdapter{ | ||
| 741 | .strtab = &context.macho_file.strtab, | ||
| 742 | }) orelse unreachable; | ||
| 743 | const resolv = context.macho_file.symbol_resolver.get(n_strx) orelse unreachable; | ||
| 744 | assert(resolv.where == .global); | ||
| 745 | subtractor = resolv.local_sym_index; | ||
| 746 | } | ||
| 747 | |||
| 748 | // Verify SUBTRACTOR is followed by UNSIGNED. | ||
| 749 | switch (arch) { | ||
| 750 | .aarch64 => { | ||
| 751 | const next = @intToEnum(macho.reloc_type_arm64, it.peek().r_type); | ||
| 752 | if (next != .ARM64_RELOC_UNSIGNED) { | ||
| 753 | log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next}); | ||
| 754 | return error.UnexpectedRelocationType; | ||
| 755 | } | ||
| 756 | }, | ||
| 757 | .x86_64 => { | ||
| 758 | const next = @intToEnum(macho.reloc_type_x86_64, it.peek().r_type); | ||
| 759 | if (next != .X86_64_RELOC_UNSIGNED) { | ||
| 760 | log.err("unexpected relocation type: expected UNSIGNED, found {s}", .{next}); | ||
| 761 | return error.UnexpectedRelocationType; | ||
| 762 | } | ||
| 763 | }, | ||
| 764 | else => unreachable, | ||
| 765 | } | ||
| 766 | continue; | ||
| 767 | } | ||
| 768 | |||
| 769 | var parsed_rel = try initRelocFromObject(rel, context); | ||
| 770 | |||
| 771 | switch (arch) { | ||
| 772 | .aarch64 => { | ||
| 773 | const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); | ||
| 774 | switch (rel_type) { | ||
| 775 | .ARM64_RELOC_ADDEND => unreachable, | ||
| 776 | .ARM64_RELOC_SUBTRACTOR => unreachable, | ||
| 777 | .ARM64_RELOC_BRANCH26 => { | ||
| 778 | self.parseBranch(rel, &parsed_rel, context); | ||
| 779 | }, | ||
| 780 | .ARM64_RELOC_UNSIGNED => { | ||
| 781 | self.parseUnsigned(rel, &parsed_rel, subtractor, context); | ||
| 782 | subtractor = null; | ||
| 783 | }, | ||
| 784 | .ARM64_RELOC_PAGE21, | ||
| 785 | .ARM64_RELOC_GOT_LOAD_PAGE21, | ||
| 786 | .ARM64_RELOC_TLVP_LOAD_PAGE21, | ||
| 787 | => { | ||
| 788 | self.parsePage(rel, &parsed_rel, addend); | ||
| 789 | if (rel_type == .ARM64_RELOC_PAGE21) | ||
| 790 | addend = 0; | ||
| 791 | }, | ||
| 792 | .ARM64_RELOC_PAGEOFF12, | ||
| 793 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12, | ||
| 794 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12, | ||
| 795 | => { | ||
| 796 | self.parsePageOff(rel, &parsed_rel, addend); | ||
| 797 | if (rel_type == .ARM64_RELOC_PAGEOFF12) | ||
| 798 | addend = 0; | ||
| 799 | }, | ||
| 800 | .ARM64_RELOC_POINTER_TO_GOT => { | ||
| 801 | self.parsePointerToGot(rel, &parsed_rel); | ||
| 802 | }, | ||
| 803 | } | ||
| 804 | }, | ||
| 805 | .x86_64 => { | ||
| 806 | switch (@intToEnum(macho.reloc_type_x86_64, rel.r_type)) { | ||
| 807 | .X86_64_RELOC_SUBTRACTOR => unreachable, | ||
| 808 | .X86_64_RELOC_BRANCH => { | ||
| 809 | self.parseBranch(rel, &parsed_rel, context); | ||
| 810 | }, | ||
| 811 | .X86_64_RELOC_UNSIGNED => { | ||
| 812 | self.parseUnsigned(rel, &parsed_rel, subtractor, context); | ||
| 813 | subtractor = null; | ||
| 814 | }, | ||
| 815 | .X86_64_RELOC_SIGNED, | ||
| 816 | .X86_64_RELOC_SIGNED_1, | ||
| 817 | .X86_64_RELOC_SIGNED_2, | ||
| 818 | .X86_64_RELOC_SIGNED_4, | ||
| 819 | => { | ||
| 820 | self.parseSigned(rel, &parsed_rel, context); | ||
| 821 | }, | ||
| 822 | .X86_64_RELOC_GOT_LOAD, | ||
| 823 | .X86_64_RELOC_GOT, | ||
| 824 | .X86_64_RELOC_TLV, | ||
| 825 | => { | ||
| 826 | self.parseLoad(rel, &parsed_rel); | ||
| 827 | }, | ||
| 828 | } | ||
| 829 | }, | ||
| 830 | else => unreachable, | ||
| 831 | } | ||
| 832 | |||
| 833 | try self.relocs.append(context.allocator, parsed_rel); | ||
| 834 | |||
| 835 | const is_via_got = switch (parsed_rel.payload) { | ||
| 836 | .pointer_to_got => true, | ||
| 837 | .load => |load| load.kind == .got, | ||
| 838 | .page => |page| page.kind == .got, | ||
| 839 | .page_off => |page_off| page_off.kind == .got, | ||
| 840 | else => false, | ||
| 841 | }; | ||
| 842 | |||
| 843 | if (is_via_got) blk: { | ||
| 844 | const key = MachO.GotIndirectionKey{ | ||
| 845 | .where = switch (parsed_rel.where) { | ||
| 846 | .local => .local, | ||
| 847 | .undef => .undef, | ||
| 848 | }, | ||
| 849 | .where_index = parsed_rel.where_index, | ||
| 850 | }; | ||
| 851 | if (context.macho_file.got_entries_map.contains(key)) break :blk; | ||
| 852 | |||
| 853 | const atom = try context.macho_file.createGotAtom(key); | ||
| 854 | try context.macho_file.got_entries_map.putNoClobber(context.macho_file.base.allocator, key, atom); | ||
| 855 | const match = MachO.MatchingSection{ | ||
| 856 | .seg = context.macho_file.data_const_segment_cmd_index.?, | ||
| 857 | .sect = context.macho_file.got_section_index.?, | ||
| 858 | }; | ||
| 859 | |||
| 860 | if (context.parsed_atoms.getPtr(match)) |last| { | ||
| 861 | last.*.next = atom; | ||
| 862 | atom.prev = last.*; | ||
| 863 | last.* = atom; | ||
| 864 | } else { | ||
| 865 | try context.parsed_atoms.putNoClobber(match, atom); | ||
| 866 | } | ||
| 867 | } else if (parsed_rel.payload == .unsigned) { | ||
| 868 | switch (parsed_rel.where) { | ||
| 869 | .undef => { | ||
| 870 | try self.bindings.append(context.allocator, .{ | ||
| 871 | .local_sym_index = parsed_rel.where_index, | ||
| 872 | .offset = parsed_rel.offset, | ||
| 873 | }); | ||
| 874 | }, | ||
| 875 | .local => { | ||
| 876 | const source_sym = context.macho_file.locals.items[self.local_sym_index]; | ||
| 877 | const match = context.macho_file.section_ordinals.keys()[source_sym.n_sect - 1]; | ||
| 878 | const seg = context.macho_file.load_commands.items[match.seg].Segment; | ||
| 879 | const sect = seg.sections.items[match.sect]; | ||
| 880 | const sect_type = commands.sectionType(sect); | ||
| 881 | |||
| 882 | const should_rebase = rebase: { | ||
| 883 | if (!parsed_rel.payload.unsigned.is_64bit) break :rebase false; | ||
| 884 | |||
| 885 | // TODO actually, a check similar to what dyld is doing, that is, verifying | ||
| 886 | // that the segment is writable should be enough here. | ||
| 887 | const is_right_segment = blk: { | ||
| 888 | if (context.macho_file.data_segment_cmd_index) |idx| { | ||
| 889 | if (match.seg == idx) { | ||
| 890 | break :blk true; | ||
| 891 | } | ||
| 892 | } | ||
| 893 | if (context.macho_file.data_const_segment_cmd_index) |idx| { | ||
| 894 | if (match.seg == idx) { | ||
| 895 | break :blk true; | ||
| 896 | } | ||
| 897 | } | ||
| 898 | break :blk false; | ||
| 899 | }; | ||
| 900 | |||
| 901 | if (!is_right_segment) break :rebase false; | ||
| 902 | if (sect_type != macho.S_LITERAL_POINTERS and | ||
| 903 | sect_type != macho.S_REGULAR and | ||
| 904 | sect_type != macho.S_MOD_INIT_FUNC_POINTERS and | ||
| 905 | sect_type != macho.S_MOD_TERM_FUNC_POINTERS) | ||
| 906 | { | ||
| 907 | break :rebase false; | ||
| 908 | } | ||
| 909 | |||
| 910 | break :rebase true; | ||
| 911 | }; | ||
| 912 | |||
| 913 | if (should_rebase) { | ||
| 914 | try self.rebases.append(context.allocator, parsed_rel.offset); | ||
| 915 | } | ||
| 916 | }, | ||
| 917 | } | ||
| 918 | } else if (parsed_rel.payload == .branch) blk: { | ||
| 919 | if (parsed_rel.where != .undef) break :blk; | ||
| 920 | if (context.macho_file.stubs_map.contains(parsed_rel.where_index)) break :blk; | ||
| 921 | |||
| 922 | const stub_helper_atom = try context.macho_file.createStubHelperAtom(); | ||
| 923 | const laptr_atom = try context.macho_file.createLazyPointerAtom( | ||
| 924 | stub_helper_atom.local_sym_index, | ||
| 925 | parsed_rel.where_index, | ||
| 926 | ); | ||
| 927 | const stub_atom = try context.macho_file.createStubAtom(laptr_atom.local_sym_index); | ||
| 928 | try context.macho_file.stubs_map.putNoClobber(context.allocator, parsed_rel.where_index, stub_atom); | ||
| 929 | // TODO clean this up! | ||
| 930 | if (context.parsed_atoms.getPtr(.{ | ||
| 931 | .seg = context.macho_file.text_segment_cmd_index.?, | ||
| 932 | .sect = context.macho_file.stub_helper_section_index.?, | ||
| 933 | })) |last| { | ||
| 934 | last.*.next = stub_helper_atom; | ||
| 935 | stub_helper_atom.prev = last.*; | ||
| 936 | last.* = stub_helper_atom; | ||
| 937 | } else { | ||
| 938 | try context.parsed_atoms.putNoClobber(.{ | ||
| 939 | .seg = context.macho_file.text_segment_cmd_index.?, | ||
| 940 | .sect = context.macho_file.stub_helper_section_index.?, | ||
| 941 | }, stub_helper_atom); | ||
| 942 | } | ||
| 943 | if (context.parsed_atoms.getPtr(.{ | ||
| 944 | .seg = context.macho_file.text_segment_cmd_index.?, | ||
| 945 | .sect = context.macho_file.stubs_section_index.?, | ||
| 946 | })) |last| { | ||
| 947 | last.*.next = stub_atom; | ||
| 948 | stub_atom.prev = last.*; | ||
| 949 | last.* = stub_atom; | ||
| 950 | } else { | ||
| 951 | try context.parsed_atoms.putNoClobber(.{ | ||
| 952 | .seg = context.macho_file.text_segment_cmd_index.?, | ||
| 953 | .sect = context.macho_file.stubs_section_index.?, | ||
| 954 | }, stub_atom); | ||
| 955 | } | ||
| 956 | if (context.parsed_atoms.getPtr(.{ | ||
| 957 | .seg = context.macho_file.data_segment_cmd_index.?, | ||
| 958 | .sect = context.macho_file.la_symbol_ptr_section_index.?, | ||
| 959 | })) |last| { | ||
| 960 | last.*.next = laptr_atom; | ||
| 961 | laptr_atom.prev = last.*; | ||
| 962 | last.* = laptr_atom; | ||
| 963 | } else { | ||
| 964 | try context.parsed_atoms.putNoClobber(.{ | ||
| 965 | .seg = context.macho_file.data_segment_cmd_index.?, | ||
| 966 | .sect = context.macho_file.la_symbol_ptr_section_index.?, | ||
| 967 | }, laptr_atom); | ||
| 968 | } | ||
| 969 | } | ||
| 970 | } | ||
| 971 | } | ||
| 972 | |||
| 973 | fn isAddend(rel: macho.relocation_info, arch: Arch) bool { | ||
| 974 | if (arch != .aarch64) return false; | ||
| 975 | return @intToEnum(macho.reloc_type_arm64, rel.r_type) == .ARM64_RELOC_ADDEND; | ||
| 976 | } | ||
| 977 | |||
| 978 | fn isSubtractor(rel: macho.relocation_info, arch: Arch) bool { | ||
| 979 | return switch (arch) { | ||
| 980 | .aarch64 => @intToEnum(macho.reloc_type_arm64, rel.r_type) == .ARM64_RELOC_SUBTRACTOR, | ||
| 981 | .x86_64 => @intToEnum(macho.reloc_type_x86_64, rel.r_type) == .X86_64_RELOC_SUBTRACTOR, | ||
| 982 | else => unreachable, | ||
| 983 | }; | ||
| 984 | } | ||
| 985 | |||
| 986 | fn parseUnsigned( | ||
| 987 | self: TextBlock, | ||
| 988 | rel: macho.relocation_info, | ||
| 989 | out: *Relocation, | ||
| 990 | subtractor: ?u32, | ||
| 991 | context: RelocContext, | ||
| 992 | ) void { | ||
| 993 | assert(rel.r_pcrel == 0); | ||
| 994 | |||
| 995 | const is_64bit: bool = switch (rel.r_length) { | ||
| 996 | 3 => true, | ||
| 997 | 2 => false, | ||
| 998 | else => unreachable, | ||
| 999 | }; | ||
| 1000 | |||
| 1001 | var addend: i64 = if (is_64bit) | ||
| 1002 | mem.readIntLittle(i64, self.code.items[out.offset..][0..8]) | ||
| 1003 | else | ||
| 1004 | mem.readIntLittle(i32, self.code.items[out.offset..][0..4]); | ||
| 1005 | |||
| 1006 | if (rel.r_extern == 0) { | ||
| 1007 | const seg = context.object.load_commands.items[context.object.segment_cmd_index.?].Segment; | ||
| 1008 | const target_sect_base_addr = seg.sections.items[rel.r_symbolnum - 1].addr; | ||
| 1009 | addend -= @intCast(i64, target_sect_base_addr); | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | out.payload = .{ | ||
| 1013 | .unsigned = .{ | ||
| 1014 | .subtractor = subtractor, | ||
| 1015 | .is_64bit = is_64bit, | ||
| 1016 | .addend = addend, | ||
| 1017 | }, | ||
| 1018 | }; | ||
| 1019 | } | ||
| 1020 | |||
| 1021 | fn parseBranch(self: TextBlock, rel: macho.relocation_info, out: *Relocation, context: RelocContext) void { | ||
| 1022 | _ = self; | ||
| 1023 | assert(rel.r_pcrel == 1); | ||
| 1024 | assert(rel.r_length == 2); | ||
| 1025 | |||
| 1026 | out.payload = .{ | ||
| 1027 | .branch = .{ | ||
| 1028 | .arch = context.macho_file.base.options.target.cpu.arch, | ||
| 1029 | }, | ||
| 1030 | }; | ||
| 1031 | } | ||
| 1032 | |||
| 1033 | fn parsePage(self: TextBlock, rel: macho.relocation_info, out: *Relocation, addend: u32) void { | ||
| 1034 | _ = self; | ||
| 1035 | assert(rel.r_pcrel == 1); | ||
| 1036 | assert(rel.r_length == 2); | ||
| 1037 | |||
| 1038 | out.payload = .{ | ||
| 1039 | .page = .{ | ||
| 1040 | .kind = switch (@intToEnum(macho.reloc_type_arm64, rel.r_type)) { | ||
| 1041 | .ARM64_RELOC_PAGE21 => .page, | ||
| 1042 | .ARM64_RELOC_GOT_LOAD_PAGE21 => .got, | ||
| 1043 | .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp, | ||
| 1044 | else => unreachable, | ||
| 1045 | }, | ||
| 1046 | .addend = addend, | ||
| 1047 | }, | ||
| 1048 | }; | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | fn parsePageOff(self: TextBlock, rel: macho.relocation_info, out: *Relocation, addend: u32) void { | ||
| 1052 | assert(rel.r_pcrel == 0); | ||
| 1053 | assert(rel.r_length == 2); | ||
| 1054 | |||
| 1055 | const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); | ||
| 1056 | const op_kind: ?Relocation.PageOff.OpKind = blk: { | ||
| 1057 | if (rel_type != .ARM64_RELOC_PAGEOFF12) break :blk null; | ||
| 1058 | const op_kind: Relocation.PageOff.OpKind = if (isArithmeticOp(self.code.items[out.offset..][0..4])) | ||
| 1059 | .arithmetic | ||
| 1060 | else | ||
| 1061 | .load; | ||
| 1062 | break :blk op_kind; | ||
| 1063 | }; | ||
| 1064 | |||
| 1065 | out.payload = .{ | ||
| 1066 | .page_off = .{ | ||
| 1067 | .kind = switch (rel_type) { | ||
| 1068 | .ARM64_RELOC_PAGEOFF12 => .page, | ||
| 1069 | .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got, | ||
| 1070 | .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp, | ||
| 1071 | else => unreachable, | ||
| 1072 | }, | ||
| 1073 | .addend = addend, | ||
| 1074 | .op_kind = op_kind, | ||
| 1075 | }, | ||
| 1076 | }; | ||
| 1077 | } | ||
| 1078 | |||
| 1079 | fn parsePointerToGot(self: TextBlock, rel: macho.relocation_info, out: *Relocation) void { | ||
| 1080 | _ = self; | ||
| 1081 | assert(rel.r_pcrel == 1); | ||
| 1082 | assert(rel.r_length == 2); | ||
| 1083 | |||
| 1084 | out.payload = .{ | ||
| 1085 | .pointer_to_got = .{}, | ||
| 1086 | }; | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | fn parseSigned(self: TextBlock, rel: macho.relocation_info, out: *Relocation, context: RelocContext) void { | ||
| 1090 | assert(rel.r_pcrel == 1); | ||
| 1091 | assert(rel.r_length == 2); | ||
| 1092 | |||
| 1093 | const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); | ||
| 1094 | const correction: u3 = switch (rel_type) { | ||
| 1095 | .X86_64_RELOC_SIGNED => 0, | ||
| 1096 | .X86_64_RELOC_SIGNED_1 => 1, | ||
| 1097 | .X86_64_RELOC_SIGNED_2 => 2, | ||
| 1098 | .X86_64_RELOC_SIGNED_4 => 4, | ||
| 1099 | else => unreachable, | ||
| 1100 | }; | ||
| 1101 | var addend: i64 = mem.readIntLittle(i32, self.code.items[out.offset..][0..4]) + correction; | ||
| 1102 | |||
| 1103 | if (rel.r_extern == 0) { | ||
| 1104 | const seg = context.object.load_commands.items[context.object.segment_cmd_index.?].Segment; | ||
| 1105 | const target_sect_base_addr = seg.sections.items[rel.r_symbolnum - 1].addr; | ||
| 1106 | addend += @intCast(i64, context.base_addr + out.offset + correction + 4) - @intCast(i64, target_sect_base_addr); | ||
| 1107 | } | ||
| 1108 | |||
| 1109 | out.payload = .{ | ||
| 1110 | .signed = .{ | ||
| 1111 | .correction = correction, | ||
| 1112 | .addend = addend, | ||
| 1113 | }, | ||
| 1114 | }; | ||
| 1115 | } | ||
| 1116 | |||
| 1117 | fn parseLoad(self: TextBlock, rel: macho.relocation_info, out: *Relocation) void { | ||
| 1118 | assert(rel.r_pcrel == 1); | ||
| 1119 | assert(rel.r_length == 2); | ||
| 1120 | |||
| 1121 | const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); | ||
| 1122 | const addend: i32 = if (rel_type == .X86_64_RELOC_GOT) | ||
| 1123 | mem.readIntLittle(i32, self.code.items[out.offset..][0..4]) | ||
| 1124 | else | ||
| 1125 | 0; | ||
| 1126 | |||
| 1127 | out.payload = .{ | ||
| 1128 | .load = .{ | ||
| 1129 | .kind = switch (rel_type) { | ||
| 1130 | .X86_64_RELOC_GOT_LOAD, .X86_64_RELOC_GOT => .got, | ||
| 1131 | .X86_64_RELOC_TLV => .tlvp, | ||
| 1132 | else => unreachable, | ||
| 1133 | }, | ||
| 1134 | .addend = addend, | ||
| 1135 | }, | ||
| 1136 | }; | ||
| 1137 | } | ||
| 1138 | |||
| 1139 | pub fn resolveRelocs(self: *TextBlock, macho_file: *MachO) !void { | ||
| 1140 | for (self.relocs.items) |rel| { | ||
| 1141 | log.debug("relocating {}", .{rel}); | ||
| 1142 | |||
| 1143 | const source_addr = blk: { | ||
| 1144 | const sym = macho_file.locals.items[self.local_sym_index]; | ||
| 1145 | break :blk sym.n_value + rel.offset; | ||
| 1146 | }; | ||
| 1147 | const target_addr = blk: { | ||
| 1148 | const is_via_got = switch (rel.payload) { | ||
| 1149 | .pointer_to_got => true, | ||
| 1150 | .page => |page| page.kind == .got, | ||
| 1151 | .page_off => |page_off| page_off.kind == .got, | ||
| 1152 | .load => |load| load.kind == .got, | ||
| 1153 | else => false, | ||
| 1154 | }; | ||
| 1155 | |||
| 1156 | if (is_via_got) { | ||
| 1157 | const atom = macho_file.got_entries_map.get(.{ | ||
| 1158 | .where = switch (rel.where) { | ||
| 1159 | .local => .local, | ||
| 1160 | .undef => .undef, | ||
| 1161 | }, | ||
| 1162 | .where_index = rel.where_index, | ||
| 1163 | }) orelse { | ||
| 1164 | const sym = switch (rel.where) { | ||
| 1165 | .local => macho_file.locals.items[rel.where_index], | ||
| 1166 | .undef => macho_file.undefs.items[rel.where_index], | ||
| 1167 | }; | ||
| 1168 | log.err("expected GOT entry for symbol '{s}'", .{macho_file.getString(sym.n_strx)}); | ||
| 1169 | log.err(" this is an internal linker error", .{}); | ||
| 1170 | return error.FailedToResolveRelocationTarget; | ||
| 1171 | }; | ||
| 1172 | break :blk macho_file.locals.items[atom.local_sym_index].n_value; | ||
| 1173 | } | ||
| 1174 | |||
| 1175 | switch (rel.where) { | ||
| 1176 | .local => { | ||
| 1177 | const sym = macho_file.locals.items[rel.where_index]; | ||
| 1178 | const is_tlv = is_tlv: { | ||
| 1179 | const source_sym = macho_file.locals.items[self.local_sym_index]; | ||
| 1180 | const match = macho_file.section_ordinals.keys()[source_sym.n_sect - 1]; | ||
| 1181 | const seg = macho_file.load_commands.items[match.seg].Segment; | ||
| 1182 | const sect = seg.sections.items[match.sect]; | ||
| 1183 | break :is_tlv commands.sectionType(sect) == macho.S_THREAD_LOCAL_VARIABLES; | ||
| 1184 | }; | ||
| 1185 | if (is_tlv) { | ||
| 1186 | // For TLV relocations, the value specified as a relocation is the displacement from the | ||
| 1187 | // TLV initializer (either value in __thread_data or zero-init in __thread_bss) to the first | ||
| 1188 | // defined TLV template init section in the following order: | ||
| 1189 | // * wrt to __thread_data if defined, then | ||
| 1190 | // * wrt to __thread_bss | ||
| 1191 | const seg = macho_file.load_commands.items[macho_file.data_segment_cmd_index.?].Segment; | ||
| 1192 | const base_address = inner: { | ||
| 1193 | if (macho_file.tlv_data_section_index) |i| { | ||
| 1194 | break :inner seg.sections.items[i].addr; | ||
| 1195 | } else if (macho_file.tlv_bss_section_index) |i| { | ||
| 1196 | break :inner seg.sections.items[i].addr; | ||
| 1197 | } else { | ||
| 1198 | log.err("threadlocal variables present but no initializer sections found", .{}); | ||
| 1199 | log.err(" __thread_data not found", .{}); | ||
| 1200 | log.err(" __thread_bss not found", .{}); | ||
| 1201 | return error.FailedToResolveRelocationTarget; | ||
| 1202 | } | ||
| 1203 | }; | ||
| 1204 | break :blk sym.n_value - base_address; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | break :blk sym.n_value; | ||
| 1208 | }, | ||
| 1209 | .undef => { | ||
| 1210 | const atom = macho_file.stubs_map.get(rel.where_index) orelse { | ||
| 1211 | // TODO this is required for incremental when we don't have every symbol | ||
| 1212 | // resolved when creating relocations. In this case, we will insert a branch | ||
| 1213 | // reloc to an undef symbol which may happen to be defined within the binary. | ||
| 1214 | // Then, the undef we point at will be a null symbol (free symbol) which we | ||
| 1215 | // should remove/repurpose. To circumvent this (for now), we check if the symbol | ||
| 1216 | // we point to is garbage, and if so we fall back to symbol resolver to find by name. | ||
| 1217 | const n_strx = macho_file.undefs.items[rel.where_index].n_strx; | ||
| 1218 | if (macho_file.symbol_resolver.get(n_strx)) |resolv| inner: { | ||
| 1219 | if (resolv.where != .global) break :inner; | ||
| 1220 | break :blk macho_file.globals.items[resolv.where_index].n_value; | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | // TODO verify in TextBlock that the symbol is indeed dynamically bound. | ||
| 1224 | break :blk 0; // Dynamically bound by dyld. | ||
| 1225 | }; | ||
| 1226 | |||
| 1227 | break :blk macho_file.locals.items[atom.local_sym_index].n_value; | ||
| 1228 | }, | ||
| 1229 | } | ||
| 1230 | }; | ||
| 1231 | |||
| 1232 | log.debug(" | source_addr = 0x{x}", .{source_addr}); | ||
| 1233 | log.debug(" | target_addr = 0x{x}", .{target_addr}); | ||
| 1234 | |||
| 1235 | try rel.resolve(.{ | ||
| 1236 | .block = self, | ||
| 1237 | .offset = rel.offset, | ||
| 1238 | .source_addr = source_addr, | ||
| 1239 | .target_addr = target_addr, | ||
| 1240 | .macho_file = macho_file, | ||
| 1241 | }); | ||
| 1242 | } | ||
| 1243 | } | ||
| 1244 | |||
| 1245 | pub fn format(self: TextBlock, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | ||
| 1246 | _ = fmt; | ||
| 1247 | _ = options; | ||
| 1248 | try std.fmt.format(writer, "TextBlock {{ ", .{}); | ||
| 1249 | try std.fmt.format(writer, ".local_sym_index = {d}, ", .{self.local_sym_index}); | ||
| 1250 | try std.fmt.format(writer, ".aliases = {any}, ", .{self.aliases.items}); | ||
| 1251 | try std.fmt.format(writer, ".contained = {any}, ", .{self.contained.items}); | ||
| 1252 | try std.fmt.format(writer, ".code = {*}, ", .{self.code.items}); | ||
| 1253 | try std.fmt.format(writer, ".size = {d}, ", .{self.size}); | ||
| 1254 | try std.fmt.format(writer, ".alignment = {d}, ", .{self.alignment}); | ||
| 1255 | try std.fmt.format(writer, ".relocs = {any}, ", .{self.relocs.items}); | ||
| 1256 | try std.fmt.format(writer, ".rebases = {any}, ", .{self.rebases.items}); | ||
| 1257 | try std.fmt.format(writer, ".bindings = {any}, ", .{self.bindings.items}); | ||
| 1258 | try std.fmt.format(writer, ".dices = {any}, ", .{self.dices.items}); | ||
| 1259 | if (self.stab) |stab| { | ||
| 1260 | try std.fmt.format(writer, ".stab = {any}, ", .{stab}); | ||
| 1261 | } | ||
| 1262 | try std.fmt.format(writer, "}}", .{}); | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | const RelocIterator = struct { | ||
| 1266 | buffer: []const macho.relocation_info, | ||
| 1267 | index: i32 = -1, | ||
| 1268 | |||
| 1269 | pub fn next(self: *RelocIterator) ?macho.relocation_info { | ||
| 1270 | self.index += 1; | ||
| 1271 | if (self.index < self.buffer.len) { | ||
| 1272 | return self.buffer[@intCast(u32, self.index)]; | ||
| 1273 | } | ||
| 1274 | return null; | ||
| 1275 | } | ||
| 1276 | |||
| 1277 | pub fn peek(self: RelocIterator) macho.relocation_info { | ||
| 1278 | assert(self.index + 1 < self.buffer.len); | ||
| 1279 | return self.buffer[@intCast(u32, self.index + 1)]; | ||
| 1280 | } | ||
| 1281 | }; | ||
| 1282 | |||
| 1283 | fn filterRelocs(relocs: []macho.relocation_info, start_addr: u64, end_addr: u64) []macho.relocation_info { | ||
| 1284 | const Predicate = struct { | ||
| 1285 | addr: u64, | ||
| 1286 | |||
| 1287 | pub fn predicate(self: @This(), rel: macho.relocation_info) bool { | ||
| 1288 | return rel.r_address < self.addr; | ||
| 1289 | } | ||
| 1290 | }; | ||
| 1291 | |||
| 1292 | const start = MachO.findFirst(macho.relocation_info, relocs, 0, Predicate{ .addr = end_addr }); | ||
| 1293 | const end = MachO.findFirst(macho.relocation_info, relocs, start, Predicate{ .addr = start_addr }); | ||
| 1294 | |||
| 1295 | return relocs[start..end]; | ||
| 1296 | } | ||
| 1297 | |||
| 1298 | inline fn isArithmeticOp(inst: *const [4]u8) bool { | ||
| 1299 | const group_decode = @truncate(u5, inst[3]); | ||
| 1300 | return ((group_decode >> 2) == 4); | ||
| 1301 | } | ||