1/// Address offset allocated for this Atom wrt to its section start address.
2value: u64 = 0,
3
4/// Name of this Atom.
5name: MachO.String = .{},
6
7/// Index into linker's input file table.
8file: File.Index = 0,
9
10/// Size of this atom
11size: u64 = 0,
12
13/// Alignment of this atom as a power of two.
14alignment: Alignment = .@"1",
15
16/// Index of the input section.
17n_sect: u32 = 0,
18
19/// Index of the output section.
20out_n_sect: u8 = 0,
21
22/// Offset within the parent section pointed to by n_sect.
23/// off + size <= parent section size.
24off: u64 = 0,
25
26/// Index of this atom in the linker's atoms table.
27atom_index: Index = 0,
28
29/// Specifies whether this atom is alive or has been garbage collected.
30alive: AtomicBool = AtomicBool.init(true),
31
32/// Specifies if this atom has been visited during garbage collection.
33visited: AtomicBool = AtomicBool.init(false),
34
35/// Points to the previous and next neighbors, based on the `text_offset`.
36/// This can be used to find, for example, the capacity of this `TextBlock`.
37prev_index: Index = 0,
38next_index: Index = 0,
39
40extra: u32 = 0,
41
42pub fn getName(self: Atom, macho_file: *MachO) [:0]const u8 {
43 return switch (self.getFile(macho_file)) {
44 .dylib => unreachable,
45 inline else => |x| x.getString(self.name),
46 };
47}
48
49pub fn getFile(self: Atom, macho_file: *MachO) File {
50 return macho_file.getFile(self.file).?;
51}
52
53pub fn getRelocs(self: Atom, macho_file: *MachO) []const Relocation {
54 return switch (self.getFile(macho_file)) {
55 .dylib => unreachable,
56 inline else => |x| x.getAtomRelocs(self, macho_file),
57 };
58}
59
60pub fn getInputSection(self: Atom, macho_file: *MachO) macho.section_64 {
61 return switch (self.getFile(macho_file)) {
62 .dylib => unreachable,
63 .zig_object => |x| x.getInputSection(self, macho_file),
64 .object => |x| x.sections.items(.header)[self.n_sect],
65 .internal => |x| x.sections.items(.header)[self.n_sect],
66 };
67}
68
69pub fn getInputAddress(self: Atom, macho_file: *MachO) u64 {
70 return self.getInputSection(macho_file).addr + self.off;
71}
72
73pub fn getAddress(self: Atom, macho_file: *MachO) u64 {
74 const header = macho_file.sections.items(.header)[self.out_n_sect];
75 return header.addr + self.value;
76}
77
78pub fn getPriority(self: Atom, macho_file: *MachO) u64 {
79 const file = self.getFile(macho_file);
80 return (@as(u64, @intCast(file.getIndex())) << 32) | @as(u64, @intCast(self.n_sect));
81}
82
83pub fn getUnwindRecords(self: Atom, macho_file: *MachO) []const UnwindInfo.Record.Index {
84 const extra = self.getExtra(macho_file);
85 return switch (self.getFile(macho_file)) {
86 .dylib => unreachable,
87 .zig_object, .internal => &[0]UnwindInfo.Record.Index{},
88 .object => |x| x.unwind_records_indexes.items[extra.unwind_index..][0..extra.unwind_count],
89 };
90}
91
92pub fn markUnwindRecordsDead(self: Atom, macho_file: *MachO) void {
93 const object = self.getFile(macho_file).object;
94 for (self.getUnwindRecords(macho_file)) |cu_index| {
95 const cu = object.getUnwindRecord(cu_index);
96 cu.alive = false;
97
98 if (cu.getFdePtr(macho_file)) |fde| {
99 fde.alive = false;
100 }
101 }
102}
103
104pub fn isAlive(self: Atom) bool {
105 return self.alive.load(.seq_cst);
106}
107
108pub fn setAlive(self: *Atom, alive: bool) void {
109 _ = self.alive.swap(alive, .seq_cst);
110}
111
112pub fn getThunk(self: Atom, macho_file: *MachO) *Thunk {
113 const extra = self.getExtra(macho_file);
114 return macho_file.getThunk(extra.thunk);
115}
116
117const AddExtraOpts = struct {
118 thunk: ?u32 = null,
119 rel_index: ?u32 = null,
120 rel_count: ?u32 = null,
121 rel_out_index: ?u32 = null,
122 rel_out_count: ?u32 = null,
123 unwind_index: ?u32 = null,
124 unwind_count: ?u32 = null,
125 literal_pool_index: ?u32 = null,
126 literal_symbol_index: ?u32 = null,
127};
128
129pub fn addExtra(atom: *Atom, opts: AddExtraOpts, macho_file: *MachO) void {
130 const file = atom.getFile(macho_file);
131 var extra = file.getAtomExtra(atom.extra);
132 inline for (@typeInfo(@TypeOf(opts)).@"struct".field_names) |field_name| {
133 if (@field(opts, field_name)) |x| {
134 @field(extra, field_name) = x;
135 }
136 }
137 file.setAtomExtra(atom.extra, extra);
138}
139
140pub inline fn getExtra(atom: Atom, macho_file: *MachO) Extra {
141 return atom.getFile(macho_file).getAtomExtra(atom.extra);
142}
143
144pub inline fn setExtra(atom: Atom, extra: Extra, macho_file: *MachO) void {
145 atom.getFile(macho_file).setAtomExtra(atom.extra, extra);
146}
147
148pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
149 if (macho_file.base.isRelocatable()) {
150 const osec = macho_file.getSectionByName(sect.segName(), sect.sectName()) orelse
151 try macho_file.addSection(
152 sect.segName(),
153 sect.sectName(),
154 .{ .flags = sect.flags },
155 );
156 return osec;
157 }
158
159 const segname, const sectname, const flags = blk: {
160 if (sect.isCode()) break :blk .{
161 "__TEXT",
162 "__text",
163 macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
164 };
165
166 switch (sect.type()) {
167 macho.S_4BYTE_LITERALS,
168 macho.S_8BYTE_LITERALS,
169 macho.S_16BYTE_LITERALS,
170 => break :blk .{ "__TEXT", "__const", macho.S_REGULAR },
171
172 macho.S_CSTRING_LITERALS => {
173 if (mem.startsWith(u8, sect.sectName(), "__objc")) break :blk .{
174 sect.segName(), sect.sectName(), macho.S_REGULAR,
175 };
176 break :blk .{ "__TEXT", "__cstring", macho.S_CSTRING_LITERALS };
177 },
178
179 macho.S_MOD_INIT_FUNC_POINTERS,
180 macho.S_MOD_TERM_FUNC_POINTERS,
181 => break :blk .{ "__DATA_CONST", sect.sectName(), sect.flags },
182
183 macho.S_LITERAL_POINTERS,
184 macho.S_ZEROFILL,
185 macho.S_GB_ZEROFILL,
186 macho.S_THREAD_LOCAL_VARIABLES,
187 macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
188 macho.S_THREAD_LOCAL_REGULAR,
189 macho.S_THREAD_LOCAL_ZEROFILL,
190 => break :blk .{ sect.segName(), sect.sectName(), sect.flags },
191
192 macho.S_COALESCED => break :blk .{
193 sect.segName(),
194 sect.sectName(),
195 macho.S_REGULAR,
196 },
197
198 macho.S_REGULAR => {
199 const segname = sect.segName();
200 const sectname = sect.sectName();
201 if (mem.eql(u8, segname, "__DATA")) {
202 if (mem.eql(u8, sectname, "__cfstring") or
203 mem.eql(u8, sectname, "__objc_classlist") or
204 mem.eql(u8, sectname, "__objc_imageinfo")) break :blk .{
205 "__DATA_CONST",
206 sectname,
207 macho.S_REGULAR,
208 };
209 }
210 break :blk .{ segname, sectname, sect.flags };
211 },
212
213 else => break :blk .{ sect.segName(), sect.sectName(), sect.flags },
214 }
215 };
216 return macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection(
217 segname,
218 sectname,
219 .{ .flags = flags },
220 );
221}
222
223/// Returns how much room there is to grow in virtual address space.
224/// File offset relocation happens transparently, so it is not included in
225/// this calculation.
226pub fn capacity(self: Atom, macho_file: *MachO) u64 {
227 const zo = macho_file.getZigObject().?;
228 const next_addr = if (zo.getAtom(self.next_index)) |next|
229 next.getAddress(macho_file)
230 else
231 std.math.maxInt(u32);
232 return next_addr - self.getAddress(macho_file);
233}
234
235pub fn freeListEligible(self: Atom, macho_file: *MachO) bool {
236 // No need to keep a free list node for the last block.
237 const zo = macho_file.getZigObject().?;
238 const next = zo.getAtom(self.next_index) orelse return false;
239 const cap = next.getAddress(macho_file) - self.getAddress(macho_file);
240 const ideal_cap = MachO.padToIdeal(self.size);
241 if (cap <= ideal_cap) return false;
242 const surplus = cap - ideal_cap;
243 return surplus >= MachO.min_text_capacity;
244}
245
246pub fn allocate(self: *Atom, macho_file: *MachO) !void {
247 const zo = macho_file.getZigObject().?;
248 const sect = &macho_file.sections.items(.header)[self.out_n_sect];
249 const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
250 const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
251 const new_atom_ideal_capacity = MachO.padToIdeal(self.size);
252
253 // We use these to indicate our intention to update metadata, placing the new atom,
254 // and possibly removing a free list node.
255 // It would be simpler to do it inside the for loop below, but that would cause a
256 // problem if an error was returned later in the function. So this action
257 // is actually carried out at the end of the function, when errors are no longer possible.
258 var atom_placement: ?Atom.Index = null;
259 var free_list_removal: ?usize = null;
260
261 // First we look for an appropriately sized free list node.
262 // The list is unordered. We'll just take the first thing that works.
263 self.value = blk: {
264 var i: usize = free_list.items.len;
265 while (i < free_list.items.len) {
266 const big_atom_index = free_list.items[i];
267 const big_atom = zo.getAtom(big_atom_index).?;
268 // We now have a pointer to a live atom that has too much capacity.
269 // Is it enough that we could fit this new atom?
270 const cap = big_atom.capacity(macho_file);
271 const ideal_capacity = MachO.padToIdeal(cap);
272 const ideal_capacity_end_vaddr = std.math.add(u64, big_atom.value, ideal_capacity) catch ideal_capacity;
273 const capacity_end_vaddr = big_atom.value + cap;
274 const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
275 const new_start_vaddr = self.alignment.backward(new_start_vaddr_unaligned);
276 if (new_start_vaddr < ideal_capacity_end_vaddr) {
277 // Additional bookkeeping here to notice if this free list node
278 // should be deleted because the block that it points to has grown to take up
279 // more of the extra capacity.
280 if (!big_atom.freeListEligible(macho_file)) {
281 _ = free_list.swapRemove(i);
282 } else {
283 i += 1;
284 }
285 continue;
286 }
287 // At this point we know that we will place the new block here. But the
288 // remaining question is whether there is still yet enough capacity left
289 // over for there to still be a free list node.
290 const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
291 const keep_free_list_node = remaining_capacity >= MachO.min_text_capacity;
292
293 // Set up the metadata to be updated, after errors are no longer possible.
294 atom_placement = big_atom_index;
295 if (!keep_free_list_node) {
296 free_list_removal = i;
297 }
298 break :blk new_start_vaddr;
299 } else if (zo.getAtom(last_atom_index.*)) |last| {
300 const ideal_capacity = MachO.padToIdeal(last.size);
301 const ideal_capacity_end_vaddr = last.value + ideal_capacity;
302 const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr);
303 // Set up the metadata to be updated, after errors are no longer possible.
304 atom_placement = last.atom_index;
305 break :blk new_start_vaddr;
306 } else {
307 break :blk 0;
308 }
309 };
310
311 log.debug("allocated atom({d}) : '{s}' at 0x{x} to 0x{x}", .{
312 self.atom_index,
313 self.getName(macho_file),
314 self.getAddress(macho_file),
315 self.getAddress(macho_file) + self.size,
316 });
317
318 const expand_section = if (atom_placement) |placement_index|
319 zo.getAtom(placement_index).?.next_index == 0
320 else
321 true;
322 if (expand_section) {
323 const needed_size = self.value + self.size;
324 try macho_file.growSection(self.out_n_sect, needed_size);
325 last_atom_index.* = self.atom_index;
326
327 // const zig_object = macho_file_file.getZigObject().?;
328 // if (zig_object.dwarf) |_| {
329 // // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address
330 // // range of the compilation unit. When we expand the text section, this range changes,
331 // // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty.
332 // zig_object.debug_info_header_dirty = true;
333 // // This becomes dirty for the same reason. We could potentially make this more
334 // // fine-grained with the addition of support for more compilation units. It is planned to
335 // // model each package as a different compilation unit.
336 // zig_object.debug_aranges_section_dirty = true;
337 // }
338 }
339 sect.@"align" = @max(sect.@"align", self.alignment.toLog2Units());
340
341 // This function can also reallocate an atom.
342 // In this case we need to "unplug" it from its previous location before
343 // plugging it in to its new location.
344 if (zo.getAtom(self.prev_index)) |prev| {
345 prev.next_index = self.next_index;
346 }
347 if (zo.getAtom(self.next_index)) |next| {
348 next.prev_index = self.prev_index;
349 }
350
351 if (atom_placement) |big_atom_index| {
352 const big_atom = zo.getAtom(big_atom_index).?;
353 self.prev_index = big_atom_index;
354 self.next_index = big_atom.next_index;
355 big_atom.next_index = self.atom_index;
356 } else {
357 self.prev_index = 0;
358 self.next_index = 0;
359 }
360 if (free_list_removal) |i| {
361 _ = free_list.swapRemove(i);
362 }
363
364 self.setAlive(true);
365}
366
367pub fn shrink(self: *Atom, macho_file: *MachO) void {
368 _ = self;
369 _ = macho_file;
370}
371
372pub fn grow(self: *Atom, macho_file: *MachO) !void {
373 if (!self.alignment.check(self.value) or self.size > self.capacity(macho_file))
374 try self.allocate(macho_file);
375}
376
377pub fn free(self: *Atom, macho_file: *MachO) void {
378 log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.getName(macho_file) });
379
380 const comp = macho_file.base.comp;
381 const gpa = comp.gpa;
382 const zo = macho_file.getZigObject().?;
383 const free_list = &macho_file.sections.items(.free_list)[self.out_n_sect];
384 const last_atom_index = &macho_file.sections.items(.last_atom_index)[self.out_n_sect];
385 var already_have_free_list_node = false;
386 {
387 var i: usize = 0;
388 // TODO turn free_list into a hash map
389 while (i < free_list.items.len) {
390 if (free_list.items[i] == self.atom_index) {
391 _ = free_list.swapRemove(i);
392 continue;
393 }
394 if (free_list.items[i] == self.prev_index) {
395 already_have_free_list_node = true;
396 }
397 i += 1;
398 }
399 }
400
401 if (zo.getAtom(last_atom_index.*)) |last_atom| {
402 if (last_atom.atom_index == self.atom_index) {
403 if (zo.getAtom(self.prev_index)) |_| {
404 // TODO shrink the section size here
405 last_atom_index.* = self.prev_index;
406 } else {
407 last_atom_index.* = 0;
408 }
409 }
410 }
411
412 if (zo.getAtom(self.prev_index)) |prev| {
413 prev.next_index = self.next_index;
414 if (!already_have_free_list_node and prev.*.freeListEligible(macho_file)) {
415 // The free list is heuristics, it doesn't have to be perfect, so we can
416 // ignore the OOM here.
417 free_list.append(gpa, prev.atom_index) catch {};
418 }
419 } else {
420 self.prev_index = 0;
421 }
422
423 if (zo.getAtom(self.next_index)) |next| {
424 next.prev_index = self.prev_index;
425 } else {
426 self.next_index = 0;
427 }
428
429 // TODO create relocs free list
430 self.freeRelocs(macho_file);
431 // TODO figure out how to free input section mappind in ZigModule
432 // const zig_object = macho_file.zigObjectPtr().?
433 // assert(zig_object.atoms.swapRemove(self.atom_index));
434 self.* = .{};
435}
436
437pub fn addReloc(self: *Atom, macho_file: *MachO, reloc: Relocation) !void {
438 const gpa = macho_file.base.comp.gpa;
439 const file = self.getFile(macho_file);
440 assert(file == .zig_object);
441 var extra = self.getExtra(macho_file);
442 const rels = &file.zig_object.relocs.items[extra.rel_index];
443 try rels.append(gpa, reloc);
444 extra.rel_count += 1;
445 self.setExtra(extra, macho_file);
446}
447
448pub fn freeRelocs(self: *Atom, macho_file: *MachO) void {
449 self.getFile(macho_file).zig_object.freeAtomRelocs(self.*, macho_file);
450 var extra = self.getExtra(macho_file);
451 extra.rel_count = 0;
452 self.setExtra(extra, macho_file);
453}
454
455pub fn scanRelocs(self: Atom, macho_file: *MachO) !void {
456 const tracy = trace(@src());
457 defer tracy.end();
458 assert(self.isAlive());
459
460 const relocs = self.getRelocs(macho_file);
461
462 for (relocs) |rel| {
463 if (try self.reportUndefSymbol(rel, macho_file)) continue;
464
465 switch (rel.type) {
466 .branch => {
467 const symbol = rel.getTargetSymbol(self, macho_file);
468 if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) {
469 symbol.setSectionFlags(.{ .stubs = true });
470 if (symbol.flags.weak) {
471 macho_file.binds_to_weak.store(true, .seq_cst);
472 }
473 } else if (mem.startsWith(u8, symbol.getName(macho_file), "_objc_msgSend$")) {
474 symbol.setSectionFlags(.{ .objc_stubs = true });
475 }
476 },
477
478 .got_load,
479 .got_load_page,
480 .got_load_pageoff,
481 => {
482 const symbol = rel.getTargetSymbol(self, macho_file);
483 if (symbol.flags.import or
484 (symbol.flags.@"export" and symbol.flags.weak) or
485 symbol.flags.interposable or
486 macho_file.getTarget().cpu.arch == .aarch64) // TODO relax on arm64
487 {
488 symbol.setSectionFlags(.{ .needs_got = true });
489 if (symbol.flags.weak) {
490 macho_file.binds_to_weak.store(true, .seq_cst);
491 }
492 }
493 },
494
495 .got => {
496 rel.getTargetSymbol(self, macho_file).setSectionFlags(.{ .needs_got = true });
497 },
498
499 .tlv,
500 .tlvp_page,
501 .tlvp_pageoff,
502 => {
503 const symbol = rel.getTargetSymbol(self, macho_file);
504 if (!symbol.flags.tlv) {
505 try macho_file.reportParseError2(
506 self.getFile(macho_file).getIndex(),
507 "{s}: illegal thread-local variable reference to regular symbol {s}",
508 .{ self.getName(macho_file), symbol.getName(macho_file) },
509 );
510 }
511 if (symbol.flags.import or (symbol.flags.@"export" and symbol.flags.weak) or symbol.flags.interposable) {
512 symbol.setSectionFlags(.{ .tlv_ptr = true });
513 if (symbol.flags.weak) {
514 macho_file.binds_to_weak.store(true, .seq_cst);
515 }
516 }
517 },
518
519 .unsigned => {
520 if (rel.meta.length == 3) { // TODO this really should check if this is pointer width
521 if (rel.tag == .@"extern") {
522 const symbol = rel.getTargetSymbol(self, macho_file);
523 if (symbol.isTlvInit(macho_file)) {
524 macho_file.has_tlv.store(true, .seq_cst);
525 continue;
526 }
527 if (symbol.flags.import) {
528 if (symbol.flags.weak) {
529 macho_file.binds_to_weak.store(true, .seq_cst);
530 }
531 continue;
532 }
533 if (symbol.flags.@"export" and symbol.flags.weak) {
534 macho_file.binds_to_weak.store(true, .seq_cst);
535 }
536 }
537 }
538 },
539
540 .signed,
541 .signed1,
542 .signed2,
543 .signed4,
544 .page,
545 .pageoff,
546 .subtractor,
547 => {},
548 }
549 }
550}
551
552fn reportUndefSymbol(self: Atom, rel: Relocation, macho_file: *MachO) !bool {
553 if (rel.tag == .local) return false;
554
555 const file = self.getFile(macho_file);
556 const ref = file.getSymbolRef(rel.target, macho_file);
557 if (ref.getFile(macho_file) == null) {
558 const gpa = macho_file.base.comp.gpa;
559 const io = macho_file.base.comp.io;
560 macho_file.undefs_mutex.lockUncancelable(io);
561 defer macho_file.undefs_mutex.unlock(io);
562 const gop = try macho_file.undefs.getOrPut(gpa, file.getGlobals()[rel.target]);
563 if (!gop.found_existing) {
564 gop.value_ptr.* = .{ .refs = .empty };
565 }
566 try gop.value_ptr.refs.append(gpa, .{ .index = self.atom_index, .file = self.file });
567 return true;
568 }
569
570 return false;
571}
572
573pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void {
574 const tracy = trace(@src());
575 defer tracy.end();
576
577 assert(!self.getInputSection(macho_file).isZerofill());
578 const file = self.getFile(macho_file);
579 const name = self.getName(macho_file);
580 const relocs = self.getRelocs(macho_file);
581
582 relocs_log.debug("{x}: {s}", .{ self.value, name });
583
584 var has_error = false;
585 var stream: Writer = .fixed(buffer);
586 var i: usize = 0;
587 while (i < relocs.len) : (i += 1) {
588 const rel = relocs[i];
589 const rel_offset: usize = @intCast(rel.offset - self.off);
590 const subtractor = if (rel.meta.has_subtractor) relocs[i - 1] else null;
591
592 if (rel.tag == .@"extern") {
593 if (rel.getTargetSymbol(self, macho_file).getFile(macho_file) == null) continue;
594 }
595
596 stream.end = rel_offset;
597 self.resolveRelocInner(rel, subtractor, buffer, macho_file, &stream) catch |err| {
598 switch (err) {
599 error.RelaxFail => {
600 const target = switch (rel.tag) {
601 .@"extern" => rel.getTargetSymbol(self, macho_file).getName(macho_file),
602 .local => rel.getTargetAtom(self, macho_file).getName(macho_file),
603 };
604 try macho_file.reportParseError2(
605 file.getIndex(),
606 "{s}: 0x{x}: 0x{x}: failed to relax relocation: type {f}, target {s}",
607 .{
608 name,
609 self.getAddress(macho_file),
610 rel.offset,
611 rel.fmtPretty(macho_file.getTarget().cpu.arch),
612 target,
613 },
614 );
615 has_error = true;
616 },
617 error.RelaxFailUnexpectedInstruction => has_error = true,
618 else => |e| return e,
619 }
620 };
621 }
622
623 if (has_error) return error.ResolveFailed;
624}
625
626const ResolveError = error{
627 RelaxFail,
628 RelaxFailUnexpectedInstruction,
629 NoSpaceLeft,
630 DivisionByZero,
631 UnexpectedRemainder,
632 Overflow,
633 OutOfMemory,
634 WriteFailed,
635};
636
637fn resolveRelocInner(
638 self: Atom,
639 rel: Relocation,
640 subtractor: ?Relocation,
641 code: []u8,
642 macho_file: *MachO,
643 writer: *Writer,
644) ResolveError!void {
645 const t = &macho_file.base.comp.root_mod.resolved_target.result;
646 const cpu_arch = t.cpu.arch;
647 const rel_offset = math.cast(usize, rel.offset - self.off) orelse return error.Overflow;
648 const P = @as(i64, @intCast(self.getAddress(macho_file))) + @as(i64, @intCast(rel_offset));
649 const A = rel.addend + rel.getRelocAddend(cpu_arch);
650 const S: i64 = @intCast(rel.getTargetAddress(self, macho_file));
651 const G: i64 = @intCast(rel.getGotTargetAddress(self, macho_file));
652 const TLS = @as(i64, @intCast(macho_file.getTlsAddress()));
653 const SUB = if (subtractor) |sub| @as(i64, @intCast(sub.getTargetAddress(self, macho_file))) else 0;
654
655 const divExact = struct {
656 fn divExact(atom: Atom, r: Relocation, num: u12, den: u12, ctx: *MachO) !u12 {
657 return math.divExact(u12, num, den) catch {
658 try ctx.reportParseError2(atom.getFile(ctx).getIndex(), "{s}: unexpected remainder when resolving {f} at offset 0x{x}", .{
659 atom.getName(ctx),
660 r.fmtPretty(ctx.getTarget().cpu.arch),
661 r.offset,
662 });
663 return error.UnexpectedRemainder;
664 };
665 }
666 }.divExact;
667
668 switch (rel.tag) {
669 .local => relocs_log.debug(" {x}<+{d}>: {f}: [=> {x}] atom({d})", .{
670 P,
671 rel_offset,
672 rel.fmtPretty(cpu_arch),
673 S + A - SUB,
674 rel.getTargetAtom(self, macho_file).atom_index,
675 }),
676 .@"extern" => relocs_log.debug(" {x}<+{d}>: {f}: [=> {x}] G({x}) ({s})", .{
677 P,
678 rel_offset,
679 rel.fmtPretty(cpu_arch),
680 S + A - SUB,
681 G + A,
682 rel.getTargetSymbol(self, macho_file).getName(macho_file),
683 }),
684 }
685
686 switch (rel.type) {
687 .subtractor => {},
688
689 .unsigned => {
690 assert(!rel.meta.pcrel);
691 if (rel.meta.length == 3) {
692 if (rel.tag == .@"extern") {
693 const sym = rel.getTargetSymbol(self, macho_file);
694 if (sym.isTlvInit(macho_file)) {
695 try writer.writeInt(u64, @intCast(S - TLS), .little);
696 return;
697 }
698 if (sym.flags.import) return;
699 }
700 try writer.writeInt(u64, @bitCast(S + A - SUB), .little);
701 } else if (rel.meta.length == 2) {
702 try writer.writeInt(u32, @bitCast(@as(i32, @truncate(S + A - SUB))), .little);
703 } else unreachable;
704 },
705
706 .got => {
707 assert(rel.tag == .@"extern");
708 assert(rel.meta.length == 2);
709 assert(rel.meta.pcrel);
710 try writer.writeInt(i32, @intCast(G + A - P), .little);
711 },
712
713 .branch => {
714 assert(rel.meta.length == 2);
715 assert(rel.meta.pcrel);
716 assert(rel.tag == .@"extern");
717
718 switch (cpu_arch) {
719 .x86_64 => try writer.writeInt(i32, @intCast(S + A - P), .little),
720 .aarch64 => {
721 const disp: i28 = math.cast(i28, S + A - P) orelse blk: {
722 const thunk = self.getThunk(macho_file);
723 const S_: i64 = @intCast(thunk.getTargetAddress(rel.getTargetSymbolRef(self, macho_file), macho_file));
724 break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow;
725 };
726 aarch64.writeBranchImm(disp, code[rel_offset..][0..4]);
727 },
728 else => unreachable,
729 }
730 },
731
732 .got_load => {
733 assert(rel.tag == .@"extern");
734 assert(rel.meta.length == 2);
735 assert(rel.meta.pcrel);
736 if (rel.getTargetSymbol(self, macho_file).getSectionFlags().has_got) {
737 try writer.writeInt(i32, @intCast(G + A - P), .little);
738 } else {
739 try x86_64.relaxGotLoad(self, code[rel_offset - 3 ..], rel, macho_file);
740 try writer.writeInt(i32, @intCast(S + A - P), .little);
741 }
742 },
743
744 .tlv => {
745 assert(rel.tag == .@"extern");
746 assert(rel.meta.length == 2);
747 assert(rel.meta.pcrel);
748 const sym = rel.getTargetSymbol(self, macho_file);
749 if (sym.getSectionFlags().tlv_ptr) {
750 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
751 try writer.writeInt(i32, @intCast(S_ + A - P), .little);
752 } else {
753 try x86_64.relaxTlv(code[rel_offset - 3 ..], t);
754 try writer.writeInt(i32, @intCast(S + A - P), .little);
755 }
756 },
757
758 .signed, .signed1, .signed2, .signed4 => {
759 assert(rel.meta.length == 2);
760 assert(rel.meta.pcrel);
761 try writer.writeInt(i32, @intCast(S + A - P), .little);
762 },
763
764 .page,
765 .got_load_page,
766 .tlvp_page,
767 => {
768 assert(rel.tag == .@"extern");
769 assert(rel.meta.length == 2);
770 assert(rel.meta.pcrel);
771 const sym = rel.getTargetSymbol(self, macho_file);
772 const source = math.cast(u64, P) orelse return error.Overflow;
773 const target = target: {
774 const target = switch (rel.type) {
775 .page => S + A,
776 .got_load_page => G + A,
777 .tlvp_page => if (sym.getSectionFlags().tlv_ptr) blk: {
778 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
779 break :blk S_ + A;
780 } else S + A,
781 else => unreachable,
782 };
783 break :target math.cast(u64, target) orelse return error.Overflow;
784 };
785 aarch64.writeAdrInst(try aarch64.calcNumberOfPages(@intCast(source), @intCast(target)), code[rel_offset..][0..aarch64.encoding.Instruction.size]);
786 },
787
788 .pageoff => {
789 assert(rel.tag == .@"extern");
790 assert(rel.meta.length == 2);
791 assert(!rel.meta.pcrel);
792 const target = math.cast(u64, S + A) orelse return error.Overflow;
793 const inst_code = code[rel_offset..][0..aarch64.encoding.Instruction.size];
794 var inst: aarch64.encoding.Instruction = .read(inst_code);
795 switch (inst.decode()) {
796 else => unreachable,
797 .data_processing_immediate => aarch64.writeAddImmInst(@truncate(target), inst_code),
798 .load_store => |load_store| {
799 inst.load_store.register_unsigned_immediate.group.imm12 = switch (load_store.register_unsigned_immediate.decode()) {
800 .integer => |integer| try divExact(self, rel, @truncate(target), @as(u4, 1) << @backingInt(integer.group.size), macho_file),
801 .vector => |vector| try divExact(self, rel, @truncate(target), @as(u5, 1) << @backingInt(vector.group.opc1.decode(vector.group.size)), macho_file),
802 };
803 try writer.writeInt(u32, @bitCast(inst), .little);
804 },
805 }
806 },
807
808 .got_load_pageoff => {
809 assert(rel.tag == .@"extern");
810 assert(rel.meta.length == 2);
811 assert(!rel.meta.pcrel);
812 const target = math.cast(u64, G + A) orelse return error.Overflow;
813 aarch64.writeLoadStoreRegInst(try divExact(self, rel, @truncate(target), 8, macho_file), code[rel_offset..][0..4]);
814 },
815
816 .tlvp_pageoff => {
817 assert(rel.tag == .@"extern");
818 assert(rel.meta.length == 2);
819 assert(!rel.meta.pcrel);
820
821 const sym = rel.getTargetSymbol(self, macho_file);
822 const target = target: {
823 const target = if (sym.getSectionFlags().tlv_ptr) blk: {
824 const S_: i64 = @intCast(sym.getTlvPtrAddress(macho_file));
825 break :blk S_ + A;
826 } else S + A;
827 break :target math.cast(u64, target) orelse return error.Overflow;
828 };
829
830 const inst_code = code[rel_offset..][0..4];
831 const rd, const rn = switch (aarch64.encoding.Instruction.read(inst_code).decode()) {
832 else => unreachable,
833 .data_processing_immediate => |decoded| .{
834 decoded.add_subtract_immediate.group.Rd.decode(.{ .sp = true }),
835 decoded.add_subtract_immediate.group.Rn.decode(.{ .sp = true }),
836 },
837 .load_store => |decoded| .{
838 decoded.register_unsigned_immediate.integer.group.Rt.decode(.{}),
839 decoded.register_unsigned_immediate.group.Rn.decode(.{ .sp = true }),
840 },
841 };
842
843 try writer.writeInt(u32, @bitCast(@as(
844 aarch64.encoding.Instruction,
845 if (sym.getSectionFlags().tlv_ptr) .ldr(rd.x(), .{ .unsigned_offset = .{
846 .base = rn.x(),
847 .offset = try divExact(self, rel, @truncate(target), 8, macho_file) * 8,
848 } }) else .add(rd.x(), rn.x(), .{ .immediate = @truncate(target) }),
849 )), .little);
850 },
851 }
852}
853
854const x86_64 = struct {
855 fn relaxGotLoad(self: Atom, code: []u8, rel: Relocation, macho_file: *MachO) ResolveError!void {
856 dev.checkAny(&.{ .llvm_backend, .x86_64_backend });
857 const t = &macho_file.base.comp.root_mod.resolved_target.result;
858 const diags = &macho_file.base.comp.link_diags;
859 const old_inst = disassemble(code) orelse return error.RelaxFail;
860 switch (old_inst.encoding.mnemonic) {
861 .mov => {
862 const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops, t) catch return error.RelaxFail;
863 relocs_log.debug(" relaxing {f} => {f}", .{ old_inst.encoding, inst.encoding });
864 encode(&.{inst}, code) catch return error.RelaxFail;
865 },
866 else => |x| {
867 var err = try diags.addErrorWithNotes(2);
868 try err.addMsg("{s}: 0x{x}: 0x{x}: failed to relax relocation of type {f}", .{
869 self.getName(macho_file),
870 self.getAddress(macho_file),
871 rel.offset,
872 rel.fmtPretty(.x86_64),
873 });
874 err.addNote("expected .mov instruction but found .{s}", .{@tagName(x)});
875 err.addNote("while parsing {f}", .{self.getFile(macho_file).fmtPath()});
876 return error.RelaxFailUnexpectedInstruction;
877 },
878 }
879 }
880
881 fn relaxTlv(code: []u8, t: *const std.Target) error{RelaxFail}!void {
882 dev.checkAny(&.{ .llvm_backend, .x86_64_backend });
883 const old_inst = disassemble(code) orelse return error.RelaxFail;
884 switch (old_inst.encoding.mnemonic) {
885 .mov => {
886 const inst = Instruction.new(old_inst.prefix, .lea, &old_inst.ops, t) catch return error.RelaxFail;
887 relocs_log.debug(" relaxing {f} => {f}", .{ old_inst.encoding, inst.encoding });
888 encode(&.{inst}, code) catch return error.RelaxFail;
889 },
890 else => return error.RelaxFail,
891 }
892 }
893
894 fn disassemble(code: []const u8) ?Instruction {
895 var disas = Disassembler.init(code);
896 const inst = disas.next() catch return null;
897 return inst;
898 }
899
900 fn encode(insts: []const Instruction, code: []u8) !void {
901 var stream: Writer = .fixed(code);
902 for (insts) |inst| try inst.encode(&stream, .{});
903 }
904
905 const bits = @import("../../codegen/x86_64/bits.zig");
906 const encoder = @import("../../codegen/x86_64/encoder.zig");
907 const Disassembler = @import("../../codegen/x86_64/Disassembler.zig");
908 const Immediate = bits.Immediate;
909 const Instruction = encoder.Instruction;
910};
911
912pub fn calcNumRelocs(self: Atom, macho_file: *MachO) u32 {
913 const relocs = self.getRelocs(macho_file);
914 switch (macho_file.getTarget().cpu.arch) {
915 .aarch64 => {
916 var nreloc: u32 = 0;
917 for (relocs) |rel| {
918 nreloc += 1;
919 switch (rel.type) {
920 .page, .pageoff => if (rel.addend > 0) {
921 nreloc += 1;
922 },
923 else => {},
924 }
925 }
926 return nreloc;
927 },
928 .x86_64 => return @intCast(relocs.len),
929 else => unreachable,
930 }
931}
932
933pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: []macho.relocation_info) error{ AlreadyReported, OutOfMemory }!void {
934 const tracy = trace(@src());
935 defer tracy.end();
936
937 relocs_log.debug("{x}: {s}", .{ self.getAddress(macho_file), self.getName(macho_file) });
938
939 const cpu_arch = macho_file.getTarget().cpu.arch;
940 const relocs = self.getRelocs(macho_file);
941
942 var i: usize = 0;
943 for (relocs) |rel| {
944 defer i += 1;
945 const rel_offset = try macho_file.cast(usize, rel.offset - self.off);
946 const r_address: i32 = try macho_file.cast(i32, self.value + rel_offset);
947 assert(r_address >= 0);
948 const r_symbolnum = r_symbolnum: {
949 const r_symbolnum: u32 = switch (rel.tag) {
950 .local => rel.getTargetAtom(self, macho_file).out_n_sect + 1,
951 .@"extern" => rel.getTargetSymbol(self, macho_file).getOutputSymtabIndex(macho_file).?,
952 };
953 break :r_symbolnum try macho_file.cast(u24, r_symbolnum);
954 };
955 const r_extern = rel.tag == .@"extern";
956 var addend = rel.addend + rel.getRelocAddend(cpu_arch);
957 if (rel.tag == .local) {
958 const target: i64 = @intCast(rel.getTargetAddress(self, macho_file));
959 addend += target;
960 }
961
962 switch (rel.tag) {
963 .local => relocs_log.debug(" {f}: [{x} => {d}({s},{s})] + {x}", .{
964 rel.fmtPretty(cpu_arch),
965 r_address,
966 r_symbolnum,
967 macho_file.sections.items(.header)[r_symbolnum - 1].segName(),
968 macho_file.sections.items(.header)[r_symbolnum - 1].sectName(),
969 addend,
970 }),
971 .@"extern" => relocs_log.debug(" {f}: [{x} => {d}({s})] + {x}", .{
972 rel.fmtPretty(cpu_arch),
973 r_address,
974 r_symbolnum,
975 rel.getTargetSymbol(self, macho_file).getName(macho_file),
976 addend,
977 }),
978 }
979
980 switch (cpu_arch) {
981 .aarch64 => {
982 if (rel.type == .unsigned) switch (rel.meta.length) {
983 0, 1 => unreachable,
984 2 => mem.writeInt(i32, code[rel_offset..][0..4], @truncate(addend), .little),
985 3 => mem.writeInt(i64, code[rel_offset..][0..8], addend, .little),
986 } else if (addend > 0) {
987 buffer[i] = .{
988 .r_address = r_address,
989 .r_symbolnum = @bitCast(try macho_file.cast(i24, addend)),
990 .r_pcrel = 0,
991 .r_length = 2,
992 .r_extern = 0,
993 .r_type = @backingInt(macho.reloc_type_arm64.ARM64_RELOC_ADDEND),
994 };
995 i += 1;
996 }
997
998 const r_type: macho.reloc_type_arm64 = switch (rel.type) {
999 .page => .ARM64_RELOC_PAGE21,
1000 .pageoff => .ARM64_RELOC_PAGEOFF12,
1001 .got_load_page => .ARM64_RELOC_GOT_LOAD_PAGE21,
1002 .got_load_pageoff => .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
1003 .tlvp_page => .ARM64_RELOC_TLVP_LOAD_PAGE21,
1004 .tlvp_pageoff => .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
1005 .branch => .ARM64_RELOC_BRANCH26,
1006 .got => .ARM64_RELOC_POINTER_TO_GOT,
1007 .subtractor => .ARM64_RELOC_SUBTRACTOR,
1008 .unsigned => .ARM64_RELOC_UNSIGNED,
1009
1010 .signed,
1011 .signed1,
1012 .signed2,
1013 .signed4,
1014 .got_load,
1015 .tlv,
1016 => unreachable,
1017 };
1018 buffer[i] = .{
1019 .r_address = r_address,
1020 .r_symbolnum = r_symbolnum,
1021 .r_pcrel = @intFromBool(rel.meta.pcrel),
1022 .r_extern = @intFromBool(r_extern),
1023 .r_length = rel.meta.length,
1024 .r_type = @backingInt(r_type),
1025 };
1026 },
1027 .x86_64 => {
1028 if (rel.meta.pcrel) {
1029 if (rel.tag == .local) {
1030 addend -= @as(i64, @intCast(self.getAddress(macho_file) + rel_offset));
1031 } else {
1032 addend += 4;
1033 }
1034 }
1035 switch (rel.meta.length) {
1036 0, 1 => unreachable,
1037 2 => mem.writeInt(i32, code[rel_offset..][0..4], @truncate(addend), .little),
1038 3 => mem.writeInt(i64, code[rel_offset..][0..8], addend, .little),
1039 }
1040
1041 const r_type: macho.reloc_type_x86_64 = switch (rel.type) {
1042 .signed => .X86_64_RELOC_SIGNED,
1043 .signed1 => .X86_64_RELOC_SIGNED_1,
1044 .signed2 => .X86_64_RELOC_SIGNED_2,
1045 .signed4 => .X86_64_RELOC_SIGNED_4,
1046 .got_load => .X86_64_RELOC_GOT_LOAD,
1047 .tlv => .X86_64_RELOC_TLV,
1048 .branch => .X86_64_RELOC_BRANCH,
1049 .got => .X86_64_RELOC_GOT,
1050 .subtractor => .X86_64_RELOC_SUBTRACTOR,
1051 .unsigned => .X86_64_RELOC_UNSIGNED,
1052
1053 .page,
1054 .pageoff,
1055 .got_load_page,
1056 .got_load_pageoff,
1057 .tlvp_page,
1058 .tlvp_pageoff,
1059 => unreachable,
1060 };
1061 buffer[i] = .{
1062 .r_address = r_address,
1063 .r_symbolnum = r_symbolnum,
1064 .r_pcrel = @intFromBool(rel.meta.pcrel),
1065 .r_extern = @intFromBool(r_extern),
1066 .r_length = rel.meta.length,
1067 .r_type = @backingInt(r_type),
1068 };
1069 },
1070 else => unreachable,
1071 }
1072 }
1073
1074 assert(i == buffer.len);
1075}
1076
1077pub fn fmt(atom: Atom, macho_file: *MachO) std.fmt.Alt(Format, Format.print) {
1078 return .{ .data = .{
1079 .atom = atom,
1080 .macho_file = macho_file,
1081 } };
1082}
1083
1084const Format = struct {
1085 atom: Atom,
1086 macho_file: *MachO,
1087
1088 fn print(f: Format, w: *Writer) Writer.Error!void {
1089 const atom = f.atom;
1090 const macho_file = f.macho_file;
1091 const file = atom.getFile(macho_file);
1092 try w.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x}) : nreloc({d}) : thunk({d})", .{
1093 atom.atom_index, atom.getName(macho_file), atom.getAddress(macho_file),
1094 atom.out_n_sect, atom.alignment, atom.size,
1095 atom.getRelocs(macho_file).len, atom.getExtra(macho_file).thunk,
1096 });
1097 if (!atom.isAlive()) try w.writeAll(" : [*]");
1098 if (atom.getUnwindRecords(macho_file).len > 0) {
1099 try w.writeAll(" : unwind{ ");
1100 const extra = atom.getExtra(macho_file);
1101 for (atom.getUnwindRecords(macho_file), extra.unwind_index..) |index, i| {
1102 const rec = file.object.getUnwindRecord(index);
1103 try w.print("{d}", .{index});
1104 if (!rec.alive) try w.writeAll("([*])");
1105 if (i < extra.unwind_index + extra.unwind_count - 1) try w.writeAll(", ");
1106 }
1107 try w.writeAll(" }");
1108 }
1109 }
1110};
1111
1112pub const Index = u32;
1113
1114pub const Extra = struct {
1115 /// Index of the range extension thunk of this atom.
1116 thunk: u32 = 0,
1117
1118 /// Start index of relocations belonging to this atom.
1119 rel_index: u32 = 0,
1120
1121 /// Count of relocations belonging to this atom.
1122 rel_count: u32 = 0,
1123
1124 /// Start index of relocations being written out to file for this atom.
1125 rel_out_index: u32 = 0,
1126
1127 /// Count of relocations written out to file for this atom.
1128 rel_out_count: u32 = 0,
1129
1130 /// Start index of relocations belonging to this atom.
1131 unwind_index: u32 = 0,
1132
1133 /// Count of relocations belonging to this atom.
1134 unwind_count: u32 = 0,
1135
1136 /// Index into LiteralPool entry for this atom.
1137 literal_pool_index: u32 = 0,
1138
1139 /// Index into the File's symbol table for local symbol representing this literal atom.
1140 literal_symbol_index: u32 = 0,
1141};
1142
1143pub const Alignment = @import("../../InternPool.zig").Alignment;
1144
1145const std = @import("std");
1146const assert = std.debug.assert;
1147const macho = std.macho;
1148const math = std.math;
1149const mem = std.mem;
1150const log = std.log.scoped(.link);
1151const relocs_log = std.log.scoped(.link_relocs);
1152const Writer = std.Io.Writer;
1153const Allocator = mem.Allocator;
1154const AtomicBool = std.atomic.Value(bool);
1155
1156const aarch64 = @import("../aarch64.zig");
1157const trace = @import("../../tracy.zig").trace;
1158const Atom = @This();
1159const File = @import("file.zig").File;
1160const MachO = @import("../MachO.zig");
1161const Object = @import("Object.zig");
1162const Relocation = @import("Relocation.zig");
1163const Symbol = @import("Symbol.zig");
1164const Thunk = @import("Thunk.zig");
1165const UnwindInfo = @import("UnwindInfo.zig");
1166const dev = @import("../../dev.zig");