authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-09-09 18:32:03+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-09-09 18:32:03+02:00
logaaacfc0d0a23918c6712272e10bb1cdca1daaf04
treebb5bdeb8dd441f1024d7236fa7b55e35c64fe149
parent56fdada577d5d7f871bed8e5ae74e395291d4140

macho: init process of renaming TextBlock to Atom

Initially, internally within the linker.

5 files changed, 1312 insertions(+), 1304 deletions(-)

CMakeLists.txt+1-1
......@@ -577,11 +577,11 @@ set(ZIG_STAGE2_SOURCES
577577 "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig"
578578 "${CMAKE_SOURCE_DIR}/src/link/MachO.zig"
579579 "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig"
580 "${CMAKE_SOURCE_DIR}/src/link/MachO/Atom.zig"
580581 "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig"
581582 "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig"
582583 "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig"
583584 "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
584 "${CMAKE_SOURCE_DIR}/src/link/MachO/TextBlock.zig"
585585 "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
586586 "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig"
587587 "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig"
src/link/MachO.zig+3-1
......@@ -24,6 +24,7 @@ const trace = @import("../tracy.zig").trace;
2424const Air = @import("../Air.zig");
2525const Allocator = mem.Allocator;
2626const Archive = @import("MachO/Archive.zig");
27const Atom = @import("MachO/Atom.zig");
2728const Cache = @import("../Cache.zig");
2829const CodeSignature = @import("MachO/CodeSignature.zig");
2930const Compilation = @import("../Compilation.zig");
......@@ -37,9 +38,10 @@ const LlvmObject = @import("../codegen/llvm.zig").Object;
3738const LoadCommand = commands.LoadCommand;
3839const Module = @import("../Module.zig");
3940const SegmentCommand = commands.SegmentCommand;
40pub const TextBlock = @import("MachO/TextBlock.zig");
4141const Trie = @import("MachO/Trie.zig");
4242
43pub const TextBlock = Atom;
44
4345pub const base_tag: File.Tag = File.Tag.macho;
4446
4547base: File,
src/link/MachO/Atom.zig created+1305
......@@ -0,0 +1,1305 @@
1const Atom = @This();
2
3const std = @import("std");
4const build_options = @import("build_options");
5const aarch64 = @import("../../codegen/aarch64.zig");
6const assert = std.debug.assert;
7const commands = @import("commands.zig");
8const log = std.log.scoped(.text_block);
9const macho = std.macho;
10const math = std.math;
11const mem = std.mem;
12const meta = std.meta;
13
14const Allocator = mem.Allocator;
15const Arch = std.Target.Cpu.Arch;
16const MachO = @import("../MachO.zig");
17const 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.
25local_sym_index: u32,
26
27/// List of symbol aliases pointing to the same atom via different nlists
28aliases: std.ArrayListUnmanaged(u32) = .{},
29
30/// List of symbols contained within this atom
31contained: std.ArrayListUnmanaged(SymbolAtOffset) = .{},
32
33/// Code (may be non-relocated) this atom represents
34code: 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.
39size: 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.
43alignment: u32,
44
45/// List of relocations belonging to this atom.
46relocs: std.ArrayListUnmanaged(Relocation) = .{},
47
48/// List of offsets contained within this atom that need rebasing by the dynamic
49/// loader in presence of ASLR.
50rebases: 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)
55bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{},
56
57/// List of lazy bindings
58lazy_bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{},
59
60/// List of data-in-code entries. This is currently specific to x86_64 only.
61dices: 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.
67stab: ?Stab = null,
68
69/// Points to the previous and next neighbours
70next: ?*Atom,
71prev: ?*Atom,
72
73/// Previous/next linked list pointers.
74/// This is the linked list node for this Decl's corresponding .debug_info tag.
75dbg_info_prev: ?*Atom,
76dbg_info_next: ?*Atom,
77/// Offset into .debug_info pointing to the tag for this Decl.
78dbg_info_off: u32,
79/// Size of the .debug_info tag for this Decl, not including padding.
80dbg_info_len: u32,
81
82dirty: bool = true,
83
84pub 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
105pub 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
171pub 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
579pub 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
591pub 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.
605pub 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
617pub 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
629const 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
638fn 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
703pub 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
977fn 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
982fn 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
990fn 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
1025fn 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
1037fn 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
1055fn 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
1083fn 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
1093fn 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
1121fn 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
1143pub 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
1249pub 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
1269const 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
1287fn 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
1302inline 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;
1616const sectionName = commands.sectionName;
1717
1818const Allocator = mem.Allocator;
19const Atom = @import("Atom.zig");
1920const LoadCommand = commands.LoadCommand;
2021const MachO = @import("../MachO.zig");
21const TextBlock = @import("TextBlock.zig");
22
23const TextBlock = Atom;
2224
2325file: fs.File,
2426name: []const u8,
src/link/MachO/TextBlock.zig deleted-1301
......@@ -1,1301 +0,0 @@
1const TextBlock = @This();
2
3const std = @import("std");
4const build_options = @import("build_options");
5const aarch64 = @import("../../codegen/aarch64.zig");
6const assert = std.debug.assert;
7const commands = @import("commands.zig");
8const log = std.log.scoped(.text_block);
9const macho = std.macho;
10const math = std.math;
11const mem = std.mem;
12const meta = std.meta;
13
14const Allocator = mem.Allocator;
15const Arch = std.Target.Cpu.Arch;
16const MachO = @import("../MachO.zig");
17const 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.
25local_sym_index: u32,
26
27/// List of symbol aliases pointing to the same block via different nlists
28aliases: std.ArrayListUnmanaged(u32) = .{},
29
30/// List of symbols contained within this block
31contained: std.ArrayListUnmanaged(SymbolAtOffset) = .{},
32
33/// Code (may be non-relocated) this block represents
34code: 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.
39size: u64,
40alignment: u32,
41
42relocs: std.ArrayListUnmanaged(Relocation) = .{},
43
44/// List of offsets contained within this block that need rebasing by the dynamic
45/// loader in presence of ASLR
46rebases: 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)
51bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{},
52
53/// List of lazy bindings
54lazy_bindings: std.ArrayListUnmanaged(SymbolAtOffset) = .{},
55
56/// List of data-in-code entries. This is currently specific to x86_64 only.
57dices: 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.
63stab: ?Stab = null,
64
65/// Points to the previous and next neighbours
66next: ?*TextBlock,
67prev: ?*TextBlock,
68
69/// Previous/next linked list pointers.
70/// This is the linked list node for this Decl's corresponding .debug_info tag.
71dbg_info_prev: ?*TextBlock,
72dbg_info_next: ?*TextBlock,
73/// Offset into .debug_info pointing to the tag for this Decl.
74dbg_info_off: u32,
75/// Size of the .debug_info tag for this Decl, not including padding.
76dbg_info_len: u32,
77
78dirty: bool = true,
79
80pub 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
101pub 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
167pub 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
575pub 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
587pub 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.
601pub 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
613pub 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
625const 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
634fn 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
699pub 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
973fn 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
978fn 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
986fn 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
1021fn 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
1033fn 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
1051fn 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
1079fn 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
1089fn 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
1117fn 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
1139pub 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
1245pub 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
1265const 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
1283fn 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
1298inline fn isArithmeticOp(inst: *const [4]u8) bool {
1299 const group_decode = @truncate(u5, inst[3]);
1300 return ((group_decode >> 2) == 4);
1301}