1pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void {
2 const gpa = comp.gpa;
3 const io = comp.io;
4 const diags = &comp.link_diags;
5
6 // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list.
7 var positionals = std.array_list.Managed(link.Input).init(gpa);
8 defer positionals.deinit();
9 try positionals.ensureUnusedCapacity(comp.link_inputs.len);
10 positionals.appendSliceAssumeCapacity(comp.link_inputs);
11
12 for (comp.c_objects.items) |c_object| {
13 try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path));
14 }
15
16 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
17
18 if (macho_file.getZigObject() == null and positionals.items.len == 1) {
19 // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all
20 // debug info segments/sections (this is apparently by design by Apple), we copy
21 // the *only* input file over.
22 const path = positionals.items[0].path().?;
23 const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err|
24 return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) });
25 const stat = in_file.stat(io) catch |err|
26 return diags.fail("failed to stat {f}: {s}", .{ path, @errorName(err) });
27 link.File.copyRangeAll2(io, in_file, macho_file.base.file.?, 0, 0, stat.size) catch |err|
28 return diags.fail("failed to copy range of file {f}: {t}", .{ path, err });
29 return;
30 }
31
32 for (positionals.items) |link_input| {
33 macho_file.classifyInputFile(link_input) catch |err|
34 diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
35 }
36
37 if (diags.hasErrors()) return error.AlreadyReported;
38
39 try macho_file.parseInputFiles();
40
41 if (diags.hasErrors()) return error.AlreadyReported;
42
43 try macho_file.resolveSymbols();
44 macho_file.dedupLiterals() catch |err| switch (err) {
45 error.OutOfMemory, error.AlreadyReported => |e| return e,
46 else => |e| return diags.fail("failed to update ar size: {s}", .{@errorName(e)}),
47 };
48 markExports(macho_file);
49 claimUnresolved(macho_file);
50 try initOutputSections(macho_file);
51 try macho_file.sortSections();
52 try macho_file.addAtomsToSections();
53 try calcSectionSizes(macho_file);
54
55 try createSegment(macho_file);
56 allocateSections(macho_file) catch |err| switch (err) {
57 error.AlreadyReported => |e| return e,
58 else => |e| return diags.fail("failed to allocate sections: {s}", .{@errorName(e)}),
59 };
60 allocateSegment(macho_file);
61
62 if (build_options.enable_logging) {
63 state_log.debug("{f}", .{macho_file.dumpState()});
64 }
65
66 try writeSections(macho_file);
67 sortRelocs(macho_file);
68 try writeSectionsToFile(macho_file);
69
70 // In order to please Apple ld (and possibly other MachO linkers in the wild),
71 // we will now sanitize segment names of Zig-specific segments.
72 sanitizeZigSections(macho_file);
73
74 const ncmds, const sizeofcmds = try writeLoadCommands(macho_file);
75 try writeHeader(macho_file, ncmds, sizeofcmds);
76}
77
78pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void {
79 const gpa = comp.gpa;
80 const io = comp.io;
81 const diags = &macho_file.base.comp.link_diags;
82
83 var positionals = std.array_list.Managed(link.Input).init(gpa);
84 defer positionals.deinit();
85
86 try positionals.ensureUnusedCapacity(comp.link_inputs.len);
87 positionals.appendSliceAssumeCapacity(comp.link_inputs);
88
89 for (comp.c_objects.items) |c_object| {
90 try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path));
91 }
92
93 if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
94
95 if (comp.compiler_rt_strat == .obj) {
96 try positionals.append(try link.openObjectInput(io, diags, comp.compiler_rt_obj.?.full_object_path));
97 }
98
99 if (comp.ubsan_rt_strat == .obj) {
100 try positionals.append(try link.openObjectInput(io, diags, comp.ubsan_rt_obj.?.full_object_path));
101 }
102
103 for (positionals.items) |link_input| {
104 macho_file.classifyInputFile(link_input) catch |err|
105 diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
106 }
107
108 if (diags.hasErrors()) return error.AlreadyReported;
109
110 try parseInputFilesAr(macho_file);
111
112 if (diags.hasErrors()) return error.AlreadyReported;
113
114 // First, we flush relocatable object file generated with our backends.
115 if (macho_file.getZigObject()) |zo| {
116 try zo.resolveSymbols(macho_file);
117 zo.asFile().markExportsRelocatable(macho_file);
118 zo.asFile().claimUnresolvedRelocatable(macho_file);
119 try macho_file.sortSections();
120 try macho_file.addAtomsToSections();
121 try calcSectionSizes(macho_file);
122 try createSegment(macho_file);
123 allocateSections(macho_file) catch |err|
124 return diags.fail("failed to allocate sections: {s}", .{@errorName(err)});
125 allocateSegment(macho_file);
126
127 if (build_options.enable_logging) {
128 state_log.debug("{f}", .{macho_file.dumpState()});
129 }
130
131 try writeSections(macho_file);
132 sortRelocs(macho_file);
133 try writeSectionsToFile(macho_file);
134
135 // In order to please Apple ld (and possibly other MachO linkers in the wild),
136 // we will now sanitize segment names of Zig-specific segments.
137 sanitizeZigSections(macho_file);
138
139 const ncmds, const sizeofcmds = try writeLoadCommands(macho_file);
140 try writeHeader(macho_file, ncmds, sizeofcmds);
141
142 try zo.readFileContents(macho_file);
143 }
144
145 var files = std.array_list.Managed(File.Index).init(gpa);
146 defer files.deinit();
147 try files.ensureTotalCapacityPrecise(macho_file.objects.items.len + 1);
148 if (macho_file.getZigObject()) |zo| files.appendAssumeCapacity(zo.index);
149 for (macho_file.objects.items) |index| files.appendAssumeCapacity(index);
150
151 const format: Archive.Format = .p32;
152
153 // Update ar symtab from parsed objects
154 var ar_symtab: Archive.ArSymtab = .{};
155 defer ar_symtab.deinit(gpa);
156
157 for (files.items) |index| {
158 try macho_file.getFile(index).?.updateArSymtab(&ar_symtab, macho_file);
159 }
160
161 ar_symtab.sort();
162
163 // Update sizes of contributing objects
164 for (files.items) |index| {
165 macho_file.getFile(index).?.updateArSize(macho_file) catch |err|
166 return diags.fail("failed to update ar size: {s}", .{@errorName(err)});
167 }
168
169 // Update file offsets of contributing objects
170 const total_size: usize = blk: {
171 var pos: usize = Archive.SARMAG;
172 pos += @sizeOf(Archive.ar_hdr);
173 pos += Archive.SYMDEF.len + 1;
174 pos = mem.alignForward(usize, pos, 8);
175 pos += ar_symtab.size(format);
176
177 for (files.items) |index| {
178 const file = macho_file.getFile(index).?;
179 switch (file) {
180 .zig_object => |zo| {
181 const state = &zo.output_ar_state;
182 pos = mem.alignForward(usize, pos, 2);
183 state.file_off = pos;
184 pos += @sizeOf(Archive.ar_hdr);
185 pos += zo.basename.len + 1;
186 pos = mem.alignForward(usize, pos, 8);
187 pos += try macho_file.cast(usize, state.size);
188 },
189 .object => |o| {
190 const state = &o.output_ar_state;
191 pos = mem.alignForward(usize, pos, 2);
192 state.file_off = pos;
193 pos += @sizeOf(Archive.ar_hdr);
194 pos += std.fs.path.basename(o.path).len + 1;
195 pos = mem.alignForward(usize, pos, 8);
196 pos += try macho_file.cast(usize, state.size);
197 },
198 else => unreachable,
199 }
200 }
201
202 break :blk pos;
203 };
204
205 if (build_options.enable_logging) {
206 state_log.debug("ar_symtab\n{f}\n", .{ar_symtab.fmt(macho_file)});
207 }
208
209 const buffer = try gpa.alloc(u8, total_size);
210 defer gpa.free(buffer);
211 var writer: Writer = .fixed(buffer);
212
213 // Write magic
214 writer.writeAll(Archive.ARMAG) catch unreachable;
215
216 // Write symtab
217 ar_symtab.write(format, macho_file, &writer) catch |err|
218 return diags.fail("failed to write archive symbol table: {t}", .{err});
219
220 // Write object files
221 for (files.items) |index| {
222 const aligned = mem.alignForward(usize, writer.end, 2);
223 const padding = aligned - writer.end;
224 if (padding > 0) {
225 writer.splatByteAll(0, padding) catch unreachable;
226 }
227 macho_file.getFile(index).?.writeAr(macho_file, &writer) catch |err|
228 return diags.fail("failed to write archive: {t}", .{err});
229 }
230
231 assert(writer.end == total_size);
232
233 try macho_file.setLength(total_size);
234 try macho_file.pwriteAll(writer.buffered(), 0);
235
236 if (diags.hasErrors()) return error.AlreadyReported;
237}
238
239fn parseInputFilesAr(macho_file: *MachO) !void {
240 const tracy = trace(@src());
241 defer tracy.end();
242
243 for (macho_file.objects.items) |index| {
244 macho_file.getFile(index).?.parseAr(macho_file) catch |err| switch (err) {
245 error.InvalidMachineType => {}, // already reported
246 else => |e| try macho_file.reportParseError2(index, "unexpected error: parsing input file failed with error {s}", .{@errorName(e)}),
247 };
248 }
249}
250
251fn markExports(macho_file: *MachO) void {
252 if (macho_file.getZigObject()) |zo| {
253 zo.asFile().markExportsRelocatable(macho_file);
254 }
255 for (macho_file.objects.items) |index| {
256 macho_file.getFile(index).?.markExportsRelocatable(macho_file);
257 }
258}
259
260pub fn claimUnresolved(macho_file: *MachO) void {
261 if (macho_file.getZigObject()) |zo| {
262 zo.asFile().claimUnresolvedRelocatable(macho_file);
263 }
264 for (macho_file.objects.items) |index| {
265 macho_file.getFile(index).?.claimUnresolvedRelocatable(macho_file);
266 }
267}
268
269fn initOutputSections(macho_file: *MachO) !void {
270 for (macho_file.objects.items) |index| {
271 const file = macho_file.getFile(index).?;
272 for (file.getAtoms()) |atom_index| {
273 const atom = file.getAtom(atom_index) orelse continue;
274 if (!atom.isAlive()) continue;
275 atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file);
276 }
277 }
278
279 const needs_unwind_info = for (macho_file.objects.items) |index| {
280 if (macho_file.getFile(index).?.object.hasUnwindRecords()) break true;
281 } else false;
282 if (needs_unwind_info) {
283 macho_file.unwind_info_sect_index = try macho_file.addSection("__LD", "__compact_unwind", .{
284 .flags = macho.S_ATTR_DEBUG,
285 });
286 }
287
288 const needs_eh_frame = for (macho_file.objects.items) |index| {
289 if (macho_file.getFile(index).?.object.hasEhFrameRecords()) break true;
290 } else false;
291 if (needs_eh_frame) {
292 assert(needs_unwind_info);
293 macho_file.eh_frame_sect_index = try macho_file.addSection("__TEXT", "__eh_frame", .{
294 .flags = std.macho.S_COALESCED | std.macho.S_ATTR_NO_TOC | std.macho.S_ATTR_STRIP_STATIC_SYMS | std.macho.S_ATTR_LIVE_SUPPORT,
295 });
296 }
297}
298
299fn calcSectionSizes(macho_file: *MachO) !void {
300 const tracy = trace(@src());
301 defer tracy.end();
302
303 const diags = &macho_file.base.comp.link_diags;
304
305 if (macho_file.getZigObject()) |zo| {
306 // TODO this will create a race as we need to track merging of debug sections which we currently don't
307 zo.calcNumRelocs(macho_file);
308 }
309
310 {
311 for (macho_file.sections.items(.atoms), 0..) |atoms, i| {
312 if (atoms.items.len == 0) continue;
313 calcSectionSizeWorker(macho_file, @as(u8, @intCast(i)));
314 }
315
316 if (macho_file.eh_frame_sect_index) |_| {
317 calcEhFrameSizeWorker(macho_file);
318 }
319
320 if (macho_file.unwind_info_sect_index) |_| {
321 for (macho_file.objects.items) |index| {
322 Object.calcCompactUnwindSizeRelocatable(
323 macho_file.getFile(index).?.object,
324 macho_file,
325 );
326 }
327 }
328
329 for (macho_file.objects.items) |index| {
330 File.calcSymtabSize(macho_file.getFile(index).?, macho_file);
331 }
332 if (macho_file.getZigObject()) |zo| {
333 File.calcSymtabSize(zo.asFile(), macho_file);
334 }
335
336 MachO.updateLinkeditSizeWorker(macho_file, .data_in_code);
337 }
338
339 if (macho_file.unwind_info_sect_index) |_| {
340 calcCompactUnwindSize(macho_file);
341 }
342 try calcSymtabSize(macho_file);
343
344 if (diags.hasErrors()) return error.AlreadyReported;
345}
346
347fn calcSectionSizeWorker(macho_file: *MachO, sect_id: u8) void {
348 const tracy = trace(@src());
349 defer tracy.end();
350
351 const slice = macho_file.sections.slice();
352 const header = &slice.items(.header)[sect_id];
353 const atoms = slice.items(.atoms)[sect_id].items;
354 for (atoms) |ref| {
355 const atom = ref.getAtom(macho_file).?;
356 const atom_alignment = atom.alignment.toByteUnits() orelse 1;
357 const offset = mem.alignForward(u64, header.size, atom_alignment);
358 const padding = offset - header.size;
359 atom.value = offset;
360 header.size += padding + atom.size;
361 header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
362 const nreloc = atom.calcNumRelocs(macho_file);
363 atom.addExtra(.{ .rel_out_index = header.nreloc, .rel_out_count = nreloc }, macho_file);
364 header.nreloc += nreloc;
365 }
366}
367
368fn calcEhFrameSizeWorker(macho_file: *MachO) void {
369 const tracy = trace(@src());
370 defer tracy.end();
371
372 const diags = &macho_file.base.comp.link_diags;
373
374 const doWork = struct {
375 fn doWork(mfile: *MachO, header: *macho.section_64) !void {
376 header.size = try eh_frame.calcSize(mfile);
377 header.@"align" = 3;
378 header.nreloc = eh_frame.calcNumRelocs(mfile);
379 }
380 }.doWork;
381
382 const header = &macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
383 doWork(macho_file, header) catch |err|
384 diags.addError("failed to calculate size of section '__TEXT,__eh_frame': {s}", .{@errorName(err)});
385}
386
387fn calcCompactUnwindSize(macho_file: *MachO) void {
388 const tracy = trace(@src());
389 defer tracy.end();
390
391 var nrec: u32 = 0;
392 var nreloc: u32 = 0;
393
394 for (macho_file.objects.items) |index| {
395 const ctx = &macho_file.getFile(index).?.object.compact_unwind_ctx;
396 ctx.rec_index = nrec;
397 ctx.reloc_index = nreloc;
398 nrec += ctx.rec_count;
399 nreloc += ctx.reloc_count;
400 }
401
402 const sect = &macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?];
403 sect.size = nrec * @sizeOf(macho.compact_unwind_entry);
404 sect.nreloc = nreloc;
405 sect.@"align" = 3;
406}
407
408fn calcSymtabSize(macho_file: *MachO) error{OutOfMemory}!void {
409 const tracy = trace(@src());
410 defer tracy.end();
411
412 const gpa = macho_file.base.comp.gpa;
413
414 var nlocals: u32 = 0;
415 var nstabs: u32 = 0;
416 var nexports: u32 = 0;
417 var nimports: u32 = 0;
418 var strsize: u32 = 1;
419
420 var objects = try std.array_list.Managed(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
421 defer objects.deinit();
422 if (macho_file.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index);
423 objects.appendSliceAssumeCapacity(macho_file.objects.items);
424
425 for (objects.items) |index| {
426 const ctx = switch (macho_file.getFile(index).?) {
427 inline else => |x| &x.output_symtab_ctx,
428 };
429 ctx.ilocal = nlocals;
430 ctx.istab = nstabs;
431 ctx.iexport = nexports;
432 ctx.iimport = nimports;
433 ctx.stroff = strsize;
434 nlocals += ctx.nlocals;
435 nstabs += ctx.nstabs;
436 nexports += ctx.nexports;
437 nimports += ctx.nimports;
438 strsize += ctx.strsize;
439 }
440
441 for (objects.items) |index| {
442 const ctx = switch (macho_file.getFile(index).?) {
443 inline else => |x| &x.output_symtab_ctx,
444 };
445 ctx.istab += nlocals;
446 ctx.iexport += nlocals + nstabs;
447 ctx.iimport += nlocals + nstabs + nexports;
448 }
449
450 {
451 const cmd = &macho_file.symtab_cmd;
452 cmd.nsyms = nlocals + nstabs + nexports + nimports;
453 cmd.strsize = strsize;
454 }
455
456 {
457 const cmd = &macho_file.dysymtab_cmd;
458 cmd.ilocalsym = 0;
459 cmd.nlocalsym = nlocals + nstabs;
460 cmd.iextdefsym = nlocals + nstabs;
461 cmd.nextdefsym = nexports;
462 cmd.iundefsym = nlocals + nstabs + nexports;
463 cmd.nundefsym = nimports;
464 }
465}
466
467fn allocateSections(macho_file: *MachO) !void {
468 const slice = macho_file.sections.slice();
469 for (slice.items(.header)) |*header| {
470 const needed_size = header.size;
471 header.size = 0;
472 const alignment = try macho_file.alignPow(header.@"align");
473 if (!header.isZerofill()) {
474 if (needed_size > macho_file.allocatedSize(header.offset)) {
475 header.offset = try macho_file.cast(u32, try macho_file.findFreeSpace(needed_size, alignment));
476 }
477 }
478 if (needed_size > macho_file.allocatedSizeVirtual(header.addr)) {
479 header.addr = macho_file.findFreeSpaceVirtual(needed_size, alignment);
480 }
481 header.size = needed_size;
482 }
483
484 var fileoff: u32 = 0;
485 for (slice.items(.header)) |header| {
486 fileoff = @max(fileoff, header.offset + @as(u32, @intCast(header.size)));
487 }
488
489 for (slice.items(.header)) |*header| {
490 if (header.nreloc == 0) continue;
491 header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info));
492 fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info);
493 }
494
495 // In -r mode, there is no LINKEDIT segment and so we allocate required LINKEDIT commands
496 // as if they were detached or part of the single segment.
497
498 // DATA_IN_CODE
499 {
500 const cmd = &macho_file.data_in_code_cmd;
501 cmd.dataoff = fileoff;
502 fileoff += cmd.datasize;
503 fileoff = mem.alignForward(u32, fileoff, @alignOf(u64));
504 }
505
506 // SYMTAB
507 {
508 const cmd = &macho_file.symtab_cmd;
509 cmd.symoff = fileoff;
510 fileoff += cmd.nsyms * @sizeOf(macho.nlist_64);
511 fileoff = mem.alignForward(u32, fileoff, @alignOf(u32));
512 cmd.stroff = fileoff;
513 }
514}
515
516/// Renames segment names in Zig sections to standard MachO segment names such as
517/// `__TEXT`, `__DATA_CONST` and `__DATA`.
518/// TODO: I think I may be able to get rid of this if I rework section/segment
519/// allocation mechanism to not rely so much on having `_ZIG` sections always
520/// pushed to the back. For instance, this is not a problem in ELF linker.
521/// Then, we can create sections with the correct name from the start in `MachO.initMetadata`.
522fn sanitizeZigSections(macho_file: *MachO) void {
523 if (macho_file.zig_text_sect_index) |index| {
524 const header = &macho_file.sections.items(.header)[index];
525 header.segname = MachO.makeStaticString("__TEXT");
526 }
527 if (macho_file.zig_const_sect_index) |index| {
528 const header = &macho_file.sections.items(.header)[index];
529 header.segname = MachO.makeStaticString("__DATA_CONST");
530 }
531 if (macho_file.zig_data_sect_index) |index| {
532 const header = &macho_file.sections.items(.header)[index];
533 header.segname = MachO.makeStaticString("__DATA");
534 }
535 if (macho_file.zig_bss_sect_index) |index| {
536 const header = &macho_file.sections.items(.header)[index];
537 header.segname = MachO.makeStaticString("__DATA");
538 }
539}
540
541fn createSegment(macho_file: *MachO) !void {
542 const gpa = macho_file.base.comp.gpa;
543
544 // For relocatable, we only ever need a single segment so create it now.
545 const prot: macho.vm_prot_t = .{ .READ = true, .WRITE = true, .EXEC = true };
546 try macho_file.segments.append(gpa, .{
547 .cmdsize = @sizeOf(macho.segment_command_64),
548 .segname = MachO.makeStaticString(""),
549 .maxprot = prot,
550 .initprot = prot,
551 });
552 const seg = &macho_file.segments.items[0];
553 seg.nsects = @intCast(macho_file.sections.items(.header).len);
554 seg.cmdsize += seg.nsects * @sizeOf(macho.section_64);
555}
556
557fn allocateSegment(macho_file: *MachO) void {
558 // Allocate the single segment.
559 const seg = &macho_file.segments.items[0];
560 var vmaddr: u64 = 0;
561 var fileoff: u64 = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64);
562 seg.vmaddr = vmaddr;
563 seg.fileoff = fileoff;
564
565 for (macho_file.sections.items(.header)) |header| {
566 vmaddr = @max(vmaddr, header.addr + header.size);
567 if (!header.isZerofill()) {
568 fileoff = @max(fileoff, header.offset + header.size);
569 }
570 }
571
572 seg.vmsize = vmaddr - seg.vmaddr;
573 seg.filesize = fileoff - seg.fileoff;
574}
575
576// We need to sort relocations in descending order to be compatible with Apple's linker.
577fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool {
578 _ = ctx;
579 return lhs.r_address > rhs.r_address;
580}
581
582fn sortRelocs(macho_file: *MachO) void {
583 const tracy = trace(@src());
584 defer tracy.end();
585
586 for (macho_file.sections.items(.relocs)) |*relocs| {
587 mem.sort(macho.relocation_info, relocs.items, {}, sortReloc);
588 }
589}
590
591fn writeSections(macho_file: *MachO) link.Error!void {
592 const tracy = trace(@src());
593 defer tracy.end();
594
595 const gpa = macho_file.base.comp.gpa;
596 const diags = &macho_file.base.comp.link_diags;
597 const cpu_arch = macho_file.getTarget().cpu.arch;
598 const slice = macho_file.sections.slice();
599 for (slice.items(.header), slice.items(.out), slice.items(.relocs), 0..) |header, *out, *relocs, n_sect| {
600 if (header.isZerofill()) continue;
601 if (!macho_file.isZigSection(@intCast(n_sect))) { // TODO this is wrong; what about debug sections?
602 const size = try macho_file.cast(usize, header.size);
603 try out.resize(gpa, size);
604 const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
605 @memset(out.items, padding_byte);
606 }
607 try relocs.resize(gpa, header.nreloc);
608 }
609
610 const cmd = macho_file.symtab_cmd;
611 try macho_file.symtab.resize(gpa, cmd.nsyms);
612 try macho_file.strtab.resize(gpa, cmd.strsize);
613 macho_file.strtab.items[0] = 0;
614
615 {
616 for (macho_file.objects.items) |index| {
617 writeAtomsWorker(macho_file, macho_file.getFile(index).?);
618 File.writeSymtab(macho_file.getFile(index).?, macho_file, macho_file);
619 }
620
621 if (macho_file.getZigObject()) |zo| {
622 writeAtomsWorker(macho_file, zo.asFile());
623 File.writeSymtab(zo.asFile(), macho_file, macho_file);
624 }
625
626 if (macho_file.eh_frame_sect_index) |_| {
627 writeEhFrameWorker(macho_file);
628 }
629
630 if (macho_file.unwind_info_sect_index) |_| {
631 for (macho_file.objects.items) |index| {
632 writeCompactUnwindWorker(macho_file, macho_file.getFile(index).?.object);
633 }
634 }
635 }
636
637 if (diags.hasErrors()) return error.AlreadyReported;
638
639 if (macho_file.getZigObject()) |zo| {
640 try zo.writeRelocs(macho_file);
641 }
642}
643
644fn writeAtomsWorker(macho_file: *MachO, file: File) void {
645 const tracy = trace(@src());
646 defer tracy.end();
647 file.writeAtomsRelocatable(macho_file) catch |err| {
648 macho_file.reportParseError2(file.getIndex(), "failed to write atoms: {s}", .{
649 @errorName(err),
650 }) catch {};
651 };
652}
653
654fn writeEhFrameWorker(macho_file: *MachO) void {
655 const tracy = trace(@src());
656 defer tracy.end();
657
658 const diags = &macho_file.base.comp.link_diags;
659 const sect_index = macho_file.eh_frame_sect_index.?;
660 const buffer = macho_file.sections.items(.out)[sect_index];
661 const relocs = macho_file.sections.items(.relocs)[sect_index];
662 eh_frame.writeRelocs(macho_file, buffer.items, relocs.items) catch |err|
663 diags.addError("failed to write '__LD,__eh_frame' section: {s}", .{@errorName(err)});
664}
665
666fn writeCompactUnwindWorker(macho_file: *MachO, object: *Object) void {
667 const tracy = trace(@src());
668 defer tracy.end();
669
670 const diags = &macho_file.base.comp.link_diags;
671 object.writeCompactUnwindRelocatable(macho_file) catch |err|
672 diags.addError("failed to write '__LD,__eh_frame' section: {s}", .{@errorName(err)});
673}
674
675fn writeSectionsToFile(macho_file: *MachO) !void {
676 const tracy = trace(@src());
677 defer tracy.end();
678
679 const slice = macho_file.sections.slice();
680 for (slice.items(.header), slice.items(.out), slice.items(.relocs)) |header, out, relocs| {
681 try macho_file.pwriteAll(out.items, header.offset);
682 try macho_file.pwriteAll(@ptrCast(relocs.items), header.reloff);
683 }
684
685 try macho_file.writeDataInCode();
686 try macho_file.pwriteAll(@ptrCast(macho_file.symtab.items), macho_file.symtab_cmd.symoff);
687 try macho_file.pwriteAll(macho_file.strtab.items, macho_file.symtab_cmd.stroff);
688}
689
690fn writeLoadCommands(macho_file: *MachO) error{ AlreadyReported, OutOfMemory }!struct { usize, usize } {
691 const gpa = macho_file.base.comp.gpa;
692 const needed_size = load_commands.calcLoadCommandsSizeObject(macho_file);
693 const buffer = try gpa.alloc(u8, needed_size);
694 defer gpa.free(buffer);
695
696 var writer: Writer = .fixed(buffer);
697
698 var ncmds: usize = 0;
699
700 // Segment and section load commands
701 {
702 assert(macho_file.segments.items.len == 1);
703 const seg = macho_file.segments.items[0];
704 writer.writeStruct(seg, .little) catch |err| switch (err) {
705 error.WriteFailed => unreachable,
706 };
707 for (macho_file.sections.items(.header)) |header| {
708 writer.writeStruct(header, .little) catch |err| switch (err) {
709 error.WriteFailed => unreachable,
710 };
711 }
712 ncmds += 1;
713 }
714
715 writer.writeStruct(macho_file.data_in_code_cmd, .little) catch |err| switch (err) {
716 error.WriteFailed => unreachable,
717 };
718 ncmds += 1;
719 writer.writeStruct(macho_file.symtab_cmd, .little) catch |err| switch (err) {
720 error.WriteFailed => unreachable,
721 };
722 ncmds += 1;
723 writer.writeStruct(macho_file.dysymtab_cmd, .little) catch |err| switch (err) {
724 error.WriteFailed => unreachable,
725 };
726 ncmds += 1;
727
728 if (macho_file.platform.isBuildVersionCompatible()) {
729 load_commands.writeBuildVersionLC(macho_file.platform, macho_file.sdk_version, &writer) catch |err| switch (err) {
730 error.WriteFailed => unreachable,
731 };
732 ncmds += 1;
733 } else {
734 load_commands.writeVersionMinLC(macho_file.platform, macho_file.sdk_version, &writer) catch |err| switch (err) {
735 error.WriteFailed => unreachable,
736 };
737 ncmds += 1;
738 }
739
740 assert(writer.end == needed_size);
741
742 try macho_file.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
743
744 return .{ ncmds, buffer.len };
745}
746
747fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
748 var header: macho.mach_header_64 = .{};
749 header.filetype = macho.MH_OBJECT;
750
751 const subsections_via_symbols = for (macho_file.objects.items) |index| {
752 const object = macho_file.getFile(index).?.object;
753 if (object.hasSubsections()) break true;
754 } else false;
755 if (subsections_via_symbols) {
756 header.flags |= macho.MH_SUBSECTIONS_VIA_SYMBOLS;
757 }
758
759 switch (macho_file.getTarget().cpu.arch) {
760 .aarch64 => {
761 header.cputype = macho.CPU_TYPE_ARM64;
762 header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
763 },
764 .x86_64 => {
765 header.cputype = macho.CPU_TYPE_X86_64;
766 header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
767 },
768 else => {},
769 }
770
771 header.ncmds = @intCast(ncmds);
772 header.sizeofcmds = @intCast(sizeofcmds);
773
774 try macho_file.pwriteAll(mem.asBytes(&header), 0);
775}
776
777const std = @import("std");
778const Path = std.Build.Cache.Path;
779const assert = std.debug.assert;
780const log = std.log.scoped(.link);
781const macho = std.macho;
782const math = std.math;
783const mem = std.mem;
784const state_log = std.log.scoped(.link_state);
785const Writer = std.Io.Writer;
786
787const Archive = @import("Archive.zig");
788const Atom = @import("Atom.zig");
789const Compilation = @import("../../Compilation.zig");
790const File = @import("file.zig").File;
791const MachO = @import("../MachO.zig");
792const Object = @import("Object.zig");
793const Symbol = @import("Symbol.zig");
794const build_options = @import("build_options");
795const eh_frame = @import("eh_frame.zig");
796const fat = @import("fat.zig");
797const link = @import("../../link.zig");
798const load_commands = @import("load_commands.zig");
799const trace = @import("../../tracy.zig").trace;