1const MachO = @This();
2
3base: link.File,
4options: link.File.OpenOptions,
5
6const_prog_node: std.Progress.Node,
7synth_prog_node: std.Progress.Node,
8input_prog_node: std.Progress.Node,
9
10mf: MappedFile,
11nodes: std.MultiArrayList(Node),
12
13node: struct {
14 macho: MappedFile.Node.Index,
15 header: MappedFile.Node.Index,
16 symtab: MappedFile.Node.Index,
17 strtab: MappedFile.Node.Index,
18 got_indirect_syms: MappedFile.Node.Index,
19 stubs_indirect_syms: MappedFile.Node.Index,
20 fixup_chains_header: MappedFile.Node.Index,
21 fixup_chains_imports: MappedFile.Node.Index,
22 fixup_chains_starts_in_image: MappedFile.Node.Index,
23 fixup_chains_symbol_names: MappedFile.Node.Index,
24},
25
26lc: struct {
27 offsets: std.ArrayList(u32),
28 pagezero: LoadCommand,
29 symtab: LoadCommand,
30 dysymtab: LoadCommand,
31 entry_point: ?LoadCommand,
32},
33known_sections: struct {
34 got: Section,
35 stubs: Section,
36},
37/// Does not contain an entry for the `__PAGEZERO` segment.
38segments: std.MultiArrayList(struct {
39 lc: LoadCommand,
40 node: MappedFile.Node.Index,
41 fixup_chain_starts: MappedFile.Node.Index.Optional,
42}),
43sections: std.MultiArrayList(struct {
44 lc: LoadCommand,
45 node: MappedFile.Node.Index,
46 segment: Segment,
47 index: u8,
48}),
49/// Accessed with `Section.NameAdapter` using `Section.NameAdapter.SegmentAndSection`.
50sections_by_name: std.array_hash_map.Custom(void, void, void, true),
51
52dirty_fixup_chain_pages: std.array_hash_map.Auto(struct { seg: Segment, page: u16 }, void),
53
54input_atoms: std.ArrayList(InputAtom),
55
56/// Accessed with `String.Adapter` using `[]const u8` keys.
57strtab: std.array_hash_map.Custom(String, void, void, true),
58
59symtab: std.ArrayList(Symbol),
60
61/// Contains all symbols which can be referenced by name. Mach-O generally calls these "external"
62/// symbols, but I'm avoiding that name in favour of ELF's term "global symbol", because the Mach-O
63/// term is needlessly confusing (they may be defined locally!).
64///
65/// Accessed with `Symbol.GlobalAdapter` using `[]const u8` keys.
66global_symbols: std.array_hash_map.Custom(Symbol.Index, void, void, true),
67
68relocs: std.ArrayList(Reloc),
69overflowed_reloc_count: u32,
70misaligned_reloc_count: u32,
71
72imports: std.array_hash_map.Auto(Symbol.Index, void),
73fixup_chain_symbol_names_len: u32,
74got: std.array_hash_map.Auto(Symbol.Index, void),
75stubs: std.array_hash_map.Auto(Symbol.Index, void),
76
77sections_changed_index: std.array_hash_map.Auto(Section, void),
78
79/// The file offset of the `__got` section relative to its parent segment at the time that fixups
80/// for the GOT were last marked dirty. We track this so that when the GOT moves, we know its
81/// previous location and can invalidate those fixup pages too (to clear the old fixup entries).
82prev_got_offset: u32,
83
84const Error = link.Error || error{MappedFileIo};
85
86/// Stable reference to a particular load command (or `std.macho.section_64`, despite those not
87/// technically being load commands). Underlying value is an index into `MachO.lc.offsets`.
88const LoadCommand = enum(u32) {
89 _,
90 fn offset(lc: LoadCommand, macho: *const MachO) u32 {
91 return macho.lc.offsets.items[@backingInt(lc)];
92 }
93 fn ptrConst(lc: LoadCommand, macho: *const MachO, comptime Cmd: type) *align(8) const Cmd {
94 return @ptrCast(@alignCast(
95 macho.node.header.sliceConst(&macho.mf)[lc.offset(macho)..][0..@sizeOf(Cmd)],
96 ));
97 }
98 fn ptr(lc: LoadCommand, macho: *MachO, comptime Cmd: type) *align(8) Cmd {
99 return @ptrCast(@alignCast(
100 macho.node.header.slice(&macho.mf)[lc.offset(macho)..][0..@sizeOf(Cmd)],
101 ));
102 }
103};
104
105/// Stable reference to a segment defined by a `std.macho.segment_command_64` load command.
106///
107/// This type is backed by `u8` instead of a larger type because Mach-O itself uses 8-bit indices to
108/// refer to sections, so the limitation exists regardless.
109const Segment = enum(u8) {
110 /// Aside from `__PAGEZERO` (which we don't assign a `Segment`), the `__TEXT` segment must come
111 /// first in memory. This segment is special: it must contain the Mach header, and some offsets
112 /// are relative to its vmaddr. So, as it's special and always exists, we give it a named tag!
113 text,
114 _,
115
116 fn lc(seg: Segment, macho: *const MachO) LoadCommand {
117 return macho.segments.items(.lc)[@backingInt(seg)];
118 }
119 fn lcPtrConst(seg: Segment, macho: *const MachO) *const std.macho.segment_command_64 {
120 return seg.lc(macho).ptrConst(macho, std.macho.segment_command_64);
121 }
122 fn lcPtr(seg: Segment, macho: *MachO) *std.macho.segment_command_64 {
123 return seg.lc(macho).ptr(macho, std.macho.segment_command_64);
124 }
125
126 fn name(seg: Segment, macho: *const MachO) []const u8 {
127 return std.mem.sliceTo(&seg.lcPtrConst(macho).segname, 0);
128 }
129
130 fn vaddr(seg: Segment, macho: *const MachO) u64 {
131 return macho.targetLoad(&seg.lcPtrConst(macho).vmaddr);
132 }
133
134 fn node(seg: Segment, macho: *const MachO) MappedFile.Node.Index {
135 return macho.segments.items(.node)[@backingInt(seg)];
136 }
137
138 fn fixupChainStartsNode(seg: Segment, macho: *const MachO) MappedFile.Node.Index.Optional {
139 return macho.segments.items(.fixup_chain_starts)[@backingInt(seg)];
140 }
141};
142
143/// Stable reference to a section defined by a `std.macho.section_64` in the load commands.
144///
145/// This type is backed by `u8` instead of a larger type because Mach-O itself uses 8-bit indices to
146/// refer to sections, so the limitation exists regardless.
147///
148/// However, the backing integer of a `Section` is *not* the same as the section index, because
149/// section indices can change. For the index of a section, see `Section.index`.
150const Section = enum(u8) {
151 _,
152
153 fn lc(sect: Section, macho: *const MachO) LoadCommand {
154 return macho.sections.items(.lc)[@backingInt(sect)];
155 }
156 fn lcPtrConst(sect: Section, macho: *const MachO) *const std.macho.section_64 {
157 return sect.lc(macho).ptrConst(macho, std.macho.section_64);
158 }
159 fn lcPtr(sect: Section, macho: *MachO) *std.macho.section_64 {
160 return sect.lc(macho).ptr(macho, std.macho.section_64);
161 }
162
163 fn name(sect: Section, macho: *const MachO) []const u8 {
164 return std.mem.sliceTo(&sect.lcPtrConst(macho).sectname, 0);
165 }
166
167 fn vaddr(sect: Section, macho: *const MachO) u64 {
168 return macho.targetLoad(&sect.lcPtrConst(macho).addr);
169 }
170
171 fn segmentName(sect: Section, macho: *const MachO) []const u8 {
172 return std.mem.sliceTo(&sect.lcPtrConst(macho).segname, 0);
173 }
174
175 fn segment(sect: Section, macho: *const MachO) Segment {
176 return macho.sections.items(.segment)[@backingInt(sect)];
177 }
178
179 fn node(sect: Section, macho: *const MachO) MappedFile.Node.Index {
180 return macho.sections.items(.node)[@backingInt(sect)];
181 }
182
183 fn alignment(sect: Section, macho: *const MachO) MappedFile.Alignment {
184 return .fromLog2Units(@intCast(macho.targetLoad(&sect.lcPtrConst(macho).@"align")));
185 }
186
187 /// Returns this section's index in the Mach-O file. Section indices are implicitly assigned
188 /// based on the order of load commands, so this value may change in response to segments or
189 /// sections being added. When this value changes for a given `Section`, it is added to
190 /// `MachO.sections_changed_index`, so that references to the section index can be updated.
191 fn index(sect: Section, macho: *const MachO) u8 {
192 return macho.sections.items(.index)[@backingInt(sect)];
193 }
194
195 const NameAdapter = struct {
196 macho: *const MachO,
197 const SegmentAndSection = struct {
198 segment: []const u8,
199 section: []const u8,
200 };
201 pub fn eql(ctx: NameAdapter, a_name: SegmentAndSection, _: void, b_sect_raw: usize) bool {
202 const b_sect: Section = @fromBackingInt(@intCast(b_sect_raw));
203 return std.mem.eql(u8, a_name.segment, b_sect.segmentName(ctx.macho)) and
204 std.mem.eql(u8, a_name.section, b_sect.name(ctx.macho));
205 }
206 pub fn hash(ctx: NameAdapter, a_name: SegmentAndSection) u32 {
207 _ = ctx;
208 var h: std.hash.Wyhash = .init(a_name.section.len);
209 h.update(a_name.section);
210 h.update(a_name.segment);
211 return @truncate(h.final());
212 }
213 };
214
215 /// Matches the layout of `std.macho.section_64.flags`.
216 ///
217 /// TODO: move to `std.macho` and use as the type of `std.macho.section_64.flags`.
218 const Flags = packed struct(u32) {
219 type: enum(u8) {
220 regular = 0x00,
221 zerofill = 0x01,
222 cstring_literals = 0x02,
223 @"4byte_literals" = 0x03,
224 @"8byte_literals" = 0x04,
225 literal_pointers = 0x05,
226 non_lazy_symbol_pointers = 0x06,
227 lazy_symbol_pointers = 0x07,
228 symbol_stubs = 0x08,
229 mod_init_func_pointers = 0x09,
230 mod_term_func_pointers = 0x0a,
231 coalesced = 0x0b,
232 gb_zerofill = 0x0c,
233 interposing = 0x0d,
234 @"16byte_literals" = 0x0e,
235 dtrace_dof = 0x0f,
236 lazy_dylib_symbol_pointers = 0x10,
237 thread_local_regular = 0x11,
238 thread_local_zerofill = 0x12,
239 thread_local_variables = 0x13,
240 thread_local_variable_pointers = 0x14,
241 thread_local_init_function_pointers = 0x15,
242 init_func_offsets = 0x16,
243 _,
244 },
245 loc_reloc: bool = false,
246 ext_reloc: bool = false,
247 some_instructions: bool = false,
248 _unused: u15 = 0,
249 self_modifying_code: bool = false,
250 live_support: bool = false,
251 no_dead_strip: bool = false,
252 strip_static_syms: bool = false,
253 no_toc: bool = false,
254 pure_instructions: bool = false,
255 };
256};
257
258const InputAtom = struct {
259 node: MappedFile.Node.Index,
260 first_symbol: Symbol.Index.Optional,
261 first_reloc: Reloc.Index,
262 vaddr: u64,
263 /// `true` if there are any relocations *in* this atom which are runtime fixups.
264 ///
265 /// TODO: storing a bool here is bad for memory utilization, but there's no point in me finding
266 /// somewhere else to put this state before we finish figuring out this linker's design. It
267 /// should definitely be moved at some point though!
268 any_fixups: bool,
269
270 const Index = enum(u32) {
271 _,
272 fn ptr(iai: InputAtom.Index, macho: *MachO) *InputAtom {
273 return &macho.input_atoms.items[@backingInt(iai)];
274 }
275 fn ptrConst(iai: InputAtom.Index, macho: *const MachO) *const InputAtom {
276 return &macho.input_atoms.items[@backingInt(iai)];
277 }
278 fn node(iai: InputAtom.Index, macho: *const MachO) MappedFile.Node.Index {
279 return iai.ptrConst(macho).node;
280 }
281 };
282};
283
284const Symbol = struct {
285 node: MappedFile.Node.Index.Optional,
286 first_target_reloc: Reloc.Index.Optional,
287 prev_in_node: Symbol.Index.Optional,
288 next_in_node: Symbol.Index.Optional,
289
290 const Index = enum(u32) {
291 _,
292 fn ptr(i: Index, macho: *MachO) *Symbol {
293 return &macho.symtab.items[@backingInt(i)];
294 }
295 fn ptrConst(i: Index, macho: *const MachO) *const Symbol {
296 return &macho.symtab.items[@backingInt(i)];
297 }
298 fn nlist(i: Index, macho: *MachO) *std.macho.nlist_64 {
299 const nsyms = macho.targetLoad(
300 &macho.lc.symtab.ptrConst(macho, std.macho.symtab_command).nsyms,
301 );
302 const slice: []std.macho.nlist_64 = @ptrCast(@alignCast(
303 macho.node.symtab.slice(&macho.mf)[0 .. nsyms * @sizeOf(std.macho.nlist_64)],
304 ));
305 return &slice[@backingInt(i)];
306 }
307 fn nlistConst(i: Index, macho: *const MachO) *const std.macho.nlist_64 {
308 const nsyms = macho.targetLoad(
309 &macho.lc.symtab.ptrConst(macho, std.macho.symtab_command).nsyms,
310 );
311 const slice: []const std.macho.nlist_64 = @ptrCast(@alignCast(
312 macho.node.symtab.slice(&macho.mf)[0 .. nsyms * @sizeOf(std.macho.nlist_64)],
313 ));
314 return &slice[@backingInt(i)];
315 }
316 fn name(i: Index, macho: *const MachO) []const u8 {
317 const n_strx = macho.targetLoad(&i.nlistConst(macho).n_strx);
318 return std.mem.sliceTo(macho.node.strtab.sliceConst(&macho.mf)[n_strx..], 0);
319 }
320 fn value(i: Index, macho: *const MachO) u64 {
321 return macho.targetLoad(&i.nlistConst(macho).n_value);
322 }
323 fn flushMoved(symbol: Index, macho: *MachO, new_value: u64) Allocator.Error!void {
324 macho.targetStore(&symbol.nlist(macho).n_value, new_value);
325 try symbol.applyTargetRelocs(macho, .init(.{
326 .symbol = true,
327 .branch = true,
328 .got = false,
329 .tlv = false,
330 }));
331 }
332 fn applyTargetRelocs(
333 i: Index,
334 macho: *MachO,
335 targets: std.enums.EnumSet(Reloc.Target),
336 ) Allocator.Error!void {
337 var reloc_it = i.ptr(macho).first_target_reloc;
338 while (reloc_it.unwrap()) |ri| : (reloc_it = ri.ptr(macho).next_with_same_target_sym) {
339 if (targets.contains(ri.ptr(macho).bits.target)) {
340 try ri.ptr(macho).apply(macho);
341 }
342 }
343 }
344 const Optional = enum(u32) {
345 none = std.math.maxInt(u32),
346 _,
347 fn unwrap(o: Optional) ?Index {
348 return switch (o) {
349 .none => null,
350 _ => @fromBackingInt(@backingInt(o)),
351 };
352 }
353 fn wrap(i: Index) Optional {
354 return @bitCast(i);
355 }
356 };
357 };
358
359 const InitValue = union(enum) {
360 abs: u64,
361 addr_in_node: struct {
362 /// Must support `getNodeVAddr`, `getNodeSection`, and `nodeFirstSymbolPtr`.
363 node: MappedFile.Node.Index,
364 offset: u64,
365 },
366 };
367
368 const GlobalAdapter = struct {
369 macho: *MachO,
370 pub fn eql(ctx: GlobalAdapter, lhs_slice: []const u8, rhs_symbol: Symbol.Index, _: usize) bool {
371 const macho = ctx.macho;
372 return std.mem.eql(u8, lhs_slice, rhs_symbol.name(macho));
373 }
374 pub fn hash(ctx: GlobalAdapter, s: []const u8) u32 {
375 _ = ctx;
376 return std.array_hash_map.hashString(s);
377 }
378 };
379};
380
381const Reloc = struct {
382 node: MappedFile.Node.Index,
383 offset: u32,
384 target_symbol: Symbol.Index,
385 addend: u64,
386 bits: Bits,
387 next_with_same_target_sym: Reloc.Index.Optional,
388
389 const Bits = packed struct(u8) {
390 target: Target,
391 type: Type,
392 result: enum(u2) {
393 ok,
394 overflowed,
395 misaligned,
396 /// This is a special value indicating that this relocation is lowered to a runtime
397 /// fixup rather than being resolved statically. TODO: I'm not entirely sure if this is
398 /// a good idea, particularly since the question of "is this a runtime fixup" can, I
399 /// believe, be fully recovered based on the `Bits.target` and `Bits.type`...?
400 fixup,
401 },
402 };
403 const Target = enum(u2) {
404 symbol,
405 branch,
406 got,
407 tlv,
408 };
409 const Type = enum(u4) {
410 abs8,
411 abs16,
412 abs32,
413 abs64,
414 rel32,
415 arm64_branchrel26,
416 arm64_pagerel21,
417 arm64_pageoff12,
418
419 /// If the given relocation requires a particular machine instruction to already exist in
420 /// the destination bytes, this function checks if that requirement is upheld and returns
421 /// `false` if not.
422 ///
423 /// This function does not perform length checks on `dest_overlong`; the caller guarantees
424 /// that it will be at least long enough to support this relocation type.
425 fn validateInst(t: Type, dest_overlong: []const u8) bool {
426 switch (t) {
427 .abs8,
428 .abs16,
429 .abs32,
430 .abs64,
431 .rel32,
432 => return true,
433
434 .arm64_branchrel26 => {
435 const inst: aarch64.encoding.Instruction = .read(dest_overlong[0..4]);
436 return switch (inst.decode()) {
437 .branch_exception_generating_system => |begs| switch (begs.decode()) {
438 .unconditional_branch_immediate => true,
439 else => false,
440 },
441 else => false,
442 };
443 },
444 .arm64_pagerel21 => {
445 const inst: aarch64.encoding.Instruction = .read(dest_overlong[0..4]);
446 return switch (inst.decode()) {
447 .data_processing_immediate => |dpi| switch (dpi.decode()) {
448 .pc_relative_addressing => |pra| switch (pra.decode()) {
449 .adrp => true,
450 else => false,
451 },
452 else => false,
453 },
454 else => false,
455 };
456 },
457 .arm64_pageoff12 => {
458 const inst: aarch64.encoding.Instruction = .read(dest_overlong[0..4]);
459 return switch (inst.decode()) {
460 .data_processing_immediate => |dpi| switch (dpi.decode()) {
461 .add_subtract_immediate => true,
462 else => false,
463 },
464 .load_store => |ls| switch (ls.decode()) {
465 .register_unsigned_immediate => true,
466 else => false,
467 },
468 else => false,
469 };
470 },
471 }
472 }
473 };
474
475 fn apply(reloc: *Reloc, macho: *MachO) Allocator.Error!void {
476 // Don't bother applying the reloc if the bytes we're writing to have a pending move event:
477 if (reloc.node.hasMoved(&macho.mf)) {
478 return;
479 }
480
481 // Also don't bother if a pending move will change the target value:
482 {
483 const target_node: MappedFile.Node.Index.Optional = switch (reloc.bits.target) {
484 .symbol => reloc.target_symbol.ptr(macho).node,
485 .branch => switch (macho.stubs.contains(reloc.target_symbol)) {
486 false => reloc.target_symbol.ptr(macho).node,
487 true => .wrap(macho.known_sections.stubs.node(macho)),
488 },
489 .got => .wrap(macho.known_sections.got.node(macho)),
490 .tlv => @panic("TODO(MachO2): apply TLV relocation"),
491 };
492 if (target_node.unwrap()) |node| {
493 if (node.hasMoved(&macho.mf)) {
494 return;
495 }
496 }
497 }
498
499 switch (reloc.bits.result) {
500 .fixup => {
501 const segment = macho.getNodeSection(reloc.node).segment(macho);
502 const offset_in_segment: u64 = reloc.node.fileLocation(&macho.mf, false).offset -
503 segment.node(macho).fileLocation(&macho.mf, false).offset +
504 reloc.offset;
505 const page_align = macho.targetPageAlign();
506 const page_start: u32 = @intCast(@divFloor(offset_in_segment, page_align.toByteUnits()));
507 const page_end: u32 = @intCast(@divCeil(offset_in_segment + 8, page_align.toByteUnits()));
508 for (page_start..page_end) |page_index| {
509 try macho.dirty_fixup_chain_pages.put(macho.base.comp.gpa, .{
510 .seg = segment,
511 .page = @intCast(page_index),
512 }, {});
513 }
514 return;
515 },
516 .ok => {},
517 .overflowed => macho.overflowed_reloc_count -= 1,
518 .misaligned => macho.misaligned_reloc_count -= 1,
519 }
520
521 if (reloc.applyInner(macho)) {
522 @branchHint(.likely);
523 reloc.bits.result = .ok;
524 } else |err| switch (err) {
525 error.RelocationOverflow => {
526 log.debug("relocation failed (overflow)", .{});
527 reloc.bits.result = .overflowed;
528 macho.overflowed_reloc_count += 1;
529 },
530 error.RelocationMisaligned => {
531 log.debug("relocation failed (misaligned)", .{});
532 reloc.bits.result = .misaligned;
533 macho.misaligned_reloc_count += 1;
534 },
535 }
536 }
537 fn applyInner(r: Reloc, macho: *MachO) error{ RelocationMisaligned, RelocationOverflow }!void {
538 log.debug("applying relocation: nodes[{d}][{d}..] = {t}({t}({q}) + {d})", .{
539 r.node, r.offset, r.bits.type, r.bits.target, r.target_symbol.name(macho), r.addend,
540 });
541
542 const target_endian = macho.targetEndian();
543
544 const dest_slice = r.node.slice(&macho.mf)[r.offset..];
545 const dest_vaddr: u64 = macho.getNodeVAddr(r.node) + r.offset;
546
547 const target_val: u64 = switch (r.bits.target) {
548 .symbol => r.target_symbol.value(macho) +% r.addend,
549 .branch => if (macho.stubs.getIndex(r.target_symbol)) |stub_index| val: {
550 const stub_offset = @as(u64, stub_index) * macho.targetStubSize();
551 const stub_vaddr = macho.known_sections.stubs.vaddr(macho) + stub_offset;
552 break :val stub_vaddr +% r.addend;
553 } else r.target_symbol.value(macho) +% r.addend,
554 .got => val: {
555 const got_entry_index = macho.got.getIndex(r.target_symbol).?;
556 const got_entry_offset = got_entry_index * macho.targetPtrSize();
557 const got_entry_vaddr = macho.known_sections.got.vaddr(macho) + got_entry_offset;
558 break :val got_entry_vaddr +% r.addend;
559 },
560 .tlv => @panic("TODO(MachO2): apply TLV relocation"),
561 };
562
563 switch (r.bits.type) {
564 .abs8 => std.mem.writeInt(
565 u8,
566 dest_slice[0..1],
567 std.math.cast(u8, target_val) orelse return error.RelocationOverflow,
568 target_endian,
569 ),
570 .abs16 => std.mem.writeInt(
571 u16,
572 dest_slice[0..2],
573 std.math.cast(u16, target_val) orelse return error.RelocationOverflow,
574 target_endian,
575 ),
576 .abs32 => std.mem.writeInt(
577 u32,
578 dest_slice[0..4],
579 std.math.cast(u32, target_val) orelse return error.RelocationOverflow,
580 target_endian,
581 ),
582 .abs64 => std.mem.writeInt(
583 u64,
584 dest_slice[0..8],
585 std.math.cast(u64, target_val) orelse return error.RelocationOverflow,
586 target_endian,
587 ),
588 .rel32 => std.mem.writeInt(
589 i32,
590 dest_slice[0..4],
591 std.math.cast(
592 i32,
593 @as(i64, @bitCast(target_val -% dest_vaddr)),
594 ) orelse return error.RelocationOverflow,
595 target_endian,
596 ),
597 .arm64_branchrel26 => {
598 const off: i64 = @bitCast(target_val -% dest_vaddr);
599 const shifted: i64 = off >> 2;
600 if (shifted << 2 != off) {
601 return error.RelocationMisaligned;
602 }
603 const casted = std.math.cast(i26, shifted) orelse return error.RelocationOverflow;
604
605 var inst: aarch64.encoding.Instruction = .read(dest_slice[0..4]);
606 inst.branch_exception_generating_system.unconditional_branch_immediate.group.imm26 = casted;
607 inst.write(dest_slice[0..4]);
608 },
609 .arm64_pagerel21 => {
610 const cur_page: u53 = @intCast(dest_vaddr >> 12);
611 const target_page: u53 = @intCast(target_val >> 12);
612 const page_off: i53 = @bitCast(target_page -% cur_page);
613 const split: packed struct(i21) { lo: u2, hi: i19 } = @bitCast(
614 std.math.cast(i21, page_off) orelse return error.RelocationOverflow,
615 );
616
617 var inst: aarch64.encoding.Instruction = .read(dest_slice[0..4]);
618 inst.data_processing_immediate.pc_relative_addressing.adrp.immhi = split.hi;
619 inst.data_processing_immediate.pc_relative_addressing.adrp.immlo = split.lo;
620 inst.write(dest_slice[0..4]);
621 },
622 .arm64_pageoff12 => {
623 const page_off: u12 = @truncate(target_val);
624
625 // This could be one of several instructions: an `ADD`, an `LDR`, or an `STR`. The
626 // `LDR` and `STR` instructions have scale factors which we need to compensate for,
627 // so we need to decode the instruction to figure out which case we're in.
628 var inst: aarch64.encoding.Instruction = .read(dest_slice[0..4]);
629 switch (inst.decode()) {
630 .data_processing_immediate => {
631 inst.data_processing_immediate.add_subtract_immediate.group.imm12 = page_off;
632 },
633 .load_store => |ls| {
634 const scale: u3 = switch (ls.register_unsigned_immediate.decode()) {
635 .integer => |i| @backingInt(i.group.size), // 1/2/4/8-byte load/store
636 .vector => |v| @backingInt(v.group.opc1.decode(v.group.size)), // 1/2/4/8/16-byte vector load/store
637 };
638 const shifted = page_off >> scale;
639 if (shifted << scale != page_off) {
640 return error.RelocationMisaligned;
641 }
642 inst.load_store.register_unsigned_immediate.group.imm12 = shifted;
643 },
644 else => unreachable,
645 }
646 inst.write(dest_slice[0..4]);
647 },
648 }
649 }
650
651 const Index = enum(u32) {
652 _,
653
654 const Optional = enum(u32) {
655 none = std.math.maxInt(u32),
656 _,
657
658 fn unwrap(o: Optional) ?Index {
659 return switch (o) {
660 .none => null,
661 _ => @fromBackingInt(@backingInt(o)),
662 };
663 }
664 fn wrap(i: Index) Optional {
665 return @bitCast(i);
666 }
667 };
668
669 fn ptr(ri: Reloc.Index, macho: *MachO) *Reloc {
670 return &macho.relocs.items[@backingInt(ri)];
671 }
672 };
673};
674
675const Node = union(enum) {
676 macho,
677 /// Contains the `mach_header_64` structure and all load commands. Inside of the `.segment` node for the `__TEXT` segment.
678 macho_header,
679
680 symtab,
681 strtab,
682
683 indirect_syms,
684 section_indirect_syms: Section,
685
686 linkedit_data: LoadCommand,
687
688 /// Header of the `LC_DYLD_CHAINED_FIXUPS` load command's `.linkedit_data` node, containing the
689 /// `std.macho.dyld_chained_fixups_header` struct.
690 fixup_chains_header,
691 fixup_chains_imports,
692 fixup_chains_symbol_names,
693 fixup_chains_starts,
694 fixup_chains_starts_in_image,
695 fixup_chains_starts_in_segment: Segment,
696
697 segment: Segment,
698 section: Section,
699 section_manual_size: Section,
700
701 input_atom: InputAtom.Index,
702};
703fn getNode(macho: *const MachO, ni: MappedFile.Node.Index) Node {
704 return macho.nodes.get(@backingInt(ni));
705}
706
707pub fn open(
708 arena: Allocator,
709 comp: *Compilation,
710 path: std.Build.Cache.Path,
711 options: link.File.OpenOptions,
712) !*MachO {
713 return create(arena, comp, path, options);
714}
715pub fn createEmpty(
716 arena: Allocator,
717 comp: *Compilation,
718 path: std.Build.Cache.Path,
719 options: link.File.OpenOptions,
720) !*MachO {
721 return create(arena, comp, path, options);
722}
723fn create(
724 arena: Allocator,
725 comp: *Compilation,
726 path: std.Build.Cache.Path,
727 options: link.File.OpenOptions,
728) !*MachO {
729 const io = comp.io;
730
731 const macho = try arena.create(MachO);
732 const file = try path.root_dir.handle.createFile(io, path.sub_path, .{
733 .read = true,
734 .permissions = link.File.determinePermissions(comp.config.output_mode, comp.config.link_mode),
735 });
736
737 macho.* = .{
738 .base = .{
739 .tag = .macho2,
740
741 .comp = comp,
742 .emit = path,
743
744 .file = file,
745 .gc_sections = false,
746 .print_gc_sections = false,
747 .build_id = .none,
748 .allow_shlib_undefined = false,
749 .stack_size = 0,
750 },
751 .options = options,
752
753 .const_prog_node = .none,
754 .synth_prog_node = .none,
755 .input_prog_node = .none,
756
757 .mf = try .init(file, comp.gpa, io),
758 .nodes = .empty,
759
760 .node = undefined,
761 .lc = .{
762 .offsets = .empty,
763 .pagezero = undefined,
764 .symtab = undefined,
765 .dysymtab = undefined,
766 .entry_point = null,
767 },
768 .known_sections = undefined,
769 .segments = .empty,
770 .sections = .empty,
771 .sections_by_name = .empty,
772
773 .dirty_fixup_chain_pages = .empty,
774
775 .input_atoms = .empty,
776
777 .strtab = .empty,
778 .symtab = .empty,
779 .global_symbols = .empty,
780
781 .relocs = .empty,
782 .overflowed_reloc_count = 0,
783 .misaligned_reloc_count = 0,
784
785 .fixup_chain_symbol_names_len = 0,
786 .imports = .empty,
787 .got = .empty,
788 .stubs = .empty,
789
790 .sections_changed_index = .empty,
791
792 .prev_got_offset = 0,
793 };
794 errdefer macho.deinit();
795
796 try macho.initInner();
797
798 return macho;
799}
800fn initInner(macho: *MachO) !void {
801 const comp = macho.base.comp;
802 const gpa = comp.gpa;
803
804 macho.node.macho = .root;
805 try macho.nodes.append(gpa, .macho);
806
807 const arch: enum { aarch64, x86_64 } = switch (comp.getTarget().cpu.arch) {
808 .aarch64 => .aarch64,
809 .x86_64 => .x86_64,
810 else => |arch| std.debug.panic("TODO: unsupported target arch {t}", .{arch}),
811 };
812
813 const mach_header_64 = std.macho.mach_header_64;
814
815 // The `__TEXT` segment is special because it contains the mach header and load commands, so we
816 // must create the text segment node before we can actually call `addSegment`.
817 const text_seg_node = try macho.node.macho.addOnlyHeaderChild(gpa, &macho.mf, .{
818 .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()),
819 .moved = true, // required by `addSegment`
820 .bubbles_moved = false,
821 });
822 try macho.nodes.append(gpa, .{ .segment = undefined });
823
824 macho.node.header = try text_seg_node.addOnlyHeaderChild(gpa, &macho.mf, .{
825 .size = @sizeOf(mach_header_64),
826 .alignment = .@"8",
827 });
828 try macho.nodes.append(gpa, .macho_header);
829
830 const mach_header: *mach_header_64 = @ptrCast(@alignCast(
831 macho.node.header.slice(&macho.mf)[0..@sizeOf(mach_header_64)],
832 ));
833 mach_header.* = .{
834 .cputype = switch (arch) {
835 .aarch64 => std.macho.CPU_TYPE_ARM64,
836 .x86_64 => std.macho.CPU_TYPE_X86_64,
837 },
838 .cpusubtype = switch (arch) {
839 .aarch64 => std.macho.CPU_SUBTYPE_ARM_ALL,
840 .x86_64 => std.macho.CPU_SUBTYPE_X86_64_ALL,
841 },
842 .filetype = switch (comp.config.output_mode) {
843 .Exe => std.macho.MH_EXECUTE,
844 .Lib => switch (comp.config.link_mode) {
845 .static => std.macho.MH_OBJECT,
846 .dynamic => std.macho.MH_DYLIB,
847 },
848 .Obj => std.macho.MH_OBJECT,
849 },
850 .ncmds = 0,
851 .sizeofcmds = 0,
852 .flags = std.macho.MH_DYLDLINK | std.macho.MH_PIE,
853 };
854 if (macho.targetEndian() != std.lang.Endian.native) {
855 std.mem.byteSwapAllFields(mach_header_64, mach_header);
856 }
857
858 // The "__LINKEDIT" segment is special in that it needs to be at the end of the Mach-O file, so
859 // like "__TEXT", we'll construct its node here instead of in `addSegment`.
860 const linkedit_seg_node = try macho.node.macho.addFooterChildBefore(gpa, &macho.mf, .none, .{
861 .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()),
862 .moved = true, // required by `addSegment`
863 .bubbles_moved = false,
864 });
865 try macho.nodes.append(gpa, .{ .segment = undefined });
866
867 // The "__PAGEZERO" segment must come before any other segment load commands. It is inaccessible
868 // and occupies 4 GiB (apparently to catch invalid 32-bit pointer accesses).
869 macho.lc.pagezero = try macho.appendSimpleLoadCommand(std.macho.segment_command_64, .{
870 .cmdsize = @sizeOf(std.macho.segment_command_64),
871 .segname = "__PAGEZERO\x00\x00\x00\x00\x00\x00".*,
872 .vmaddr = 0,
873 .vmsize = 0x1_0000_0000,
874 .fileoff = 0,
875 .filesize = 0,
876 .maxprot = .{},
877 .initprot = .{},
878 .nsects = 0,
879 .flags = 0,
880 });
881 assert(try macho.addSegment(.{
882 .name = "__TEXT",
883 .prot = .{ .READ = true, .EXEC = true },
884 .existing_node = text_seg_node,
885 .flags = .{},
886 }) == .text);
887 const data_seg = try macho.addSegment(.{
888 .name = "__DATA",
889 .prot = .{ .READ = true, .WRITE = true },
890 .flags = .{},
891 });
892 // TODO: we should also create a `__DATA_CONST` segment with `.{ .flags = .read_only = true }`,
893 // and when we do, `loadObject` should use it for `InputSection.Usage.data_const_fixups`.
894 // Unfortunately, doing that now introduces a second floating segment node, which reveals that
895 // dyld has an annoying ordering requirement:
896 //
897 // dyld[77415]: segment '__DATA' file offset out of order
898 //
899 // We need to decide the best way to resolve this. Reordering the segments in the load commands
900 // and in virtual memory is probably too expensive to risk doing often, so we need to preserve
901 // the segment order somehow, e.g. by having all but one segment be a header or footer.
902 const linkedit_seg = try macho.addSegment(.{
903 .name = "__LINKEDIT",
904 .prot = .{ .READ = true },
905 .existing_node = linkedit_seg_node,
906 .flags = .{},
907 });
908
909 _ = try macho.addSection(.{
910 .segment = .text,
911 .name = "__text",
912 .@"align" = .@"4",
913 .flags = .{ .type = .regular, .some_instructions = true },
914 });
915 _ = try macho.addSection(.{
916 .segment = .text,
917 .name = "__const",
918 .@"align" = .@"1",
919 .flags = .{
920 .type = .regular,
921 },
922 });
923 _ = try macho.addSection(.{
924 .segment = data_seg,
925 .name = "__data",
926 .@"align" = .@"1",
927 .flags = .{
928 .type = .regular,
929 },
930 });
931
932 macho.known_sections.got = try macho.addSection(.{
933 .segment = data_seg,
934 .name = "__got",
935 .@"align" = .@"8",
936 .manual_size = true,
937 .flags = .{ .type = .non_lazy_symbol_pointers },
938 // This field holds an offset into the "indirect symbol table" at which this section's
939 // indirect symbol references begin. It will be populated by `flushMoved` of a
940 // `.section_indirect_syms` node we'll create shortly.
941 .reserved1 = 0,
942 });
943
944 macho.known_sections.stubs = try macho.addSection(.{
945 .segment = .text,
946 .name = "__stubs",
947 .@"align" = .@"4",
948 .manual_size = true,
949 .flags = .{
950 .type = .symbol_stubs,
951 .some_instructions = true,
952 .pure_instructions = true,
953 },
954 // As with `__got`, will be populated by `flushMoved` of a `.section_indirect_syms` node.
955 .reserved1 = 0,
956 // `reserved2` holds the size of each stub entry.
957 .reserved2 = macho.targetStubSize(),
958 });
959
960 macho.node.symtab = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
961 .alignment = macho.mf.flags.block_size.max(.of(std.macho.nlist_64)),
962 .moved = true, // so that `symoff` in the symtab load command will be set
963 });
964 try macho.nodes.append(gpa, .symtab);
965
966 macho.node.strtab = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
967 .alignment = macho.mf.flags.block_size,
968 .moved = true, // so that `stroff` in the symtab load command will be set
969 });
970 try macho.nodes.append(gpa, .strtab);
971
972 try macho.node.strtab.ensureMinimumSize(gpa, &macho.mf, 1);
973 macho.node.strtab.slice(&macho.mf)[0] = 0;
974
975 const indirect_syms_node = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
976 .alignment = .@"4",
977 .moved = true, // so that `indirectsymoff` in the dysymtab load command will be set
978 });
979 try macho.nodes.append(gpa, .indirect_syms);
980
981 macho.node.got_indirect_syms = try indirect_syms_node.addFloatingChild(gpa, &macho.mf, .{
982 .alignment = .@"4",
983 .moved = true, // so that `reserved1` in the section will be set
984 });
985 try macho.nodes.append(gpa, .{ .section_indirect_syms = macho.known_sections.got });
986
987 macho.node.stubs_indirect_syms = try indirect_syms_node.addFloatingChild(gpa, &macho.mf, .{
988 .alignment = .@"4",
989 .moved = true, // so that `reserved1` in the section will be set
990 });
991 try macho.nodes.append(gpa, .{ .section_indirect_syms = macho.known_sections.stubs });
992
993 const chained_fixups_lc = try macho.appendSimpleLoadCommand(std.macho.linkedit_data_command, .{
994 .cmd = .DYLD_CHAINED_FIXUPS,
995 .dataoff = 0,
996 .datasize = 0,
997 });
998 const chained_fixups_node = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{
999 .alignment = macho.mf.flags.block_size.max(.@"8"),
1000 .moved = true, // so that `dataoff` will be set
1001 .bubbles_moved = false,
1002 });
1003 try macho.nodes.append(gpa, .{ .linkedit_data = chained_fixups_lc });
1004
1005 macho.node.fixup_chains_header = try chained_fixups_node.addOnlyHeaderChild(gpa, &macho.mf, .{
1006 .size = @sizeOf(std.macho.dyld_chained_fixups_header),
1007 .alignment = .@"4",
1008 });
1009 try macho.nodes.append(gpa, .fixup_chains_header);
1010
1011 {
1012 const chained_fixups_hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
1013 macho.node.fixup_chains_header.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_fixups_header)],
1014 ));
1015 chained_fixups_hdr.* = .{
1016 .fixups_version = 0,
1017 .starts_offset = 0,
1018 .imports_offset = 0,
1019 .symbols_offset = 0,
1020 .imports_count = 0,
1021 .imports_format = .addend_64,
1022 .symbols_format = .uncompressed,
1023 };
1024 if (macho.targetEndian() != std.lang.Endian.native) {
1025 std.mem.byteSwapAllFields(std.macho.dyld_chained_fixups_header, chained_fixups_hdr);
1026 }
1027 }
1028
1029 // We are now going to create the `std.macho.dyld_chained_starts_in_segment` nodes for every
1030 // segment, which means we are assuming that no more segments will be created. Therefore, all
1031 // `addSegment` calls must come *before* this point.
1032
1033 const fixup_chains_starts = try chained_fixups_node.addFloatingChild(gpa, &macho.mf, .{
1034 .size = 0,
1035 .alignment = .@"8",
1036 .moved = true, // so that `starts_offset` is populated
1037 });
1038 try macho.nodes.append(gpa, .fixup_chains_starts);
1039
1040 macho.node.fixup_chains_starts_in_image = try fixup_chains_starts.addOnlyHeaderChild(gpa, &macho.mf, .{
1041 .size = 8 + 4 * macho.segments.len,
1042 .alignment = .@"4",
1043 });
1044 try macho.nodes.append(gpa, .fixup_chains_starts_in_image);
1045
1046 {
1047 const chained_starts_in_image: []u32 = @ptrCast(@alignCast(
1048 macho.node.fixup_chains_starts_in_image.slice(&macho.mf),
1049 ));
1050 chained_starts_in_image[0] = @intCast(macho.segments.len + 1); // +1 for `__PAGEZERO`
1051 chained_starts_in_image[1] = 0; // `__PAGEZERO`
1052 chained_starts_in_image[macho.segments.len + 1] = 0; // `__LINKEDIT`
1053 for (
1054 macho.segments.items(.fixup_chain_starts)[0 .. macho.segments.len - 1],
1055 0..,
1056 ) |*starts_in_segment, segment_usize| {
1057 const segment: Segment = @fromBackingInt(@intCast(segment_usize));
1058 assert(!std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
1059 starts_in_segment.* = .wrap(try fixup_chains_starts.addFloatingChild(gpa, &macho.mf, .{
1060 .alignment = .@"8",
1061 .size = @sizeOf(std.macho.dyld_chained_starts_in_segment),
1062 .moved = true,
1063 }));
1064 try macho.nodes.append(gpa, .{ .fixup_chains_starts_in_segment = segment });
1065 const starts_hdr: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
1066 starts_in_segment.unwrap().?.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
1067 ));
1068 starts_hdr.* = .{
1069 .size = @sizeOf(std.macho.dyld_chained_starts_in_segment),
1070 .page_size = @intCast(macho.targetPageAlign().toByteUnits()),
1071 .pointer_format = .@"64",
1072 .segment_offset = 0, // populated by `flushMoved` of the corresponding segment
1073 .max_valid_pointer = 0,
1074 .page_count = 0,
1075 };
1076 if (macho.targetEndian() != std.lang.Endian.native) {
1077 std.mem.byteSwapAllFields(std.macho.dyld_chained_starts_in_segment, starts_hdr);
1078 }
1079 }
1080 }
1081
1082 // This is a footer because dyld has an undocumented requirement that the imports must be
1083 // ordered after the starts.
1084 macho.node.fixup_chains_imports = try chained_fixups_node.addOnlyFooterChild(gpa, &macho.mf, .{
1085 .alignment = .@"8",
1086 .moved = true, // so that `imports_offset` is populated
1087 });
1088 try macho.nodes.append(gpa, .fixup_chains_imports);
1089
1090 // This is a footer because `dyld_info` complains if the imports appear after the symbol name
1091 // string table. Since there's no length associated with that string table, `dyld_info` can only
1092 // be sure they don't overlap if the string table is the last thing in the chained fixups data.
1093 macho.node.fixup_chains_symbol_names = try chained_fixups_node.addFooterChildBefore(gpa, &macho.mf, .none, .{
1094 .alignment = .@"8", // so that the alignment of `macho.node.fixup_chains_imports` is not violated
1095 .moved = true, // so that `symbols_offset` is populated
1096 });
1097 try macho.nodes.append(gpa, .fixup_chains_symbol_names);
1098
1099 macho.lc.symtab = try macho.appendSimpleLoadCommand(std.macho.symtab_command, .{
1100 .symoff = 0,
1101 .nsyms = 0,
1102 .stroff = 0,
1103 .strsize = 1, // empty string (null terminator)
1104 });
1105
1106 // Nowadays, `LC_DYSYMTAB` is redundant with chained fixups and the symbol export trie. However,
1107 // if we omit it, some macOS tooling does get annoyed at us. Therefore, we try to emit a minimum
1108 // viable `LC_DYSYMTAB` command, just to keep the tooling happy.
1109 macho.lc.dysymtab = try macho.appendSimpleLoadCommand(std.macho.dysymtab_command, .{
1110 // Some tooling will check that the number of symbols according to the `LC_DYSYMTAB` command
1111 // matches the actual length of the symbol table in `LC_SYMTAB`. However, we don't want to
1112 // need to partition our symbol table into local/extdef/undef symbols, because that makes
1113 // incremental linking less efficient. Luckily, since this information is basically useless
1114 // nowadays, nothing seems to mind if we just say that every symbol is local. So, whenever
1115 // we add any symbol to the symtab, we will increase `ilocalsym` in lockstep with the symbol
1116 // count. We also need to increment `iextdefsym` and `iundefsym`, because some tooling may
1117 // check that local symbols come before extdef and undef symbols here. I don't even know why
1118 // they *have* the `iextdefsym` and `iundefsym` fields if they're only going to allow them
1119 // to have specific values...
1120 .ilocalsym = 0, // always 0
1121 .nlocalsym = 0, // equal to symbol count
1122 .iextdefsym = 0, // equal to symbol count
1123 .nextdefsym = 0, // always 0
1124 .iundefsym = 0, // equal to symbol count
1125 .nundefsym = 0, // always 0
1126
1127 // We *do* need to emit a valid indirect symbol table, which is an array of symbol indices
1128 // indicating which symbols are being targeted by entries in `__got` and `__stubs`. Although
1129 // not required for fixups, some debug tooling will still try to read this table without any
1130 // validation, so will just crash if it's absent or invalid. Luckily, the indirection here
1131 // actually makes this pretty straightforward for us to emit.
1132 .indirectsymoff = 0, // populated by `flushMachOFileOffset` of the `.indirect_syms` node
1133 .nindirectsyms = 0, // populated by `flushResized` of the `.indirect_syms` node
1134
1135 // These fields are *required* to be zeroed in modern Mach-O binaries.
1136 .tocoff = 0,
1137 .ntoc = 0,
1138 .modtaboff = 0,
1139 .nmodtab = 0,
1140 .extrefsymoff = 0,
1141 .nextrefsyms = 0,
1142 .extreloff = 0,
1143 .nextrel = 0,
1144 .locreloff = 0,
1145 .nlocrel = 0,
1146 });
1147
1148 const want_dyld_path: bool = switch (comp.config.output_mode) {
1149 .Obj => false,
1150 .Lib => switch (comp.config.link_mode) {
1151 .static => false,
1152 .dynamic => comp.root_mod.resolved_target.is_explicit_dynamic_linker,
1153 },
1154 .Exe => true,
1155 };
1156 if (want_dyld_path) {
1157 if (comp.getTarget().dynamic_linker.get()) |dyld_path| {
1158 const cmd = try macho.appendLoadCommand(std.macho.dylinker_command, .{
1159 .cmd = .LOAD_DYLINKER,
1160 .cmdsize = std.mem.alignForward(u32, @intCast(
1161 @sizeOf(std.macho.dylinker_command) + dyld_path.len + 1,
1162 ), 8),
1163 .name = @sizeOf(std.macho.dylinker_command),
1164 });
1165 @memcpy(cmd.trailing[0..dyld_path.len], dyld_path);
1166 @memset(cmd.trailing[dyld_path.len..], 0);
1167 }
1168 }
1169
1170 entry: switch (macho.options.entry) {
1171 .default => switch (comp.config.output_mode) {
1172 .Exe => continue :entry .enabled,
1173 .Lib, .Obj => continue :entry .disabled,
1174 },
1175 .disabled => {},
1176 .enabled, .named => {
1177 macho.lc.entry_point = try macho.appendSimpleLoadCommand(
1178 std.macho.entry_point_command,
1179 .{ .entryoff = 0, .stacksize = macho.options.stack_size orelse 0 },
1180 );
1181 },
1182 }
1183
1184 _ = try macho.appendSimpleLoadCommand(std.macho.uuid_command, .{
1185 .uuid = @splat(0),
1186 });
1187
1188 try macho.updateSegmentLoadAddresses();
1189}
1190fn addInternalSymbol(macho: *MachO, opts: struct {
1191 name: []const u8,
1192 value: Symbol.InitValue,
1193}) Error!Symbol.Index {
1194 const gpa = macho.base.comp.gpa;
1195
1196 try macho.symtab.ensureUnusedCapacity(gpa, 1);
1197 try macho.node.symtab.ensureMinimumSize(gpa, &macho.mf, (macho.symtab.items.len + 1) * @sizeOf(std.macho.nlist_64));
1198
1199 const strx = try macho.string(opts.name);
1200
1201 const symtab_ptr = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
1202 assert(macho.targetLoad(&symtab_ptr.nsyms) == macho.symtab.items.len);
1203 macho.targetStore(&symtab_ptr.nsyms, @intCast(macho.symtab.items.len + 1));
1204
1205 {
1206 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
1207 // See comment in `initInner` (around the creation of this `dysymtab_command`) for details
1208 // on why we're doing this.
1209 macho.targetStore(&dysymtab_cmd.nlocalsym, @intCast(macho.symtab.items.len + 1));
1210 macho.targetStore(&dysymtab_cmd.iextdefsym, @intCast(macho.symtab.items.len + 1));
1211 macho.targetStore(&dysymtab_cmd.iundefsym, @intCast(macho.symtab.items.len + 1));
1212 }
1213
1214 const sym_index: Symbol.Index = @fromBackingInt(@intCast(macho.symtab.items.len));
1215 macho.symtab.appendAssumeCapacity(.{
1216 .node = switch (opts.value) {
1217 .abs => .none,
1218 .addr_in_node => |addr| .wrap(addr.node),
1219 },
1220 .prev_in_node = .none,
1221 .next_in_node = .none,
1222 .first_target_reloc = .none,
1223 });
1224 if (sym_index.ptr(macho).node.unwrap()) |node| {
1225 const head_ptr = macho.nodeFirstSymbolPtr(node);
1226 sym_index.ptr(macho).next_in_node = head_ptr.*;
1227 if (head_ptr.*.unwrap()) |next_symbol| {
1228 next_symbol.ptr(macho).prev_in_node = .wrap(sym_index);
1229 }
1230 head_ptr.* = .wrap(sym_index);
1231 }
1232
1233 sym_index.nlist(macho).* = .{
1234 .n_strx = @backingInt(strx),
1235 .n_type = .{ .bits = .{
1236 .ext = false,
1237 .type = switch (opts.value) {
1238 .abs => .abs,
1239 .addr_in_node => .sect,
1240 },
1241 .pext = false,
1242 .is_stab = 0,
1243 } },
1244 .n_sect = switch (opts.value) {
1245 .abs => 0,
1246 .addr_in_node => |addr| macho.getNodeSection(addr.node).index(macho),
1247 },
1248 .n_desc = .{
1249 .arm_thumb_def = false,
1250 .referenced_dynamically = false,
1251 .discarded_or_no_dead_strip = false,
1252 .weak_ref = false,
1253 .weak_def_or_ref_to_weak = false,
1254 .symbol_resolver = false,
1255 .alt_entry = false,
1256 },
1257 .n_value = switch (opts.value) {
1258 .abs => |x| x,
1259 .addr_in_node => |addr| macho.getNodeVAddr(addr.node) + addr.offset,
1260 },
1261 };
1262 if (macho.targetEndian() != std.lang.Endian.native) {
1263 std.mem.byteSwapAllFields(std.macho.nlist_64, sym_index.nlist(macho));
1264 }
1265
1266 return sym_index;
1267}
1268fn addGlobalSymbol(macho: *MachO, opts: struct {
1269 name: []const u8,
1270 linkage: std.lang.GlobalLinkage,
1271 visibility: enum { default, hidden },
1272 /// `null` means the symbol is undefined.
1273 value: ?Symbol.InitValue,
1274}) (Error || error{MultipleDefinitions})!Symbol.Index {
1275 const gpa = macho.base.comp.gpa;
1276
1277 try macho.symtab.ensureUnusedCapacity(gpa, 1);
1278 try macho.node.symtab.ensureMinimumSize(gpa, &macho.mf, (macho.symtab.items.len + 1) * @sizeOf(std.macho.nlist_64));
1279
1280 const adapter: Symbol.GlobalAdapter = .{ .macho = macho };
1281 const gop = try macho.global_symbols.getOrPutAdapted(gpa, opts.name, adapter);
1282 if (gop.found_existing) {
1283 const symbol = gop.key_ptr.*;
1284
1285 switch (opts.visibility) {
1286 .default => {},
1287 .hidden => {
1288 var n_type = macho.targetLoad(&symbol.nlist(macho).n_type);
1289 n_type.bits.pext = true;
1290 switch (macho.machFiletype()) {
1291 .OBJECT => {},
1292 .EXECUTE, .DYLIB => n_type.bits.ext = false,
1293 }
1294 macho.targetStore(&symbol.nlist(macho).n_type, n_type);
1295 // TODO: once the exports trie is implemented, remove `symbol` from it here
1296 },
1297 }
1298
1299 const existing_defined: bool, const existing_linkage: std.lang.GlobalLinkage = existing: {
1300 const n_type = macho.targetLoad(&symbol.nlist(macho).n_type);
1301 const n_desc = macho.targetLoad(&symbol.nlist(macho).n_desc);
1302 break :existing switch (n_type.bits.type) {
1303 .undf => .{ false, if (n_desc.weak_ref) .weak else .strong },
1304 else => .{ true, if (n_desc.weak_def_or_ref_to_weak) .weak else .strong },
1305 };
1306 };
1307
1308 const override: bool = if (opts.value != null) override: {
1309 if (!existing_defined) break :override true;
1310 // Two definitions of the same name!
1311 break :override switch (opts.linkage) {
1312 .strong => switch (existing_linkage) {
1313 .strong => return error.MultipleDefinitions,
1314 .weak => true,
1315 },
1316 .weak => false,
1317 };
1318 } else override: {
1319 if (existing_defined) break :override false;
1320 break :override switch (opts.linkage) {
1321 .strong => switch (existing_linkage) {
1322 .strong => false,
1323 .weak => true,
1324 },
1325 .weak => false,
1326 };
1327 };
1328
1329 if (override) {
1330 try macho.setGlobalSymbolValue(symbol, .{
1331 .linkage = opts.linkage,
1332 .value = opts.value,
1333 });
1334 }
1335
1336 return symbol;
1337 }
1338 errdefer _ = macho.global_symbols.pop().?;
1339
1340 const strx = try macho.string(opts.name);
1341
1342 const symtab_ptr = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
1343 assert(macho.targetLoad(&symtab_ptr.nsyms) == macho.symtab.items.len);
1344 macho.targetStore(&symtab_ptr.nsyms, @intCast(macho.symtab.items.len + 1));
1345
1346 {
1347 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
1348 // See comment in `initInner` (around the creation of this `dysymtab_command`) for details
1349 // on why we're doing this.
1350 macho.targetStore(&dysymtab_cmd.nlocalsym, @intCast(macho.symtab.items.len + 1));
1351 macho.targetStore(&dysymtab_cmd.iextdefsym, @intCast(macho.symtab.items.len + 1));
1352 macho.targetStore(&dysymtab_cmd.iundefsym, @intCast(macho.symtab.items.len + 1));
1353 }
1354
1355 const symbol: Symbol.Index = @fromBackingInt(@intCast(macho.symtab.items.len));
1356 gop.key_ptr.* = symbol;
1357 macho.symtab.appendAssumeCapacity(.{
1358 .node = .none,
1359 .prev_in_node = .none,
1360 .next_in_node = .none,
1361 .first_target_reloc = .none,
1362 });
1363 symbol.nlist(macho).* = .{
1364 .n_strx = @backingInt(strx),
1365 .n_type = .{ .bits = .{
1366 .ext = switch (macho.machFiletype()) {
1367 .OBJECT => true,
1368 .EXECUTE, .DYLIB => switch (opts.visibility) {
1369 .default => true,
1370 .hidden => false,
1371 },
1372 },
1373 .pext = switch (opts.visibility) {
1374 .default => false,
1375 .hidden => true,
1376 },
1377 .type = .undf,
1378 .is_stab = 0,
1379 } },
1380 .n_sect = 0,
1381 .n_desc = .{
1382 .arm_thumb_def = false,
1383 .referenced_dynamically = false,
1384 .discarded_or_no_dead_strip = false,
1385 .weak_ref = false,
1386 .weak_def_or_ref_to_weak = false,
1387 .symbol_resolver = false,
1388 .alt_entry = false,
1389 },
1390 .n_value = 0,
1391 };
1392 if (macho.targetEndian() != std.lang.Endian.native) {
1393 std.mem.byteSwapAllFields(std.macho.nlist_64, symbol.nlist(macho));
1394 }
1395
1396 try macho.setGlobalSymbolValue(symbol, .{
1397 .linkage = opts.linkage,
1398 .value = opts.value,
1399 });
1400
1401 return symbol;
1402}
1403fn setGlobalSymbolValue(macho: *MachO, symbol: Symbol.Index, opts: struct {
1404 linkage: std.lang.GlobalLinkage,
1405 value: ?Symbol.InitValue,
1406}) Allocator.Error!void {
1407 const gpa = macho.base.comp.gpa;
1408
1409 log.debug("setGlobalSymbolValue({q}, {?any})", .{ symbol.name(macho), opts.value });
1410
1411 const nlist = symbol.nlist(macho);
1412
1413 {
1414 const weak: bool = switch (opts.linkage) {
1415 .strong => false,
1416 .weak => true,
1417 };
1418 var n_desc = macho.targetLoad(&nlist.n_desc);
1419 n_desc.weak_ref = opts.value == null and weak;
1420 n_desc.weak_def_or_ref_to_weak = opts.value != null and weak;
1421 macho.targetStore(&nlist.n_desc, n_desc);
1422 }
1423
1424 // Remove this symbol from its old node.
1425 if (symbol.ptr(macho).node.unwrap()) |old_node| {
1426 if (symbol.ptr(macho).next_in_node.unwrap()) |next_symbol| {
1427 assert(next_symbol.ptr(macho).prev_in_node == Symbol.Index.Optional.wrap(symbol));
1428 next_symbol.ptr(macho).prev_in_node = symbol.ptr(macho).prev_in_node;
1429 }
1430 if (symbol.ptr(macho).prev_in_node.unwrap()) |prev_symbol| {
1431 assert(prev_symbol.ptr(macho).next_in_node == Symbol.Index.Optional.wrap(symbol));
1432 prev_symbol.ptr(macho).next_in_node = symbol.ptr(macho).next_in_node;
1433 } else {
1434 // We're the head of the list.
1435 macho.nodeFirstSymbolPtr(old_node).* = symbol.ptr(macho).next_in_node;
1436 }
1437 }
1438
1439 if (opts.value) |value| switch (value) {
1440 .abs => |val| {
1441 var n_type = macho.targetLoad(&nlist.n_type);
1442 n_type.bits.type = .abs;
1443 macho.targetStore(&nlist.n_type, n_type);
1444
1445 macho.targetStore(&nlist.n_sect, 0);
1446 macho.targetStore(&nlist.n_value, val);
1447
1448 symbol.ptr(macho).node = .none;
1449 symbol.ptr(macho).prev_in_node = .none;
1450 symbol.ptr(macho).next_in_node = .none;
1451 },
1452 .addr_in_node => |addr| {
1453 var n_type = macho.targetLoad(&nlist.n_type);
1454 n_type.bits.type = .sect;
1455 macho.targetStore(&nlist.n_type, n_type);
1456
1457 macho.targetStore(&nlist.n_sect, macho.getNodeSection(addr.node).index(macho));
1458 macho.targetStore(&nlist.n_value, macho.getNodeVAddr(addr.node) + addr.offset);
1459
1460 const head_ptr = macho.nodeFirstSymbolPtr(addr.node);
1461 symbol.ptr(macho).node = .wrap(addr.node);
1462 symbol.ptr(macho).prev_in_node = .none;
1463 symbol.ptr(macho).next_in_node = head_ptr.*;
1464 if (head_ptr.*.unwrap()) |next_symbol| {
1465 next_symbol.ptr(macho).prev_in_node = .wrap(symbol);
1466 }
1467 head_ptr.* = .wrap(symbol);
1468 },
1469 } else {
1470 var n_type = macho.targetLoad(&nlist.n_type);
1471 n_type.bits.type = .undf;
1472 macho.targetStore(&nlist.n_type, n_type);
1473
1474 macho.targetStore(&nlist.n_sect, 0);
1475 macho.targetStore(&nlist.n_value, 0);
1476
1477 symbol.ptr(macho).node = .none;
1478 symbol.ptr(macho).prev_in_node = .none;
1479 symbol.ptr(macho).next_in_node = .none;
1480 }
1481
1482 // If `symbol` has a GOT entry, it needs regenerating.
1483 if (macho.got.getIndex(symbol)) |got_index| {
1484 if (!macho.known_sections.got.node(macho).hasMoved(&macho.mf)) {
1485 const segment = macho.known_sections.got.segment(macho);
1486 const page_index: u16 = @intCast(@divFloor(
1487 macho.prev_got_offset + got_index * macho.targetPtrSize(),
1488 macho.targetPageAlign().toByteUnits(),
1489 ));
1490 try macho.dirty_fixup_chain_pages.put(gpa, .{ .seg = segment, .page = page_index }, {});
1491 }
1492 }
1493
1494 // If `symbol` has an `imports` entry, it should not exist.
1495 if (macho.imports.getIndex(symbol)) |import_index| {
1496 // TODO: delete the import. For now we just mark it as weak so that dyld doesn't complain.
1497 const imports: []std.macho.dyld_chained_import_addend64 = @ptrCast(@alignCast(
1498 macho.node.fixup_chains_imports.slice(&macho.mf)[0 .. macho.imports.count() * @sizeOf(std.macho.dyld_chained_import_addend64)],
1499 ));
1500 macho.targetStore(&imports[import_index].bits, .{
1501 .lib_ordinal = macho.targetLoad(&imports[import_index].bits).lib_ordinal,
1502 .name_offset = macho.targetLoad(&imports[import_index].bits).name_offset,
1503 .weak_import = true,
1504 });
1505 }
1506}
1507fn globalByName(macho: *MachO, name: []const u8) Symbol.Index.Optional {
1508 const adapter: Symbol.GlobalAdapter = .{ .macho = macho };
1509 if (macho.global_symbols.getKeyAdapted(name, adapter)) |symbol| {
1510 return .wrap(symbol);
1511 } else {
1512 return .none;
1513 }
1514}
1515
1516/// All calls to this function must occur in `initInner`, and must be before we initialize fixup
1517/// chains, as fixup chains requires some per-segment tracking and the logic to initialize them
1518/// assumes that no more segments will be created.
1519fn addSegment(macho: *MachO, opts: struct {
1520 name: []const u8,
1521 prot: std.macho.vm_prot_t,
1522 /// The caller is allowed to create the section's node themselves, in which case it is passed in
1523 /// here. The caller must initialize the `MachO.nodes` entry to `.{ .segment = undefined }`, and
1524 /// the node must be created with the option `.moved = true`.
1525 existing_node: ?MappedFile.Node.Index = null,
1526 /// This matches the layout of `std.macho.segment_command_64.flags`.
1527 flags: packed struct(u32) {
1528 _unused0: u4 = 0,
1529 read_only: bool = false,
1530 _unused1: u27 = 0,
1531 },
1532}) Error!Segment {
1533 const gpa = macho.base.comp.gpa;
1534
1535 const segment_command_64 = std.macho.segment_command_64;
1536
1537 // We limit the number of segments to 255, because there can only be 255 *sections*, so there's
1538 // no need to allow more. See corresponding check in `addSection`.
1539 if (macho.segments.len == 255) {
1540 return macho.base.comp.link_diags.fail("maximum segment count exceeded", .{});
1541 }
1542
1543 try macho.nodes.ensureUnusedCapacity(gpa, 1);
1544 try macho.segments.ensureUnusedCapacity(gpa, 1);
1545
1546 const root_ni: MappedFile.Node.Index = .root;
1547
1548 const lc = try macho.appendSimpleLoadCommand(std.macho.segment_command_64, .{
1549 .cmdsize = @sizeOf(segment_command_64),
1550 .segname = name: {
1551 var name: [16]u8 = undefined;
1552 @memcpy(name[0..opts.name.len], opts.name);
1553 @memset(name[opts.name.len..], 0);
1554 break :name name;
1555 },
1556 .vmaddr = 0,
1557 .vmsize = 0,
1558 .fileoff = 0, // to be set by `flushMoved`
1559 .filesize = 0,
1560 .maxprot = opts.prot,
1561 .initprot = opts.prot,
1562 .nsects = 0,
1563 .flags = @bitCast(opts.flags),
1564 });
1565
1566 const segment: Segment = @fromBackingInt(@intCast(macho.segments.len));
1567 const segment_node: MappedFile.Node.Index = node: {
1568 if (opts.existing_node) |node| {
1569 assert(node.hasMoved(&macho.mf)); // so that `fileoff` will be set
1570 assert(macho.nodes.items(.tags)[@backingInt(node)] == .segment);
1571 macho.nodes.set(@backingInt(node), .{ .segment = segment });
1572 break :node node;
1573 }
1574 const node = try root_ni.addFloatingChild(gpa, &macho.mf, .{
1575 .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()),
1576 .moved = true, // so that `fileoff` will be set
1577 .bubbles_moved = false,
1578 });
1579 macho.nodes.appendAssumeCapacity(.{ .segment = segment });
1580 break :node node;
1581 };
1582 macho.segments.appendAssumeCapacity(.{
1583 .lc = lc,
1584 .node = segment_node,
1585 .fixup_chain_starts = .none,
1586 });
1587
1588 return segment;
1589}
1590
1591const String = enum(u32) {
1592 empty = 0,
1593 _,
1594 fn slice(s: String, macho: *const MachO) [:0]const u8 {
1595 const overlong = macho.node.strtab.slice(&macho.mf)[@backingInt(s)..];
1596 return overlong[0..std.mem.findScalar(u8, overlong, 0).? :0];
1597 }
1598
1599 const Adapter = struct {
1600 macho: *const MachO,
1601 pub fn eql(ctx: Adapter, lhs_slice: []const u8, rhs: String, _: usize) bool {
1602 const macho = ctx.macho;
1603 return std.mem.eql(u8, lhs_slice, rhs.slice(macho));
1604 }
1605 pub fn hash(ctx: Adapter, s: []const u8) u32 {
1606 _ = ctx;
1607 return std.array_hash_map.hashString(s);
1608 }
1609 };
1610};
1611
1612fn string(macho: *MachO, slice: []const u8) Error!String {
1613 const gpa = macho.base.comp.gpa;
1614
1615 const old_strsize = macho.targetLoad(
1616 &macho.lc.symtab.ptr(macho, std.macho.symtab_command).strsize,
1617 );
1618 try macho.node.strtab.ensureMinimumSize(gpa, &macho.mf, old_strsize + slice.len + 1);
1619
1620 try macho.strtab.ensureUnusedCapacity(gpa, 1);
1621
1622 errdefer comptime unreachable;
1623
1624 const adapter: String.Adapter = .{ .macho = macho };
1625 const gop = macho.strtab.getOrPutAssumeCapacityAdapted(slice, adapter);
1626 if (!gop.found_existing) {
1627 macho.targetStore(
1628 &macho.lc.symtab.ptr(macho, std.macho.symtab_command).strsize,
1629 @intCast(old_strsize + slice.len + 1),
1630 );
1631 gop.key_ptr.* = @fromBackingInt(old_strsize);
1632 const dest_slice = macho.node.strtab.slice(&macho.mf)[old_strsize..];
1633 @memcpy(dest_slice[0..slice.len], slice);
1634 dest_slice[slice.len] = 0;
1635 }
1636 return gop.key_ptr.*;
1637}
1638
1639fn updateSegmentLoadAddresses(macho: *MachO) Error!void {
1640 const gpa = macho.base.comp.gpa;
1641
1642 // Grow all `.fixup_chains_starts_in_segment` node to the necessary length. We need to do this
1643 // before updating the load addresses because this could cause `__LINKEDIT` to grow.
1644 for (0..macho.segments.len) |segment_raw| {
1645 const segment: Segment = @fromBackingInt(@intCast(segment_raw));
1646
1647 const starts_node = segment.fixupChainStartsNode(macho).unwrap() orelse {
1648 assert(std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
1649 continue;
1650 };
1651
1652 const size = segment.node(macho).location(&macho.mf).resolve(&macho.mf)[1];
1653
1654 const page_count: u16 = @intCast(@divExact(size, macho.targetPageAlign().toByteUnits()));
1655 try starts_node.ensureMinimumSize(gpa, &macho.mf, MappedFile.Alignment.@"8".forward(
1656 @sizeOf(std.macho.dyld_chained_starts_in_segment) + page_count * 2,
1657 ));
1658 const starts_hdr: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
1659 starts_node.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
1660 ));
1661 const old_page_count = macho.targetLoad(&starts_hdr.page_count);
1662 macho.targetStore(&starts_hdr.page_count, page_count);
1663 macho.targetStore(&starts_hdr.size, @sizeOf(std.macho.dyld_chained_starts_in_segment) + 2 * page_count);
1664 const page_starts: []u16 = @ptrCast(@alignCast(
1665 starts_node.slice(&macho.mf)[@sizeOf(std.macho.dyld_chained_starts_in_segment) - 2 ..],
1666 ));
1667 @memset(page_starts[old_page_count..page_count], 0xFFFF);
1668 }
1669
1670 // TODO: this segment load address allocation logic is bad because it moves segments quite
1671 // frequently. We should avoid that by introducing padding between segments so that they have
1672 // space to grow in-place. We could also consider allowing a segment which can't fit to jump
1673 // over another segment, like in `Elf2.allocateSegmentLoadAddress`.
1674 var cur_addr: u64 = start_addr: {
1675 const pagezero_ptr = macho.lc.pagezero.ptr(macho, std.macho.segment_command_64);
1676 assert(macho.targetLoad(&pagezero_ptr.vmaddr) == 0);
1677 break :start_addr macho.targetLoad(&pagezero_ptr.vmsize);
1678 };
1679
1680 for (0..macho.segments.len) |segment_raw| {
1681 const segment: Segment = @fromBackingInt(@intCast(segment_raw));
1682
1683 _, const size: u64 = segment.node(macho).location(&macho.mf).resolve(&macho.mf);
1684 const cmd_ptr = segment.lcPtr(macho);
1685
1686 macho.targetStore(&cmd_ptr.vmsize, size);
1687
1688 const old_addr = macho.targetLoad(&cmd_ptr.vmaddr);
1689 if (old_addr >= cur_addr) {
1690 cur_addr = old_addr;
1691 } else {
1692 macho.targetStore(&cmd_ptr.vmaddr, cur_addr);
1693 try segment.node(macho).childrenMoved(gpa, &macho.mf);
1694 }
1695
1696 cur_addr = macho.targetPageAlign().forward(cur_addr + size);
1697 }
1698}
1699
1700/// Like `appendLoadCommand`, but asserts that `cmd.cmdsize` exactly equals `@sizeOf(Cmd)` and does
1701/// not return the (empty) trailing slice.
1702fn appendSimpleLoadCommand(
1703 macho: *MachO,
1704 comptime Cmd: type,
1705 cmd: Cmd,
1706) Error!LoadCommand {
1707 comptime assert(@sizeOf(Cmd) % 8 == 0);
1708 assert(cmd.cmdsize == @sizeOf(Cmd));
1709 return (try macho.appendLoadCommand(Cmd, cmd)).lc;
1710}
1711
1712/// Asserts that the first two fields of `Cmd` are `cmd: LC` and `cmdsize: u32`, and that the value
1713/// of `lc.cmdsize` is 8-byte aligned.
1714fn appendLoadCommand(
1715 macho: *MachO,
1716 comptime Cmd: type,
1717 cmd: Cmd,
1718) Error!struct {
1719 lc: LoadCommand,
1720 trailing: []u8,
1721} {
1722 const gpa = macho.base.comp.gpa;
1723
1724 try macho.lc.offsets.ensureUnusedCapacity(gpa, 1);
1725
1726 comptime {
1727 const s = @typeInfo(Cmd).@"struct";
1728 assert(s.layout == .@"extern");
1729 // First field of load command should be `cmd: LC`
1730 assert(std.mem.eql(u8, s.field_names[0], "cmd"));
1731 assert(s.field_types[0] == std.macho.LC);
1732 // Second field of load command should be `cmdsize: u32`
1733 assert(std.mem.eql(u8, s.field_names[1], "cmdsize"));
1734 assert(s.field_types[1] == u32);
1735 }
1736
1737 const cmd_size = @as(*const std.macho.load_command, @ptrCast(&cmd)).cmdsize;
1738 assert(cmd_size % 8 == 0);
1739
1740 const mach_header_64 = std.macho.mach_header_64;
1741 const old_cmds_size: u32 = size: {
1742 const mach_header: *const mach_header_64 = @ptrCast(@alignCast(
1743 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(mach_header_64)],
1744 ));
1745 break :size macho.targetLoad(&mach_header.sizeofcmds);
1746 };
1747 const new_cmds_size = old_cmds_size + cmd_size;
1748 try macho.node.header.ensureMinimumSize(gpa, &macho.mf, @sizeOf(mach_header_64) + new_cmds_size);
1749
1750 const header_slice = macho.node.header.slice(&macho.mf);
1751 const mach_header: *mach_header_64 = @ptrCast(@alignCast(
1752 header_slice[0..@sizeOf(mach_header_64)],
1753 ));
1754 macho.targetStore(&mach_header.sizeofcmds, new_cmds_size);
1755 macho.targetStore(&mach_header.ncmds, macho.targetLoad(&mach_header.ncmds) + 1);
1756
1757 const cmd_slice: []align(8) u8 = @alignCast(
1758 header_slice[@sizeOf(mach_header_64) + old_cmds_size ..][0..cmd_size],
1759 );
1760
1761 const cmd_ptr: *Cmd = @ptrCast(cmd_slice[0..@sizeOf(Cmd)]);
1762 cmd_ptr.* = cmd;
1763 if (macho.targetEndian() != std.lang.Endian.native) {
1764 std.mem.byteSwapAllFields(Cmd, cmd_ptr);
1765 }
1766
1767 const lc: LoadCommand = @fromBackingInt(@intCast(macho.lc.offsets.items.len));
1768 macho.lc.offsets.appendAssumeCapacity(@sizeOf(mach_header_64) + old_cmds_size);
1769
1770 return .{ .lc = lc, .trailing = cmd_slice[@sizeOf(Cmd)..] };
1771}
1772
1773fn addSection(macho: *MachO, opts: struct {
1774 name: []const u8,
1775 segment: Segment,
1776 @"align": MappedFile.Alignment,
1777 manual_size: bool = false,
1778 flags: Section.Flags,
1779 reserved1: u32 = 0,
1780 reserved2: u32 = 0,
1781}) Error!Section {
1782 const gpa = macho.base.comp.gpa;
1783
1784 const mach_header_64 = std.macho.mach_header_64;
1785 const segment_command_64 = std.macho.segment_command_64;
1786 const section_64 = std.macho.section_64;
1787
1788 // Check if we can actually fit another section. The limit is 255 because Mach-O implicitly
1789 // assigns 8-bit indices to sections starting from 1 (0 is reserved).
1790 if (macho.sections.len == 255) {
1791 return macho.base.comp.link_diags.fail("maximum section count exceeded", .{});
1792 }
1793
1794 const old_cmds_size: u32 = size: {
1795 const mach_header: *const mach_header_64 = @ptrCast(@alignCast(
1796 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(mach_header_64)],
1797 ));
1798 break :size macho.targetLoad(&mach_header.sizeofcmds);
1799 };
1800 const new_cmds_size = old_cmds_size + @sizeOf(section_64);
1801 try macho.node.header.ensureMinimumSize(gpa, &macho.mf, @sizeOf(mach_header_64) + new_cmds_size);
1802
1803 try macho.nodes.ensureUnusedCapacity(gpa, 1);
1804 try macho.lc.offsets.ensureUnusedCapacity(gpa, 1);
1805 try macho.sections.ensureUnusedCapacity(gpa, 1);
1806 try macho.sections_by_name.ensureUnusedCapacity(gpa, 1);
1807
1808 const section: Section = @fromBackingInt(@intCast(macho.sections.len));
1809 const section_node = try opts.segment.node(macho).addFloatingChild(gpa, &macho.mf, .{
1810 .alignment = macho.mf.flags.block_size.max(opts.@"align"),
1811 .moved = true, // so that `offset` will be set
1812 });
1813 if (opts.manual_size) {
1814 macho.nodes.appendAssumeCapacity(.{ .section_manual_size = section });
1815 } else {
1816 macho.nodes.appendAssumeCapacity(.{ .section = section });
1817 }
1818 macho.sections.appendAssumeCapacity(.{
1819 .lc = @fromBackingInt(@intCast(macho.lc.offsets.items.len)),
1820 .node = section_node,
1821 .segment = opts.segment,
1822 .index = undefined, // populated later
1823 });
1824
1825 {
1826 const adapter: Section.NameAdapter = .{ .macho = macho };
1827 const key: Section.NameAdapter.SegmentAndSection = .{
1828 .segment = opts.segment.name(macho),
1829 .section = opts.name,
1830 };
1831 const gop = macho.sections_by_name.getOrPutAssumeCapacityAdapted(key, adapter);
1832 assert(!gop.found_existing);
1833 assert(gop.index == @backingInt(section));
1834 }
1835
1836 const header_slice = macho.node.header.slice(&macho.mf);
1837
1838 {
1839 const mach_header: *mach_header_64 = @ptrCast(@alignCast(
1840 header_slice[0..@sizeOf(mach_header_64)],
1841 ));
1842 macho.targetStore(&mach_header.sizeofcmds, new_cmds_size);
1843 }
1844
1845 const seg_cmd = opts.segment.lcPtr(macho);
1846 const old_nsects = macho.targetLoad(&seg_cmd.nsects);
1847 const old_cmdsize = macho.targetLoad(&seg_cmd.cmdsize);
1848 assert(old_cmdsize == @sizeOf(segment_command_64) + old_nsects * @sizeOf(section_64));
1849 const new_cmdsize = old_cmdsize + @sizeOf(section_64);
1850 macho.targetStore(&seg_cmd.cmdsize, new_cmdsize);
1851 macho.targetStore(&seg_cmd.nsects, old_nsects + 1);
1852
1853 const seg_cmd_off = opts.segment.lc(macho).offset(macho);
1854 const offset = seg_cmd_off + old_cmdsize;
1855
1856 // Shift forward every load command which comes after this segment command, to make space
1857 // for the new `section_64` (and update the offsets in `macho.lc.offsets` accordingly).
1858 @memmove(
1859 header_slice[seg_cmd_off + new_cmdsize .. @sizeOf(mach_header_64) + new_cmds_size],
1860 header_slice[seg_cmd_off + old_cmdsize .. @sizeOf(mach_header_64) + old_cmds_size],
1861 );
1862 for (macho.lc.offsets.items) |*other_off| {
1863 if (other_off.* >= offset) {
1864 other_off.* += @sizeOf(section_64);
1865 }
1866 }
1867 // Only now do we append our *own* offset (otherwise the above loop would have changed it!).
1868 macho.lc.offsets.appendAssumeCapacity(offset);
1869
1870 // Increment the index of every section after where we've inserted ourselves.
1871 {
1872 var new_section_index: u8 = 1;
1873 try macho.sections_changed_index.ensureUnusedCapacity(gpa, macho.sections.len);
1874 for (0..macho.sections.len) |other_section_raw| {
1875 const other_section: Section = @fromBackingInt(@intCast(other_section_raw));
1876 if (other_section == section) continue;
1877 if (other_section.lc(macho).offset(macho) > offset) {
1878 macho.sections.items(.index)[@backingInt(other_section)] += 1;
1879 macho.sections_changed_index.putAssumeCapacity(other_section, {});
1880 } else {
1881 new_section_index += 1;
1882 }
1883 }
1884 macho.sections.items(.index)[@backingInt(section)] = new_section_index;
1885 }
1886
1887 // Write the new `section_64`.
1888 const section_cmd: *section_64 = @ptrCast(@alignCast(
1889 header_slice[offset..][0..@sizeOf(section_64)],
1890 ));
1891 section_cmd.* = .{
1892 .sectname = name: {
1893 var name: [16]u8 = undefined;
1894 @memcpy(name[0..opts.name.len], opts.name);
1895 @memset(name[opts.name.len..], 0);
1896 break :name name;
1897 },
1898 .segname = seg_cmd.segname,
1899 .addr = 0,
1900 .size = 0,
1901 .offset = 0,
1902 .@"align" = opts.@"align".toLog2Units(),
1903 .reloff = 0,
1904 .nreloc = 0,
1905 .flags = @bitCast(opts.flags),
1906 .reserved1 = opts.reserved1,
1907 .reserved2 = opts.reserved2,
1908 };
1909 if (macho.targetEndian() != std.lang.Endian.native) {
1910 std.mem.byteSwapAllFields(section_64, section_cmd);
1911 }
1912
1913 return section;
1914}
1915
1916fn sectionByName(macho: *const MachO, segment_name: []const u8, section_name: []const u8) ?Section {
1917 const adapter: Section.NameAdapter = .{ .macho = macho };
1918 const key: Section.NameAdapter.SegmentAndSection = .{
1919 .segment = segment_name,
1920 .section = section_name,
1921 };
1922 if (macho.sections_by_name.getIndexAdapted(key, adapter)) |section_raw| {
1923 return @fromBackingInt(@intCast(section_raw));
1924 } else {
1925 return null;
1926 }
1927}
1928
1929fn machFiletype(macho: *const MachO) enum(u32) {
1930 OBJECT = std.macho.MH_OBJECT,
1931 DYLIB = std.macho.MH_DYLIB,
1932 EXECUTE = std.macho.MH_EXECUTE,
1933} {
1934 const hdr: *const std.macho.mach_header_64 = @ptrCast(@alignCast(
1935 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(std.macho.mach_header_64)],
1936 ));
1937 return @fromBackingInt(macho.targetLoad(&hdr.filetype));
1938}
1939
1940fn targetEndian(macho: *const MachO) std.lang.Endian {
1941 _ = macho;
1942 return .little;
1943}
1944fn targetPtrSize(macho: *const MachO) u8 {
1945 _ = macho;
1946 return 8;
1947}
1948fn targetPageAlign(macho: *const MachO) MappedFile.Alignment {
1949 return switch (macho.base.comp.getTarget().cpu.arch) {
1950 else => unreachable,
1951 .aarch64 => .fromByteUnits(0x4000),
1952 .x86_64 => .fromByteUnits(0x1000),
1953 };
1954}
1955fn targetLoad(macho: *const MachO, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
1956 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;
1957 const Child = pointer_ty.child;
1958 const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child);
1959 return switch (@typeInfo(Child)) {
1960 else => @compileError(@typeName(Child)),
1961 .int => std.mem.toNative(Child, ptr.*, macho.targetEndian()),
1962 .@"enum" => |@"enum"| @fromBackingInt(macho.targetLoad(@as(*align(alignment) const @"enum".tag_type, @ptrCast(ptr)))),
1963 inline .@"struct", .@"union" => |bitpack| @bitCast(
1964 macho.targetLoad(@as(*align(alignment) const bitpack.backing_integer.?, @ptrCast(ptr))),
1965 ),
1966 };
1967}
1968fn targetStore(macho: *const MachO, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).pointer.child) void {
1969 const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer;
1970 const Child = pointer_ty.child;
1971 const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child);
1972 return switch (@typeInfo(Child)) {
1973 else => @compileError(@typeName(Child)),
1974 .int => ptr.* = std.mem.nativeTo(Child, val, macho.targetEndian()),
1975 .@"enum" => |@"enum"| macho.targetStore(
1976 @as(*align(alignment) @"enum".tag_type, @ptrCast(ptr)),
1977 @backingInt(val),
1978 ),
1979 inline .@"struct", .@"union" => |bitpack| macho.targetStore(
1980 @as(*align(alignment) bitpack.backing_integer.?, @ptrCast(ptr)),
1981 @bitCast(val),
1982 ),
1983 };
1984}
1985
1986pub fn deinit(macho: *MachO) void {
1987 const gpa = macho.base.comp.gpa;
1988 macho.mf.deinit(gpa);
1989 macho.nodes.deinit(gpa);
1990 macho.segments.deinit(gpa);
1991 macho.sections.deinit(gpa);
1992 macho.sections_by_name.deinit(gpa);
1993 macho.dirty_fixup_chain_pages.deinit(gpa);
1994 macho.input_atoms.deinit(gpa);
1995 macho.strtab.deinit(gpa);
1996 macho.symtab.deinit(gpa);
1997 macho.global_symbols.deinit(gpa);
1998 macho.relocs.deinit(gpa);
1999 macho.imports.deinit(gpa);
2000 macho.got.deinit(gpa);
2001 macho.stubs.deinit(gpa);
2002 macho.sections_changed_index.deinit(gpa);
2003 macho.* = undefined;
2004}
2005
2006pub fn startProgress(macho: *MachO, prog_node: std.Progress.Node) void {
2007 prog_node.increaseEstimatedTotalItems(4);
2008 macho.const_prog_node = prog_node.start("Constants", 0);
2009 macho.synth_prog_node = prog_node.start("Synthetics", 0);
2010 macho.input_prog_node = prog_node.start("Inputs", 0);
2011 macho.mf.update_prog_node = prog_node.start("Relocations", macho.mf.updates.items.len);
2012}
2013pub fn endProgress(macho: *MachO) void {
2014 macho.const_prog_node.end();
2015 macho.const_prog_node = .none;
2016 macho.synth_prog_node.end();
2017 macho.synth_prog_node = .none;
2018 macho.input_prog_node.end();
2019 macho.input_prog_node = .none;
2020 macho.mf.update_prog_node.end();
2021 macho.mf.update_prog_node = .none;
2022}
2023
2024pub fn navSymbol(macho: *MachO, nav_id: InternPool.Nav.Index) link.Error!link.File.SymbolId {
2025 _ = macho;
2026 _ = nav_id;
2027 @panic("TODO");
2028}
2029pub fn uavSymbol(
2030 macho: *MachO,
2031 pt: Zcu.PerThread,
2032 uav_val: InternPool.Index,
2033 uav_align: InternPool.Alignment,
2034) link.Error!link.File.SymbolId {
2035 _ = macho;
2036 _ = pt;
2037 _ = uav_val;
2038 _ = uav_align;
2039 @panic("TODO");
2040}
2041pub fn relocSymAddr(
2042 macho: *MachO,
2043 reloc_info: link.File.RelocInfo,
2044) link.Error!void {
2045 _ = macho;
2046 _ = reloc_info;
2047 @panic("TODO");
2048}
2049
2050pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void {
2051 const diags = &macho.base.comp.link_diags;
2052 macho.loadInputInner(input) catch |err| switch (err) {
2053 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
2054 else => |e| return e,
2055 };
2056 macho.updateSegmentLoadAddresses() catch |err| switch (err) {
2057 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
2058 else => |e| return e,
2059 };
2060}
2061fn loadInputInner(macho: *MachO, input: link.Input) Error!void {
2062 const comp = macho.base.comp;
2063 const diags = &comp.link_diags;
2064 const gpa = comp.gpa;
2065 const io = comp.io;
2066 switch (input) {
2067 .res => unreachable,
2068 .object => |object| {
2069 var buf: [4096]u8 = undefined;
2070 var fr = object.file.reader(io, &buf);
2071 macho.loadObject(object.path, null, &fr, .{
2072 .offset = fr.logicalPos(),
2073 .size = fr.getSize() catch |err| switch (err) {
2074 error.Canceled => |e| return e,
2075 else => |e| return diags.fail(
2076 "failed to stat \"{f}\": {t}",
2077 .{ object.path.fmtEscapeString(), e },
2078 ),
2079 },
2080 }) catch |err| switch (err) {
2081 else => |e| return e,
2082 error.EndOfStream => return diags.failParse(
2083 object.path,
2084 "unexpected eof",
2085 .{},
2086 ),
2087 error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail(
2088 "failed to read \"{f}\": {t}",
2089 .{ object.path.fmtEscapeString(), e },
2090 ),
2091 error.ReadFailed => switch (fr.err.?) {
2092 error.Canceled => |e| return e,
2093 else => |e| return diags.fail(
2094 "failed to read \"{f}\": {t}",
2095 .{ object.path.fmtEscapeString(), e },
2096 ),
2097 },
2098 };
2099 },
2100 .archive => |archive| {
2101 var buf: [4096]u8 = undefined;
2102 var fr = archive.file.reader(io, &buf);
2103 macho.loadArchive(archive.path, &fr) catch |err| switch (err) {
2104 else => |e| return e,
2105 error.EndOfStream => return diags.failParse(
2106 archive.path,
2107 "unexpected eof",
2108 .{},
2109 ),
2110 error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail(
2111 "failed to read \"{f}\": {t}",
2112 .{ archive.path.fmtEscapeString(), e },
2113 ),
2114 error.ReadFailed => switch (fr.err.?) {
2115 error.Canceled => |e| return e,
2116 else => |e| return diags.fail(
2117 "failed to read \"{f}\": {t}",
2118 .{ archive.path.fmtEscapeString(), e },
2119 ),
2120 },
2121 };
2122 },
2123 .dso => @panic("TODO(MachO2): load dso"),
2124 .tbd => |tbd| {
2125 log.debug("load tbd {qf}", .{tbd.path.fmtEscapeString()});
2126
2127 var lib_stub = tapi.LibStub.loadFromFile(gpa, io, tbd.file) catch |err| switch (err) {
2128 error.Canceled => |e| return e,
2129 else => |e| return diags.failParse(tbd.path, "failed to parse tbd: {t}", .{e}),
2130 };
2131 defer lib_stub.deinit();
2132
2133 const umbrella_lib = lib_stub.inner[0];
2134
2135 const dylib_name = umbrella_lib.installName();
2136 const cmd = try macho.appendLoadCommand(std.macho.dylib_command, .{
2137 .cmd = .LOAD_DYLIB,
2138 .cmdsize = std.mem.alignForward(u32, @intCast(
2139 @sizeOf(std.macho.dylib_command) + dylib_name.len + 1,
2140 ), 8),
2141 .dylib = .{
2142 .name = @sizeOf(std.macho.dylib_command),
2143 .timestamp = 0,
2144 .current_version = 0, // TODO
2145 .compatibility_version = 0, // TODO
2146 },
2147 });
2148 @memcpy(cmd.trailing[0..dylib_name.len], dylib_name);
2149 @memset(cmd.trailing[dylib_name.len..], 0);
2150 },
2151 }
2152}
2153
2154/// Internal error set used by input parsing functions `loadObject`, `loadArchive`, `loadDso`.
2155const LoadParseInputError = Error || Io.File.SeekError || Io.Reader.Error;
2156
2157fn loadArchive(macho: *MachO, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadParseInputError!void {
2158 const comp = macho.base.comp;
2159 const gpa = comp.gpa;
2160 const diags = &comp.link_diags;
2161 const r = &fr.interface;
2162
2163 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
2164
2165 {
2166 const magic = r.take(std.macho.ARMAG.len) catch |err| switch (err) {
2167 error.ReadFailed => |e| return e,
2168 error.EndOfStream => return diags.failParse(path, "invalid archive file", .{}),
2169 };
2170 if (!std.mem.eql(u8, magic, std.macho.ARMAG)) {
2171 return diags.failParse(path, "invalid archive file", .{});
2172 }
2173 }
2174
2175 var long_name_buf: std.ArrayList(u8) = .empty;
2176 defer long_name_buf.deinit(gpa);
2177
2178 while (r.takeStruct(std.macho.ar_hdr, .native)) |header| {
2179 if (!std.mem.eql(u8, &header.ar_fmag, std.macho.ARFMAG))
2180 return diags.failParse(path, "bad file magic", .{});
2181 const offset = fr.logicalPos();
2182 const size = header.size() catch
2183 return diags.failParse(path, "bad member size", .{});
2184 const member_name: []const u8, const member_offset: u64, const member_size: u64 = member: {
2185 const name_len = header.nameLength() orelse {
2186 break :member .{ header.name(), offset, size };
2187 };
2188 try long_name_buf.resize(gpa, name_len);
2189 try fr.interface.readSliceAll(long_name_buf.items);
2190 break :member .{
2191 std.mem.sliceTo(long_name_buf.items, 0),
2192 offset + name_len,
2193 size - name_len,
2194 };
2195 };
2196 if (std.mem.eql(u8, member_name, std.macho.SYMDEF) or
2197 std.mem.eql(u8, member_name, std.macho.SYMDEF64) or
2198 std.mem.eql(u8, member_name, std.macho.SYMDEF_SORTED) or
2199 std.mem.eql(u8, member_name, std.macho.SYMDEF64_SORTED))
2200 {
2201 // Ignore these special members.
2202 } else {
2203 try macho.loadObject(path, member_name, fr, .{
2204 .offset = member_offset,
2205 .size = member_size,
2206 });
2207 }
2208 try fr.seekTo(std.mem.alignForward(u64, offset + size, 2));
2209 } else |err| switch (err) {
2210 else => |e| return e,
2211 error.EndOfStream => if (!fr.atEnd()) return error.EndOfStream,
2212 }
2213}
2214fn loadObject(
2215 macho: *MachO,
2216 path: std.Build.Cache.Path,
2217 member: ?[]const u8,
2218 fr: *Io.File.Reader,
2219 fl: MappedFile.Node.FileLocation,
2220) LoadParseInputError!void {
2221 const comp = macho.base.comp;
2222 const diags = &comp.link_diags;
2223 const gpa = comp.gpa;
2224
2225 log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberString(member) });
2226
2227 const target_endian = macho.targetEndian();
2228
2229 switch (try fr.interface.peekInt(u32, target_endian)) {
2230 std.macho.MH_MAGIC_64 => {},
2231 std.macho.MH_MAGIC => {
2232 return diags.failParse(path, "incorrect pointer bit width (expected 64)", .{});
2233 },
2234 std.macho.MH_CIGAM_64, std.macho.MH_CIGAM => {
2235 return diags.failParse(path, "incorrect endian (expected {t})", .{target_endian});
2236 },
2237 else => {
2238 return diags.failParse(path, "invalid Mach-O object file", .{});
2239 },
2240 }
2241
2242 const header = try fr.interface.takeStruct(std.macho.mach_header_64, target_endian);
2243 assert(header.magic == std.macho.MH_MAGIC_64); // checked above
2244
2245 if (header.filetype != std.macho.MH_OBJECT) return diags.failParse(
2246 path,
2247 "not a relocatable",
2248 .{},
2249 );
2250
2251 {
2252 const output_header: *const std.macho.mach_header_64 = @ptrCast(@alignCast(
2253 macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(std.macho.mach_header_64)],
2254 ));
2255 const output_cputype = macho.targetLoad(&output_header.cputype);
2256 const output_cpusubtype = macho.targetLoad(&output_header.cpusubtype);
2257 if (header.cputype != output_cputype) return diags.failParse(
2258 path,
2259 "cputype '{d}' does not match target cputype '{d}'",
2260 .{ header.cputype, output_cputype },
2261 );
2262 if (header.cpusubtype != output_cpusubtype) return diags.failParse(
2263 path,
2264 "cpusubtype '{d}' does not match target cpusubtype '{d}'",
2265 .{ header.cpusubtype, output_cpusubtype },
2266 );
2267 }
2268
2269 if (@sizeOf(std.macho.mach_header_64) + header.sizeofcmds > fl.size) {
2270 return diags.failParse(path, "load commands exceed file size", .{});
2271 }
2272
2273 var maybe_symtab_lc: ?std.macho.symtab_command = null;
2274
2275 const InputSection = struct {
2276 segname: [16]u8,
2277 sectname: [16]u8,
2278
2279 reloff: u32,
2280 nreloc: u32,
2281
2282 addr: u64,
2283 size: u64,
2284 @"align": MappedFile.Alignment,
2285
2286 usage: Usage,
2287 file_offset: u32,
2288
2289 atoms_start: usize,
2290 atoms_end: usize,
2291
2292 const Usage = enum {
2293 text,
2294 data_const,
2295 data_const_split_4,
2296 data_const_split_8,
2297 data_const_split_16,
2298 data_const_split_c_strings,
2299 data_const_fixups,
2300 data_mut,
2301 bss,
2302 fn parentSection(usage: Usage, m: *const MachO) Section {
2303 return switch (usage) {
2304 .text => m.sectionByName("__TEXT", "__text").?,
2305
2306 .data_const,
2307 .data_const_split_4,
2308 .data_const_split_8,
2309 .data_const_split_16,
2310 .data_const_split_c_strings,
2311 => m.sectionByName("__TEXT", "__const").?,
2312
2313 // TODO: this should really be "__DATA_CONST,__data". See comment in `initInner`
2314 .data_const_fixups => m.sectionByName("__DATA", "__data").?,
2315
2316 .data_mut => m.sectionByName("__DATA", "__data").?,
2317 .bss => m.sectionByName("__DATA", "__data").?,
2318 };
2319 }
2320 };
2321 };
2322 var input_sections: std.ArrayList(InputSection) = .empty;
2323 defer input_sections.deinit(gpa);
2324
2325 var cmd_offset: u64 = 0;
2326 for (0..header.ncmds) |_| {
2327 const lc = try fr.interface.peekStruct(std.macho.load_command, target_endian);
2328 if (lc.cmdsize < @sizeOf(std.macho.load_command)) return diags.failParse(
2329 path,
2330 "load command at offset 0x{X} has invalid size",
2331 .{cmd_offset + @sizeOf(std.macho.mach_header_64)},
2332 );
2333 if (cmd_offset + lc.cmdsize > header.sizeofcmds) return diags.failParse(
2334 path,
2335 "load command at offset 0x{X} exceeds 'sizeofcmds'",
2336 .{cmd_offset + @sizeOf(std.macho.mach_header_64)},
2337 );
2338 cmd_offset += lc.cmdsize;
2339 switch (lc.cmd) {
2340 else => if (std.enums.tagName(std.macho.LC, lc.cmd)) |cmd_name| {
2341 return diags.failParse(path, "unhandled load command 'LC_{s}'", .{cmd_name});
2342 } else {
2343 return diags.failParse(path, "unknown load command 0x{X}", .{lc.cmd});
2344 },
2345
2346 .NONE,
2347 .DYSYMTAB,
2348 .NOTE,
2349 .BUILD_VERSION,
2350 .DATA_IN_CODE,
2351 .LINKER_OPTIMIZATION_HINT,
2352 => try fr.interface.discardAll(lc.cmdsize), // ignore
2353
2354 .SEGMENT_64 => {
2355 if (lc.cmdsize < @sizeOf(std.macho.segment_command_64)) {
2356 return diags.failParse(path, "bad cmdsize in 'LC_SEGMENT_64' load command", .{});
2357 }
2358 const segment_lc = try fr.interface.takeStruct(std.macho.segment_command_64, target_endian);
2359 if (lc.cmdsize - @sizeOf(std.macho.segment_command_64) < segment_lc.nsects * @sizeOf(std.macho.section_64)) {
2360 return diags.failParse(path, "nsects in 'LC_SEGMENT_64' load command exceeds cmdsize", .{});
2361 }
2362 try input_sections.ensureUnusedCapacity(gpa, segment_lc.nsects);
2363 for (0..segment_lc.nsects) |_| {
2364 const section = try fr.interface.takeStruct(std.macho.section_64, target_endian);
2365
2366 const flags: Section.Flags = @bitCast(section.flags);
2367
2368 const segname = std.mem.sliceTo(&section.segname, 0);
2369 const sectname = std.mem.sliceTo(&section.sectname, 0);
2370
2371 const usage: InputSection.Usage = switch (flags.type) {
2372 _ => return diags.failParse(
2373 path,
2374 "section '{s},{s}' has unknown type '{d}'",
2375 .{ segname, sectname, flags.type },
2376 ),
2377
2378 else => return diags.failParse(
2379 path,
2380 "section '{s},{s}' has unsupported type '{t}'",
2381 .{ segname, sectname, flags.type },
2382 ),
2383
2384 .zerofill => .bss,
2385
2386 .@"4byte_literals" => .data_const_split_4,
2387 .@"8byte_literals" => .data_const_split_8,
2388 .@"16byte_literals" => .data_const_split_16,
2389 .cstring_literals => .data_const_split_c_strings,
2390
2391 .regular => usage: {
2392 if (flags.some_instructions or flags.pure_instructions) {
2393 break :usage .text;
2394 }
2395
2396 // "__DATA,__const" is constant data with fixups.
2397 if (std.mem.eql(u8, segname, "__DATA") and
2398 std.mem.eql(u8, sectname, "__const"))
2399 {
2400 break :usage .data_const_fixups;
2401 }
2402
2403 // Otherwise, the decision is based solely on segment name.
2404 if (std.mem.eql(u8, segname, "__TEXT")) {
2405 break :usage .data_const;
2406 }
2407 if (std.mem.eql(u8, segname, "__DATA_CONST")) {
2408 break :usage .data_const_fixups;
2409 }
2410 if (std.mem.eql(u8, segname, "__DATA")) {
2411 break :usage .data_mut;
2412 }
2413
2414 return diags.failParse(path, "unknown segment name '{s}'", .{segname});
2415 },
2416 };
2417
2418 if (usage != .bss and section.offset + section.size > fl.size) {
2419 return diags.failParse(path, "section '{s},{s}' exceeds file size", .{
2420 segname, sectname,
2421 });
2422 }
2423
2424 const @"align": MappedFile.Alignment = .fromLog2Units(std.math.cast(u6, section.@"align") orelse {
2425 return diags.failParse(path, "section '{s},{s}' has unsupported alignment '1 << {d}'", .{
2426 segname, sectname, section.@"align",
2427 });
2428 });
2429
2430 // Before creating the input section nodes, make sure the parent section is
2431 // suitably aligned. Unlike ELF, Mach-O does not allow customizing segment
2432 // alignment, so section alignments greater than the page size cannot be
2433 // fulfilled and must trigger an error.
2434 if (@"align".compare(.gt, macho.targetPageAlign())) {
2435 return diags.failParse(path, "section '{s},{s}' has alignment '{d}' which exceeds page alignment '{d}'", .{
2436 segname, sectname, @"align".toByteUnits(), macho.targetPageAlign().toByteUnits(),
2437 });
2438 }
2439 const parent_section = usage.parentSection(macho);
2440 if (@"align".compare(.gt, parent_section.alignment(macho))) {
2441 macho.targetStore(&parent_section.lcPtr(macho).@"align", @"align".toLog2Units());
2442 if (@"align".compare(.gt, parent_section.node(macho).alignment(&macho.mf))) {
2443 try parent_section.node(macho).realign(gpa, &macho.mf, @"align");
2444 }
2445 }
2446
2447 input_sections.appendAssumeCapacity(.{
2448 .segname = section.segname,
2449 .sectname = section.sectname,
2450 .reloff = section.reloff,
2451 .nreloc = section.nreloc,
2452 .addr = section.addr,
2453 .size = section.size,
2454 .@"align" = @"align",
2455 .usage = usage,
2456 .file_offset = section.offset,
2457 .atoms_start = undefined,
2458 .atoms_end = undefined,
2459 });
2460 }
2461 try fr.interface.discardAll(lc.cmdsize -
2462 @sizeOf(std.macho.segment_command_64) -
2463 segment_lc.nsects * @sizeOf(std.macho.section_64));
2464 },
2465
2466 .SYMTAB => {
2467 if (lc.cmdsize < @sizeOf(std.macho.symtab_command)) {
2468 return diags.failParse(path, "bad cmdsize in 'LC_SYMTAB' load command", .{});
2469 }
2470 if (maybe_symtab_lc != null) {
2471 return diags.failParse(path, "duplicate 'LC_SYMTAB' load command", .{});
2472 }
2473 maybe_symtab_lc = try fr.interface.peekStruct(std.macho.symtab_command, target_endian);
2474 try fr.interface.discardAll(lc.cmdsize);
2475 },
2476 }
2477 }
2478
2479 const symtab_lc = maybe_symtab_lc orelse return diags.failParse(
2480 path,
2481 "object file does not contain any 'LC_SYMTAB' load command",
2482 .{},
2483 );
2484 if (symtab_lc.symoff + symtab_lc.nsyms * @sizeOf(std.macho.nlist_64) > fl.size) {
2485 return diags.failParse(path, "symbol table in 'LC_SYMTAB' load command exceeds file size", .{});
2486 }
2487 if (symtab_lc.stroff + symtab_lc.strsize > fl.size) {
2488 return diags.failParse(path, "string table in 'LC_SYMTAB' load command exceeds file size", .{});
2489 }
2490
2491 const symbols = try gpa.alloc(Symbol.Index, symtab_lc.nsyms);
2492 defer gpa.free(symbols);
2493
2494 const Atom = struct {
2495 offset_in_section: u64,
2496 index: InputAtom.Index,
2497 };
2498 var atoms: std.ArrayList(Atom) = .empty;
2499 defer atoms.deinit(gpa);
2500
2501 // This block processes symbols and atoms (populating `symbols` and `atoms` in the process).
2502 // After the block exits, the only work remining is relocation lowering.
2503 {
2504 try fr.seekTo(fl.offset + symtab_lc.stroff);
2505 const strtab = try fr.interface.readAllocAll(gpa, symtab_lc.strsize);
2506 defer gpa.free(strtab);
2507
2508 try fr.seekTo(fl.offset + symtab_lc.symoff);
2509 const symtab = try fr.interface.readSliceEndianAlloc(gpa, std.macho.nlist_64, symtab_lc.nsyms, target_endian);
2510 defer gpa.free(symtab);
2511
2512 // Sort the symbols so that we can do subsection splitting efficiently. This is okay because
2513 // there is no significance to the order of the symbol table in an object file. We'll put
2514 // any symbols not in a section (undef, absolute, etc) first, and then we'll sort the
2515 // remaining symbols by section index and address.
2516 //
2517 // Small caveat: when we lower symbols into the `symbols` array we allocated earlier, we do
2518 // need to use the *original* indices rather than our sorted indices, so that we can look up
2519 // relocation target symbols by index later. To help us do that, we'll also build a mapping
2520 // from our sorted indices to the original indices.
2521 const orig_sym_indices = try gpa.alloc(u32, symtab.len);
2522 defer gpa.free(orig_sym_indices);
2523 for (orig_sym_indices, 0..) |*orig_index, i| {
2524 orig_index.* = @intCast(i);
2525 }
2526 const SymtabSortContext = struct {
2527 symtab: []std.macho.nlist_64,
2528 map: []u32,
2529 pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool {
2530 const lhs = ctx.symtab[lhs_index];
2531 const rhs = ctx.symtab[rhs_index];
2532 // Don't need to check `n_type.bits.is_stab` here, because valid object files do not
2533 // contain stabs symbols, so if any are present we'll error out later anyway.
2534 const lhs_sect: u8, const lhs_addr: u64 = switch (lhs.n_type.bits.type) {
2535 .sect => .{ lhs.n_sect, lhs.n_value },
2536 else => .{ 0, 0 },
2537 };
2538 const rhs_sect: u8, const rhs_addr: u64 = switch (rhs.n_type.bits.type) {
2539 .sect => .{ rhs.n_sect, rhs.n_value },
2540 else => .{ 0, 0 },
2541 };
2542 const order = std.math.order(lhs_sect, rhs_sect).differ() orelse std.math.order(lhs_addr, rhs_addr);
2543 return order.compare(.lt);
2544 }
2545 pub fn swap(ctx: @This(), lhs_index: usize, rhs_index: usize) void {
2546 std.mem.swap(std.macho.nlist_64, &ctx.symtab[lhs_index], &ctx.symtab[rhs_index]);
2547 std.mem.swap(u32, &ctx.map[lhs_index], &ctx.map[rhs_index]);
2548 }
2549 };
2550 std.mem.sortUnstableContext(0, symtab.len, @as(SymtabSortContext, .{
2551 .symtab = symtab,
2552 .map = orig_sym_indices,
2553 }));
2554
2555 var sym_index: u32 = 0;
2556
2557 // This initial loop processes all of the non-section symbols (undef, absolute, etc). When
2558 // it exits, `sym_index` points to the first symbol with `n_type.bits.type == .sect`, which
2559 // is guaranteed to have `n_sect > 0`.
2560 while (sym_index < symtab.len) : (sym_index += 1) {
2561 const nlist = &symtab[sym_index];
2562
2563 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2564 path,
2565 "invalid symtab entry at index {d}: object file cannot contain stabs",
2566 .{sym_index},
2567 );
2568 if (nlist.n_strx > strtab.len) return diags.failParse(
2569 path,
2570 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2571 .{ sym_index, nlist.n_strx },
2572 );
2573 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2574
2575 symbols[orig_sym_indices[sym_index]] = switch (nlist.n_type.bits.type) {
2576 .sect => if (nlist.n_sect == 0) return diags.failParse(
2577 path,
2578 "symbol '{s}' of type 'N_SECT' has invalid section index '{d}'",
2579 .{ sym_name, nlist.n_sect },
2580 ) else {
2581 // All done with the non-section symbols!
2582 break;
2583 },
2584
2585 .undf => if (!nlist.n_type.bits.ext) return diags.failParse(
2586 path,
2587 "internal symbol '{s}' cannot be undefined",
2588 .{sym_name},
2589 ) else macho.addGlobalSymbol(.{
2590 .name = sym_name,
2591 .linkage = switch (nlist.n_desc.weak_ref) {
2592 false => .strong,
2593 true => .weak,
2594 },
2595 .visibility = switch (nlist.n_type.bits.pext) {
2596 false => .default,
2597 true => .hidden,
2598 },
2599 .value = null,
2600 }) catch |err| switch (err) {
2601 error.MultipleDefinitions => unreachable, // not a definition
2602 else => |e| return e,
2603 },
2604
2605 .abs => if (!nlist.n_type.bits.ext) try macho.addInternalSymbol(.{
2606 .name = sym_name,
2607 .value = .{ .abs = nlist.n_value },
2608 }) else macho.addGlobalSymbol(.{
2609 .name = sym_name,
2610 .linkage = switch (nlist.n_desc.weak_def_or_ref_to_weak) {
2611 false => .strong,
2612 true => .weak,
2613 },
2614 .visibility = switch (nlist.n_type.bits.pext) {
2615 false => .default,
2616 true => .hidden,
2617 },
2618 .value = null,
2619 }) catch |err| switch (err) {
2620 error.MultipleDefinitions => return diags.failParse(
2621 path,
2622 "multiple definitions of '{s}'",
2623 .{sym_name},
2624 ),
2625 else => |e| return e,
2626 },
2627
2628 .pbud, .indr => |sym_type| return diags.failParse(
2629 path,
2630 "symbol '{s}' has unsupported symbol type '{t}",
2631 .{ sym_name, sym_type },
2632 ),
2633
2634 _ => |sym_type| return diags.failParse(
2635 path,
2636 "symbol '{s}' has unknown symbol type '0x{X}'",
2637 .{ sym_name, sym_type },
2638 ),
2639 };
2640 }
2641
2642 // Now we need to process the symbols defined in sections. We're going to do this by looping
2643 // over *sections*, just because subsection splitting turns out to be much easier this way.
2644 for (input_sections.items, 0..) |*section, section_index| {
2645 if (section.usage != .bss) {
2646 try fr.seekTo(fl.offset + section.file_offset);
2647 }
2648
2649 const parent_section = section.usage.parentSection(macho);
2650
2651 section.atoms_start = atoms.items.len;
2652 var cur_atom_end: u64 = 0;
2653 while (true) {
2654 const sym_in_section: bool = sym_in_section: {
2655 if (sym_index == symtab.len) break :sym_in_section false;
2656
2657 const nlist = &symtab[sym_index];
2658
2659 assert(nlist.n_sect - 1 >= section_index); // we sorted the symtab by section
2660 if (nlist.n_sect - 1 != section_index) break :sym_in_section false;
2661
2662 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2663 path,
2664 "invalid symtab entry at index {d}: object file cannot contain stabs",
2665 .{sym_index},
2666 );
2667 if (nlist.n_strx > strtab.len) return diags.failParse(
2668 path,
2669 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2670 .{ sym_index, nlist.n_strx },
2671 );
2672
2673 if (nlist.n_value < section.addr or
2674 // This comparison intentionally uses '>=' to disallow defining a symbol at
2675 // the exclusive end of a section. This is because of an annoying quirk of
2676 // Mach-O: when using `MH_SUBSECTIONS_VIA_SYMBOLS`, it is impossible to
2677 // define a symbol at the exclusive end of any subsection, because such a
2678 // symbol would be considered a part of the *next* subsection.
2679 nlist.n_value >= section.addr + section.size)
2680 {
2681 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2682 const segname = std.mem.sliceTo(&section.segname, 0);
2683 const sectname = std.mem.sliceTo(&section.sectname, 0);
2684 return diags.failParse(
2685 path,
2686 "symbol '{s}' address '0x{X}' exceeds range of containing section '{s},{s}'",
2687 .{ sym_name, nlist.n_value, segname, sectname },
2688 );
2689 }
2690
2691 break :sym_in_section true;
2692 };
2693
2694 const target_atom_offset: u64 = target_atom_offset: {
2695 if (sym_in_section) {
2696 break :target_atom_offset symtab[sym_index].n_value - section.addr;
2697 }
2698 if (cur_atom_end != section.size) {
2699 // There were no more symbols in this section, but we still need to create
2700 // the atoms for the rest of the section! So just process everything up to
2701 // and including the last byte of the section.
2702 break :target_atom_offset section.size - 1;
2703 }
2704 // There are no symbols or atoms left to process, so we're done.
2705 break;
2706 };
2707 assert(target_atom_offset < section.size);
2708
2709 while (cur_atom_end <= target_atom_offset) {
2710 // We need to start a new atom at `cur_atom_end`.
2711 const atom_offset: u64 = cur_atom_end;
2712 const atom_size: u64 = usage: switch (section.usage) {
2713 // TODO: we should also deduplicate these
2714 .data_const_split_4 => 4,
2715 .data_const_split_8 => 8,
2716 .data_const_split_16 => 16,
2717
2718 // TODO: split C string sections (although `MH_SUBSECTIONS_VIA_SYMBOLS` is
2719 // usually sufficient anyway); and deduplicate those too
2720 .data_const_split_c_strings => continue :usage .data_const,
2721
2722 .text,
2723 .data_const,
2724 .data_const_fixups,
2725 .data_mut,
2726 .bss,
2727 => atom_size: {
2728 if (header.flags & std.macho.MH_SUBSECTIONS_VIA_SYMBOLS == 0) {
2729 // We aren't splitting the section into multiple atoms, so this must
2730 // mean that we haven't created *any* atom for this section yet.
2731 assert(atom_offset == 0);
2732 break :atom_size section.size;
2733 }
2734
2735 // Find the next symbol which splits this section. Usually this is just
2736 // the next symbol, but there could be some alt entry or alias symbols.
2737 var other_sym_index = sym_index + 1;
2738 const new_atom_end: u64 = while (other_sym_index < symtab.len and
2739 symtab[other_sym_index].n_sect - 1 == section_index) : (other_sym_index += 1)
2740 {
2741 if (symtab[other_sym_index].n_value == section.addr + atom_offset) continue;
2742 if (symtab[other_sym_index].n_desc.alt_entry) continue;
2743 // This symbol splits up the subsections. We haven't validated the
2744 // value of the symbol, so it might exceed the section bounds. We
2745 // will check for that error case later when the outer loop reaches
2746 // that symbol, so we don't need to check now, but we should limit
2747 // the size of the atom so that we don't allocate a potentially huge
2748 // node just before failing.
2749 break @min(
2750 symtab[other_sym_index].n_value - section.addr,
2751 section.size,
2752 );
2753 } else new_atom_end: {
2754 // There is no other symbol which splits this section, so the new
2755 // atom must cover the entire remainder of the section.
2756 break :new_atom_end section.size;
2757 };
2758
2759 break :atom_size new_atom_end - atom_offset;
2760 },
2761 };
2762 assert(atom_size > 0);
2763 assert(atom_offset + atom_size <= section.size);
2764
2765 const atom_align = section.@"align".offset(atom_offset);
2766
2767 try macho.nodes.ensureUnusedCapacity(gpa, 1);
2768 try macho.input_atoms.ensureUnusedCapacity(gpa, 1);
2769 const atom_node = try parent_section.node(macho).addFloatingChild(gpa, &macho.mf, .{
2770 .size = atom_align.forward(atom_size),
2771 .alignment = atom_align,
2772 .moved = true, // `flushMoved` will set up vaddrs and apply relocs
2773 });
2774 const iai: InputAtom.Index = @fromBackingInt(@intCast(macho.input_atoms.items.len));
2775 macho.input_atoms.appendAssumeCapacity(.{
2776 .node = atom_node,
2777 .first_symbol = .none,
2778 .first_reloc = @fromBackingInt(0), // populated for real later
2779 .vaddr = 0,
2780 .any_fixups = false,
2781 });
2782 macho.nodes.appendAssumeCapacity(.{ .input_atom = iai });
2783
2784 if (section.usage != .bss) {
2785 const slice = atom_node.slice(&macho.mf);
2786 try fr.interface.readSliceAll(slice[0..@intCast(atom_size)]);
2787 }
2788
2789 try atoms.append(gpa, .{
2790 .offset_in_section = atom_offset,
2791 .index = iai,
2792 });
2793 cur_atom_end = atom_offset + atom_size;
2794 }
2795
2796 if (sym_in_section) {
2797 // We just advanced to the atom containing this symbol, so now we can add it.
2798 const atom = atoms.lastPtr().?;
2799 const nlist = &symtab[sym_index];
2800 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2801 const symbol_offset_in_section = symtab[sym_index].n_value - section.addr;
2802 symbols[orig_sym_indices[sym_index]] = if (!nlist.n_type.bits.ext) try macho.addInternalSymbol(.{
2803 .name = sym_name,
2804 .value = .{ .addr_in_node = .{
2805 .node = atom.index.node(macho),
2806 .offset = symbol_offset_in_section - atom.offset_in_section,
2807 } },
2808 }) else macho.addGlobalSymbol(.{
2809 .name = sym_name,
2810 .linkage = switch (nlist.n_desc.weak_def_or_ref_to_weak) {
2811 false => .strong,
2812 true => .weak,
2813 },
2814 .visibility = switch (nlist.n_type.bits.pext) {
2815 false => .default,
2816 true => .hidden,
2817 },
2818 .value = .{ .addr_in_node = .{
2819 .node = atom.index.node(macho),
2820 .offset = symbol_offset_in_section - atom.offset_in_section,
2821 } },
2822 }) catch |err| switch (err) {
2823 error.MultipleDefinitions => return diags.failParse(
2824 path,
2825 "multiple definitions of '{s}'",
2826 .{sym_name},
2827 ),
2828 else => |e| return e,
2829 };
2830
2831 sym_index += 1;
2832 }
2833 }
2834 section.atoms_end = atoms.items.len;
2835 }
2836
2837 // If, after handling all of the input sections, there is still a symbol remaining, then
2838 // that symbol must have an out-of-range `n_sect` value.
2839 if (sym_index != symtab.len) {
2840 const nlist = &symtab[sym_index];
2841 if (nlist.n_type.bits.is_stab != 0) return diags.failParse(
2842 path,
2843 "invalid symtab entry at index {d}: object file cannot contain stabs",
2844 .{sym_index},
2845 );
2846 if (nlist.n_strx > strtab.len) return diags.failParse(
2847 path,
2848 "invalid symtab entry at index {d}: 'n_strx' value '{d}' out of range",
2849 .{ sym_index, nlist.n_strx },
2850 );
2851 const sym_name = std.mem.sliceTo(strtab[nlist.n_strx..], 0);
2852 assert(nlist.n_type.bits.type == .sect);
2853 assert(nlist.n_sect - 1 > input_sections.items.len);
2854 return diags.failParse(
2855 path,
2856 "symbol '{s}' of type 'N_SECT' has invalid section index '{d}'",
2857 .{ sym_name, nlist.n_sect },
2858 );
2859 }
2860 }
2861
2862 // Now that the symbols are done, we can process relocations.
2863
2864 var relocs_buf: std.ArrayList(std.macho.relocation_info) = .empty;
2865 defer relocs_buf.deinit(gpa);
2866
2867 for (input_sections.items) |*section| {
2868 if (section.reloff + section.nreloc > fl.size) {
2869 return diags.failParse(path, "relocations for '{s},{s}' exceed file size", .{
2870 std.mem.sliceTo(&section.segname, 0),
2871 std.mem.sliceTo(&section.sectname, 0),
2872 });
2873 }
2874
2875 try relocs_buf.resize(gpa, section.nreloc);
2876 try fr.seekTo(fl.offset + section.reloff);
2877 try fr.interface.readSliceEndian(std.macho.relocation_info, relocs_buf.items, target_endian);
2878
2879 // We need to sort the section's relocations by address for two reasons:
2880 //
2881 // * We need the relocations in each atom to be contiguous. This allows us to use sub-arrays
2882 // of `macho.relocs` for the relocations in an atom, rather than e.g. linked lists.
2883 //
2884 // * We need the relocations *within* each atom to be sorted, so that we can efficiently
2885 // generate runtime fixup chains if necessary.
2886 //
2887 // This sort must be stable so that `*_RELOC_ADDEND` and `*_RELOC_SUBTRACTOR` relocations
2888 // remain correctly paired.
2889 const SortRelocsContext = struct {
2890 fn lessThan(_: @This(), lhs: std.macho.relocation_info, rhs: std.macho.relocation_info) bool {
2891 return lhs.r_address < rhs.r_address;
2892 }
2893 };
2894 std.mem.sort(
2895 std.macho.relocation_info,
2896 relocs_buf.items,
2897 @as(SortRelocsContext, .{}),
2898 SortRelocsContext.lessThan,
2899 );
2900
2901 try macho.relocs.ensureUnusedCapacity(gpa, relocs_buf.items.len);
2902
2903 assert(atoms.items[section.atoms_start].offset_in_section == 0);
2904
2905 var atom_index = section.atoms_start;
2906 atoms.items[atom_index].index.ptr(macho).first_reloc = @fromBackingInt(@intCast(
2907 macho.relocs.items.len,
2908 ));
2909
2910 var paired_addend: ?u64 = null;
2911 for (relocs_buf.items) |reloc| {
2912 if (reloc.r_address < 0) {
2913 return diags.failParse(path, "scattered relocations are not supported", .{});
2914 }
2915 const offset_in_section: u32 = @intCast(reloc.r_address);
2916
2917 // Find the atom containing `reloc`. (This is why we sorted the relocations!)
2918 while (atom_index + 1 < section.atoms_end and
2919 offset_in_section >= atoms.items[atom_index + 1].offset_in_section)
2920 {
2921 atom_index += 1;
2922 atoms.items[atom_index].index.ptr(macho).first_reloc = @fromBackingInt(@intCast(
2923 macho.relocs.items.len,
2924 ));
2925 }
2926
2927 {
2928 const atom_end: u64 = atom_end: {
2929 if (atom_index == section.atoms_end - 1) {
2930 break :atom_end section.size;
2931 }
2932 const next_atom = &atoms.items[atom_index + 1];
2933 break :atom_end next_atom.offset_in_section;
2934 };
2935 if (offset_in_section + (@as(u8, 1) << reloc.r_length) > atom_end) {
2936 const segname = std.mem.sliceTo(&section.segname, 0);
2937 const sectname = std.mem.sliceTo(&section.sectname, 0);
2938 return diags.failParse(
2939 path,
2940 "relocation at offset '{d}' in '{s},{s}' spans multiple atoms",
2941 .{ offset_in_section, segname, sectname },
2942 );
2943 }
2944 }
2945
2946 const offset_in_atom: u32 = @intCast(offset_in_section - atoms.items[atom_index].offset_in_section);
2947
2948 const length_types: [4]?Reloc.Type, const target: Reloc.Target, const pcrel: bool, const implicit_negative_addend: u64 = switch (header.cputype) {
2949 else => unreachable,
2950
2951 std.macho.CPU_TYPE_X86_64 => reloc_type: switch (std.enums.fromInt(
2952 std.macho.reloc_type_x86_64,
2953 reloc.r_type,
2954 ) orelse {
2955 return diags.failParse(path, "unknown relocation type '{d}'", .{reloc.r_type});
2956 }) {
2957 .X86_64_RELOC_SUBTRACTOR => @panic("TODO(MachO2): SUBTRACTOR relocation"),
2958
2959 .X86_64_RELOC_UNSIGNED => .{ .{ .abs8, .abs16, .abs32, .abs64 }, .symbol, false, 0 },
2960 .X86_64_RELOC_SIGNED => .{ .{ null, null, .rel32, null }, .symbol, true, 4 },
2961 .X86_64_RELOC_SIGNED_1 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 5 else 4 },
2962 .X86_64_RELOC_SIGNED_2 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 6 else 4 },
2963 .X86_64_RELOC_SIGNED_4 => .{ .{ null, null, .rel32, null }, .symbol, true, if (reloc.r_extern == 0) 8 else 4 },
2964
2965 .X86_64_RELOC_BRANCH => .{ .{ null, null, .rel32, null }, .branch, true, 4 },
2966
2967 .X86_64_RELOC_TLV => .{ .{ null, null, .rel32, null }, .tlv, true, 4 },
2968
2969 .X86_64_RELOC_GOT_LOAD => continue :reloc_type .X86_64_RELOC_GOT, // relaxable
2970 .X86_64_RELOC_GOT => .{ .{ null, null, .rel32, null }, .got, true, 4 },
2971 },
2972
2973 std.macho.CPU_TYPE_ARM64 => switch (std.enums.fromInt(
2974 std.macho.reloc_type_arm64,
2975 reloc.r_type,
2976 ) orelse {
2977 return diags.failParse(path, "unknown relocation type '{d}'", .{reloc.r_type});
2978 }) {
2979 .ARM64_RELOC_SUBTRACTOR => @panic("TODO(MachO2): SUBTRACTOR relocation"),
2980
2981 .ARM64_RELOC_ADDEND => {
2982 const addend: i24 = @bitCast(reloc.r_symbolnum);
2983 const extended: i64 = addend;
2984 paired_addend = @bitCast(extended);
2985 continue;
2986 },
2987
2988 .ARM64_RELOC_UNSIGNED => .{ .{ .abs8, .abs16, .abs32, .abs64 }, .symbol, false, 0 },
2989 .ARM64_RELOC_BRANCH26 => .{ .{ null, null, .arm64_branchrel26, null }, .branch, true, 0 },
2990 .ARM64_RELOC_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .symbol, true, 0 },
2991 .ARM64_RELOC_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .symbol, false, 0 },
2992
2993 .ARM64_RELOC_TLVP_LOAD_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .tlv, true, 0 },
2994 .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .tlv, false, 0 },
2995
2996 .ARM64_RELOC_GOT_LOAD_PAGE21 => .{ .{ null, null, .arm64_pagerel21, null }, .got, true, 0 },
2997 .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .{ .{ null, null, .arm64_pageoff12, null }, .got, false, 0 },
2998
2999 // Apple's ld_classic supported an 8-byte non-pcrel version of this relocation,
3000 // but it had very weird semantics which didn't actually involve the GOT at all.
3001 // My guess is that they intended it to do the obvious thing but shipped with a
3002 // bug. In any case, it is not supported in Apple's new linker, and LLD does not
3003 // support it either, so therefore we should follow suit and only support the
3004 // usual form of this relocation (4-byte pcrel).
3005 .ARM64_RELOC_POINTER_TO_GOT => .{ .{ null, null, .rel32, null }, .got, true, 0 },
3006 },
3007 };
3008 const reloc_type_str: []const u8 = switch (header.cputype) {
3009 else => unreachable,
3010 std.macho.CPU_TYPE_ARM64 => @tagName(@as(
3011 std.macho.reloc_type_arm64,
3012 @fromBackingInt(reloc.r_type),
3013 )),
3014 std.macho.CPU_TYPE_X86_64 => @tagName(@as(
3015 std.macho.reloc_type_x86_64,
3016 @fromBackingInt(reloc.r_type),
3017 )),
3018 };
3019 if (@intFromBool(pcrel) != reloc.r_pcrel) {
3020 return diags.failParse(path, "relocation '{s}' {s} be pc-relative", .{
3021 reloc_type_str,
3022 if (pcrel) "must" else "cannot",
3023 });
3024 }
3025 const internal_type: Reloc.Type = length_types[reloc.r_length] orelse {
3026 return diags.failParse(path, "relocation '{s}' does not support length '{d}'", .{
3027 reloc_type_str,
3028 @as(u8, 1) << reloc.r_length,
3029 });
3030 };
3031
3032 const dest_slice = atoms.items[atom_index].index.node(macho).sliceConst(&macho.mf)[offset_in_atom..];
3033 if (!internal_type.validateInst(dest_slice)) {
3034 return diags.failParse(path, "relocation '{s}' against invalid instruction", .{
3035 reloc_type_str,
3036 });
3037 }
3038 const stored_addend: u64 = switch (internal_type) {
3039 .abs8 => std.mem.readInt(u8, dest_slice[0..1], target_endian),
3040 .abs16 => std.mem.readInt(u16, dest_slice[0..2], target_endian),
3041 .abs32 => std.mem.readInt(u32, dest_slice[0..4], target_endian),
3042 .abs64 => std.mem.readInt(u64, dest_slice[0..8], target_endian),
3043 .rel32 => a: {
3044 const extended: i64 = std.mem.readInt(i32, dest_slice[0..4], target_endian);
3045 break :a @bitCast(extended);
3046 },
3047
3048 .arm64_branchrel26,
3049 .arm64_pagerel21,
3050 .arm64_pageoff12,
3051 => a: {
3052 defer paired_addend = null;
3053 break :a paired_addend orelse 0;
3054 },
3055 };
3056 if (paired_addend != null) {
3057 return diags.failParse(path, "relocation '{s}' does not support paired addend", .{reloc_type_str});
3058 }
3059 const target_symbol: Symbol.Index, const final_addend: u64 = switch (reloc.r_extern) {
3060 1 => target: {
3061 // The relocation targets a symbol. This is the nice and reasonable case.
3062 if (reloc.r_symbolnum >= symbols.len) {
3063 return diags.failParse(path, "relocation target symbol '{d}' is invalid", .{reloc.r_symbolnum});
3064 }
3065 const target_sym = symbols[reloc.r_symbolnum];
3066 break :target .{ target_sym, stored_addend -% implicit_negative_addend };
3067 },
3068 0 => target: {
3069 // In this case, the relocation targets an offset into a *section*. I think the
3070 // best way to interpret this is to say that the "stored addend" value contains
3071 // what the relocated value *would* be if the linked image's section addresses
3072 // matched those of the relocatable itself (unlike ELF, Mach-O uses a flat
3073 // virtual address space even in relocatables). So, the best way to handle this
3074 // in the pcrel case is to reverse the computation to figure out what the actual
3075 // target address is.
3076 //
3077 // Unfortunately, this is further complicated by the existence of subsections.
3078 // Subsections split up the target section's virtual address space, and the
3079 // subsection being targeted by a relocation is determined implicitly based on
3080 // the target address. Therefore, to ensure we target the correct subsection, it
3081 // is necessary to compute the *exact* target address (no off-by-N). This Mach-O
3082 // quirk is why we have `implicit_negative_addend` on x86_64: it indicates that
3083 // the encoded pc-relative value is relative not to the exact address of the
3084 // relocation, but rather to an address a few bytes later, allowing us to
3085 // compute the exact target address instead of being 4--8 bytes short.
3086 //
3087 // Mach-O is a tremendously well-designed object format with no flaws whatsoever.
3088 if (reloc.r_symbolnum > input_sections.items.len) {
3089 return diags.failParse(path, "relocation target section '{d}' is invalid", .{reloc.r_symbolnum});
3090 }
3091 const target_addr_in_object: u64 = switch (pcrel) {
3092 false => stored_addend +% implicit_negative_addend,
3093 true => target_addr: {
3094 const pcrel_base: u64 = section.addr + @as(u32, @intCast(reloc.r_address));
3095 break :target_addr pcrel_base +% stored_addend +% implicit_negative_addend;
3096 },
3097 };
3098 const target_section = &input_sections.items[reloc.r_symbolnum - 1];
3099 const target_offset_in_section = target_addr_in_object -% target_section.addr;
3100 if (target_offset_in_section >= target_section.size) {
3101 const segname = std.mem.sliceTo(&target_section.segname, 0);
3102 const sectname = std.mem.sliceTo(&target_section.sectname, 0);
3103 return diags.failParse(
3104 path,
3105 "relocation target offset '{d}' exceeds range of target section '{s},{s}'",
3106 .{ target_offset_in_section, segname, sectname },
3107 );
3108 }
3109 // We need to know which atom we're targeting.
3110 const FindAtomContext = struct {
3111 offset_in_section: u64,
3112 fn order(lhs: @This(), rhs: Atom) std.math.Order {
3113 return std.math.order(lhs.offset_in_section, rhs.offset_in_section);
3114 }
3115 };
3116 assert(target_section.atoms_start < target_section.atoms_end);
3117 assert(atoms.items[target_section.atoms_start].offset_in_section == 0);
3118 const target_atom_index = target_section.atoms_start + std.sort.upperBound(
3119 Atom,
3120 atoms.items[target_section.atoms_start..target_section.atoms_end],
3121 @as(FindAtomContext, .{ .offset_in_section = target_offset_in_section }),
3122 FindAtomContext.order,
3123 ) - 1; // safe because atom 0 is at offset 0 so never >rhs
3124 assert(target_offset_in_section >= atoms.items[target_atom_index].offset_in_section);
3125 if (target_atom_index != target_section.atoms_end - 1) {
3126 assert(target_offset_in_section < atoms.items[target_atom_index + 1].offset_in_section);
3127 }
3128 // To preserve my own sanity, I'm just creating symbols to represent these
3129 // relocation targets. It's fine, I'm sure nobody will notice.
3130 var sym_name_buf: [64]u8 = undefined; // sectname is capped at 16 bytes
3131 const sym_name = std.mem.print(
3132 &sym_name_buf,
3133 "l_{s}_{s}_{x}",
3134 .{
3135 std.mem.trim(u8, std.mem.sliceTo(&target_section.segname, 0), "_"),
3136 std.mem.trim(u8, std.mem.sliceTo(&target_section.sectname, 0), "_"),
3137 target_offset_in_section,
3138 },
3139 ) catch unreachable;
3140 const target_sym = try macho.addInternalSymbol(.{
3141 .name = sym_name,
3142 .value = .{ .addr_in_node = .{
3143 .node = atoms.items[target_atom_index].index.node(macho),
3144 .offset = target_offset_in_section - atoms.items[target_atom_index].offset_in_section,
3145 } },
3146 });
3147 // Lovely, we have an actual symbol at the correct address which we can target.
3148 // Just remember to add the implicit addend back in so that the relocated
3149 // PC-relative address is relative to the correct base address!
3150 break :target .{ target_sym, -%implicit_negative_addend };
3151 },
3152 };
3153 const target_is_undef = macho.targetLoad(&target_symbol.nlist(macho).n_type).bits.type == .undf;
3154 if (target_is_undef) {
3155 const imports_gop = try macho.imports.getOrPut(gpa, target_symbol);
3156 if (!imports_gop.found_existing) {
3157 assert(imports_gop.index == macho.imports.count() - 1);
3158 try macho.appendImportEntry(macho.imports.count() - 1, target_symbol);
3159 }
3160 }
3161 if (target == .got or (target_is_undef and target == .branch)) {
3162 const got_gop = try macho.got.getOrPut(gpa, target_symbol);
3163 if (!got_gop.found_existing) {
3164 assert(got_gop.index == macho.got.count() - 1);
3165 try macho.appendGotEntry(macho.got.count() - 1, target_symbol);
3166 }
3167 }
3168 if (target_is_undef and target == .branch) {
3169 const stubs_gop = try macho.stubs.getOrPut(gpa, target_symbol);
3170 if (!stubs_gop.found_existing) {
3171 assert(stubs_gop.index == macho.stubs.count() - 1);
3172 const got_index = macho.got.getIndex(target_symbol).?; // added above
3173 try macho.appendStubEntry(macho.stubs.count() - 1, got_index, target_symbol);
3174 }
3175 }
3176 const ri: Reloc.Index = @fromBackingInt(@intCast(macho.relocs.items.len));
3177 macho.relocs.appendAssumeCapacity(.{
3178 .node = atoms.items[atom_index].index.node(macho),
3179 .offset = offset_in_atom,
3180 .target_symbol = target_symbol,
3181 .addend = final_addend,
3182 .bits = .{ .target = target, .type = internal_type, .result = .ok },
3183 .next_with_same_target_sym = target_symbol.ptr(macho).first_target_reloc,
3184 });
3185 target_symbol.ptr(macho).first_target_reloc = .wrap(ri);
3186
3187 if (target == .symbol and internal_type == .abs64) {
3188 // In this case the relocation needs a runtime fixup. This is the case even if the
3189 // target symbol is internal, because we still need to rebase the address.
3190 ri.ptr(macho).bits.result = .fixup;
3191 atoms.items[atom_index].index.ptr(macho).any_fixups = true;
3192 }
3193 }
3194 }
3195}
3196fn fmtMemberString(member: ?[]const u8) std.fmt.Alt(?[]const u8, memberStringEscape) {
3197 return .{ .data = member };
3198}
3199fn memberStringEscape(member: ?[]const u8, w: *Io.Writer) Io.Writer.Error!void {
3200 try w.print("({f})", .{std.zig.fmtString(member orelse return)});
3201}
3202
3203pub fn setDarwinSdkVersion(macho: *MachO, sdk_version: link.DarwinSdkVersion) link.Error!void {
3204 const comp = macho.base.comp;
3205 const diags = &comp.link_diags;
3206 const target = comp.getTarget();
3207
3208 const os_min_version: link.DarwinSdkVersion = .{
3209 .major = @intCast(target.os.version_range.semver.min.major),
3210 .minor = @intCast(target.os.version_range.semver.min.minor),
3211 .patch = @intCast(target.os.version_range.semver.min.patch),
3212 };
3213
3214 _ = macho.appendSimpleLoadCommand(std.macho.version_min_command, .{
3215 .cmd = switch (target.os.tag) {
3216 .macos => .VERSION_MIN_MACOSX,
3217 .ios => .VERSION_MIN_IPHONEOS,
3218 .tvos => .VERSION_MIN_TVOS,
3219 .watchos => .VERSION_MIN_WATCHOS,
3220 .maccatalyst, .driverkit, .visionos => |os_tag| std.debug.panic(
3221 "TODO(MachO2): sdk version for '{t}'",
3222 .{os_tag},
3223 ),
3224 else => unreachable,
3225 },
3226 .version = @backingInt(os_min_version),
3227 .sdk = @backingInt(sdk_version),
3228 }) catch |err| switch (err) {
3229 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
3230 else => |e| return e,
3231 };
3232}
3233pub fn prelink(macho: *MachO, prog_node: std.Progress.Node) link.Error!void {
3234 _ = macho;
3235 _ = prog_node;
3236}
3237pub fn flush(
3238 macho: *MachO,
3239 arena: Allocator,
3240 tid: Zcu.PerThread.Id,
3241 prog_node: std.Progress.Node,
3242) link.Error!void {
3243 const comp = macho.base.comp;
3244 const diags = &comp.link_diags;
3245 _ = arena;
3246 _ = tid;
3247 _ = prog_node;
3248
3249 macho.updateSegmentLoadAddresses() catch |err| switch (err) {
3250 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
3251 else => |e| return e,
3252 };
3253
3254 while (try macho.idle()) {}
3255
3256 if (macho.overflowed_reloc_count > 0) {
3257 diags.addError("failed to apply {d} relocations: overflow", .{macho.overflowed_reloc_count});
3258 }
3259 if (macho.misaligned_reloc_count > 0) {
3260 diags.addError("failed to apply {d} relocations: misaligned value", .{macho.misaligned_reloc_count});
3261 }
3262
3263 if (macho.lc.entry_point) |entry_point_lc| {
3264 const sym_name: []const u8 = name: switch (macho.options.entry) {
3265 .default => switch (comp.config.output_mode) {
3266 .Exe => continue :name .enabled,
3267 .Lib, .Obj => continue :name .disabled,
3268 },
3269 .disabled => unreachable, // `macho.lc.entry_point` is only set if entry is enabled
3270 .enabled => "_main",
3271 .named => |named| named,
3272 };
3273 const symbol = macho.globalByName(sym_name).unwrap() orelse {
3274 return diags.fail("no definition of entry point symbol {q}", .{sym_name});
3275 };
3276 if (macho.targetLoad(&symbol.nlist(macho).n_type).bits.type == .undf) {
3277 return diags.fail("no definition of entry point symbol {q}", .{sym_name});
3278 }
3279
3280 // Documentation says that this field should be the offset of the entry point in the file,
3281 // but that's a blatant lie; if you read the dyld source code, it's actually the vaddr minus
3282 // the text base vaddr. Because, like, obviously---what the fuck would they do with the
3283 // offset in the Mach-O file?
3284 const entry_point_ptr = entry_point_lc.ptr(macho, std.macho.entry_point_command);
3285 macho.targetStore(&entry_point_ptr.entryoff, std.math.cast(
3286 u32,
3287 symbol.value(macho) - Segment.text.vaddr(macho),
3288 ) orelse {
3289 return diags.fail("entry point address too big", .{});
3290 });
3291 }
3292
3293 macho.mf.flush() catch |err| switch (err) {
3294 error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}),
3295 else => |e| return e,
3296 };
3297
3298 if (macho.options.enable_link_snapshots)
3299 macho.dumpStderr() catch |err|
3300 return diags.fail("dumping link snapshot failed: {t}", .{err});
3301}
3302pub fn updateErrorData(macho: *MachO, pt: Zcu.PerThread) link.Error!void {
3303 _ = macho;
3304 _ = pt;
3305 @panic("TODO");
3306}
3307pub fn idle(macho: *MachO) link.Error!bool {
3308 macho.mf.nodes_lock.lock();
3309 defer macho.mf.nodes_lock.unlock();
3310
3311 task: {
3312 if (macho.sections_changed_index.pop()) |kv| {
3313 const section = kv.key;
3314 var child_it = section.node(macho).first(&macho.mf);
3315 while (child_it.unwrap()) |child| : (child_it = child.next(&macho.mf)) {
3316 switch (macho.getNode(child)) {
3317 .macho => unreachable,
3318 .macho_header => unreachable,
3319 .symtab => unreachable,
3320 .strtab => unreachable,
3321 .indirect_syms => unreachable,
3322 .section_indirect_syms => unreachable,
3323 .linkedit_data => unreachable,
3324 .fixup_chains_header => unreachable,
3325 .fixup_chains_imports => unreachable,
3326 .fixup_chains_symbol_names => unreachable,
3327 .fixup_chains_starts => unreachable,
3328 .fixup_chains_starts_in_image => unreachable,
3329 .fixup_chains_starts_in_segment => unreachable,
3330 .segment => unreachable,
3331 .section => unreachable,
3332 .section_manual_size => unreachable,
3333 .input_atom => |iai| {
3334 var sym_it = iai.ptrConst(macho).first_symbol;
3335 while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_node) {
3336 macho.targetStore(&sym.nlist(macho).n_sect, section.index(macho));
3337 }
3338 },
3339 }
3340 }
3341 break :task;
3342 }
3343
3344 while (macho.mf.updates.pop()) |ni| {
3345 ni.cleanUpdated(&macho.mf);
3346 const clean_moved = ni.cleanMoved(&macho.mf);
3347 const clean_resized = ni.cleanResized(&macho.mf);
3348 const clean_next_moved = ni.cleanNextMoved(&macho.mf);
3349 if (!clean_moved and !clean_resized and !clean_next_moved) continue;
3350
3351 const sub_prog_node = macho.mf.update_prog_node.start(@tagName(macho.getNode(ni)), 0);
3352 defer sub_prog_node.end();
3353
3354 if (clean_moved) try macho.flushMoved(ni);
3355 if (clean_resized) try macho.flushResized(ni);
3356
3357 break :task;
3358 }
3359
3360 if (macho.dirty_fixup_chain_pages.pop()) |kv| {
3361 const sub_prog_node = macho.mf.update_prog_node.start("Fixup Chains", 0);
3362 defer sub_prog_node.end();
3363 macho.flushFixupChains(kv.key.seg, kv.key.page);
3364 break :task;
3365 }
3366 }
3367
3368 return macho.sections_changed_index.count() > 0 or
3369 macho.mf.updates.items.len > 0 or
3370 macho.dirty_fixup_chain_pages.count() > 0;
3371}
3372fn flushMoved(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
3373 macho.flushMachOFileOffset(ni);
3374 switch (macho.getNode(ni)) {
3375 .macho => unreachable,
3376 .macho_header => {},
3377 .indirect_syms => {},
3378 .section_indirect_syms => |section| {
3379 const offset, _ = ni.location(&macho.mf).resolve(&macho.mf);
3380 macho.targetStore(&section.lcPtr(macho).reserved1, @intCast(
3381 @divExact(offset, 4), // valid due to alignment
3382 ));
3383 },
3384 .segment => |segment| {
3385 if (segment.fixupChainStartsNode(macho).unwrap()) |starts_in_segment_node| {
3386 const starts_in_segment: *std.macho.dyld_chained_starts_in_segment = @ptrCast(@alignCast(
3387 starts_in_segment_node.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_starts_in_segment)],
3388 ));
3389 macho.targetStore(
3390 &starts_in_segment.segment_offset,
3391 segment.vaddr(macho) - Segment.text.vaddr(macho),
3392 );
3393 }
3394 return; // segments don't bubble moved
3395 },
3396 .section, .section_manual_size => |section| {
3397 const segment_vaddr: u64 = section.segment(macho).vaddr(macho);
3398 const section_offset = ni.location(&macho.mf).resolve(&macho.mf)[0];
3399 macho.targetStore(&section.lcPtr(macho).addr, segment_vaddr + section_offset);
3400
3401 if (section == macho.known_sections.stubs) {
3402 for (macho.stubs.keys(), 0..) |symbol, stub_index| {
3403 const got_index = macho.got.getIndex(symbol).?;
3404 macho.writeStubEntry(stub_index, got_index);
3405 }
3406 for (macho.got.keys()) |symbol| {
3407 try symbol.applyTargetRelocs(macho, .init(.{
3408 .symbol = false,
3409 .branch = true,
3410 .got = false,
3411 .tlv = false,
3412 }));
3413 }
3414 }
3415
3416 if (section == macho.known_sections.got) {
3417 for (macho.stubs.keys(), 0..) |symbol, stub_index| {
3418 const got_index = macho.got.getIndex(symbol).?;
3419 macho.writeStubEntry(stub_index, got_index);
3420 }
3421 for (macho.got.keys()) |symbol| {
3422 try symbol.applyTargetRelocs(macho, .init(.{
3423 .symbol = false,
3424 .branch = false,
3425 .got = true,
3426 .tlv = false,
3427 }));
3428 }
3429 }
3430
3431 // If it is the GOT which has moved, its runtime fixup need to be flushed.
3432 if (section == macho.known_sections.got and macho.got.count() > 0) {
3433 const segment = section.segment(macho);
3434
3435 const old_start = macho.prev_got_offset;
3436 const new_start: u32 = off: {
3437 const got_file_off = ni.fileLocation(&macho.mf, false).offset;
3438 const seg_file_off = segment.node(macho).fileLocation(&macho.mf, false).offset;
3439 break :off @intCast(got_file_off - seg_file_off);
3440 };
3441
3442 const page_align = macho.targetPageAlign();
3443 const got_size = @as(u64, macho.got.count()) * macho.targetPtrSize();
3444
3445 const old_page_start: u32 = @intCast(@divFloor(old_start, page_align.toByteUnits()));
3446 const old_page_end: u32 = @intCast(@divCeil(old_start + got_size, page_align.toByteUnits()));
3447 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3448 macho.base.comp.gpa,
3449 old_page_end - old_page_start,
3450 );
3451 for (old_page_start..old_page_end) |page_index| {
3452 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3453 .seg = segment,
3454 .page = @intCast(page_index),
3455 }, {});
3456 }
3457
3458 const new_page_start: u32 = @intCast(@divFloor(new_start, page_align.toByteUnits()));
3459 const new_page_end: u32 = @intCast(@divCeil(new_start + got_size, page_align.toByteUnits()));
3460 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3461 macho.base.comp.gpa,
3462 new_page_end - new_page_start,
3463 );
3464 for (new_page_start..new_page_end) |page_index| {
3465 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3466 .seg = segment,
3467 .page = @intCast(page_index),
3468 }, {});
3469 }
3470
3471 macho.prev_got_offset = new_start;
3472 }
3473 },
3474 .symtab => {},
3475 .strtab => {},
3476 .linkedit_data => {
3477 // No need to bubble moved.
3478 return;
3479 },
3480 .fixup_chains_header => {},
3481 .fixup_chains_imports => {
3482 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3483 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3484 macho.node.fixup_chains_header.slice(&macho.mf),
3485 ));
3486 macho.targetStore(&hdr.imports_offset, @intCast(offset));
3487 },
3488 .fixup_chains_symbol_names => {
3489 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3490 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3491 macho.node.fixup_chains_header.slice(&macho.mf),
3492 ));
3493 macho.targetStore(&hdr.symbols_offset, @intCast(offset));
3494 },
3495 .fixup_chains_starts => {
3496 const offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3497 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3498 macho.node.fixup_chains_header.slice(&macho.mf),
3499 ));
3500 macho.targetStore(&hdr.starts_offset, @intCast(offset));
3501 },
3502 .fixup_chains_starts_in_image => {},
3503 .fixup_chains_starts_in_segment => |segment| {
3504 const starts_in_image_off = macho.node.fixup_chains_starts_in_image.location(&macho.mf).resolve(&macho.mf)[0];
3505 const starts_in_segment_off = ni.location(&macho.mf).resolve(&macho.mf)[0];
3506
3507 const chained_starts_in_image: []u32 = @ptrCast(@alignCast(
3508 macho.node.fixup_chains_starts_in_image.slice(&macho.mf),
3509 ));
3510 macho.targetStore(
3511 &chained_starts_in_image[@backingInt(segment) + 2],
3512 @intCast(starts_in_segment_off - starts_in_image_off),
3513 );
3514 },
3515 .input_atom => |iai| {
3516 const input_atom_offset: u64, _ = ni.location(&macho.mf).resolve(&macho.mf);
3517 const section = macho.getNode(iai.node(macho).parent(&macho.mf).unwrap().?).section;
3518
3519 const old_atom_addr = iai.ptrConst(macho).vaddr;
3520 const new_atom_addr = macho.targetLoad(&section.lcPtrConst(macho).addr) + input_atom_offset;
3521 iai.ptr(macho).vaddr = new_atom_addr;
3522
3523 const first_reloc = iai.ptr(macho).first_reloc;
3524 for (macho.relocs.items[@backingInt(first_reloc)..]) |*reloc| {
3525 if (reloc.node != ni) break;
3526 try reloc.apply(macho);
3527 }
3528
3529 var sym_it = iai.ptrConst(macho).first_symbol;
3530 while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_node) {
3531 const new_addr = sym.value(macho) - old_atom_addr + new_atom_addr;
3532 try sym.flushMoved(macho, new_addr);
3533 }
3534
3535 // If the atom which has moved contains runtime fixups, they need to be flushed.
3536 if (iai.ptr(macho).any_fixups) {
3537 const segment = section.segment(macho);
3538
3539 const page_align = macho.targetPageAlign();
3540
3541 const new_start: u32 = off: {
3542 const ni_file_off = ni.fileLocation(&macho.mf, false).offset;
3543 const seg_file_off = segment.node(macho).fileLocation(&macho.mf, false).offset;
3544 break :off @intCast(ni_file_off - seg_file_off);
3545 };
3546 const new_end = new_start + ni.location(&macho.mf).resolve(&macho.mf)[1];
3547
3548 const page_start: u32 = @intCast(@divFloor(new_start, page_align.toByteUnits()));
3549 const page_end: u32 = @intCast(@divCeil(new_end, page_align.toByteUnits()));
3550 try macho.dirty_fixup_chain_pages.ensureUnusedCapacity(
3551 macho.base.comp.gpa,
3552 page_end - page_start,
3553 );
3554 for (page_start..page_end) |page_index| {
3555 macho.dirty_fixup_chain_pages.putAssumeCapacity(.{
3556 .seg = segment,
3557 .page = @intCast(page_index),
3558 }, {});
3559 }
3560 }
3561 },
3562 }
3563 try ni.childrenMoved(macho.base.comp.gpa, &macho.mf);
3564}
3565fn flushMachOFileOffset(macho: *MachO, ni: MappedFile.Node.Index) void {
3566 const offset = ni.fileLocation(&macho.mf, false).offset;
3567 switch (macho.getNode(ni)) {
3568 .macho => unreachable,
3569 .macho_header => {
3570 assert(offset == 0);
3571 },
3572 .symtab => {
3573 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
3574 macho.targetStore(&symtab_cmd.symoff, @intCast(offset));
3575 },
3576 .strtab => {
3577 const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command);
3578 macho.targetStore(&symtab_cmd.stroff, @intCast(offset));
3579 },
3580 .indirect_syms => {
3581 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
3582 macho.targetStore(&dysymtab_cmd.indirectsymoff, @intCast(offset));
3583 },
3584 .section_indirect_syms => {},
3585 .linkedit_data => |lc| {
3586 const linkedit_data_cmd = lc.ptr(macho, std.macho.linkedit_data_command);
3587 macho.targetStore(&linkedit_data_cmd.dataoff, @intCast(offset));
3588 },
3589 .fixup_chains_header => {},
3590 .fixup_chains_imports => {},
3591 .fixup_chains_symbol_names => {},
3592 .fixup_chains_starts => {},
3593 .fixup_chains_starts_in_image => {},
3594 .fixup_chains_starts_in_segment => {},
3595 .segment => |segment| {
3596 macho.targetStore(&segment.lcPtr(macho).fileoff, @intCast(offset));
3597 var child_oni = ni.first(&macho.mf);
3598 while (child_oni.unwrap()) |child_ni| : (child_oni = child_ni.next(&macho.mf)) {
3599 macho.flushMachOFileOffset(child_ni);
3600 }
3601 },
3602 .section, .section_manual_size => |section| {
3603 macho.targetStore(&section.lcPtr(macho).offset, @intCast(offset));
3604 },
3605 .input_atom => {},
3606 }
3607}
3608fn flushResized(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void {
3609 const size = ni.location(&macho.mf).resolve(&macho.mf)[1];
3610 switch (macho.getNode(ni)) {
3611 .macho => {},
3612 .macho_header => {},
3613 .symtab, .strtab => {
3614 // Don't modify the symtab load command's `nsyms` or `strsize` field here; we only do
3615 // that when we actually add strings or symbols.
3616 },
3617 .indirect_syms => {
3618 const dysymtab_cmd = macho.lc.dysymtab.ptr(macho, std.macho.dysymtab_command);
3619 macho.targetStore(&dysymtab_cmd.nindirectsyms, @intCast(@divExact(size, 4)));
3620 },
3621 .section_indirect_syms => {},
3622 .linkedit_data => |lc| {
3623 const linkedit_data_cmd = lc.ptr(macho, std.macho.linkedit_data_command);
3624 macho.targetStore(&linkedit_data_cmd.datasize, @intCast(size));
3625 },
3626 .fixup_chains_header => {},
3627 .fixup_chains_imports => {},
3628 .fixup_chains_symbol_names => {},
3629 .fixup_chains_starts => {},
3630 .fixup_chains_starts_in_image => {},
3631 .fixup_chains_starts_in_segment => {},
3632 .segment => |segment| {
3633 macho.targetStore(&segment.lcPtr(macho).filesize, size);
3634 },
3635 .section => |section| {
3636 macho.targetStore(&section.lcPtr(macho).size, size);
3637 },
3638 .section_manual_size => {},
3639 .input_atom => {},
3640 }
3641}
3642
3643fn getNodeVAddr(macho: *MachO, ni: MappedFile.Node.Index) u64 {
3644 return switch (macho.getNode(ni)) {
3645 .macho => unreachable,
3646 .macho_header => unreachable,
3647 .symtab => unreachable,
3648 .strtab => unreachable,
3649 .indirect_syms => unreachable,
3650 .section_indirect_syms => unreachable,
3651 .linkedit_data => unreachable,
3652 .fixup_chains_header => unreachable,
3653 .fixup_chains_imports => unreachable,
3654 .fixup_chains_symbol_names => unreachable,
3655 .fixup_chains_starts => unreachable,
3656 .fixup_chains_starts_in_image => unreachable,
3657 .fixup_chains_starts_in_segment => unreachable,
3658 .segment => |segment| segment.vaddr(macho),
3659 .section, .section_manual_size => |section| section.vaddr(macho),
3660 .input_atom => |iai| iai.ptr(macho).vaddr,
3661 };
3662}
3663fn getNodeSection(macho: *MachO, ni: MappedFile.Node.Index) Section {
3664 var cur_ni = ni;
3665 while (true) {
3666 switch (macho.getNode(cur_ni)) {
3667 .section, .section_manual_size => |section| return section,
3668 else => cur_ni = cur_ni.parent(&macho.mf).unwrap().?,
3669 }
3670 }
3671}
3672fn nodeFirstSymbolPtr(macho: *MachO, ni: MappedFile.Node.Index) *Symbol.Index.Optional {
3673 return switch (macho.getNode(ni)) {
3674 .input_atom => |iai| &iai.ptr(macho).first_symbol,
3675 else => unreachable,
3676 };
3677}
3678
3679fn targetStubSize(macho: *const MachO) u8 {
3680 return switch (macho.base.comp.getTarget().cpu.arch) {
3681 else => unreachable,
3682 .aarch64 => 12,
3683 .x86_64 => 6,
3684 };
3685}
3686fn appendStubEntry(macho: *MachO, old_len: u64, got_index: u64, target_symbol: Symbol.Index) Error!void {
3687 const gpa = macho.base.comp.gpa;
3688
3689 const entry_size = macho.targetStubSize();
3690 const stub_offset = entry_size * old_len;
3691 assert(macho.targetLoad(&macho.known_sections.stubs.lcPtrConst(macho).size) == stub_offset);
3692
3693 const new_size = stub_offset + entry_size;
3694 try macho.known_sections.stubs.node(macho).ensureMinimumSize(gpa, &macho.mf, new_size);
3695 macho.targetStore(&macho.known_sections.stubs.lcPtr(macho).size, new_size);
3696
3697 macho.writeStubEntry(old_len, got_index);
3698
3699 try macho.node.stubs_indirect_syms.ensureMinimumSize(gpa, &macho.mf, (old_len + 1) * 4);
3700 const indirect_syms: []Symbol.Index = @ptrCast(@alignCast(
3701 macho.node.stubs_indirect_syms.slice(&macho.mf),
3702 ));
3703 indirect_syms[@intCast(old_len)] = target_symbol;
3704}
3705fn writeStubEntry(macho: *MachO, stub_index: u64, got_index: u64) void {
3706 if (macho.known_sections.stubs.node(macho).hasMoved(&macho.mf)) return;
3707 if (macho.known_sections.got.node(macho).hasMoved(&macho.mf)) return;
3708
3709 const entry_size = macho.targetStubSize();
3710 const stub_offset = entry_size * stub_index;
3711
3712 const got_entry_vaddr = macho.known_sections.got.vaddr(macho) + got_index * macho.targetPtrSize();
3713 const new_stub_vaddr = macho.known_sections.stubs.vaddr(macho) + stub_offset;
3714
3715 switch (macho.base.comp.getTarget().cpu.arch) {
3716 else => unreachable,
3717 .aarch64 => {
3718 // The first instruction of the stub will be an ADRP for which we need to compute the
3719 // offset from that instruction's 4k page to the GOT entry's 4k page.
3720 const cur_page_base = (new_stub_vaddr >> 12) << 12;
3721 const target_page_base = (got_entry_vaddr >> 12) << 12;
3722 const cur_page_to_target_page: i64 = @bitCast(target_page_base - cur_page_base);
3723
3724 // Then we will use an LDR to reference the GOT entry on that page.
3725 const target_page_off: u12 = @truncate(got_entry_vaddr);
3726
3727 const insts: []align(4) aarch64.encoding.Instruction = @ptrCast(@alignCast(
3728 macho.known_sections.stubs.node(macho).slice(&macho.mf)[@intCast(stub_offset)..][0..entry_size],
3729 ));
3730 @memcpy(insts, @as([]const aarch64.encoding.Instruction, &.{
3731 .adrp(.x16, @intCast(cur_page_to_target_page)),
3732 .ldr(.x16, .{ .unsigned_offset = .{ .base = .x16, .offset = target_page_off } }),
3733 .br(.x16),
3734 }));
3735 if (.little != std.lang.Endian.native) { // aarch64 instructions are little-endian
3736 std.mem.byteSwapAllElements(aarch64.encoding.Instruction, insts);
3737 }
3738 },
3739 .x86_64 => {
3740 const new_stub_slice: []align(2) u8 = @ptrCast(@alignCast(
3741 macho.known_sections.stubs.node(macho).slice(&macho.mf)[@intCast(stub_offset)..][0..entry_size],
3742 ));
3743 // jmpq *got_entry(%rip)
3744 @memcpy(new_stub_slice[0..2], @as([]const u8, &.{ 0xff, 0x25 }));
3745 const offset_ptr: *align(2) i32 = @ptrCast(new_stub_slice[2..]);
3746 const offset: i64 = @bitCast(got_entry_vaddr -% (new_stub_vaddr + 6));
3747 macho.targetStore(offset_ptr, @intCast(offset));
3748 },
3749 }
3750}
3751
3752fn appendGotEntry(macho: *MachO, old_len: u64, target_symbol: Symbol.Index) Error!void {
3753 const gpa = macho.base.comp.gpa;
3754
3755 const ptr_size = macho.targetPtrSize();
3756 const entry_offset = ptr_size * old_len;
3757 assert(macho.targetLoad(&macho.known_sections.got.lcPtrConst(macho).size) == entry_offset);
3758
3759 const new_size = entry_offset + ptr_size;
3760 try macho.known_sections.got.node(macho).ensureMinimumSize(gpa, &macho.mf, new_size);
3761 macho.targetStore(&macho.known_sections.got.lcPtr(macho).size, new_size);
3762
3763 const new_entry = macho.known_sections.got.node(macho).slice(&macho.mf)[@intCast(entry_offset)..][0..ptr_size];
3764 @memset(new_entry, 0);
3765
3766 if (!macho.known_sections.got.node(macho).hasMoved(&macho.mf)) {
3767 const segment = macho.known_sections.got.segment(macho);
3768 const page_index: u16 = @intCast(@divFloor(
3769 macho.prev_got_offset + entry_offset,
3770 macho.targetPageAlign().toByteUnits(),
3771 ));
3772 try macho.dirty_fixup_chain_pages.put(gpa, .{ .seg = segment, .page = page_index }, {});
3773 }
3774
3775 try macho.node.got_indirect_syms.ensureMinimumSize(gpa, &macho.mf, (old_len + 1) * 4);
3776 const indirect_syms: []Symbol.Index = @ptrCast(@alignCast(
3777 macho.node.got_indirect_syms.slice(&macho.mf),
3778 ));
3779 indirect_syms[@intCast(old_len)] = target_symbol;
3780}
3781
3782fn appendImportEntry(macho: *MachO, old_len: u64, symbol_index: Symbol.Index) Error!void {
3783 const gpa = macho.base.comp.gpa;
3784
3785 const new_size = @sizeOf(std.macho.dyld_chained_import_addend64) * (old_len + 1);
3786 try macho.node.fixup_chains_imports.ensureMinimumSize(gpa, &macho.mf, new_size);
3787
3788 const symbol_name_off = macho.fixup_chain_symbol_names_len;
3789 macho.fixup_chain_symbol_names_len += @intCast(symbol_index.name(macho).len + 1);
3790 try macho.node.fixup_chains_symbol_names.ensureMinimumSize(gpa, &macho.mf, macho.fixup_chain_symbol_names_len);
3791 {
3792 const sym_name = symbol_index.name(macho);
3793 const name_slice = macho.node.fixup_chains_symbol_names.slice(&macho.mf)[symbol_name_off..];
3794 @memcpy(name_slice[0..sym_name.len], sym_name);
3795 name_slice[sym_name.len] = 0;
3796 }
3797
3798 {
3799 const hdr: *std.macho.dyld_chained_fixups_header = @ptrCast(@alignCast(
3800 macho.node.fixup_chains_header.slice(&macho.mf)[0..@sizeOf(std.macho.dyld_chained_fixups_header)],
3801 ));
3802 assert(macho.targetLoad(&hdr.imports_count) == old_len);
3803 macho.targetStore(&hdr.imports_count, @intCast(old_len + 1));
3804 }
3805
3806 const imports: []std.macho.dyld_chained_import_addend64 = @ptrCast(@alignCast(
3807 macho.node.fixup_chains_imports.slice(&macho.mf)[0..@intCast(new_size)],
3808 ));
3809 imports[@intCast(old_len)] = .{
3810 .bits = .{
3811 .lib_ordinal = 0xFFFE,
3812 .weak_import = false,
3813 .name_offset = symbol_name_off,
3814 },
3815 .addend = 0,
3816 };
3817 if (macho.targetEndian() != std.lang.Endian.native) {
3818 std.mem.byteSwapAllFields(std.macho.dyld_chained_import_addend64, &imports[@intCast(old_len)]);
3819 }
3820}
3821
3822pub fn updateFunc(
3823 macho: *MachO,
3824 pt: Zcu.PerThread,
3825 func_index: InternPool.Index,
3826 mir: *const codegen.AnyMir,
3827) link.Error!void {
3828 _ = macho;
3829 _ = pt;
3830 _ = func_index;
3831 _ = mir;
3832 @panic("TODO");
3833}
3834pub fn updateNav(macho: *MachO, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.Error!void {
3835 _ = macho;
3836 _ = pt;
3837 _ = nav_index;
3838 @panic("TODO");
3839}
3840pub fn updateExports(
3841 macho: *MachO,
3842 pt: Zcu.PerThread,
3843 export_indices: []const Zcu.Export.Index,
3844) link.Error!void {
3845 _ = macho;
3846 _ = pt;
3847 _ = export_indices;
3848 @panic("TODO");
3849}
3850
3851fn dumpStderr(macho: *MachO) Io.File.Writer.Error!void {
3852 const comp = macho.base.comp;
3853 const io = comp.io;
3854 var buffer: [512]u8 = undefined;
3855 const stderr = try io.lockStderr(&buffer, null);
3856 defer io.unlockStderr();
3857 const w = &stderr.file_writer.interface;
3858 _ = macho.dump(w) catch |err| switch (err) {
3859 error.WriteFailed => return stderr.file_writer.err.?,
3860 };
3861}
3862
3863pub fn dump(macho: *MachO, w: *Io.Writer) !link.File.DumpResult {
3864 if (macho.options.enable_link_snapshots) {
3865 try macho.printNode(w, .root, 0);
3866 return .enabled;
3867 }
3868 return .disabled;
3869}
3870fn printNode(
3871 macho: *const MachO,
3872 w: *Io.Writer,
3873 ni: MappedFile.Node.Index,
3874 indent: usize,
3875) Io.Writer.Error!void {
3876 try w.splatByteAll(' ', indent);
3877 switch (macho.getNode(ni)) {
3878 .macho => try w.writeAll("macho"),
3879 .macho_header => try w.writeAll("macho_header"),
3880 .symtab => try w.writeAll("symtab"),
3881 .strtab => try w.writeAll("strtab"),
3882 .indirect_syms => try w.writeAll("indirect_syms"),
3883 .section_indirect_syms => |section| try w.print("section_indirect_syms({s},{s})", .{
3884 section.segmentName(macho),
3885 section.name(macho),
3886 }),
3887 .linkedit_data => |lc| try w.print("linkedit_data(LC_{t})", .{
3888 macho.targetLoad(&lc.ptrConst(macho, std.macho.load_command).cmd),
3889 }),
3890 .fixup_chains_header => try w.writeAll("fixup_chains_header"),
3891 .fixup_chains_imports => try w.writeAll("fixup_chains_imports"),
3892 .fixup_chains_symbol_names => try w.writeAll("fixup_chains_symbol_names"),
3893 .fixup_chains_starts => try w.writeAll("fixup_chains_starts"),
3894 .fixup_chains_starts_in_image => try w.writeAll("fixup_chains_starts_in_image"),
3895 .fixup_chains_starts_in_segment => |seg| try w.print("fixup_chains_starts_in_segment({s})", .{seg.name(macho)}),
3896 .section => |section| try w.print("section({s},{s})", .{
3897 section.segmentName(macho),
3898 section.name(macho),
3899 }),
3900 .section_manual_size => |section| try w.print("section_manual_size({s},{s})", .{
3901 section.segmentName(macho),
3902 section.name(macho),
3903 }),
3904 .segment => |segment| try w.print("segment({s})", .{segment.name(macho)}),
3905 .input_atom => |iai| {
3906 // It's useful to try and give a name to the input atom, but input atoms don't actually
3907 // have names. However, a decent approach if the input atom only contains one symbol is
3908 // to print that symbol's name. We also accept the input atom containing many global
3909 // symbols but only one internal symbol (since that's how Zig exports work).
3910 if (iai.ptrConst(macho).first_symbol.unwrap()) |first_symbol| {
3911 if (first_symbol.ptrConst(macho).next_in_node == .none) {
3912 // There is only one symbol in the section, so it's a good identifier.
3913 try w.print("input_atom({q})", .{first_symbol.name(macho)});
3914 } else {
3915 // Also, if it has N global symbols but only one internal symbol, go with the
3916 // internal name---this works well for how Zig handles exports.
3917 var prev_local_sym: Symbol.Index.Optional = .none;
3918 var sym_it: Symbol.Index.Optional = .wrap(first_symbol);
3919 const only_local_sym: Symbol.Index.Optional = while (sym_it.unwrap()) |sym| : (sym_it = sym.ptrConst(macho).next_in_node) {
3920 const n_type = macho.targetLoad(&sym.nlistConst(macho).n_type);
3921 if (!n_type.bits.ext and !n_type.bits.pext) {
3922 // this is an internal symbol
3923 if (prev_local_sym != .none) break .none;
3924 prev_local_sym = .wrap(sym);
3925 }
3926 } else prev_local_sym;
3927
3928 if (only_local_sym.unwrap()) |local_sym| {
3929 try w.print("input_atom({q})", .{local_sym.name(macho)});
3930 } else {
3931 try w.writeAll("input_atom([multiple symbols])");
3932 }
3933 }
3934 } else {
3935 try w.writeAll("input_atom([no symbols])");
3936 }
3937 },
3938 }
3939 {
3940 const mf_node = &macho.mf.nodes.items[@backingInt(ni)];
3941 const off, const size = mf_node.location().resolve(&macho.mf);
3942 try w.print(" index={d} offset=0x{x} size=0x{x} align=0x{x} {t}{s}{s}{s}{s}{s}{s}\n", .{
3943 @backingInt(ni),
3944 off,
3945 size,
3946 mf_node.flags.alignment.toByteUnits(),
3947 mf_node.flags.position,
3948 if (mf_node.flags.bubbles_moved) " bubbles_moved" else "",
3949 if (mf_node.flags.moved) " moved" else "",
3950 if (mf_node.flags.resized) " resized" else "",
3951 if (mf_node.flags.enable_next_moved) " enable_next_moved" else "",
3952 if (mf_node.flags.next_moved) " next_moved" else "",
3953 if (mf_node.flags.has_content) " has_content" else "",
3954 });
3955 }
3956 if (ni.first(&macho.mf).unwrap()) |first_ni| {
3957 // non-leaf, just print children
3958 var child_ni = first_ni;
3959 while (true) {
3960 try macho.printNode(w, child_ni, indent + 1);
3961 child_ni = child_ni.next(&macho.mf).unwrap() orelse break;
3962 }
3963 return;
3964 }
3965 const start_address: usize, const end_address: usize = file_loc: {
3966 const file_loc = ni.fileLocation(&macho.mf, false);
3967 break :file_loc .{ @intCast(file_loc.offset), @intCast(file_loc.offset + file_loc.size) };
3968 };
3969 var address = start_address;
3970 const line_len = 0x10;
3971 while (true) : (address = @min(std.mem.alignForward(usize, address + 1, line_len), end_address)) {
3972 try w.splatByteAll(' ', indent + 1);
3973 try w.print("{x:0>8}", .{address});
3974 if (address == end_address) break try w.writeByte('\n');
3975 try w.splatByteAll(' ', 2);
3976 const start_byte_address = std.mem.alignBackward(usize, address, line_len);
3977 const end_byte_address = start_byte_address + line_len;
3978 for (start_byte_address..end_byte_address) |byte_address|
3979 if (byte_address < start_address or byte_address >= end_address)
3980 try w.splatByteAll(' ', 3)
3981 else
3982 try w.print("{x:0>2} ", .{macho.mf.memory_map.memory[byte_address]});
3983 try w.writeByte(' ');
3984 for (start_byte_address..@min(end_address, end_byte_address)) |byte_address|
3985 try w.writeByte(if (byte_address < start_address or byte_address >= end_address) ' ' else char: {
3986 const byte = macho.mf.memory_map.memory[byte_address];
3987 break :char if (std.ascii.isPrint(byte)) byte else '.';
3988 });
3989 try w.writeByte('\n');
3990 }
3991}
3992
3993fn flushFixupChains(
3994 macho: *MachO,
3995 segment: Segment,
3996 page: u16,
3997) void {
3998 assert(!std.mem.eql(u8, segment.name(macho), "__LINKEDIT"));
3999
4000 const page_size = macho.targetPageAlign().toByteUnits();
4001 const start_vaddr = segment.vaddr(macho) + @as(u64, page) * page_size;
4002 const end_vaddr = start_vaddr + page_size;
4003
4004 var next_vaddr: u64 = 0;
4005 var node_it = segment.node(macho).last(&macho.mf);
4006 while (node_it.unwrap()) |node| : (node_it = node.prev(&macho.mf)) {
4007 // Other than `__LINKEDIT`, segments can only contain sections.
4008 const sect: Section = switch (macho.getNode(node)) {
4009 .section, .section_manual_size => |sect| sect,
4010 .macho => unreachable,
4011 .macho_header => continue, // the Mach header and load commands are in the __TEXT section
4012 .symtab => unreachable,
4013 .strtab => unreachable,
4014 .indirect_syms => unreachable,
4015 .section_indirect_syms => unreachable,
4016 .linkedit_data => unreachable,
4017 .fixup_chains_header => unreachable,
4018 .fixup_chains_imports => unreachable,
4019 .fixup_chains_symbol_names => unreachable,
4020 .fixup_chains_starts => unreachable,
4021 .fixup_chains_starts_in_image => unreachable,
4022 .fixup_chains_starts_in_segment => unreachable,
4023 .segment => unreachable,
4024 .input_atom => unreachable,
4025 };
4026 if (sect.vaddr(macho) >= end_vaddr) {
4027 // This section is after the page we're interested in. We're iterating backwards, so
4028 // that means we've not yet found the page.
4029 continue;
4030 }
4031 if (sect.vaddr(macho) + macho.targetLoad(&sect.lcPtrConst(macho).size) < start_vaddr) {
4032 // This section is before the page we're interested in. We're iterating backwards, so
4033 // that means we've finished scanning the page.
4034 break;
4035 }
4036 if (sect == macho.known_sections.got) {
4037 macho.flushFixupChainsGot(
4038 &next_vaddr,
4039 start_vaddr,
4040 start_vaddr + page_size,
4041 );
4042 } else {
4043 var sub_node_it = node.last(&macho.mf);
4044 while (sub_node_it.unwrap()) |sub_node| : (sub_node_it = sub_node.prev(&macho.mf)) {
4045 macho.flushFixupChainsNonSpecial(
4046 sub_node,
4047 &next_vaddr,
4048 start_vaddr,
4049 start_vaddr + page_size,
4050 );
4051 }
4052 }
4053 }
4054
4055 const page_starts: []u16 = @ptrCast(@alignCast(
4056 segment.fixupChainStartsNode(macho).unwrap().?.slice(&macho.mf)[@sizeOf(std.macho.dyld_chained_starts_in_segment) - 2 ..],
4057 ));
4058 if (next_vaddr == 0) {
4059 macho.targetStore(&page_starts[page], 0xFFFF);
4060 } else {
4061 macho.targetStore(&page_starts[page], @intCast(next_vaddr - start_vaddr));
4062 }
4063}
4064fn flushFixupChainsGot(
4065 macho: *MachO,
4066 next_vaddr: *u64,
4067 start_vaddr: u64,
4068 end_vaddr: u64,
4069) void {
4070 const ptr_size = macho.targetPtrSize();
4071 const got_start_vaddr = macho.known_sections.got.vaddr(macho);
4072 const got_end_vaddr = got_start_vaddr + @as(u64, macho.got.count()) * ptr_size;
4073 if (got_start_vaddr >= end_vaddr) return;
4074 if (got_end_vaddr <= start_vaddr) return;
4075 // There is some overlap between the GOT and the page range.
4076 const start_index = @divExact(start_vaddr -| got_start_vaddr, ptr_size);
4077 const end_index = @min(@divExact(end_vaddr -| got_start_vaddr, ptr_size), macho.got.count());
4078
4079 const got_node = macho.known_sections.got.node(macho);
4080 const got_syms = macho.got.keys();
4081
4082 var got_index = end_index;
4083 while (got_index > start_index) {
4084 got_index -= 1;
4085 macho.writeOneFixup(
4086 got_syms[got_index],
4087 0,
4088 got_node,
4089 got_index * ptr_size,
4090 start_vaddr,
4091 end_vaddr,
4092 next_vaddr,
4093 );
4094 }
4095}
4096fn flushFixupChainsNonSpecial(
4097 macho: *MachO,
4098 node: MappedFile.Node.Index,
4099 next_vaddr: *u64,
4100 start_vaddr: u64,
4101 end_vaddr: u64,
4102) void {
4103 switch (macho.getNode(node)) {
4104 .macho => unreachable,
4105 .macho_header => unreachable,
4106 .symtab => unreachable,
4107 .strtab => unreachable,
4108 .indirect_syms => unreachable,
4109 .section_indirect_syms => unreachable,
4110 .linkedit_data => unreachable,
4111 .fixup_chains_header => unreachable,
4112 .fixup_chains_imports => unreachable,
4113 .fixup_chains_symbol_names => unreachable,
4114 .fixup_chains_starts => unreachable,
4115 .fixup_chains_starts_in_image => unreachable,
4116 .fixup_chains_starts_in_segment => unreachable,
4117 .segment => unreachable,
4118 .section => unreachable,
4119 .section_manual_size => unreachable,
4120 .input_atom => |iai| {
4121 const first_reloc = iai.ptr(macho).first_reloc;
4122
4123 var reloc = first_reloc;
4124 var prev_reloc_off: ?u32 = null;
4125 while (@backingInt(reloc) < macho.relocs.items.len and
4126 reloc.ptr(macho).node == node)
4127 {
4128 if (prev_reloc_off) |prev_off| {
4129 assert(reloc.ptr(macho).offset > prev_off);
4130 }
4131 prev_reloc_off = reloc.ptr(macho).offset;
4132 reloc = @fromBackingInt(@backingInt(reloc) + 1);
4133 }
4134
4135 while (reloc != first_reloc) {
4136 reloc = @fromBackingInt(@backingInt(reloc) - 1);
4137 if (reloc.ptr(macho).bits.result == .fixup) {
4138 macho.writeOneFixup(
4139 reloc.ptr(macho).target_symbol,
4140 reloc.ptr(macho).addend,
4141 reloc.ptr(macho).node,
4142 reloc.ptr(macho).offset,
4143 start_vaddr,
4144 end_vaddr,
4145 next_vaddr,
4146 );
4147 }
4148 }
4149 },
4150 }
4151}
4152fn writeOneFixup(
4153 macho: *MachO,
4154 target: Symbol.Index,
4155 addend: u64,
4156 dest_node: MappedFile.Node.Index,
4157 dest_offset: u64,
4158 start_vaddr: u64,
4159 end_vaddr: u64,
4160 next_vaddr: *u64,
4161) void {
4162 assert(macho.targetPtrSize() == 8);
4163 const dest_vaddr = macho.getNodeVAddr(dest_node) + dest_offset;
4164 if (dest_vaddr < start_vaddr or dest_vaddr >= end_vaddr) return;
4165 const dest_ptr: *align(4) std.macho.dyld_chained_ptr_64 = @ptrCast(@alignCast(
4166 dest_node.slice(&macho.mf)[@intCast(dest_offset)..][0..8],
4167 ));
4168 switch (macho.targetLoad(&target.nlist(macho).n_type).bits.type) {
4169 .undf => {
4170 const import_index = macho.imports.getIndex(target).?;
4171 macho.targetStore(dest_ptr, .{
4172 .kind = .bind,
4173 .next = if (next_vaddr.* != 0) @intCast(
4174 @divExact(next_vaddr.* - dest_vaddr, 4),
4175 ) else 0,
4176 .per_kind = .{ .bind = .{
4177 .ordinal = @intCast(import_index),
4178 .addend = std.math.cast(u8, addend) orelse {
4179 std.debug.panic("TODO(MachO2): large fixup addend (target={q}, addend={d})", .{
4180 target.name(macho), addend,
4181 });
4182 },
4183 } },
4184 });
4185 next_vaddr.* = dest_vaddr;
4186 },
4187 .sect => {
4188 const target_val: packed struct(u64) {
4189 low36: u36,
4190 mid: u20,
4191 high8: u8,
4192 } = @bitCast(target.value(macho) +% addend);
4193 if (target_val.mid != 0) {
4194 @panic("TODO(MachO2): report non-zero mid bits in rebase fixup (vmaddrs too large?)");
4195 }
4196 macho.targetStore(dest_ptr, .{
4197 .kind = .rebase,
4198 .next = if (next_vaddr.* != 0) @intCast(
4199 @divExact(next_vaddr.* - dest_vaddr, 4),
4200 ) else 0,
4201 .per_kind = .{ .rebase = .{
4202 .target = target_val.low36,
4203 .high8 = target_val.high8,
4204 } },
4205 });
4206 next_vaddr.* = dest_vaddr;
4207 },
4208 .abs => {
4209 // We actually *don't* need a fixup---this is just a static relocation with extra steps!
4210 const target_val = target.value(macho) +% addend;
4211 macho.targetStore(dest_ptr, @bitCast(target_val));
4212 },
4213 else => unreachable,
4214 }
4215}
4216
4217const std = @import("std");
4218const assert = std.debug.assert;
4219const log = std.log.scoped(.link);
4220const Allocator = std.mem.Allocator;
4221const Io = std.Io;
4222
4223const aarch64 = @import("../codegen/aarch64.zig");
4224const link = @import("../link.zig");
4225const tapi = @import("tapi.zig");
4226const codegen = @import("../codegen.zig");
4227const Compilation = @import("../Compilation.zig");
4228const InternPool = @import("../InternPool.zig");
4229const MappedFile = @import("MappedFile.zig");
4230const Zcu = @import("../Zcu.zig");