1pub const Atom = @import("MachO/Atom.zig");
2pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
3pub const Relocation = @import("MachO/Relocation.zig");
4
5base: link.File,
6
7rpath_list: []const []const u8,
8
9/// Debug symbols bundle (or dSym).
10d_sym: ?DebugSymbols = null,
11
12/// A list of all input files.
13/// Index of each input file also encodes the priority or precedence of one input file
14/// over another.
15files: std.MultiArrayList(File.Entry) = .{},
16/// Long-lived list of all file descriptors.
17/// We store them globally rather than per actual File so that we can re-use
18/// one file handle per every object file within an archive.
19file_handles: std.ArrayList(File.Handle) = .empty,
20zig_object: ?File.Index = null,
21internal_object: ?File.Index = null,
22objects: std.ArrayList(File.Index) = .empty,
23dylibs: std.ArrayList(File.Index) = .empty,
24
25segments: std.ArrayList(macho.segment_command_64) = .empty,
26sections: std.MultiArrayList(Section) = .{},
27/// Populated by `allocateSections`.
28header_size: ?u32 = null,
29
30resolver: SymbolResolver = .{},
31/// This table will be populated after `scanRelocs` has run.
32/// Key is symbol index.
33undefs: std.array_hash_map.Auto(SymbolResolver.Index, UndefRefs) = .empty,
34undefs_mutex: std.Io.Mutex = .init,
35dupes: std.array_hash_map.Auto(SymbolResolver.Index, std.ArrayList(File.Index)) = .empty,
36dupes_mutex: std.Io.Mutex = .init,
37
38dyld_info_cmd: macho.dyld_info_command = .{},
39symtab_cmd: macho.symtab_command = .{},
40dysymtab_cmd: macho.dysymtab_command = .{},
41function_starts_cmd: macho.linkedit_data_command = .{ .cmd = .FUNCTION_STARTS },
42data_in_code_cmd: macho.linkedit_data_command = .{ .cmd = .DATA_IN_CODE },
43uuid_cmd: macho.uuid_command = .{ .uuid = @splat(0) },
44codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE },
45
46pagezero_seg_index: ?u8 = null,
47text_seg_index: ?u8 = null,
48linkedit_seg_index: ?u8 = null,
49text_sect_index: ?u8 = null,
50data_sect_index: ?u8 = null,
51got_sect_index: ?u8 = null,
52stubs_sect_index: ?u8 = null,
53stubs_helper_sect_index: ?u8 = null,
54la_symbol_ptr_sect_index: ?u8 = null,
55tlv_ptr_sect_index: ?u8 = null,
56eh_frame_sect_index: ?u8 = null,
57unwind_info_sect_index: ?u8 = null,
58objc_stubs_sect_index: ?u8 = null,
59
60thunks: std.ArrayList(Thunk) = .empty,
61
62/// Output synthetic sections
63symtab: std.ArrayList(macho.nlist_64) = .empty,
64strtab: std.ArrayList(u8) = .empty,
65indsymtab: Indsymtab = .{},
66got: GotSection = .{},
67stubs: StubsSection = .{},
68stubs_helper: StubsHelperSection = .{},
69objc_stubs: ObjcStubsSection = .{},
70la_symbol_ptr: LaSymbolPtrSection = .{},
71tlv_ptr: TlvPtrSection = .{},
72rebase_section: Rebase = .{},
73bind_section: Bind = .{},
74weak_bind_section: WeakBind = .{},
75lazy_bind_section: LazyBind = .{},
76export_trie: ExportTrie = .{},
77unwind_info: UnwindInfo = .{},
78data_in_code: DataInCode = .{},
79
80/// Tracked loadable segments during incremental linking.
81zig_text_seg_index: ?u8 = null,
82zig_const_seg_index: ?u8 = null,
83zig_data_seg_index: ?u8 = null,
84zig_bss_seg_index: ?u8 = null,
85
86/// Tracked section headers with incremental updates to Zig object.
87zig_text_sect_index: ?u8 = null,
88zig_const_sect_index: ?u8 = null,
89zig_data_sect_index: ?u8 = null,
90zig_bss_sect_index: ?u8 = null,
91
92/// Tracked DWARF section headers that apply only when we emit relocatable.
93/// For executable and loadable images, DWARF is tracked directly by dSYM bundle object.
94debug_info_sect_index: ?u8 = null,
95debug_abbrev_sect_index: ?u8 = null,
96debug_str_sect_index: ?u8 = null,
97debug_aranges_sect_index: ?u8 = null,
98debug_line_sect_index: ?u8 = null,
99debug_line_str_sect_index: ?u8 = null,
100debug_loclists_sect_index: ?u8 = null,
101debug_rnglists_sect_index: ?u8 = null,
102
103has_tlv: AtomicBool = AtomicBool.init(false),
104binds_to_weak: AtomicBool = AtomicBool.init(false),
105weak_defines: AtomicBool = AtomicBool.init(false),
106
107/// Options
108/// SDK layout
109sdk_layout: ?SdkLayout,
110/// Size of the __PAGEZERO segment.
111pagezero_size: ?u64,
112/// Minimum space for future expansion of the load commands.
113headerpad_size: ?u32,
114/// Set enough space as if all paths were MATPATHLEN.
115headerpad_max_install_names: bool,
116/// Remove dylibs that are unreachable by the entry point or exported symbols.
117dead_strip_dylibs: bool,
118/// Treatment of undefined symbols
119undefined_treatment: UndefinedTreatment,
120/// TODO: delete this, libraries need to be resolved by the frontend instead
121lib_directories: []const Directory,
122/// Resolved list of framework search directories
123framework_dirs: []const []const u8,
124/// List of input frameworks
125frameworks: []const Framework,
126/// Install name for the dylib.
127/// TODO: unify with soname
128install_name: ?[]const u8,
129/// Path to entitlements file.
130entitlements: ?Path,
131compatibility_version: ?std.SemanticVersion,
132/// Entry name
133entry_name: ?[]const u8,
134platform: Platform,
135sdk_version: ?std.SemanticVersion,
136/// When set to true, the linker will hoist all dylibs including system dependent dylibs.
137no_implicit_dylibs: bool = false,
138/// Whether the linker should parse and always force load objects containing ObjC in archives.
139// TODO: in Zig we currently take -ObjC as always on
140force_load_objc: bool = true,
141/// Whether local symbols should be discarded from the symbol table.
142discard_local_symbols: bool = false,
143
144/// Hot-code swapping state.
145hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
146
147/// When adding a new field, remember to update `hashAddFrameworks`.
148pub const Framework = struct {
149 needed: bool = false,
150 weak: bool = false,
151 path: Path,
152};
153
154pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void {
155 for (hm) |value| {
156 man.hash.add(value.needed);
157 man.hash.add(value.weak);
158 _ = try man.addFilePath(value.path, null);
159 }
160}
161
162pub fn createEmpty(
163 arena: Allocator,
164 comp: *Compilation,
165 emit: Path,
166 options: link.File.OpenOptions,
167) !*MachO {
168 const target = &comp.root_mod.resolved_target.result;
169 assert(target.ofmt == .macho);
170
171 const gpa = comp.gpa;
172 const use_llvm = comp.config.use_llvm;
173 const opt_zcu = comp.zcu;
174 const optimize_mode = comp.root_mod.optimize_mode;
175 const output_mode = comp.config.output_mode;
176 const link_mode = comp.config.link_mode;
177
178 const allow_shlib_undefined = options.allow_shlib_undefined orelse false;
179
180 const self = try arena.create(MachO);
181 self.* = .{
182 .base = .{
183 .tag = .macho,
184 .comp = comp,
185 .emit = emit,
186 .gc_sections = options.gc_sections orelse (optimize_mode != .debug),
187 .print_gc_sections = options.print_gc_sections,
188 .stack_size = options.stack_size orelse 16777216,
189 .allow_shlib_undefined = allow_shlib_undefined,
190 .file = null,
191 .build_id = options.build_id,
192 },
193 .rpath_list = options.rpath_list,
194 .pagezero_size = options.pagezero_size,
195 .headerpad_size = options.headerpad_size,
196 .headerpad_max_install_names = options.headerpad_max_install_names,
197 .dead_strip_dylibs = options.dead_strip_dylibs,
198 .sdk_layout = options.darwin_sdk_layout,
199 .frameworks = options.frameworks,
200 .install_name = options.install_name,
201 .entitlements = options.entitlements,
202 .compatibility_version = options.compatibility_version,
203 .entry_name = switch (options.entry) {
204 .disabled => null,
205 .default => if (output_mode != .Exe) null else default_entry_symbol_name,
206 .enabled => default_entry_symbol_name,
207 .named => |name| name,
208 },
209 .platform = Platform.fromTarget(target),
210 .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null,
211 .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
212 // TODO delete this, directories must instead be resolved by the frontend
213 .lib_directories = options.lib_directories,
214 .framework_dirs = options.framework_dirs,
215 .force_load_objc = options.force_load_objc,
216 .discard_local_symbols = options.discard_local_symbols,
217 };
218 errdefer self.base.destroy();
219
220 const io = comp.io;
221
222 self.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{
223 .truncate = true,
224 .read = true,
225 .permissions = link.File.determinePermissions(output_mode, link_mode),
226 });
227
228 // Append null file
229 try self.files.append(gpa, .null);
230 // Append empty string to string tables
231 try self.strtab.append(gpa, 0);
232
233 if (opt_zcu) |zcu| {
234 if (!use_llvm) {
235 const index: File.Index = @intCast(try self.files.addOne(gpa));
236 self.files.set(index, .{ .zig_object = .{
237 .index = index,
238 .basename = try std.fmt.allocPrint(arena, "{s}.o", .{
239 fs.path.stem(zcu.main_mod.root_src_path),
240 }),
241 } });
242 self.zig_object = index;
243 const zo = self.getZigObject().?;
244 try zo.init(self);
245
246 try self.initMetadata(.{
247 .emit = emit,
248 .zo = zo,
249 .symbol_count_hint = options.symbol_count_hint,
250 .program_code_size_hint = options.program_code_size_hint,
251 });
252 }
253 }
254
255 return self;
256}
257
258pub fn open(
259 arena: Allocator,
260 comp: *Compilation,
261 emit: Path,
262 options: link.File.OpenOptions,
263) !*MachO {
264 // TODO: restore saved linker state, don't truncate the file, and
265 // participate in incremental compilation.
266 return createEmpty(arena, comp, emit, options);
267}
268
269pub fn deinit(self: *MachO) void {
270 const comp = self.base.comp;
271 const gpa = comp.gpa;
272 const io = comp.io;
273
274 if (self.d_sym) |*d_sym| {
275 d_sym.deinit();
276 }
277
278 for (self.file_handles.items) |handle| {
279 handle.close(io);
280 }
281 self.file_handles.deinit(gpa);
282
283 for (self.files.items(.tags), self.files.items(.data)) |tag, *data| switch (tag) {
284 .null => {},
285 .zig_object => data.zig_object.deinit(gpa),
286 .internal => data.internal.deinit(gpa),
287 .object => data.object.deinit(gpa),
288 .dylib => data.dylib.deinit(gpa),
289 };
290 self.files.deinit(gpa);
291 self.objects.deinit(gpa);
292 self.dylibs.deinit(gpa);
293
294 self.segments.deinit(gpa);
295 for (
296 self.sections.items(.atoms),
297 self.sections.items(.out),
298 self.sections.items(.thunks),
299 self.sections.items(.relocs),
300 ) |*atoms, *out, *thnks, *relocs| {
301 atoms.deinit(gpa);
302 out.deinit(gpa);
303 thnks.deinit(gpa);
304 relocs.deinit(gpa);
305 }
306 self.sections.deinit(gpa);
307
308 self.resolver.deinit(gpa);
309
310 for (self.undefs.values()) |*val| {
311 val.deinit(gpa);
312 }
313 self.undefs.deinit(gpa);
314 for (self.dupes.values()) |*val| {
315 val.deinit(gpa);
316 }
317 self.dupes.deinit(gpa);
318
319 self.symtab.deinit(gpa);
320 self.strtab.deinit(gpa);
321 self.got.deinit(gpa);
322 self.stubs.deinit(gpa);
323 self.objc_stubs.deinit(gpa);
324 self.tlv_ptr.deinit(gpa);
325 self.rebase_section.deinit(gpa);
326 self.bind_section.deinit(gpa);
327 self.weak_bind_section.deinit(gpa);
328 self.lazy_bind_section.deinit(gpa);
329 self.export_trie.deinit(gpa);
330 self.unwind_info.deinit(gpa);
331 self.data_in_code.deinit(gpa);
332
333 for (self.thunks.items) |*thunk| thunk.deinit(gpa);
334 self.thunks.deinit(gpa);
335}
336
337pub fn flush(
338 self: *MachO,
339 arena: Allocator,
340 tid: Zcu.PerThread.Id,
341 prog_node: std.Progress.Node,
342) link.Error!void {
343 const tracy = trace(@src());
344 defer tracy.end();
345
346 const comp = self.base.comp;
347 const gpa = comp.gpa;
348 const io = comp.io;
349 const diags = &comp.link_diags;
350
351 const sub_prog_node = prog_node.start("MachO Flush", 0);
352 defer sub_prog_node.end();
353
354 const zcu_obj_path: ?Path = p: {
355 const zcu = comp.zcu orelse break :p null;
356 const llvm_object = zcu.llvm_object orelse break :p null;
357 break :p try comp.resolveEmitPathFlush(arena, .temp, llvm_object.out_bin_basename);
358 };
359
360 // --verbose-link
361 if (comp.verbose_link) try self.dumpArgv(comp);
362
363 if (self.getZigObject()) |zo| try zo.flush(self, tid);
364 if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path);
365 if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path);
366
367 var positionals = std.array_list.Managed(link.Input).init(gpa);
368 defer positionals.deinit();
369
370 try positionals.ensureUnusedCapacity(comp.link_inputs.len);
371
372 for (comp.link_inputs) |link_input| switch (link_input) {
373 .dso => continue, // handled below
374 .object, .archive => positionals.appendAssumeCapacity(link_input),
375 .dso_exact => @panic("TODO"),
376 .res => unreachable,
377 };
378
379 // This is a set of object files emitted by clang in a single `build-exe` invocation.
380 // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up
381 // in this set.
382 try positionals.ensureUnusedCapacity(comp.c_objects.items.len);
383 for (comp.c_objects.items) |c_object| {
384 positionals.appendAssumeCapacity(try link.openObjectInput(io, diags, c_object.status.success.object_path));
385 }
386
387 if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path));
388
389 if (comp.config.any_sanitize_thread) {
390 try positionals.append(try link.openObjectInput(io, diags, comp.tsan_lib.?.full_object_path));
391 }
392
393 if (comp.config.any_fuzz) {
394 try positionals.append(try link.openArchiveInput(io, diags, comp.fuzzer_lib.?.full_object_path, false, false));
395 }
396
397 if (comp.ubsan_rt_lib) |crt_file| {
398 const path = crt_file.full_object_path;
399 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
400 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
401 } else if (comp.ubsan_rt_obj) |crt_file| {
402 const path = crt_file.full_object_path;
403 self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err|
404 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
405 }
406
407 for (positionals.items) |link_input| {
408 self.classifyInputFile(link_input) catch |err|
409 diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
410 }
411
412 var system_libs = std.array_list.Managed(SystemLib).init(gpa);
413 defer system_libs.deinit();
414
415 // frameworks
416 try system_libs.ensureUnusedCapacity(self.frameworks.len);
417 for (self.frameworks) |info| {
418 system_libs.appendAssumeCapacity(.{
419 .needed = info.needed,
420 .weak = info.weak,
421 .path = info.path,
422 });
423 }
424
425 // libc++ dep
426 if (comp.config.link_libcpp) {
427 try system_libs.ensureUnusedCapacity(2);
428 system_libs.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path });
429 system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
430 }
431
432 const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or
433 (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
434
435 if (comp.config.link_libc and is_exe_or_dyn_lib) {
436 if (comp.zigc_static_lib) |zigc| {
437 const path = zigc.full_object_path;
438 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
439 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
440 }
441 }
442
443 // libc/libSystem dep
444 self.resolveLibSystem(arena, comp, &system_libs) catch |err| switch (err) {
445 error.MissingLibSystem => {}, // already reported
446 else => |e| return diags.fail("failed to resolve libSystem: {s}", .{@errorName(e)}),
447 };
448
449 for (comp.link_inputs) |link_input| switch (link_input) {
450 .object, .archive, .dso_exact => continue,
451 .res => unreachable,
452 .dso => {
453 self.classifyInputFile(link_input) catch |err|
454 diags.addParseError(link_input.path().?, "failed to parse input file: {s}", .{@errorName(err)});
455 },
456 };
457
458 for (system_libs.items) |lib| {
459 switch (Compilation.classifyFileExt(lib.path.sub_path)) {
460 .shared_library => {
461 const dso_input = try link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport);
462 self.classifyInputFile(dso_input) catch |err|
463 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
464 },
465 .static_library => {
466 const archive_input = try link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden);
467 self.classifyInputFile(archive_input) catch |err|
468 diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)});
469 },
470 else => unreachable,
471 }
472 }
473
474 // Finally, link against compiler_rt.
475 if (comp.compiler_rt_lib) |crt_file| {
476 const path = crt_file.full_object_path;
477 self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err|
478 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
479 } else if (comp.compiler_rt_obj) |crt_file| {
480 const path = crt_file.full_object_path;
481 self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err|
482 diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
483 }
484
485 try self.parseInputFiles();
486 self.parseDependentDylibs() catch |err| {
487 switch (err) {
488 error.MissingLibraryDependencies => {},
489 else => |e| return diags.fail("failed to parse dependent libraries: {s}", .{@errorName(e)}),
490 }
491 };
492
493 if (diags.hasErrors()) return error.AlreadyReported;
494
495 {
496 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
497 self.files.set(index, .{ .internal = .{ .index = index } });
498 self.internal_object = index;
499 const object = self.getInternalObject().?;
500 try object.init(gpa);
501 try object.initSymbols(self);
502 }
503
504 try self.resolveSymbols();
505 try self.convertTentativeDefsAndResolveSpecialSymbols();
506 self.dedupLiterals() catch |err| switch (err) {
507 error.AlreadyReported => |e| return e,
508 else => |e| return diags.fail("failed to deduplicate literals: {s}", .{@errorName(e)}),
509 };
510
511 if (self.base.gc_sections) {
512 try dead_strip.gcAtoms(self);
513 }
514
515 self.checkDuplicates() catch |err| switch (err) {
516 error.HasDuplicates => return error.AlreadyReported,
517 else => |e| return diags.fail("failed to check for duplicate symbol definitions: {s}", .{@errorName(e)}),
518 };
519
520 self.markImportsAndExports();
521 self.deadStripDylibs();
522
523 for (self.dylibs.items, 1..) |index, ord| {
524 const dylib = self.getFile(index).?.dylib;
525 dylib.ordinal = @intCast(ord);
526 }
527
528 self.claimUnresolved();
529
530 self.scanRelocs() catch |err| switch (err) {
531 error.HasUndefinedSymbols => return error.AlreadyReported,
532 else => |e| return diags.fail("failed to scan relocations: {s}", .{@errorName(e)}),
533 };
534
535 try self.initOutputSections();
536 try self.initSyntheticSections();
537 try self.sortSections();
538 try self.addAtomsToSections();
539 try self.calcSectionSizes();
540
541 try self.generateUnwindInfo();
542
543 try self.initSegments();
544 self.allocateSections() catch |err| switch (err) {
545 error.AlreadyReported => |e| return e,
546 else => |e| return diags.fail("failed to allocate sections: {s}", .{@errorName(e)}),
547 };
548 self.allocateSegments();
549 self.allocateSyntheticSymbols();
550
551 if (build_options.enable_logging) {
552 state_log.debug("{f}", .{self.dumpState()});
553 }
554
555 // Beyond this point, everything has been allocated a virtual address and we can resolve
556 // the relocations, and commit objects to file.
557 try self.resizeSections();
558
559 if (self.getZigObject()) |zo| {
560 zo.resolveRelocs(self) catch |err| switch (err) {
561 error.ResolveFailed => return error.AlreadyReported,
562 else => |e| return e,
563 };
564 }
565 try self.writeSectionsAndUpdateLinkeditSizes();
566
567 try self.writeSectionsToFile();
568 try self.allocateLinkeditSegment();
569 self.writeLinkeditSectionsToFile() catch |err| switch (err) {
570 error.OutOfMemory, error.AlreadyReported => |e| return e,
571 else => |e| return diags.fail("failed to write linkedit sections to file: {t}", .{e}),
572 };
573
574 var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: {
575 // Preallocate space for the code signature.
576 // We need to do this at this stage so that we have the load commands with proper values
577 // written out to the file.
578 // The most important here is to have the correct vm and filesize of the __LINKEDIT segment
579 // where the code signature goes into.
580 var codesig = CodeSignature.init(self.getPageSize());
581 codesig.code_directory.ident = fs.path.basename(self.base.emit.sub_path);
582 if (self.entitlements) |path| codesig.addEntitlements(gpa, io, path) catch |err|
583 return diags.fail("failed to add entitlements from {f}: {t}", .{ path, err });
584 try self.writeCodeSignaturePadding(&codesig);
585 break :blk codesig;
586 } else null;
587 defer if (codesig) |*csig| csig.deinit(gpa);
588
589 self.getLinkeditSegment().vmsize = mem.alignForward(
590 u64,
591 self.getLinkeditSegment().filesize,
592 self.getPageSize(),
593 );
594
595 const ncmds, const sizeofcmds, const uuid_cmd_offset = self.writeLoadCommands() catch |err| switch (err) {
596 error.WriteFailed => unreachable,
597 error.OutOfMemory, error.AlreadyReported => |e| return e,
598 };
599 try self.writeHeader(ncmds, sizeofcmds);
600 self.writeUuid(uuid_cmd_offset, self.requiresCodeSig()) catch |err| switch (err) {
601 error.OutOfMemory, error.AlreadyReported => |e| return e,
602 else => |e| return diags.fail("failed to calculate and write uuid: {s}", .{@errorName(e)}),
603 };
604 if (self.getDebugSymbols()) |dsym| dsym.flush(self) catch |err| switch (err) {
605 error.OutOfMemory => |e| return e,
606 else => |e| return diags.fail("failed to get debug symbols: {s}", .{@errorName(e)}),
607 };
608
609 // Code signing always comes last.
610 if (codesig) |*csig| {
611 self.writeCodeSignature(csig) catch |err| switch (err) {
612 error.OutOfMemory, error.AlreadyReported => |e| return e,
613 else => |e| return diags.fail("failed to write code signature: {s}", .{@errorName(e)}),
614 };
615 const emit = self.base.emit;
616 invalidateKernelCache(io, emit.root_dir.handle, emit.sub_path) catch |err| switch (err) {
617 else => |e| return diags.fail("failed to invalidate kernel cache: {t}", .{e}),
618 };
619 }
620}
621
622/// --verbose-link output
623fn dumpArgv(self: *MachO, comp: *Compilation) !void {
624 const gpa = comp.gpa;
625 const io = comp.io;
626
627 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
628 defer arena_allocator.deinit();
629 const arena = arena_allocator.allocator();
630
631 const directory = self.base.emit.root_dir;
632 const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
633 const zcu_obj_path: ?[]const u8 = p: {
634 const zcu = comp.zcu orelse break :p null;
635 const llvm_object = zcu.llvm_object orelse break :p null;
636 const p = try comp.resolveEmitPathFlush(arena, .temp, llvm_object.out_bin_basename);
637 break :p try p.toString(arena);
638 };
639
640 var argv = std.array_list.Managed([]const u8).init(arena);
641
642 try argv.append("zig");
643
644 if (self.base.isStaticLib()) {
645 try argv.append("ar");
646 } else {
647 try argv.append("ld");
648 }
649
650 if (self.base.isObject()) {
651 try argv.append("-r");
652 }
653
654 if (self.base.isRelocatable()) {
655 for (comp.link_inputs) |link_input| switch (link_input) {
656 .object, .archive => |obj| try argv.append(try obj.path.toString(arena)),
657 .res => |res| try argv.append(try res.path.toString(arena)),
658 .dso => |dso| try argv.append(try dso.path.toString(arena)),
659 .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }),
660 };
661
662 for (comp.c_objects.items) |c_object| {
663 try argv.append(try c_object.status.success.object_path.toString(arena));
664 }
665
666 if (zcu_obj_path) |p| {
667 try argv.append(p);
668 }
669 } else {
670 if (!self.base.isStatic()) {
671 try argv.append("-dynamic");
672 }
673
674 if (self.base.isDynLib()) {
675 try argv.append("-dylib");
676
677 if (self.install_name) |install_name| {
678 try argv.append("-install_name");
679 try argv.append(install_name);
680 }
681 }
682
683 try argv.append("-platform_version");
684 try argv.append(@tagName(self.platform.os_tag));
685 try argv.append(try std.fmt.allocPrint(arena, "{f}", .{self.platform.version}));
686
687 if (self.sdk_version) |ver| {
688 try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor }));
689 } else {
690 try argv.append(try std.fmt.allocPrint(arena, "{f}", .{self.platform.version}));
691 }
692
693 if (comp.sysroot) |syslibroot| {
694 try argv.append("-syslibroot");
695 try argv.append(syslibroot);
696 }
697
698 for (self.rpath_list) |rpath| {
699 try argv.appendSlice(&.{ "-rpath", rpath });
700 }
701
702 if (self.pagezero_size) |size| {
703 try argv.append("-pagezero_size");
704 try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size}));
705 }
706
707 if (self.headerpad_size) |size| {
708 try argv.append("-headerpad_size");
709 try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size}));
710 }
711
712 if (self.headerpad_max_install_names) {
713 try argv.append("-headerpad_max_install_names");
714 }
715
716 if (self.base.gc_sections) {
717 try argv.append("-dead_strip");
718 }
719
720 if (self.dead_strip_dylibs) {
721 try argv.append("-dead_strip_dylibs");
722 }
723
724 if (self.force_load_objc) {
725 try argv.append("-ObjC");
726 }
727
728 if (self.discard_local_symbols) {
729 try argv.append("-x");
730 }
731
732 if (self.entry_name) |entry_name| {
733 try argv.appendSlice(&.{ "-e", entry_name });
734 }
735
736 try argv.append("-o");
737 try argv.append(full_out_path);
738
739 if (self.base.isDynLib() and self.base.allow_shlib_undefined) {
740 try argv.append("-undefined");
741 try argv.append("dynamic_lookup");
742 }
743
744 for (comp.link_inputs) |link_input| switch (link_input) {
745 .dso => continue, // handled below
746 .res => unreachable, // windows only
747 .object, .archive => |obj| {
748 if (obj.must_link) try argv.append("-force_load"); // TODO: verify this
749 try argv.append(try obj.path.toString(arena));
750 },
751 .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }),
752 };
753
754 for (comp.c_objects.items) |c_object| {
755 try argv.append(try c_object.status.success.object_path.toString(arena));
756 }
757
758 if (zcu_obj_path) |p| {
759 try argv.append(p);
760 }
761
762 if (comp.config.any_sanitize_thread) {
763 const path = try comp.tsan_lib.?.full_object_path.toString(arena);
764 try argv.appendSlice(&.{ path, "-rpath", std.fs.path.dirname(path) orelse "." });
765 }
766
767 if (comp.config.any_fuzz) {
768 try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena));
769 }
770
771 for (self.lib_directories) |lib_directory| {
772 // TODO delete this, directories must instead be resolved by the frontend
773 const arg = try std.fmt.allocPrint(arena, "-L{s}", .{lib_directory.path orelse "."});
774 try argv.append(arg);
775 }
776
777 for (comp.link_inputs) |link_input| switch (link_input) {
778 .object, .archive, .dso_exact => continue, // handled above
779 .res => unreachable, // windows only
780 .dso => |dso| {
781 if (dso.needed) {
782 try argv.appendSlice(&.{ "-needed-l", try dso.path.toString(arena) });
783 } else if (dso.weak) {
784 try argv.appendSlice(&.{ "-weak-l", try dso.path.toString(arena) });
785 } else {
786 try argv.appendSlice(&.{ "-l", try dso.path.toString(arena) });
787 }
788 },
789 };
790
791 for (self.framework_dirs) |f_dir| {
792 try argv.append("-F");
793 try argv.append(f_dir);
794 }
795
796 for (self.frameworks) |framework| {
797 const name = framework.path.stem();
798 const arg = if (framework.needed)
799 try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name})
800 else if (framework.weak)
801 try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name})
802 else
803 try std.fmt.allocPrint(arena, "-framework {s}", .{name});
804 try argv.append(arg);
805 }
806
807 if (comp.config.link_libcpp) {
808 try argv.appendSlice(&.{
809 try comp.libcxxabi_static_lib.?.full_object_path.toString(arena),
810 try comp.libcxx_static_lib.?.full_object_path.toString(arena),
811 });
812 }
813
814 try argv.append("-lSystem");
815
816 if (comp.zigc_static_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
817 if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
818 if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
819 if (comp.ubsan_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
820 if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
821 }
822
823 try Compilation.dumpArgv(io, argv.items);
824}
825
826/// TODO delete this, libsystem must be resolved when setting up the compilation pipeline
827pub fn resolveLibSystem(
828 self: *MachO,
829 arena: Allocator,
830 comp: *Compilation,
831 out_libs: anytype,
832) !void {
833 const io = comp.io;
834 const diags = &comp.link_diags;
835
836 var test_path = std.array_list.Managed(u8).init(arena);
837 var checked_paths = std.array_list.Managed([]const u8).init(arena);
838
839 success: {
840 if (self.sdk_layout) |sdk_layout| switch (sdk_layout) {
841 .sdk => {
842 const dir = try fs.path.join(arena, &.{ comp.sysroot.?, "usr", "lib" });
843 if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success;
844 },
845 .vendored => {
846 const dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "darwin" });
847 if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success;
848 },
849 };
850
851 for (self.lib_directories) |directory| {
852 if (try accessLibPath(arena, io, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success;
853 }
854
855 diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{});
856 return error.MissingLibSystem;
857 }
858
859 const libsystem_path = Path.initCwd(try arena.dupe(u8, test_path.items));
860 try out_libs.append(.{
861 .needed = true,
862 .path = libsystem_path,
863 });
864}
865
866pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
867 const tracy = trace(@src());
868 defer tracy.end();
869
870 const comp = self.base.comp;
871 const io = comp.io;
872
873 const path, const file = input.pathAndFile().?;
874 // TODO don't classify now, it's too late. The input file has already been classified
875 log.debug("classifying input file {f}", .{path});
876
877 const fh = try self.addFileHandle(file);
878 var buffer: [Archive.SARMAG]u8 = undefined;
879
880 const fat_arch: ?fat.Arch = try self.parseFatFile(file, path);
881 const offset = if (fat_arch) |fa| fa.offset else 0;
882
883 if (readMachHeader(io, file, offset) catch null) |h| blk: {
884 if (h.magic != macho.MH_MAGIC_64) break :blk;
885 switch (h.filetype) {
886 macho.MH_OBJECT => try self.addObject(path, fh, offset),
887 macho.MH_DYLIB => _ = try self.addDylib(.fromLinkInput(input), true, fh, offset),
888 else => return error.UnknownFileType,
889 }
890 return;
891 }
892 if (readArMagic(io, file, offset, &buffer) catch null) |ar_magic| blk: {
893 if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk;
894 try self.addArchive(input.archive, fh, fat_arch);
895 return;
896 }
897 _ = try self.addTbd(.fromLinkInput(input), true, fh);
898}
899
900fn parseFatFile(self: *MachO, file: Io.File, path: Path) !?fat.Arch {
901 const comp = self.base.comp;
902 const io = comp.io;
903 const diags = &comp.link_diags;
904 const fat_h = fat.readFatHeader(io, file) catch return null;
905 if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null;
906 var fat_archs_buffer: [2]fat.Arch = undefined;
907 const fat_archs = try fat.parseArchs(io, file, fat_h, &fat_archs_buffer);
908 const cpu_arch = self.getTarget().cpu.arch;
909 for (fat_archs) |arch| {
910 if (arch.tag == cpu_arch) return arch;
911 }
912 return diags.failParse(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)});
913}
914
915pub fn readMachHeader(io: Io, file: Io.File, offset: usize) !macho.mach_header_64 {
916 var buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
917 const nread = try file.readPositionalAll(io, &buffer, offset);
918 if (nread != buffer.len) return error.InputOutput;
919 const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(&buffer)).*;
920 return hdr;
921}
922
923pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 {
924 const nread = try file.readPositionalAll(io, buffer, offset);
925 if (nread != buffer.len) return error.InputOutput;
926 return buffer[0..Archive.SARMAG];
927}
928
929fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u64) !void {
930 const tracy = trace(@src());
931 defer tracy.end();
932
933 const comp = self.base.comp;
934 const gpa = comp.gpa;
935 const io = comp.io;
936
937 const abs_path = try std.fs.path.resolvePosix(gpa, &.{
938 comp.dirs.cwd,
939 path.root_dir.path orelse ".",
940 path.sub_path,
941 });
942 errdefer gpa.free(abs_path);
943
944 const file = self.getFileHandle(handle_index);
945 const stat = try file.stat(io);
946 const mtime = stat.mtime.toSeconds();
947 const index: File.Index = @intCast(try self.files.addOne(gpa));
948 self.files.set(index, .{ .object = .{
949 .offset = offset,
950 .path = abs_path,
951 .file_handle = handle_index,
952 .mtime = @intCast(mtime),
953 .index = index,
954 } });
955 try self.objects.append(gpa, index);
956}
957
958pub fn parseInputFiles(self: *MachO) !void {
959 const tracy = trace(@src());
960 defer tracy.end();
961
962 const diags = &self.base.comp.link_diags;
963
964 {
965 for (self.objects.items) |index| {
966 parseInputFileWorker(self, self.getFile(index).?);
967 }
968 for (self.dylibs.items) |index| {
969 parseInputFileWorker(self, self.getFile(index).?);
970 }
971 }
972
973 if (diags.hasErrors()) return error.AlreadyReported;
974}
975
976fn parseInputFileWorker(self: *MachO, file: File) void {
977 file.parse(self) catch |err| {
978 switch (err) {
979 error.MalformedObject,
980 error.MalformedDylib,
981 error.MalformedTbd,
982 error.InvalidMachineType,
983 error.InvalidTarget,
984 => {}, // already reported
985
986 else => |e| self.reportParseError2(file.getIndex(), "unexpected error: parsing input file failed with error {s}", .{@errorName(e)}) catch {},
987 }
988 };
989}
990
991fn addArchive(self: *MachO, lib: link.Input.Object, handle: File.HandleIndex, fat_arch: ?fat.Arch) !void {
992 const tracy = trace(@src());
993 defer tracy.end();
994
995 if (self.base.isStaticLib()) {
996 // Ignore static library inputs when generating a static library.
997 return;
998 }
999
1000 const gpa = self.base.comp.gpa;
1001
1002 var archive: Archive = .{};
1003 defer archive.deinit(gpa);
1004 try archive.unpack(self, lib.path, handle, fat_arch);
1005
1006 for (archive.objects.items) |unpacked| {
1007 const index: File.Index = @intCast(try self.files.addOne(gpa));
1008 self.files.set(index, .{ .object = unpacked });
1009 const object = &self.files.items(.data)[index].object;
1010 object.index = index;
1011 object.alive = lib.must_link; // TODO: or self.options.all_load;
1012 object.hidden = lib.hidden;
1013 try self.objects.append(gpa, index);
1014 }
1015}
1016
1017fn addDylib(self: *MachO, lib: SystemLib, explicit: bool, handle: File.HandleIndex, offset: u64) !File.Index {
1018 const tracy = trace(@src());
1019 defer tracy.end();
1020
1021 const gpa = self.base.comp.gpa;
1022
1023 const index: File.Index = @intCast(try self.files.addOne(gpa));
1024 self.files.set(index, .{ .dylib = .{
1025 .offset = offset,
1026 .file_handle = handle,
1027 .tag = .dylib,
1028 .path = .{
1029 .root_dir = lib.path.root_dir,
1030 .sub_path = try gpa.dupe(u8, lib.path.sub_path),
1031 },
1032 .index = index,
1033 .needed = lib.needed,
1034 .weak = lib.weak,
1035 .reexport = lib.reexport,
1036 .explicit = explicit,
1037 .umbrella = index,
1038 } });
1039 try self.dylibs.append(gpa, index);
1040
1041 return index;
1042}
1043
1044fn addTbd(self: *MachO, lib: SystemLib, explicit: bool, handle: File.HandleIndex) !File.Index {
1045 const tracy = trace(@src());
1046 defer tracy.end();
1047
1048 const gpa = self.base.comp.gpa;
1049 const index: File.Index = @intCast(try self.files.addOne(gpa));
1050 self.files.set(index, .{ .dylib = .{
1051 .offset = 0,
1052 .file_handle = handle,
1053 .tag = .tbd,
1054 .path = .{
1055 .root_dir = lib.path.root_dir,
1056 .sub_path = try gpa.dupe(u8, lib.path.sub_path),
1057 },
1058 .index = index,
1059 .needed = lib.needed,
1060 .weak = lib.weak,
1061 .reexport = lib.reexport,
1062 .explicit = explicit,
1063 .umbrella = index,
1064 } });
1065 try self.dylibs.append(gpa, index);
1066
1067 return index;
1068}
1069
1070/// According to ld64's manual, public (i.e., system) dylibs/frameworks are hoisted into the final
1071/// image unless overriden by -no_implicit_dylibs.
1072fn isHoisted(self: *MachO, install_name: []const u8) bool {
1073 if (self.no_implicit_dylibs) return true;
1074 if (fs.path.dirname(install_name)) |dirname| {
1075 if (mem.startsWith(u8, dirname, "/usr/lib")) return true;
1076 if (eatPrefix(dirname, "/System/Library/Frameworks/")) |path| {
1077 const basename = fs.path.basename(install_name);
1078 if (mem.findScalar(u8, path, '.')) |index| {
1079 if (mem.eql(u8, basename, path[0..index])) return true;
1080 }
1081 }
1082 }
1083 return false;
1084}
1085
1086/// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline
1087fn accessLibPath(
1088 arena: Allocator,
1089 io: Io,
1090 test_path: *std.array_list.Managed(u8),
1091 checked_paths: *std.array_list.Managed([]const u8),
1092 search_dir: []const u8,
1093 name: []const u8,
1094) !bool {
1095 const sep = fs.path.sep_str;
1096
1097 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
1098 test_path.clearRetainingCapacity();
1099 try test_path.print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext });
1100 try checked_paths.append(try arena.dupe(u8, test_path.items));
1101 Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) {
1102 error.FileNotFound => continue,
1103 else => |e| return e,
1104 };
1105 return true;
1106 }
1107
1108 return false;
1109}
1110
1111fn accessFrameworkPath(
1112 arena: Allocator,
1113 io: Io,
1114 test_path: *std.array_list.Managed(u8),
1115 checked_paths: *std.array_list.Managed([]const u8),
1116 search_dir: []const u8,
1117 name: []const u8,
1118) !bool {
1119 const sep = fs.path.sep_str;
1120
1121 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
1122 test_path.clearRetainingCapacity();
1123 try test_path.print("{s}" ++ sep ++ "{s}.framework" ++ sep ++ "{s}{s}", .{
1124 search_dir,
1125 name,
1126 name,
1127 ext,
1128 });
1129 try checked_paths.append(try arena.dupe(u8, test_path.items));
1130 Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) {
1131 error.FileNotFound => continue,
1132 else => |e| return e,
1133 };
1134 return true;
1135 }
1136
1137 return false;
1138}
1139
1140fn parseDependentDylibs(self: *MachO) !void {
1141 const tracy = trace(@src());
1142 defer tracy.end();
1143
1144 if (self.dylibs.items.len == 0) return;
1145
1146 const comp = self.base.comp;
1147 const gpa = comp.gpa;
1148 const io = comp.io;
1149 const framework_dirs = self.framework_dirs;
1150
1151 // TODO delete this, directories must instead be resolved by the frontend
1152 const lib_directories = self.lib_directories;
1153
1154 var arena_alloc = std.heap.ArenaAllocator.init(gpa);
1155 defer arena_alloc.deinit();
1156 const arena = arena_alloc.allocator();
1157
1158 // TODO handle duplicate dylibs - it is not uncommon to have the same dylib loaded multiple times
1159 // in which case we should track that and return File.Index immediately instead re-parsing paths.
1160
1161 var has_errors = false;
1162 var index: usize = 0;
1163 while (index < self.dylibs.items.len) : (index += 1) {
1164 const dylib_index = self.dylibs.items[index];
1165
1166 var dependents = std.array_list.Managed(File.Index).init(gpa);
1167 defer dependents.deinit();
1168 try dependents.ensureTotalCapacityPrecise(self.getFile(dylib_index).?.dylib.dependents.items.len);
1169
1170 const is_weak = self.getFile(dylib_index).?.dylib.weak;
1171 for (self.getFile(dylib_index).?.dylib.dependents.items) |id| {
1172 // We will search for the dependent dylibs in the following order:
1173 // 1. Basename is in search lib directories or framework directories
1174 // 2. If name is an absolute path, search as-is optionally prepending a syslibroot
1175 // if specified.
1176 // 3. If name is a relative path, substitute @rpath, @loader_path, @executable_path with
1177 // dependees list of rpaths, and search there.
1178 // 4. Finally, just search the provided relative path directly in CWD.
1179 var test_path = std.array_list.Managed(u8).init(arena);
1180 var checked_paths = std.array_list.Managed([]const u8).init(arena);
1181
1182 const full_path = full_path: {
1183 {
1184 const stem = fs.path.stem(id.name);
1185
1186 // Framework
1187 for (framework_dirs) |dir| {
1188 test_path.clearRetainingCapacity();
1189 if (try accessFrameworkPath(arena, io, &test_path, &checked_paths, dir, stem)) break :full_path test_path.items;
1190 }
1191
1192 // Library
1193 const lib_name = eatPrefix(stem, "lib") orelse stem;
1194 for (lib_directories) |lib_directory| {
1195 test_path.clearRetainingCapacity();
1196 if (try accessLibPath(arena, io, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items;
1197 }
1198 }
1199
1200 if (fs.path.isAbsolute(id.name)) {
1201 const existing_ext = fs.path.extension(id.name);
1202 const path = if (existing_ext.len > 0) id.name[0 .. id.name.len - existing_ext.len] else id.name;
1203 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
1204 test_path.clearRetainingCapacity();
1205 if (comp.sysroot) |root| {
1206 try test_path.print("{s}" ++ fs.path.sep_str ++ "{s}{s}", .{ root, path, ext });
1207 } else {
1208 try test_path.print("{s}{s}", .{ path, ext });
1209 }
1210 try checked_paths.append(try arena.dupe(u8, test_path.items));
1211 Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) {
1212 error.FileNotFound => continue,
1213 else => |e| return e,
1214 };
1215 break :full_path test_path.items;
1216 }
1217 }
1218
1219 if (eatPrefix(id.name, "@rpath/")) |path| {
1220 const dylib = self.getFile(dylib_index).?.dylib;
1221 for (self.getFile(dylib.umbrella).?.dylib.rpaths.keys()) |rpath| {
1222 const prefix = eatPrefix(rpath, "@loader_path/") orelse rpath;
1223 const rel_path = try fs.path.join(arena, &.{ prefix, path });
1224 try checked_paths.append(rel_path);
1225 var buffer: [fs.max_path_bytes]u8 = undefined;
1226 // TODO don't use realpath
1227 const full_path = buffer[0 .. Io.Dir.realPathFileAbsolute(io, rel_path, &buffer) catch continue];
1228 break :full_path try arena.dupe(u8, full_path);
1229 }
1230 } else if (eatPrefix(id.name, "@loader_path/")) |_| {
1231 try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
1232 return error.Unhandled;
1233 } else if (eatPrefix(id.name, "@executable_path/")) |_| {
1234 try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
1235 return error.Unhandled;
1236 }
1237
1238 try checked_paths.append(try arena.dupe(u8, id.name));
1239 var buffer: [fs.max_path_bytes]u8 = undefined;
1240 // TODO don't use realpath
1241 if (Io.Dir.realPathFileAbsolute(io, id.name, &buffer)) |full_path_n| {
1242 break :full_path try arena.dupe(u8, buffer[0..full_path_n]);
1243 } else |_| {
1244 try self.reportMissingDependencyError(
1245 self.getFile(dylib_index).?.dylib.getUmbrella(self).index,
1246 id.name,
1247 checked_paths.items,
1248 "unable to resolve dependency",
1249 .{},
1250 );
1251 has_errors = true;
1252 continue;
1253 }
1254 };
1255 const lib: SystemLib = .{
1256 .path = Path.initCwd(full_path),
1257 .weak = is_weak,
1258 };
1259 const file = try lib.path.root_dir.handle.openFile(io, lib.path.sub_path, .{});
1260 const fh = try self.addFileHandle(file);
1261 const fat_arch = try self.parseFatFile(file, lib.path);
1262 const offset = if (fat_arch) |fa| fa.offset else 0;
1263 const file_index = file_index: {
1264 if (readMachHeader(io, file, offset) catch null) |h| blk: {
1265 if (h.magic != macho.MH_MAGIC_64) break :blk;
1266 switch (h.filetype) {
1267 macho.MH_DYLIB => break :file_index try self.addDylib(lib, false, fh, offset),
1268 else => break :file_index @as(File.Index, 0),
1269 }
1270 }
1271 break :file_index try self.addTbd(lib, false, fh);
1272 };
1273 dependents.appendAssumeCapacity(file_index);
1274 }
1275
1276 const dylib = self.getFile(dylib_index).?.dylib;
1277 for (dylib.dependents.items, dependents.items) |id, file_index| {
1278 if (self.getFile(file_index)) |file| {
1279 const dep_dylib = file.dylib;
1280 try dep_dylib.parse(self); // TODO in parallel
1281 dep_dylib.hoisted = self.isHoisted(id.name);
1282 dep_dylib.umbrella = dylib.umbrella;
1283 if (!dep_dylib.hoisted) {
1284 const umbrella = dep_dylib.getUmbrella(self);
1285 for (dep_dylib.exports.items(.name), dep_dylib.exports.items(.flags)) |off, flags| {
1286 // TODO rethink this entire algorithm
1287 try umbrella.addExport(gpa, dep_dylib.getString(off), flags);
1288 }
1289 try umbrella.rpaths.ensureUnusedCapacity(gpa, dep_dylib.rpaths.keys().len);
1290 for (dep_dylib.rpaths.keys()) |rpath| {
1291 umbrella.rpaths.putAssumeCapacity(try gpa.dupe(u8, rpath), {});
1292 }
1293 }
1294 } else try self.reportDependencyError(
1295 dylib.getUmbrella(self).index,
1296 id.name,
1297 "unable to resolve dependency",
1298 .{},
1299 );
1300 has_errors = true;
1301 }
1302 }
1303
1304 if (has_errors) return error.MissingLibraryDependencies;
1305}
1306
1307/// When resolving symbols, we approach the problem similarly to `mold`.
1308/// 1. Resolve symbols across all objects (including those preemptively extracted archives).
1309/// 2. Resolve symbols across all shared objects.
1310/// 3. Mark live objects (see `MachO.markLive`)
1311/// 4. Reset state of all resolved globals since we will redo this bit on the pruned set.
1312/// 5. Remove references to dead objects/shared objects
1313/// 6. Re-run symbol resolution on pruned objects and shared objects sets.
1314pub fn resolveSymbols(self: *MachO) !void {
1315 const tracy = trace(@src());
1316 defer tracy.end();
1317
1318 // Resolve symbols in the ZigObject. For now, we assume that it's always live.
1319 if (self.getZigObject()) |zo| try zo.asFile().resolveSymbols(self);
1320 // Resolve symbols on the set of all objects and shared objects (even if some are unneeded).
1321 for (self.objects.items) |index| try self.getFile(index).?.resolveSymbols(self);
1322 for (self.dylibs.items) |index| try self.getFile(index).?.resolveSymbols(self);
1323 if (self.getInternalObject()) |obj| try obj.resolveSymbols(self);
1324
1325 // Mark live objects.
1326 self.markLive();
1327
1328 // Reset state of all globals after marking live objects.
1329 self.resolver.reset();
1330
1331 // Prune dead objects.
1332 var i: usize = 0;
1333 while (i < self.objects.items.len) {
1334 const index = self.objects.items[i];
1335 if (!self.getFile(index).?.object.alive) {
1336 _ = self.objects.orderedRemove(i);
1337 self.files.items(.data)[index].object.deinit(self.base.comp.gpa);
1338 self.files.set(index, .null);
1339 } else i += 1;
1340 }
1341
1342 // Re-resolve the symbols.
1343 if (self.getZigObject()) |zo| try zo.resolveSymbols(self);
1344 for (self.objects.items) |index| try self.getFile(index).?.resolveSymbols(self);
1345 for (self.dylibs.items) |index| try self.getFile(index).?.resolveSymbols(self);
1346 if (self.getInternalObject()) |obj| try obj.resolveSymbols(self);
1347
1348 // Merge symbol visibility
1349 if (self.getZigObject()) |zo| zo.mergeSymbolVisibility(self);
1350 for (self.objects.items) |index| self.getFile(index).?.object.mergeSymbolVisibility(self);
1351}
1352
1353fn markLive(self: *MachO) void {
1354 const tracy = trace(@src());
1355 defer tracy.end();
1356
1357 if (self.getZigObject()) |zo| zo.markLive(self);
1358 for (self.objects.items) |index| {
1359 const object = self.getFile(index).?.object;
1360 if (object.alive) object.markLive(self);
1361 }
1362 if (self.getInternalObject()) |obj| obj.markLive(self);
1363}
1364
1365fn convertTentativeDefsAndResolveSpecialSymbols(self: *MachO) !void {
1366 const diags = &self.base.comp.link_diags;
1367 {
1368 for (self.objects.items) |index| {
1369 convertTentativeDefinitionsWorker(self, self.getFile(index).?.object);
1370 }
1371 if (self.getInternalObject()) |obj| {
1372 resolveSpecialSymbolsWorker(self, obj);
1373 }
1374 }
1375 if (diags.hasErrors()) return error.AlreadyReported;
1376}
1377
1378fn convertTentativeDefinitionsWorker(self: *MachO, object: *Object) void {
1379 const tracy = trace(@src());
1380 defer tracy.end();
1381 object.convertTentativeDefinitions(self) catch |err| {
1382 self.reportParseError2(
1383 object.index,
1384 "unexpected error occurred while converting tentative symbols into defined symbols: {s}",
1385 .{@errorName(err)},
1386 ) catch {};
1387 };
1388}
1389
1390fn resolveSpecialSymbolsWorker(self: *MachO, obj: *InternalObject) void {
1391 const tracy = trace(@src());
1392 defer tracy.end();
1393
1394 const diags = &self.base.comp.link_diags;
1395
1396 obj.resolveBoundarySymbols(self) catch |err|
1397 return diags.addError("failed to resolve boundary symbols: {s}", .{@errorName(err)});
1398 obj.resolveObjcMsgSendSymbols(self) catch |err|
1399 return diags.addError("failed to resolve ObjC msgsend stubs: {s}", .{@errorName(err)});
1400}
1401
1402pub fn dedupLiterals(self: *MachO) !void {
1403 const tracy = trace(@src());
1404 defer tracy.end();
1405
1406 const gpa = self.base.comp.gpa;
1407 var lp: LiteralPool = .{};
1408 defer lp.deinit(gpa);
1409
1410 if (self.getZigObject()) |zo| {
1411 try zo.resolveLiterals(&lp, self);
1412 }
1413 for (self.objects.items) |index| {
1414 try self.getFile(index).?.object.resolveLiterals(&lp, self);
1415 }
1416 if (self.getInternalObject()) |object| {
1417 try object.resolveLiterals(&lp, self);
1418 }
1419
1420 {
1421 if (self.getZigObject()) |zo| {
1422 File.dedupLiterals(zo.asFile(), lp, self);
1423 }
1424 for (self.objects.items) |index| {
1425 File.dedupLiterals(self.getFile(index).?, lp, self);
1426 }
1427 if (self.getInternalObject()) |object| {
1428 File.dedupLiterals(object.asFile(), lp, self);
1429 }
1430 }
1431}
1432
1433fn claimUnresolved(self: *MachO) void {
1434 if (self.getZigObject()) |zo| {
1435 zo.asFile().claimUnresolved(self);
1436 }
1437 for (self.objects.items) |index| {
1438 self.getFile(index).?.claimUnresolved(self);
1439 }
1440}
1441
1442fn checkDuplicates(self: *MachO) !void {
1443 const tracy = trace(@src());
1444 defer tracy.end();
1445
1446 const diags = &self.base.comp.link_diags;
1447
1448 {
1449 if (self.getZigObject()) |zo| {
1450 checkDuplicatesWorker(self, zo.asFile());
1451 }
1452 for (self.objects.items) |index| {
1453 checkDuplicatesWorker(self, self.getFile(index).?);
1454 }
1455 if (self.getInternalObject()) |obj| {
1456 checkDuplicatesWorker(self, obj.asFile());
1457 }
1458 }
1459
1460 if (diags.hasErrors()) return error.AlreadyReported;
1461
1462 try self.reportDuplicates();
1463}
1464
1465fn checkDuplicatesWorker(self: *MachO, file: File) void {
1466 const tracy = trace(@src());
1467 defer tracy.end();
1468 file.checkDuplicates(self) catch |err| {
1469 self.reportParseError2(file.getIndex(), "failed to check for duplicate definitions: {s}", .{
1470 @errorName(err),
1471 }) catch {};
1472 };
1473}
1474
1475fn markImportsAndExports(self: *MachO) void {
1476 const tracy = trace(@src());
1477 defer tracy.end();
1478
1479 if (self.getZigObject()) |zo| {
1480 zo.asFile().markImportsExports(self);
1481 }
1482 for (self.objects.items) |index| {
1483 self.getFile(index).?.markImportsExports(self);
1484 }
1485 if (self.getInternalObject()) |obj| {
1486 obj.asFile().markImportsExports(self);
1487 }
1488}
1489
1490fn deadStripDylibs(self: *MachO) void {
1491 const tracy = trace(@src());
1492 defer tracy.end();
1493
1494 for (self.dylibs.items) |index| {
1495 self.getFile(index).?.dylib.markReferenced(self);
1496 }
1497
1498 var i: usize = 0;
1499 while (i < self.dylibs.items.len) {
1500 const index = self.dylibs.items[i];
1501 if (!self.getFile(index).?.dylib.isAlive(self)) {
1502 _ = self.dylibs.orderedRemove(i);
1503 self.files.items(.data)[index].dylib.deinit(self.base.comp.gpa);
1504 self.files.set(index, .null);
1505 } else i += 1;
1506 }
1507}
1508
1509fn scanRelocs(self: *MachO) !void {
1510 const tracy = trace(@src());
1511 defer tracy.end();
1512
1513 const diags = &self.base.comp.link_diags;
1514
1515 {
1516 if (self.getZigObject()) |zo| {
1517 scanRelocsWorker(self, zo.asFile());
1518 }
1519 for (self.objects.items) |index| {
1520 scanRelocsWorker(self, self.getFile(index).?);
1521 }
1522 if (self.getInternalObject()) |obj| {
1523 scanRelocsWorker(self, obj.asFile());
1524 }
1525 }
1526
1527 if (diags.hasErrors()) return error.AlreadyReported;
1528
1529 if (self.getInternalObject()) |obj| {
1530 try obj.checkUndefs(self);
1531 }
1532 try self.reportUndefs();
1533
1534 if (self.getZigObject()) |zo| {
1535 try zo.asFile().createSymbolIndirection(self);
1536 }
1537 for (self.objects.items) |index| {
1538 try self.getFile(index).?.createSymbolIndirection(self);
1539 }
1540 for (self.dylibs.items) |index| {
1541 try self.getFile(index).?.createSymbolIndirection(self);
1542 }
1543 if (self.getInternalObject()) |obj| {
1544 try obj.asFile().createSymbolIndirection(self);
1545 }
1546}
1547
1548fn scanRelocsWorker(self: *MachO, file: File) void {
1549 file.scanRelocs(self) catch |err| {
1550 self.reportParseError2(file.getIndex(), "failed to scan relocations: {s}", .{
1551 @errorName(err),
1552 }) catch {};
1553 };
1554}
1555
1556fn sortGlobalSymbolsByName(self: *MachO, symbols: []SymbolResolver.Index) void {
1557 const lessThan = struct {
1558 fn lessThan(ctx: *MachO, lhs: SymbolResolver.Index, rhs: SymbolResolver.Index) bool {
1559 const lhs_name = ctx.resolver.keys.items[lhs - 1].getName(ctx);
1560 const rhs_name = ctx.resolver.keys.items[rhs - 1].getName(ctx);
1561 return mem.order(u8, lhs_name, rhs_name) == .lt;
1562 }
1563 }.lessThan;
1564 mem.sort(SymbolResolver.Index, symbols, self, lessThan);
1565}
1566
1567fn reportUndefs(self: *MachO) !void {
1568 const tracy = trace(@src());
1569 defer tracy.end();
1570
1571 if (self.undefined_treatment == .suppress or
1572 self.undefined_treatment == .dynamic_lookup) return;
1573 if (self.undefs.keys().len == 0) return; // Nothing to do
1574
1575 const gpa = self.base.comp.gpa;
1576 const diags = &self.base.comp.link_diags;
1577 const max_notes = 4;
1578
1579 // We will sort by name, and then by file to ensure deterministic output.
1580 var keys = try std.array_list.Managed(SymbolResolver.Index).initCapacity(gpa, self.undefs.keys().len);
1581 defer keys.deinit();
1582 keys.appendSliceAssumeCapacity(self.undefs.keys());
1583 self.sortGlobalSymbolsByName(keys.items);
1584
1585 const refLessThan = struct {
1586 fn lessThan(ctx: void, lhs: Ref, rhs: Ref) bool {
1587 _ = ctx;
1588 return lhs.lessThan(rhs);
1589 }
1590 }.lessThan;
1591
1592 for (self.undefs.values()) |*undefs| switch (undefs.*) {
1593 .refs => |refs| mem.sort(Ref, refs.items, {}, refLessThan),
1594 else => {},
1595 };
1596
1597 for (keys.items) |key| {
1598 const undef_sym = self.resolver.keys.items[key - 1];
1599 const notes = self.undefs.get(key).?;
1600 const nnotes = nnotes: {
1601 const nnotes = switch (notes) {
1602 .refs => |refs| refs.items.len,
1603 else => 1,
1604 };
1605 break :nnotes @min(nnotes, max_notes) + @intFromBool(nnotes > max_notes);
1606 };
1607
1608 var err = try diags.addErrorWithNotes(nnotes);
1609 try err.addMsg("undefined symbol: {s}", .{undef_sym.getName(self)});
1610
1611 switch (notes) {
1612 .force_undefined => err.addNote("referenced with linker flag -u", .{}),
1613 .entry => err.addNote("referenced with linker flag -e", .{}),
1614 .dyld_stub_binder, .objc_msgsend => err.addNote("referenced implicitly", .{}),
1615 .refs => |refs| {
1616 var inote: usize = 0;
1617 while (inote < @min(refs.items.len, max_notes)) : (inote += 1) {
1618 const ref = refs.items[inote];
1619 const file = self.getFile(ref.file).?;
1620 const atom = ref.getAtom(self).?;
1621 err.addNote("referenced by {f}:{s}", .{ file.fmtPath(), atom.getName(self) });
1622 }
1623
1624 if (refs.items.len > max_notes) {
1625 const remaining = refs.items.len - max_notes;
1626 err.addNote("referenced {d} more times", .{remaining});
1627 }
1628 },
1629 }
1630 }
1631
1632 return error.HasUndefinedSymbols;
1633}
1634
1635fn initOutputSections(self: *MachO) !void {
1636 const tracy = trace(@src());
1637 defer tracy.end();
1638
1639 for (self.objects.items) |index| {
1640 try self.getFile(index).?.initOutputSections(self);
1641 }
1642 if (self.getInternalObject()) |obj| {
1643 try obj.asFile().initOutputSections(self);
1644 }
1645 self.text_sect_index = self.getSectionByName("__TEXT", "__text") orelse
1646 try self.addSection("__TEXT", "__text", .{
1647 .alignment = switch (self.getTarget().cpu.arch) {
1648 .x86_64 => 0,
1649 .aarch64 => 2,
1650 else => unreachable,
1651 },
1652 .flags = macho.S_REGULAR |
1653 macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1654 });
1655 self.data_sect_index = self.getSectionByName("__DATA", "__data") orelse
1656 try self.addSection("__DATA", "__data", .{});
1657}
1658
1659fn initSyntheticSections(self: *MachO) !void {
1660 const cpu_arch = self.getTarget().cpu.arch;
1661
1662 if (self.got.symbols.items.len > 0) {
1663 self.got_sect_index = try self.addSection("__DATA_CONST", "__got", .{
1664 .flags = macho.S_NON_LAZY_SYMBOL_POINTERS,
1665 .reserved1 = @intCast(self.stubs.symbols.items.len),
1666 });
1667 }
1668
1669 if (self.stubs.symbols.items.len > 0) {
1670 self.stubs_sect_index = try self.addSection("__TEXT", "__stubs", .{
1671 .flags = macho.S_SYMBOL_STUBS |
1672 macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1673 .reserved1 = 0,
1674 .reserved2 = switch (cpu_arch) {
1675 .x86_64 => 6,
1676 .aarch64 => 3 * @sizeOf(u32),
1677 else => 0,
1678 },
1679 });
1680 self.stubs_helper_sect_index = try self.addSection("__TEXT", "__stub_helper", .{
1681 .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1682 });
1683 self.la_symbol_ptr_sect_index = try self.addSection("__DATA", "__la_symbol_ptr", .{
1684 .flags = macho.S_LAZY_SYMBOL_POINTERS,
1685 .reserved1 = @intCast(self.stubs.symbols.items.len + self.got.symbols.items.len),
1686 });
1687 }
1688
1689 if (self.objc_stubs.symbols.items.len > 0) {
1690 self.objc_stubs_sect_index = try self.addSection("__TEXT", "__objc_stubs", .{
1691 .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1692 });
1693 }
1694
1695 if (self.tlv_ptr.symbols.items.len > 0) {
1696 self.tlv_ptr_sect_index = try self.addSection("__DATA", "__thread_ptrs", .{
1697 .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
1698 });
1699 }
1700
1701 const needs_unwind_info = for (self.objects.items) |index| {
1702 if (self.getFile(index).?.object.hasUnwindRecords()) break true;
1703 } else false;
1704 if (needs_unwind_info) {
1705 self.unwind_info_sect_index = try self.addSection("__TEXT", "__unwind_info", .{});
1706 }
1707
1708 const needs_eh_frame = for (self.objects.items) |index| {
1709 if (self.getFile(index).?.object.hasEhFrameRecords()) break true;
1710 } else false;
1711 if (needs_eh_frame) {
1712 assert(needs_unwind_info);
1713 self.eh_frame_sect_index = try self.addSection("__TEXT", "__eh_frame", .{
1714 .flags = macho.S_COALESCED | macho.S_ATTR_NO_TOC | macho.S_ATTR_STRIP_STATIC_SYMS | macho.S_ATTR_LIVE_SUPPORT,
1715 });
1716 }
1717
1718 if (self.getInternalObject()) |obj| {
1719 const gpa = self.base.comp.gpa;
1720
1721 for (obj.boundary_symbols.items) |sym_index| {
1722 const ref = obj.getSymbolRef(sym_index, self);
1723 const sym = ref.getSymbol(self).?;
1724 const name = sym.getName(self);
1725
1726 if (eatPrefix(name, "segment$start$")) |segname| {
1727 if (self.getSegmentByName(segname) == null) { // TODO check segname is valid
1728 const prot = getSegmentProt(segname);
1729 _ = try self.segments.append(gpa, .{
1730 .cmdsize = @sizeOf(macho.segment_command_64),
1731 .segname = makeStaticString(segname),
1732 .initprot = prot,
1733 .maxprot = prot,
1734 });
1735 }
1736 } else if (eatPrefix(name, "segment$end$")) |segname| {
1737 if (self.getSegmentByName(segname) == null) { // TODO check segname is valid
1738 const prot = getSegmentProt(segname);
1739 _ = try self.segments.append(gpa, .{
1740 .cmdsize = @sizeOf(macho.segment_command_64),
1741 .segname = makeStaticString(segname),
1742 .initprot = prot,
1743 .maxprot = prot,
1744 });
1745 }
1746 } else if (eatPrefix(name, "section$start$")) |actual_name| {
1747 const sep = mem.findScalar(u8, actual_name, '$').?; // TODO error rather than a panic
1748 const segname = actual_name[0..sep]; // TODO check segname is valid
1749 const sectname = actual_name[sep + 1 ..]; // TODO check sectname is valid
1750 if (self.getSectionByName(segname, sectname) == null) {
1751 _ = try self.addSection(segname, sectname, .{});
1752 }
1753 } else if (eatPrefix(name, "section$end$")) |actual_name| {
1754 const sep = mem.findScalar(u8, actual_name, '$').?; // TODO error rather than a panic
1755 const segname = actual_name[0..sep]; // TODO check segname is valid
1756 const sectname = actual_name[sep + 1 ..]; // TODO check sectname is valid
1757 if (self.getSectionByName(segname, sectname) == null) {
1758 _ = try self.addSection(segname, sectname, .{});
1759 }
1760 } else unreachable;
1761 }
1762 }
1763}
1764
1765fn getSegmentProt(segname: []const u8) macho.vm_prot_t {
1766 if (mem.eql(u8, segname, "__PAGEZERO")) return .{};
1767 if (mem.eql(u8, segname, "__TEXT")) return .{ .READ = true, .EXEC = true };
1768 if (mem.eql(u8, segname, "__LINKEDIT")) return .{ .READ = true };
1769 return .{ .READ = true, .WRITE = true };
1770}
1771
1772fn getSegmentRank(segname: []const u8) u8 {
1773 if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0;
1774 if (mem.eql(u8, segname, "__LINKEDIT")) return 0xf;
1775 if (mem.find(u8, segname, "ZIG")) |_| return 0xe;
1776 if (mem.startsWith(u8, segname, "__TEXT")) return 0x1;
1777 if (mem.startsWith(u8, segname, "__DATA_CONST")) return 0x2;
1778 if (mem.startsWith(u8, segname, "__DATA")) return 0x3;
1779 return 0x4;
1780}
1781
1782fn segmentLessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool {
1783 _ = ctx;
1784 const lhs_rank = getSegmentRank(lhs);
1785 const rhs_rank = getSegmentRank(rhs);
1786 if (lhs_rank == rhs_rank) {
1787 return mem.order(u8, lhs, rhs) == .lt;
1788 }
1789 return lhs_rank < rhs_rank;
1790}
1791
1792fn getSectionRank(section: macho.section_64) u8 {
1793 if (section.isCode()) {
1794 if (mem.eql(u8, "__text", section.sectName())) return 0x0;
1795 if (section.type() == macho.S_SYMBOL_STUBS) return 0x1;
1796 return 0x2;
1797 }
1798 switch (section.type()) {
1799 macho.S_NON_LAZY_SYMBOL_POINTERS,
1800 macho.S_LAZY_SYMBOL_POINTERS,
1801 => return 0x0,
1802
1803 macho.S_MOD_INIT_FUNC_POINTERS => return 0x1,
1804 macho.S_MOD_TERM_FUNC_POINTERS => return 0x2,
1805 macho.S_ZEROFILL => return 0xf,
1806 macho.S_THREAD_LOCAL_REGULAR => return 0xd,
1807 macho.S_THREAD_LOCAL_ZEROFILL => return 0xe,
1808
1809 else => {
1810 if (mem.eql(u8, "__unwind_info", section.sectName())) return 0xe;
1811 if (mem.eql(u8, "__compact_unwind", section.sectName())) return 0xe;
1812 if (mem.eql(u8, "__eh_frame", section.sectName())) return 0xf;
1813 return 0x3;
1814 },
1815 }
1816}
1817
1818fn sectionLessThan(ctx: void, lhs: macho.section_64, rhs: macho.section_64) bool {
1819 if (mem.eql(u8, lhs.segName(), rhs.segName())) {
1820 const lhs_rank = getSectionRank(lhs);
1821 const rhs_rank = getSectionRank(rhs);
1822 if (lhs_rank == rhs_rank) {
1823 return mem.order(u8, lhs.sectName(), rhs.sectName()) == .lt;
1824 }
1825 return lhs_rank < rhs_rank;
1826 }
1827 return segmentLessThan(ctx, lhs.segName(), rhs.segName());
1828}
1829
1830pub fn sortSections(self: *MachO) !void {
1831 const Entry = struct {
1832 index: u8,
1833
1834 pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool {
1835 return sectionLessThan(
1836 {},
1837 macho_file.sections.items(.header)[lhs.index],
1838 macho_file.sections.items(.header)[rhs.index],
1839 );
1840 }
1841 };
1842
1843 const gpa = self.base.comp.gpa;
1844
1845 var entries = try std.array_list.Managed(Entry).initCapacity(gpa, self.sections.slice().len);
1846 defer entries.deinit();
1847 for (0..self.sections.slice().len) |index| {
1848 entries.appendAssumeCapacity(.{ .index = @intCast(index) });
1849 }
1850
1851 mem.sort(Entry, entries.items, self, Entry.lessThan);
1852
1853 const backlinks = try gpa.alloc(u8, entries.items.len);
1854 defer gpa.free(backlinks);
1855 for (entries.items, 0..) |entry, i| {
1856 backlinks[entry.index] = @intCast(i);
1857 }
1858
1859 var slice = self.sections.toOwnedSlice();
1860 defer slice.deinit(gpa);
1861
1862 try self.sections.ensureTotalCapacity(gpa, slice.len);
1863 for (entries.items) |sorted| {
1864 self.sections.appendAssumeCapacity(slice.get(sorted.index));
1865 }
1866
1867 for (&[_]*?u8{
1868 &self.data_sect_index,
1869 &self.got_sect_index,
1870 &self.zig_text_sect_index,
1871 &self.zig_const_sect_index,
1872 &self.zig_data_sect_index,
1873 &self.zig_bss_sect_index,
1874 &self.stubs_sect_index,
1875 &self.stubs_helper_sect_index,
1876 &self.la_symbol_ptr_sect_index,
1877 &self.tlv_ptr_sect_index,
1878 &self.eh_frame_sect_index,
1879 &self.unwind_info_sect_index,
1880 &self.objc_stubs_sect_index,
1881 &self.debug_str_sect_index,
1882 &self.debug_info_sect_index,
1883 &self.debug_abbrev_sect_index,
1884 &self.debug_aranges_sect_index,
1885 &self.debug_line_sect_index,
1886 &self.debug_line_str_sect_index,
1887 &self.debug_loclists_sect_index,
1888 &self.debug_rnglists_sect_index,
1889 }) |maybe_index| {
1890 if (maybe_index.*) |*index| {
1891 index.* = backlinks[index.*];
1892 }
1893 }
1894
1895 if (self.getZigObject()) |zo| {
1896 for (zo.getAtoms()) |atom_index| {
1897 const atom = zo.getAtom(atom_index) orelse continue;
1898 if (!atom.isAlive()) continue;
1899 atom.out_n_sect = backlinks[atom.out_n_sect];
1900 }
1901 if (zo.dwarf) |*dwarf| dwarf.reloadSectionMetadata();
1902 }
1903
1904 for (self.objects.items) |index| {
1905 const file = self.getFile(index).?;
1906 for (file.getAtoms()) |atom_index| {
1907 const atom = file.getAtom(atom_index) orelse continue;
1908 if (!atom.isAlive()) continue;
1909 atom.out_n_sect = backlinks[atom.out_n_sect];
1910 }
1911 }
1912
1913 if (self.getInternalObject()) |object| {
1914 for (object.getAtoms()) |atom_index| {
1915 const atom = object.getAtom(atom_index) orelse continue;
1916 if (!atom.isAlive()) continue;
1917 atom.out_n_sect = backlinks[atom.out_n_sect];
1918 }
1919 }
1920}
1921
1922pub fn addAtomsToSections(self: *MachO) !void {
1923 const tracy = trace(@src());
1924 defer tracy.end();
1925
1926 const gpa = self.base.comp.gpa;
1927
1928 if (self.getZigObject()) |zo| {
1929 for (zo.getAtoms()) |atom_index| {
1930 const atom = zo.getAtom(atom_index) orelse continue;
1931 if (!atom.isAlive()) continue;
1932 if (self.isZigSection(atom.out_n_sect)) continue;
1933 const atoms = &self.sections.items(.atoms)[atom.out_n_sect];
1934 try atoms.append(gpa, .{ .index = atom_index, .file = zo.index });
1935 }
1936 }
1937 for (self.objects.items) |index| {
1938 const file = self.getFile(index).?;
1939 for (file.getAtoms()) |atom_index| {
1940 const atom = file.getAtom(atom_index) orelse continue;
1941 if (!atom.isAlive()) continue;
1942 const atoms = &self.sections.items(.atoms)[atom.out_n_sect];
1943 try atoms.append(gpa, .{ .index = atom_index, .file = index });
1944 }
1945 }
1946 if (self.getInternalObject()) |object| {
1947 for (object.getAtoms()) |atom_index| {
1948 const atom = object.getAtom(atom_index) orelse continue;
1949 if (!atom.isAlive()) continue;
1950 const atoms = &self.sections.items(.atoms)[atom.out_n_sect];
1951 try atoms.append(gpa, .{ .index = atom_index, .file = object.index });
1952 }
1953 }
1954}
1955
1956fn calcSectionSizes(self: *MachO) !void {
1957 const tracy = trace(@src());
1958 defer tracy.end();
1959
1960 const diags = &self.base.comp.link_diags;
1961 const cpu_arch = self.getTarget().cpu.arch;
1962
1963 if (self.data_sect_index) |idx| {
1964 const header = &self.sections.items(.header)[idx];
1965 header.size += @sizeOf(u64);
1966 header.@"align" = 3;
1967 }
1968
1969 {
1970 const slice = self.sections.slice();
1971 for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| {
1972 if (atoms.items.len == 0) continue;
1973 if (self.requiresThunks() and header.isCode()) continue;
1974 calcSectionSizeWorker(self, @as(u8, @intCast(i)));
1975 }
1976
1977 if (self.requiresThunks()) {
1978 for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| {
1979 if (!header.isCode()) continue;
1980 if (atoms.items.len == 0) continue;
1981 createThunksWorker(self, @as(u8, @intCast(i)));
1982 }
1983 }
1984
1985 // At this point, we can also calculate most of the symtab and data-in-code linkedit section sizes
1986 if (self.getZigObject()) |zo| {
1987 File.calcSymtabSize(zo.asFile(), self);
1988 }
1989 for (self.objects.items) |index| {
1990 File.calcSymtabSize(self.getFile(index).?, self);
1991 }
1992 for (self.dylibs.items) |index| {
1993 File.calcSymtabSize(self.getFile(index).?, self);
1994 }
1995 if (self.getInternalObject()) |obj| {
1996 File.calcSymtabSize(obj.asFile(), self);
1997 }
1998 }
1999
2000 if (diags.hasErrors()) return error.AlreadyReported;
2001
2002 try self.calcSymtabSize();
2003
2004 if (self.got_sect_index) |idx| {
2005 const header = &self.sections.items(.header)[idx];
2006 header.size = self.got.size();
2007 header.@"align" = 3;
2008 }
2009
2010 if (self.stubs_sect_index) |idx| {
2011 const header = &self.sections.items(.header)[idx];
2012 header.size = self.stubs.size(self);
2013 header.@"align" = switch (cpu_arch) {
2014 .x86_64 => 1,
2015 .aarch64 => 2,
2016 else => 0,
2017 };
2018 }
2019
2020 if (self.stubs_helper_sect_index) |idx| {
2021 const header = &self.sections.items(.header)[idx];
2022 header.size = self.stubs_helper.size(self);
2023 header.@"align" = 2;
2024 }
2025
2026 if (self.la_symbol_ptr_sect_index) |idx| {
2027 const header = &self.sections.items(.header)[idx];
2028 header.size = self.la_symbol_ptr.size(self);
2029 header.@"align" = 3;
2030 }
2031
2032 if (self.tlv_ptr_sect_index) |idx| {
2033 const header = &self.sections.items(.header)[idx];
2034 header.size = self.tlv_ptr.size();
2035 header.@"align" = 3;
2036 }
2037
2038 if (self.objc_stubs_sect_index) |idx| {
2039 const header = &self.sections.items(.header)[idx];
2040 header.size = self.objc_stubs.size(self);
2041 header.@"align" = switch (cpu_arch) {
2042 .x86_64 => 0,
2043 .aarch64 => 2,
2044 else => 0,
2045 };
2046 }
2047}
2048
2049fn calcSectionSizeWorker(self: *MachO, sect_id: u8) void {
2050 const tracy = trace(@src());
2051 defer tracy.end();
2052
2053 const diags = &self.base.comp.link_diags;
2054
2055 const doWork = struct {
2056 fn doWork(macho_file: *MachO, header: *macho.section_64, atoms: []const Ref) !void {
2057 for (atoms) |ref| {
2058 const atom = ref.getAtom(macho_file).?;
2059 const atom_alignment = atom.alignment.toByteUnits() orelse 1;
2060 const offset = mem.alignForward(u64, header.size, atom_alignment);
2061 const padding = offset - header.size;
2062 atom.value = offset;
2063 header.size += padding + atom.size;
2064 header.@"align" = @max(header.@"align", atom.alignment.toLog2Units());
2065 }
2066 }
2067 }.doWork;
2068 const slice = self.sections.slice();
2069 const header = &slice.items(.header)[sect_id];
2070 const atoms = slice.items(.atoms)[sect_id].items;
2071 doWork(self, header, atoms) catch |err| {
2072 try diags.addError("failed to calculate size of section '{s},{s}': {s}", .{
2073 header.segName(), header.sectName(), @errorName(err),
2074 });
2075 };
2076}
2077
2078fn createThunksWorker(self: *MachO, sect_id: u8) void {
2079 const tracy = trace(@src());
2080 defer tracy.end();
2081 const diags = &self.base.comp.link_diags;
2082 self.createThunks(sect_id) catch |err| {
2083 const header = self.sections.items(.header)[sect_id];
2084 diags.addError("failed to create thunks and calculate size of section '{s},{s}': {s}", .{
2085 header.segName(), header.sectName(), @errorName(err),
2086 });
2087 };
2088}
2089
2090fn generateUnwindInfo(self: *MachO) !void {
2091 const tracy = trace(@src());
2092 defer tracy.end();
2093
2094 const diags = &self.base.comp.link_diags;
2095
2096 if (self.eh_frame_sect_index) |index| {
2097 const sect = &self.sections.items(.header)[index];
2098 sect.size = try eh_frame.calcSize(self);
2099 sect.@"align" = 3;
2100 }
2101 if (self.unwind_info_sect_index) |index| {
2102 const sect = &self.sections.items(.header)[index];
2103 self.unwind_info.generate(self) catch |err| switch (err) {
2104 error.TooManyPersonalities => return diags.fail("too many personalities in unwind info", .{}),
2105 else => |e| return e,
2106 };
2107 sect.size = self.unwind_info.calcSize();
2108 sect.@"align" = 2;
2109 }
2110}
2111
2112fn initSegments(self: *MachO) !void {
2113 const gpa = self.base.comp.gpa;
2114 const slice = self.sections.slice();
2115
2116 // Add __PAGEZERO if required
2117 const pagezero_size = self.pagezero_size orelse default_pagezero_size;
2118 const aligned_pagezero_size = mem.alignBackward(u64, pagezero_size, self.getPageSize());
2119 if (!self.base.isDynLib() and aligned_pagezero_size > 0) {
2120 if (aligned_pagezero_size != pagezero_size) {
2121 // TODO convert into a warning
2122 log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_size});
2123 log.warn(" rounding down to 0x{x}", .{aligned_pagezero_size});
2124 }
2125 self.pagezero_seg_index = try self.addSegment("__PAGEZERO", .{ .vmsize = aligned_pagezero_size });
2126 }
2127
2128 // __TEXT segment is non-optional
2129 self.text_seg_index = try self.addSegment("__TEXT", .{ .prot = getSegmentProt("__TEXT") });
2130
2131 // Next, create segments required by sections
2132 for (slice.items(.header)) |header| {
2133 const segname = header.segName();
2134 if (self.getSegmentByName(segname) == null) {
2135 _ = try self.addSegment(segname, .{ .prot = getSegmentProt(segname) });
2136 }
2137 }
2138
2139 // Add __LINKEDIT
2140 self.linkedit_seg_index = try self.addSegment("__LINKEDIT", .{ .prot = getSegmentProt("__LINKEDIT") });
2141
2142 // Sort segments
2143 const Entry = struct {
2144 index: u8,
2145
2146 pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool {
2147 return segmentLessThan(
2148 {},
2149 macho_file.segments.items[lhs.index].segName(),
2150 macho_file.segments.items[rhs.index].segName(),
2151 );
2152 }
2153 };
2154
2155 var entries = try std.array_list.Managed(Entry).initCapacity(gpa, self.segments.items.len);
2156 defer entries.deinit();
2157 for (0..self.segments.items.len) |index| {
2158 entries.appendAssumeCapacity(.{ .index = @intCast(index) });
2159 }
2160
2161 mem.sort(Entry, entries.items, self, Entry.lessThan);
2162
2163 const backlinks = try gpa.alloc(u8, entries.items.len);
2164 defer gpa.free(backlinks);
2165 for (entries.items, 0..) |entry, i| {
2166 backlinks[entry.index] = @intCast(i);
2167 }
2168
2169 const segments = try self.segments.toOwnedSlice(gpa);
2170 defer gpa.free(segments);
2171
2172 try self.segments.ensureTotalCapacityPrecise(gpa, segments.len);
2173 for (entries.items) |sorted| {
2174 self.segments.appendAssumeCapacity(segments[sorted.index]);
2175 }
2176
2177 for (&[_]*?u8{
2178 &self.pagezero_seg_index,
2179 &self.text_seg_index,
2180 &self.linkedit_seg_index,
2181 &self.zig_text_seg_index,
2182 &self.zig_const_seg_index,
2183 &self.zig_data_seg_index,
2184 &self.zig_bss_seg_index,
2185 }) |maybe_index| {
2186 if (maybe_index.*) |*index| {
2187 index.* = backlinks[index.*];
2188 }
2189 }
2190
2191 // Attach sections to segments
2192 for (slice.items(.header), slice.items(.segment_id)) |header, *seg_id| {
2193 const segname = header.segName();
2194 const segment_id = self.getSegmentByName(segname) orelse blk: {
2195 const segment_id = @as(u8, @intCast(self.segments.items.len));
2196 const protection = getSegmentProt(segname);
2197 try self.segments.append(gpa, .{
2198 .cmdsize = @sizeOf(macho.segment_command_64),
2199 .segname = makeStaticString(segname),
2200 .maxprot = protection,
2201 .initprot = protection,
2202 });
2203 break :blk segment_id;
2204 };
2205 const segment = &self.segments.items[segment_id];
2206 segment.cmdsize += @sizeOf(macho.section_64);
2207 segment.nsects += 1;
2208 seg_id.* = segment_id;
2209 }
2210
2211 // Set __DATA_CONST as READ_ONLY
2212 if (self.getSegmentByName("__DATA_CONST")) |seg_id| {
2213 const seg = &self.segments.items[seg_id];
2214 seg.flags |= macho.SG_READ_ONLY;
2215 }
2216}
2217
2218fn allocateSections(self: *MachO) !void {
2219 const header_size = try load_commands.calcMinHeaderSize(self);
2220 self.header_size = header_size;
2221 var vmaddr: u64 = if (self.pagezero_seg_index) |index|
2222 self.segments.items[index].vmaddr + self.segments.items[index].vmsize
2223 else
2224 0;
2225 vmaddr += header_size;
2226 var fileoff = header_size;
2227 var prev_seg_id: u8 = if (self.pagezero_seg_index) |index| index + 1 else 0;
2228
2229 const page_size = self.getPageSize();
2230 const slice = self.sections.slice();
2231 const last_index = for (0..slice.items(.header).len) |i| {
2232 if (self.isZigSection(@intCast(i))) break i;
2233 } else slice.items(.header).len;
2234
2235 for (slice.items(.header)[0..last_index], slice.items(.segment_id)[0..last_index]) |*header, curr_seg_id| {
2236 if (prev_seg_id != curr_seg_id) {
2237 vmaddr = mem.alignForward(u64, vmaddr, page_size);
2238 fileoff = mem.alignForward(u32, fileoff, page_size);
2239 }
2240
2241 const alignment = try self.alignPow(header.@"align");
2242
2243 vmaddr = mem.alignForward(u64, vmaddr, alignment);
2244 header.addr = vmaddr;
2245 vmaddr += header.size;
2246
2247 if (!header.isZerofill()) {
2248 fileoff = mem.alignForward(u32, fileoff, alignment);
2249 header.offset = fileoff;
2250 fileoff += @intCast(header.size);
2251 }
2252
2253 prev_seg_id = curr_seg_id;
2254 }
2255
2256 fileoff = mem.alignForward(u32, fileoff, page_size);
2257 for (slice.items(.header)[last_index..], slice.items(.segment_id)[last_index..]) |*header, seg_id| {
2258 if (header.isZerofill()) continue;
2259 if (header.offset < fileoff) {
2260 const existing_size = header.size;
2261 header.size = 0;
2262
2263 // Must move the entire section.
2264 const new_offset = try self.findFreeSpace(existing_size, page_size);
2265
2266 log.debug("moving '{s},{s}' from 0x{x} to 0x{x}", .{
2267 header.segName(),
2268 header.sectName(),
2269 header.offset,
2270 new_offset,
2271 });
2272
2273 try self.copyRangeAllZeroOut(header.offset, new_offset, existing_size);
2274
2275 header.offset = @intCast(new_offset);
2276 header.size = existing_size;
2277 self.segments.items[seg_id].fileoff = new_offset;
2278 }
2279 }
2280}
2281
2282/// We allocate segments in a separate step to also consider segments that have no sections.
2283fn allocateSegments(self: *MachO) void {
2284 const first_index = if (self.pagezero_seg_index) |index| index + 1 else 0;
2285 const last_index = for (0..self.segments.items.len) |i| {
2286 if (self.isZigSegment(@intCast(i))) break i;
2287 } else self.segments.items.len;
2288
2289 var vmaddr: u64 = if (self.pagezero_seg_index) |index|
2290 self.segments.items[index].vmaddr + self.segments.items[index].vmsize
2291 else
2292 0;
2293 var fileoff: u64 = 0;
2294
2295 const page_size = self.getPageSize();
2296 const slice = self.sections.slice();
2297
2298 var next_sect_id: u8 = 0;
2299 for (self.segments.items[first_index..last_index], first_index..last_index) |*seg, seg_id| {
2300 seg.vmaddr = vmaddr;
2301 seg.fileoff = fileoff;
2302
2303 while (next_sect_id < slice.items(.header).len) : (next_sect_id += 1) {
2304 const header = slice.items(.header)[next_sect_id];
2305 const sid = slice.items(.segment_id)[next_sect_id];
2306
2307 if (seg_id != sid) break;
2308
2309 vmaddr = header.addr + header.size;
2310 if (!header.isZerofill()) {
2311 fileoff = header.offset + header.size;
2312 }
2313 }
2314
2315 seg.vmsize = vmaddr - seg.vmaddr;
2316 seg.filesize = fileoff - seg.fileoff;
2317
2318 vmaddr = mem.alignForward(u64, vmaddr, page_size);
2319 fileoff = mem.alignForward(u64, fileoff, page_size);
2320 }
2321}
2322
2323fn allocateSyntheticSymbols(self: *MachO) void {
2324 if (self.getInternalObject()) |obj| {
2325 obj.allocateSyntheticSymbols(self);
2326
2327 const text_seg = self.getTextSegment();
2328
2329 for (obj.boundary_symbols.items) |sym_index| {
2330 const ref = obj.getSymbolRef(sym_index, self);
2331 const sym = ref.getSymbol(self).?;
2332 const name = sym.getName(self);
2333
2334 sym.value = text_seg.vmaddr;
2335
2336 if (mem.startsWith(u8, name, "segment$start$")) {
2337 const segname = name["segment$start$".len..];
2338 if (self.getSegmentByName(segname)) |seg_id| {
2339 const seg = self.segments.items[seg_id];
2340 sym.value = seg.vmaddr;
2341 }
2342 } else if (mem.startsWith(u8, name, "segment$end$")) {
2343 const segname = name["segment$end$".len..];
2344 if (self.getSegmentByName(segname)) |seg_id| {
2345 const seg = self.segments.items[seg_id];
2346 sym.value = seg.vmaddr + seg.vmsize;
2347 }
2348 } else if (mem.startsWith(u8, name, "section$start$")) {
2349 const actual_name = name["section$start$".len..];
2350 const sep = mem.findScalar(u8, actual_name, '$').?; // TODO error rather than a panic
2351 const segname = actual_name[0..sep];
2352 const sectname = actual_name[sep + 1 ..];
2353 if (self.getSectionByName(segname, sectname)) |sect_id| {
2354 const sect = self.sections.items(.header)[sect_id];
2355 sym.value = sect.addr;
2356 sym.out_n_sect = sect_id;
2357 }
2358 } else if (mem.startsWith(u8, name, "section$end$")) {
2359 const actual_name = name["section$end$".len..];
2360 const sep = mem.findScalar(u8, actual_name, '$').?; // TODO error rather than a panic
2361 const segname = actual_name[0..sep];
2362 const sectname = actual_name[sep + 1 ..];
2363 if (self.getSectionByName(segname, sectname)) |sect_id| {
2364 const sect = self.sections.items(.header)[sect_id];
2365 sym.value = sect.addr + sect.size;
2366 sym.out_n_sect = sect_id;
2367 }
2368 } else unreachable;
2369 }
2370
2371 if (self.objc_stubs.symbols.items.len > 0) {
2372 const addr = self.sections.items(.header)[self.objc_stubs_sect_index.?].addr;
2373
2374 for (self.objc_stubs.symbols.items, 0..) |ref, idx| {
2375 const sym = ref.getSymbol(self).?;
2376 sym.value = addr + idx * ObjcStubsSection.entrySize(self.getTarget().cpu.arch);
2377 sym.out_n_sect = self.objc_stubs_sect_index.?;
2378 }
2379 }
2380 }
2381}
2382
2383fn allocateLinkeditSegment(self: *MachO) !void {
2384 var fileoff: u64 = 0;
2385 var vmaddr: u64 = 0;
2386
2387 for (self.segments.items) |seg| {
2388 if (fileoff < seg.fileoff + seg.filesize) fileoff = seg.fileoff + seg.filesize;
2389 if (vmaddr < seg.vmaddr + seg.vmsize) vmaddr = seg.vmaddr + seg.vmsize;
2390 }
2391
2392 const page_size = self.getPageSize();
2393 const seg = self.getLinkeditSegment();
2394 seg.vmaddr = mem.alignForward(u64, vmaddr, page_size);
2395 seg.fileoff = mem.alignForward(u64, fileoff, page_size);
2396
2397 var off = try self.cast(u32, seg.fileoff);
2398 // DYLD_INFO_ONLY
2399 {
2400 const cmd = &self.dyld_info_cmd;
2401 cmd.rebase_off = off;
2402 off += cmd.rebase_size;
2403 cmd.bind_off = off;
2404 off += cmd.bind_size;
2405 cmd.weak_bind_off = off;
2406 off += cmd.weak_bind_size;
2407 cmd.lazy_bind_off = off;
2408 off += cmd.lazy_bind_size;
2409 cmd.export_off = off;
2410 off += cmd.export_size;
2411 off = mem.alignForward(u32, off, @alignOf(u64));
2412 }
2413
2414 // FUNCTION_STARTS
2415 {
2416 const cmd = &self.function_starts_cmd;
2417 cmd.dataoff = off;
2418 off += cmd.datasize;
2419 off = mem.alignForward(u32, off, @alignOf(u64));
2420 }
2421
2422 // DATA_IN_CODE
2423 {
2424 const cmd = &self.data_in_code_cmd;
2425 cmd.dataoff = off;
2426 off += cmd.datasize;
2427 off = mem.alignForward(u32, off, @alignOf(u64));
2428 }
2429
2430 // SYMTAB (symtab)
2431 {
2432 const cmd = &self.symtab_cmd;
2433 cmd.symoff = off;
2434 off += cmd.nsyms * @sizeOf(macho.nlist_64);
2435 off = mem.alignForward(u32, off, @alignOf(u32));
2436 }
2437
2438 // DYSYMTAB
2439 {
2440 const cmd = &self.dysymtab_cmd;
2441 cmd.indirectsymoff = off;
2442 off += cmd.nindirectsyms * @sizeOf(u32);
2443 off = mem.alignForward(u32, off, @alignOf(u64));
2444 }
2445
2446 // SYMTAB (strtab)
2447 {
2448 const cmd = &self.symtab_cmd;
2449 cmd.stroff = off;
2450 off += cmd.strsize;
2451 }
2452
2453 seg.filesize = off - seg.fileoff;
2454}
2455
2456fn resizeSections(self: *MachO) !void {
2457 const slice = self.sections.slice();
2458 for (slice.items(.header), slice.items(.out), 0..) |header, *out, n_sect| {
2459 if (header.isZerofill()) continue;
2460 if (self.isZigSection(@intCast(n_sect))) continue; // TODO this is horrible
2461 const cpu_arch = self.getTarget().cpu.arch;
2462 const size = try self.cast(usize, header.size);
2463 try out.resize(self.base.comp.gpa, size);
2464 const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
2465 @memset(out.items, padding_byte);
2466 }
2467}
2468
2469fn writeSectionsAndUpdateLinkeditSizes(self: *MachO) !void {
2470 const tracy = trace(@src());
2471 defer tracy.end();
2472
2473 const gpa = self.base.comp.gpa;
2474 const diags = &self.base.comp.link_diags;
2475
2476 const cmd = self.symtab_cmd;
2477 try self.symtab.resize(gpa, cmd.nsyms);
2478 try self.strtab.resize(gpa, cmd.strsize);
2479 self.strtab.items[0] = 0;
2480
2481 {
2482 for (self.objects.items) |index| {
2483 writeAtomsWorker(self, self.getFile(index).?);
2484 }
2485 if (self.getZigObject()) |zo| {
2486 writeAtomsWorker(self, zo.asFile());
2487 }
2488 if (self.getInternalObject()) |obj| {
2489 writeAtomsWorker(self, obj.asFile());
2490 }
2491 for (self.thunks.items) |thunk| {
2492 writeThunkWorker(self, thunk);
2493 }
2494
2495 const slice = self.sections.slice();
2496 for (&[_]?u8{
2497 self.eh_frame_sect_index,
2498 self.unwind_info_sect_index,
2499 self.got_sect_index,
2500 self.stubs_sect_index,
2501 self.la_symbol_ptr_sect_index,
2502 self.tlv_ptr_sect_index,
2503 self.objc_stubs_sect_index,
2504 }) |maybe_sect_id| {
2505 if (maybe_sect_id) |sect_id| {
2506 const out = slice.items(.out)[sect_id].items;
2507 writeSyntheticSectionWorker(self, sect_id, out);
2508 }
2509 }
2510
2511 if (self.la_symbol_ptr_sect_index) |_| {
2512 updateLazyBindSizeWorker(self);
2513 }
2514
2515 updateLinkeditSizeWorker(self, .rebase);
2516 updateLinkeditSizeWorker(self, .bind);
2517 updateLinkeditSizeWorker(self, .weak_bind);
2518 updateLinkeditSizeWorker(self, .export_trie);
2519 updateLinkeditSizeWorker(self, .data_in_code);
2520
2521 if (self.getZigObject()) |zo| {
2522 File.writeSymtab(zo.asFile(), self, self);
2523 }
2524 for (self.objects.items) |index| {
2525 File.writeSymtab(self.getFile(index).?, self, self);
2526 }
2527 for (self.dylibs.items) |index| {
2528 File.writeSymtab(self.getFile(index).?, self, self);
2529 }
2530 if (self.getInternalObject()) |obj| {
2531 File.writeSymtab(obj.asFile(), self, self);
2532 }
2533 if (self.requiresThunks()) for (self.thunks.items) |th| {
2534 Thunk.writeSymtab(th, self, self);
2535 };
2536 }
2537
2538 if (diags.hasErrors()) return error.AlreadyReported;
2539}
2540
2541fn writeAtomsWorker(self: *MachO, file: File) void {
2542 const tracy = trace(@src());
2543 defer tracy.end();
2544 file.writeAtoms(self) catch |err| {
2545 self.reportParseError2(file.getIndex(), "failed to resolve relocations and write atoms: {s}", .{
2546 @errorName(err),
2547 }) catch {};
2548 };
2549}
2550
2551fn writeThunkWorker(self: *MachO, thunk: Thunk) void {
2552 const tracy = trace(@src());
2553 defer tracy.end();
2554
2555 const diags = &self.base.comp.link_diags;
2556
2557 const doWork = struct {
2558 fn doWork(th: Thunk, buffer: []u8, macho_file: *MachO) !void {
2559 const off = try macho_file.cast(usize, th.value);
2560 const size = th.size();
2561 var stream: Writer = .fixed(buffer[off..][0..size]);
2562 try th.write(macho_file, &stream);
2563 }
2564 }.doWork;
2565 const out = self.sections.items(.out)[thunk.out_n_sect].items;
2566 doWork(thunk, out, self) catch |err| {
2567 diags.addError("failed to write contents of thunk: {s}", .{@errorName(err)});
2568 };
2569}
2570
2571fn writeSyntheticSectionWorker(self: *MachO, sect_id: u8, out: []u8) void {
2572 const tracy = trace(@src());
2573 defer tracy.end();
2574
2575 const diags = &self.base.comp.link_diags;
2576
2577 const Tag = enum {
2578 eh_frame,
2579 unwind_info,
2580 got,
2581 stubs,
2582 la_symbol_ptr,
2583 tlv_ptr,
2584 objc_stubs,
2585 };
2586
2587 const doWork = struct {
2588 fn doWork(macho_file: *MachO, tag: Tag, buffer: []u8) !void {
2589 var stream: Writer = .fixed(buffer);
2590 switch (tag) {
2591 .eh_frame => eh_frame.write(macho_file, buffer),
2592 .unwind_info => try macho_file.unwind_info.write(macho_file, buffer),
2593 .got => try macho_file.got.write(macho_file, &stream),
2594 .stubs => try macho_file.stubs.write(macho_file, &stream),
2595 .la_symbol_ptr => try macho_file.la_symbol_ptr.write(macho_file, &stream),
2596 .tlv_ptr => try macho_file.tlv_ptr.write(macho_file, &stream),
2597 .objc_stubs => try macho_file.objc_stubs.write(macho_file, &stream),
2598 }
2599 }
2600 }.doWork;
2601
2602 const header = self.sections.items(.header)[sect_id];
2603 const tag: Tag = tag: {
2604 if (self.eh_frame_sect_index != null and
2605 self.eh_frame_sect_index.? == sect_id) break :tag .eh_frame;
2606 if (self.unwind_info_sect_index != null and
2607 self.unwind_info_sect_index.? == sect_id) break :tag .unwind_info;
2608 if (self.got_sect_index != null and
2609 self.got_sect_index.? == sect_id) break :tag .got;
2610 if (self.stubs_sect_index != null and
2611 self.stubs_sect_index.? == sect_id) break :tag .stubs;
2612 if (self.la_symbol_ptr_sect_index != null and
2613 self.la_symbol_ptr_sect_index.? == sect_id) break :tag .la_symbol_ptr;
2614 if (self.tlv_ptr_sect_index != null and
2615 self.tlv_ptr_sect_index.? == sect_id) break :tag .tlv_ptr;
2616 if (self.objc_stubs_sect_index != null and
2617 self.objc_stubs_sect_index.? == sect_id) break :tag .objc_stubs;
2618 unreachable;
2619 };
2620 doWork(self, tag, out) catch |err| {
2621 diags.addError("could not write section '{s},{s}': {s}", .{
2622 header.segName(), header.sectName(), @errorName(err),
2623 });
2624 };
2625}
2626
2627fn updateLazyBindSizeWorker(self: *MachO) void {
2628 const tracy = trace(@src());
2629 defer tracy.end();
2630
2631 const diags = &self.base.comp.link_diags;
2632
2633 const doWork = struct {
2634 fn doWork(macho_file: *MachO) !void {
2635 try macho_file.lazy_bind_section.updateSize(macho_file);
2636 const sect_id = macho_file.stubs_helper_sect_index.?;
2637 const out = &macho_file.sections.items(.out)[sect_id];
2638 var stream: Writer = .fixed(out.items);
2639 try macho_file.stubs_helper.write(macho_file, &stream);
2640 }
2641 }.doWork;
2642 doWork(self) catch |err|
2643 diags.addError("could not calculate size of lazy binding section: {s}", .{@errorName(err)});
2644}
2645
2646pub fn updateLinkeditSizeWorker(self: *MachO, tag: enum {
2647 rebase,
2648 bind,
2649 weak_bind,
2650 export_trie,
2651 data_in_code,
2652}) void {
2653 const diags = &self.base.comp.link_diags;
2654 const res = switch (tag) {
2655 .rebase => self.rebase_section.updateSize(self),
2656 .bind => self.bind_section.updateSize(self),
2657 .weak_bind => self.weak_bind_section.updateSize(self),
2658 .export_trie => self.export_trie.updateSize(self),
2659 .data_in_code => self.data_in_code.updateSize(self),
2660 };
2661 res catch |err|
2662 diags.addError("could not calculate size of {s} section: {s}", .{ @tagName(tag), @errorName(err) });
2663}
2664
2665fn writeSectionsToFile(self: *MachO) !void {
2666 const tracy = trace(@src());
2667 defer tracy.end();
2668
2669 const slice = self.sections.slice();
2670 for (slice.items(.header), slice.items(.out)) |header, out| {
2671 try self.pwriteAll(out.items, header.offset);
2672 }
2673}
2674
2675fn writeLinkeditSectionsToFile(self: *MachO) !void {
2676 const tracy = trace(@src());
2677 defer tracy.end();
2678 try self.writeDyldInfo();
2679 try self.writeDataInCode();
2680 try self.writeSymtabToFile();
2681 try self.writeIndsymtab();
2682}
2683
2684fn writeDyldInfo(self: *MachO) !void {
2685 const tracy = trace(@src());
2686 defer tracy.end();
2687
2688 const gpa = self.base.comp.gpa;
2689 const base_off = self.getLinkeditSegment().fileoff;
2690 const cmd = self.dyld_info_cmd;
2691 var needed_size: u32 = 0;
2692 needed_size += cmd.rebase_size;
2693 needed_size += cmd.bind_size;
2694 needed_size += cmd.weak_bind_size;
2695 needed_size += cmd.lazy_bind_size;
2696 needed_size += cmd.export_size;
2697
2698 const buffer = try gpa.alloc(u8, needed_size);
2699 defer gpa.free(buffer);
2700 @memset(buffer, 0);
2701
2702 var writer: Writer = .fixed(buffer);
2703
2704 try self.rebase_section.write(&writer);
2705 writer.end = @intCast(cmd.bind_off - base_off);
2706 try self.bind_section.write(&writer);
2707 writer.end = @intCast(cmd.weak_bind_off - base_off);
2708 try self.weak_bind_section.write(&writer);
2709 writer.end = @intCast(cmd.lazy_bind_off - base_off);
2710 try self.lazy_bind_section.write(&writer);
2711 writer.end = @intCast(cmd.export_off - base_off);
2712 try self.export_trie.write(&writer);
2713 try self.pwriteAll(buffer, cmd.rebase_off);
2714}
2715
2716pub fn writeDataInCode(self: *MachO) !void {
2717 const tracy = trace(@src());
2718 defer tracy.end();
2719 const gpa = self.base.comp.gpa;
2720 const cmd = self.data_in_code_cmd;
2721 var buffer = try std.Io.Writer.Allocating.initCapacity(gpa, self.data_in_code.size());
2722 defer buffer.deinit();
2723 self.data_in_code.write(self, &buffer.writer) catch return error.OutOfMemory;
2724 try self.pwriteAll(buffer.written(), cmd.dataoff);
2725}
2726
2727fn writeIndsymtab(self: *MachO) !void {
2728 const tracy = trace(@src());
2729 defer tracy.end();
2730 const gpa = self.base.comp.gpa;
2731 const cmd = self.dysymtab_cmd;
2732 const needed_size = cmd.nindirectsyms * @sizeOf(u32);
2733 const buffer = try gpa.alloc(u8, needed_size);
2734 defer gpa.free(buffer);
2735 var writer: Writer = .fixed(buffer);
2736 try self.indsymtab.write(self, &writer);
2737 try self.pwriteAll(buffer, cmd.indirectsymoff);
2738}
2739
2740pub fn writeSymtabToFile(self: *MachO) !void {
2741 const tracy = trace(@src());
2742 defer tracy.end();
2743 const cmd = self.symtab_cmd;
2744 try self.pwriteAll(@ptrCast(self.symtab.items), cmd.symoff);
2745 try self.pwriteAll(self.strtab.items, cmd.stroff);
2746}
2747
2748fn writeUnwindInfo(self: *MachO) !void {
2749 const tracy = trace(@src());
2750 defer tracy.end();
2751
2752 const gpa = self.base.comp.gpa;
2753
2754 if (self.eh_frame_sect_index) |index| {
2755 const header = self.sections.items(.header)[index];
2756 const size = try self.cast(usize, header.size);
2757 const buffer = try gpa.alloc(u8, size);
2758 defer gpa.free(buffer);
2759 eh_frame.write(self, buffer);
2760 try self.pwriteAll(buffer, header.offset);
2761 }
2762
2763 if (self.unwind_info_sect_index) |index| {
2764 const header = self.sections.items(.header)[index];
2765 const size = try self.cast(usize, header.size);
2766 const buffer = try gpa.alloc(u8, size);
2767 defer gpa.free(buffer);
2768 try self.unwind_info.write(self, buffer);
2769 try self.pwriteAll(buffer, header.offset);
2770 }
2771}
2772
2773fn calcSymtabSize(self: *MachO) !void {
2774 const tracy = trace(@src());
2775 defer tracy.end();
2776
2777 const gpa = self.base.comp.gpa;
2778
2779 var files = std.array_list.Managed(File.Index).init(gpa);
2780 defer files.deinit();
2781 try files.ensureTotalCapacityPrecise(self.objects.items.len + self.dylibs.items.len + 2);
2782 if (self.zig_object) |index| files.appendAssumeCapacity(index);
2783 for (self.objects.items) |index| files.appendAssumeCapacity(index);
2784 for (self.dylibs.items) |index| files.appendAssumeCapacity(index);
2785 if (self.internal_object) |index| files.appendAssumeCapacity(index);
2786
2787 var nlocals: u32 = 0;
2788 var nstabs: u32 = 0;
2789 var nexports: u32 = 0;
2790 var nimports: u32 = 0;
2791 var strsize: u32 = 1;
2792
2793 if (self.requiresThunks()) for (self.thunks.items) |*th| {
2794 th.output_symtab_ctx.ilocal = nlocals;
2795 th.output_symtab_ctx.stroff = strsize;
2796 th.calcSymtabSize(self);
2797 nlocals += th.output_symtab_ctx.nlocals;
2798 strsize += th.output_symtab_ctx.strsize;
2799 };
2800
2801 for (files.items) |index| {
2802 const file = self.getFile(index).?;
2803 const ctx = switch (file) {
2804 inline else => |x| &x.output_symtab_ctx,
2805 };
2806 ctx.ilocal = nlocals;
2807 ctx.istab = nstabs;
2808 ctx.iexport = nexports;
2809 ctx.iimport = nimports;
2810 ctx.stroff = strsize;
2811 nlocals += ctx.nlocals;
2812 nstabs += ctx.nstabs;
2813 nexports += ctx.nexports;
2814 nimports += ctx.nimports;
2815 strsize += ctx.strsize;
2816 }
2817
2818 for (files.items) |index| {
2819 const file = self.getFile(index).?;
2820 const ctx = switch (file) {
2821 inline else => |x| &x.output_symtab_ctx,
2822 };
2823 ctx.istab += nlocals;
2824 ctx.iexport += nlocals + nstabs;
2825 ctx.iimport += nlocals + nstabs + nexports;
2826 }
2827
2828 try self.indsymtab.updateSize(self);
2829
2830 {
2831 const cmd = &self.symtab_cmd;
2832 cmd.nsyms = nlocals + nstabs + nexports + nimports;
2833 cmd.strsize = strsize;
2834 }
2835
2836 {
2837 const cmd = &self.dysymtab_cmd;
2838 cmd.ilocalsym = 0;
2839 cmd.nlocalsym = nlocals + nstabs;
2840 cmd.iextdefsym = nlocals + nstabs;
2841 cmd.nextdefsym = nexports;
2842 cmd.iundefsym = nlocals + nstabs + nexports;
2843 cmd.nundefsym = nimports;
2844 }
2845}
2846
2847fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } {
2848 const comp = self.base.comp;
2849 const gpa = comp.gpa;
2850 const needed_size = try load_commands.calcLoadCommandsSize(self, false);
2851 const buffer = try gpa.alloc(u8, needed_size);
2852 defer gpa.free(buffer);
2853
2854 var writer: Writer = .fixed(buffer);
2855
2856 var ncmds: usize = 0;
2857
2858 // Segment and section load commands
2859 {
2860 const slice = self.sections.slice();
2861 var sect_id: usize = 0;
2862 for (self.segments.items) |seg| {
2863 try writer.writeStruct(seg, .little);
2864 for (slice.items(.header)[sect_id..][0..seg.nsects]) |header| {
2865 try writer.writeStruct(header, .little);
2866 }
2867 sect_id += seg.nsects;
2868 }
2869 ncmds += self.segments.items.len;
2870 }
2871
2872 try writer.writeStruct(self.dyld_info_cmd, .little);
2873 ncmds += 1;
2874 try writer.writeStruct(self.function_starts_cmd, .little);
2875 ncmds += 1;
2876 try writer.writeStruct(self.data_in_code_cmd, .little);
2877 ncmds += 1;
2878 try writer.writeStruct(self.symtab_cmd, .little);
2879 ncmds += 1;
2880 try writer.writeStruct(self.dysymtab_cmd, .little);
2881 ncmds += 1;
2882 try load_commands.writeDylinkerLC(&writer);
2883 ncmds += 1;
2884
2885 if (self.getInternalObject()) |obj| {
2886 if (obj.getEntryRef(self)) |ref| {
2887 const sym = ref.getSymbol(self).?;
2888 const seg = self.getTextSegment();
2889 const entryoff: u32 = if (sym.getFile(self) == null)
2890 0
2891 else
2892 @as(u32, @intCast(sym.getAddress(.{ .stubs = true }, self) - seg.vmaddr));
2893 try writer.writeStruct(@as(macho.entry_point_command, .{
2894 .entryoff = entryoff,
2895 .stacksize = self.base.stack_size,
2896 }), .little);
2897 ncmds += 1;
2898 }
2899 }
2900
2901 if (self.base.isDynLib()) {
2902 try load_commands.writeDylibIdLC(self, &writer);
2903 ncmds += 1;
2904 }
2905
2906 if (self.needsEncryptionInfo()) {
2907 try load_commands.writeEncryptionInfoLC(self, &writer);
2908 ncmds += 1;
2909 }
2910
2911 for (self.rpath_list) |rpath| {
2912 try load_commands.writeRpathLC(rpath, &writer);
2913 ncmds += 1;
2914 }
2915 if (comp.config.any_sanitize_thread) {
2916 const path = try comp.tsan_lib.?.full_object_path.toString(gpa);
2917 defer gpa.free(path);
2918 const rpath = std.fs.path.dirname(path) orelse ".";
2919 try load_commands.writeRpathLC(rpath, &writer);
2920 ncmds += 1;
2921 }
2922
2923 try writer.writeStruct(@as(macho.source_version_command, .{ .version = 0 }), .little);
2924 ncmds += 1;
2925
2926 if (self.platform.isBuildVersionCompatible()) {
2927 try load_commands.writeBuildVersionLC(self.platform, self.sdk_version, &writer);
2928 ncmds += 1;
2929 } else {
2930 try load_commands.writeVersionMinLC(self.platform, self.sdk_version, &writer);
2931 ncmds += 1;
2932 }
2933
2934 const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + writer.end;
2935 try writer.writeStruct(self.uuid_cmd, .little);
2936 ncmds += 1;
2937
2938 for (self.dylibs.items) |index| {
2939 const dylib = self.getFile(index).?.dylib;
2940 assert(dylib.isAlive(self));
2941 const dylib_id = dylib.id.?;
2942 try load_commands.writeDylibLC(.{
2943 .cmd = if (dylib.weak)
2944 .LOAD_WEAK_DYLIB
2945 else if (dylib.reexport)
2946 .REEXPORT_DYLIB
2947 else
2948 .LOAD_DYLIB,
2949 .name = dylib_id.name,
2950 .timestamp = dylib_id.timestamp,
2951 .current_version = dylib_id.current_version,
2952 .compatibility_version = dylib_id.compatibility_version,
2953 }, &writer);
2954 ncmds += 1;
2955 }
2956
2957 if (self.requiresCodeSig()) {
2958 try writer.writeStruct(self.codesig_cmd, .little);
2959 ncmds += 1;
2960 }
2961
2962 assert(writer.end == needed_size);
2963
2964 try self.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
2965
2966 return .{ ncmds, buffer.len, uuid_cmd_offset };
2967}
2968
2969fn writeHeader(self: *MachO, ncmds: usize, sizeofcmds: usize) !void {
2970 var header: macho.mach_header_64 = .{};
2971 header.flags = macho.MH_DYLDLINK;
2972
2973 // Only set MH_NOUNDEFS if we're not allowing undefined symbols via dynamic lookup.
2974 // When dynamic_lookup is enabled, undefined symbols are resolved at runtime by dyld.
2975 if (self.undefined_treatment != .dynamic_lookup) {
2976 header.flags |= macho.MH_NOUNDEFS;
2977 }
2978
2979 // TODO: if (self.options.namespace == .two_level) {
2980 header.flags |= macho.MH_TWOLEVEL;
2981 // }
2982
2983 switch (self.getTarget().cpu.arch) {
2984 .aarch64 => {
2985 header.cputype = macho.CPU_TYPE_ARM64;
2986 header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
2987 },
2988 .x86_64 => {
2989 header.cputype = macho.CPU_TYPE_X86_64;
2990 header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
2991 },
2992 else => {},
2993 }
2994
2995 if (self.base.isDynLib()) {
2996 header.filetype = macho.MH_DYLIB;
2997 } else {
2998 header.filetype = macho.MH_EXECUTE;
2999 header.flags |= macho.MH_PIE;
3000 }
3001
3002 const has_reexports = for (self.dylibs.items) |index| {
3003 if (self.getFile(index).?.dylib.reexport) break true;
3004 } else false;
3005 if (!has_reexports) {
3006 header.flags |= macho.MH_NO_REEXPORTED_DYLIBS;
3007 }
3008
3009 if (self.has_tlv.load(.seq_cst)) {
3010 header.flags |= macho.MH_HAS_TLV_DESCRIPTORS;
3011 }
3012 if (self.binds_to_weak.load(.seq_cst)) {
3013 header.flags |= macho.MH_BINDS_TO_WEAK;
3014 }
3015 if (self.weak_defines.load(.seq_cst)) {
3016 header.flags |= macho.MH_WEAK_DEFINES;
3017 }
3018
3019 header.ncmds = @intCast(ncmds);
3020 header.sizeofcmds = @intCast(sizeofcmds);
3021
3022 log.debug("writing Mach-O header {}", .{header});
3023
3024 try self.pwriteAll(mem.asBytes(&header), 0);
3025}
3026
3027fn writeUuid(self: *MachO, uuid_cmd_offset: u64, has_codesig: bool) !void {
3028 const file_size = if (!has_codesig) blk: {
3029 const seg = self.getLinkeditSegment();
3030 break :blk seg.fileoff + seg.filesize;
3031 } else self.codesig_cmd.dataoff;
3032 try calcUuid(self.base.comp, self.base.file.?, file_size, &self.uuid_cmd.uuid);
3033 const offset = uuid_cmd_offset + @sizeOf(macho.load_command);
3034 try self.pwriteAll(&self.uuid_cmd.uuid, offset);
3035}
3036
3037pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
3038 const seg = self.getLinkeditSegment();
3039 // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
3040 // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
3041 const offset = mem.alignForward(u64, seg.fileoff + seg.filesize, 16);
3042 const needed_size = code_sig.estimateSize(offset);
3043 seg.filesize = offset + needed_size - seg.fileoff;
3044 seg.vmsize = mem.alignForward(u64, seg.filesize, self.getPageSize());
3045 log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
3046 // Pad out the space. We need to do this to calculate valid hashes for everything in the file
3047 // except for code signature data.
3048 try self.pwriteAll(&[_]u8{0}, offset + needed_size - 1);
3049
3050 self.codesig_cmd.dataoff = @as(u32, @intCast(offset));
3051 self.codesig_cmd.datasize = @as(u32, @intCast(needed_size));
3052}
3053
3054pub fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void {
3055 const seg = self.getTextSegment();
3056 const offset = self.codesig_cmd.dataoff;
3057 const gpa = self.base.comp.gpa;
3058
3059 var buffer: std.Io.Writer.Allocating = .init(gpa);
3060 defer buffer.deinit();
3061 // The writeAdhocSignature function internally changes code_sig.size()
3062 // during the execution.
3063 try buffer.ensureUnusedCapacity(code_sig.size());
3064
3065 code_sig.writeAdhocSignature(self, .{
3066 .file = self.base.file.?,
3067 .exec_seg_base = seg.fileoff,
3068 .exec_seg_limit = seg.filesize,
3069 .file_size = offset,
3070 .dylib = self.base.isDynLib(),
3071 }, &buffer.writer) catch |err| switch (err) {
3072 error.WriteFailed => return error.OutOfMemory,
3073 else => |e| return e,
3074 };
3075 assert(buffer.written().len == code_sig.size());
3076
3077 log.debug("writing code signature from 0x{x} to 0x{x}", .{
3078 offset,
3079 offset + buffer.written().len,
3080 });
3081
3082 try self.pwriteAll(buffer.written(), offset);
3083}
3084
3085pub fn updateFunc(
3086 self: *MachO,
3087 pt: Zcu.PerThread,
3088 func_index: InternPool.Index,
3089 mir: *const codegen.AnyMir,
3090) link.Error!void {
3091 return self.getZigObject().?.updateFunc(self, pt, func_index, mir);
3092}
3093
3094pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.Error!void {
3095 return self.getZigObject().?.updateNav(self, pt, nav);
3096}
3097
3098pub fn updateLineNumber(self: *MachO, pt: Zcu.PerThread, inst: InternPool.TrackedInst.Index, line: u32) link.Error!void {
3099 return self.getZigObject().?.updateLineNumber(pt, inst, line);
3100}
3101
3102pub fn updateExports(
3103 self: *MachO,
3104 pt: Zcu.PerThread,
3105 export_indices: []const Zcu.Export.Index,
3106) link.Error!void {
3107 return self.getZigObject().?.updateExports(self, pt, export_indices);
3108}
3109
3110pub fn freeNav(self: *MachO, nav: InternPool.Nav.Index) void {
3111 return self.getZigObject().?.freeNav(nav);
3112}
3113
3114pub fn getNavVAddr(self: *MachO, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: link.File.RelocInfo) !u64 {
3115 return self.getZigObject().?.getNavVAddr(self, pt, nav_index, reloc_info);
3116}
3117
3118pub fn lowerUav(
3119 self: *MachO,
3120 pt: Zcu.PerThread,
3121 uav: InternPool.Index,
3122 explicit_alignment: InternPool.Alignment,
3123) !link.File.SymbolId {
3124 return self.getZigObject().?.lowerUav(self, pt, uav, explicit_alignment);
3125}
3126
3127pub fn getUavVAddr(self: *MachO, uav: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 {
3128 return self.getZigObject().?.getUavVAddr(self, uav, reloc_info);
3129}
3130
3131pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 {
3132 return self.getZigObject().?.getGlobalSymbol(self, name, lib_name);
3133}
3134
3135pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
3136 return actual_size +| (actual_size / ideal_factor);
3137}
3138
3139fn detectAllocCollision(self: *MachO, start: u64, size: u64) !?u64 {
3140 // Conservatively commit one page size as reserved space for the headers as we
3141 // expect it to grow and everything else be moved in flush anyhow.
3142 const header_size = self.getPageSize();
3143 if (start < header_size)
3144 return header_size;
3145
3146 var at_end = true;
3147 const end = start + padToIdeal(size);
3148
3149 for (self.sections.items(.header)) |header| {
3150 if (header.isZerofill()) continue;
3151 const increased_size = padToIdeal(header.size);
3152 const test_end = header.offset +| increased_size;
3153 if (start < test_end) {
3154 if (end > header.offset) return test_end;
3155 if (test_end < std.math.maxInt(u64)) at_end = false;
3156 }
3157 }
3158
3159 for (self.segments.items) |seg| {
3160 const increased_size = padToIdeal(seg.filesize);
3161 const test_end = seg.fileoff +| increased_size;
3162 if (start < test_end) {
3163 if (end > seg.fileoff) return test_end;
3164 if (test_end < std.math.maxInt(u64)) at_end = false;
3165 }
3166 }
3167
3168 const comp = self.base.comp;
3169 const io = comp.io;
3170 if (at_end) try self.base.file.?.setLength(io, end);
3171 return null;
3172}
3173
3174fn detectAllocCollisionVirtual(self: *MachO, start: u64, size: u64) ?u64 {
3175 // Conservatively commit one page size as reserved space for the headers as we
3176 // expect it to grow and everything else be moved in flush anyhow.
3177 const header_size = self.getPageSize();
3178 if (start < header_size)
3179 return header_size;
3180
3181 const end = start + padToIdeal(size);
3182
3183 for (self.sections.items(.header)) |header| {
3184 const increased_size = padToIdeal(header.size);
3185 const test_end = header.addr +| increased_size;
3186 if (end > header.addr and start < test_end) {
3187 return test_end;
3188 }
3189 }
3190
3191 for (self.segments.items) |seg| {
3192 const increased_size = padToIdeal(seg.vmsize);
3193 const test_end = seg.vmaddr +| increased_size;
3194 if (end > seg.vmaddr and start < test_end) {
3195 return test_end;
3196 }
3197 }
3198
3199 return null;
3200}
3201
3202pub fn allocatedSize(self: *MachO, start: u64) u64 {
3203 if (start == 0) return 0;
3204
3205 var min_pos: u64 = std.math.maxInt(u64);
3206
3207 for (self.sections.items(.header)) |header| {
3208 if (header.offset <= start) continue;
3209 if (header.offset < min_pos) min_pos = header.offset;
3210 }
3211
3212 for (self.segments.items) |seg| {
3213 if (seg.fileoff <= start) continue;
3214 if (seg.fileoff < min_pos) min_pos = seg.fileoff;
3215 }
3216
3217 return min_pos - start;
3218}
3219
3220pub fn allocatedSizeVirtual(self: *MachO, start: u64) u64 {
3221 if (start == 0) return 0;
3222
3223 var min_pos: u64 = std.math.maxInt(u64);
3224
3225 for (self.sections.items(.header)) |header| {
3226 if (header.addr <= start) continue;
3227 if (header.addr < min_pos) min_pos = header.addr;
3228 }
3229
3230 for (self.segments.items) |seg| {
3231 if (seg.vmaddr <= start) continue;
3232 if (seg.vmaddr < min_pos) min_pos = seg.vmaddr;
3233 }
3234
3235 return min_pos - start;
3236}
3237
3238pub fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) !u64 {
3239 var start: u64 = 0;
3240 while (try self.detectAllocCollision(start, object_size)) |item_end| {
3241 start = mem.alignForward(u64, item_end, min_alignment);
3242 }
3243 return start;
3244}
3245
3246pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32) u64 {
3247 var start: u64 = 0;
3248 while (self.detectAllocCollisionVirtual(start, object_size)) |item_end| {
3249 start = mem.alignForward(u64, item_end, min_alignment);
3250 }
3251 return start;
3252}
3253
3254pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
3255 return self.base.copyRangeAll(old_offset, new_offset, size);
3256}
3257
3258/// Like copyRangeAll but also ensures the source region is zeroed out after copy.
3259/// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader.
3260fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
3261 const comp = self.base.comp;
3262 const io = comp.io;
3263 const file = self.base.file.?;
3264 var write_buffer: [2048]u8 = undefined;
3265 var file_reader = file.reader(io, &.{});
3266 file_reader.pos = old_offset;
3267 var file_writer = file.writer(io, &write_buffer);
3268 file_writer.pos = new_offset;
3269 const size_u = math.cast(usize, size) orelse return error.Overflow;
3270 const n = file_writer.interface.sendFileAll(&file_reader, .limited(size_u)) catch |err| switch (err) {
3271 error.ReadFailed => switch (file_reader.err.?) {
3272 error.ConnectionResetByPeer => return error.Unexpected, // not a socket
3273 error.SocketUnconnected => return error.Unexpected, // not a socket
3274 else => |e| return e,
3275 },
3276 error.WriteFailed => return file_writer.err.?,
3277 };
3278 assert(n == size_u);
3279 file_writer.seekTo(old_offset) catch |err| switch (err) {
3280 error.WriteFailed => return file_writer.err.?,
3281 else => |e| return e,
3282 };
3283 file_writer.interface.splatByteAll(0, size_u) catch |err| switch (err) {
3284 error.WriteFailed => return file_writer.err.?,
3285 };
3286 file_writer.interface.flush() catch |err| switch (err) {
3287 error.WriteFailed => return file_writer.err.?,
3288 };
3289}
3290
3291const InitMetadataOptions = struct {
3292 emit: Path,
3293 zo: *ZigObject,
3294 symbol_count_hint: u64,
3295 program_code_size_hint: u64,
3296};
3297
3298pub fn closeDebugInfo(self: *MachO) bool {
3299 const comp = self.base.comp;
3300 const io = comp.io;
3301 const d_sym = &(self.d_sym orelse return false);
3302 d_sym.file.?.close(io);
3303 d_sym.file = null;
3304 return true;
3305}
3306
3307pub fn reopenDebugInfo(self: *MachO) !void {
3308 assert(self.d_sym.?.file == null);
3309
3310 assert(!self.base.comp.config.use_llvm);
3311 assert(self.base.comp.config.debug_format == .dwarf);
3312
3313 const comp = self.base.comp;
3314 const io = comp.io;
3315 const gpa = comp.gpa;
3316 const sep = fs.path.sep_str;
3317 const d_sym_path = try std.fmt.allocPrint(
3318 gpa,
3319 "{s}.dSYM" ++ sep ++ "Contents" ++ sep ++ "Resources" ++ sep ++ "DWARF",
3320 .{self.base.emit.sub_path},
3321 );
3322 defer gpa.free(d_sym_path);
3323
3324 var d_sym_bundle = try self.base.emit.root_dir.handle.createDirPathOpen(io, d_sym_path, .{});
3325 defer d_sym_bundle.close(io);
3326
3327 self.d_sym.?.file = try d_sym_bundle.createFile(io, fs.path.basename(self.base.emit.sub_path), .{
3328 .truncate = false,
3329 .read = true,
3330 });
3331}
3332
3333// TODO: move to ZigObject
3334fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
3335 const comp = self.base.comp;
3336 const gpa = comp.gpa;
3337 const io = comp.io;
3338
3339 if (!self.base.isRelocatable()) {
3340 const base_vmaddr = blk: {
3341 const pagezero_size = self.pagezero_size orelse default_pagezero_size;
3342 break :blk mem.alignBackward(u64, pagezero_size, self.getPageSize());
3343 };
3344
3345 {
3346 const filesize = options.program_code_size_hint;
3347 const off = try self.findFreeSpace(filesize, self.getPageSize());
3348 self.zig_text_seg_index = try self.addSegment("__TEXT_ZIG", .{
3349 .fileoff = off,
3350 .filesize = filesize,
3351 .vmaddr = base_vmaddr + 0x4000000,
3352 .vmsize = filesize,
3353 .prot = .{ .READ = true, .EXEC = true },
3354 });
3355 }
3356
3357 {
3358 const filesize: u64 = 1024;
3359 const off = try self.findFreeSpace(filesize, self.getPageSize());
3360 self.zig_const_seg_index = try self.addSegment("__CONST_ZIG", .{
3361 .fileoff = off,
3362 .filesize = filesize,
3363 .vmaddr = base_vmaddr + 0xc000000,
3364 .vmsize = filesize,
3365 .prot = .{ .READ = true, .WRITE = true },
3366 });
3367 }
3368
3369 {
3370 const filesize: u64 = 1024;
3371 const off = try self.findFreeSpace(filesize, self.getPageSize());
3372 self.zig_data_seg_index = try self.addSegment("__DATA_ZIG", .{
3373 .fileoff = off,
3374 .filesize = filesize,
3375 .vmaddr = base_vmaddr + 0x10000000,
3376 .vmsize = filesize,
3377 .prot = .{ .READ = true, .WRITE = true },
3378 });
3379 }
3380
3381 {
3382 const memsize: u64 = 1024;
3383 self.zig_bss_seg_index = try self.addSegment("__BSS_ZIG", .{
3384 .vmaddr = base_vmaddr + 0x14000000,
3385 .vmsize = memsize,
3386 .prot = .{ .READ = true, .WRITE = true },
3387 });
3388 }
3389
3390 if (options.zo.dwarf) |*dwarf| {
3391 // Create dSYM bundle.
3392 log.debug("creating {s}.dSYM bundle", .{options.emit.sub_path});
3393 self.d_sym = .{
3394 .io = io,
3395 .allocator = gpa,
3396 .file = null,
3397 };
3398 try self.reopenDebugInfo();
3399 try self.d_sym.?.initMetadata(self);
3400 try dwarf.initMetadata();
3401 }
3402 }
3403
3404 const appendSect = struct {
3405 fn appendSect(macho_file: *MachO, sect_id: u8, seg_id: u8) void {
3406 const sect = &macho_file.sections.items(.header)[sect_id];
3407 const seg = macho_file.segments.items[seg_id];
3408 sect.addr = seg.vmaddr;
3409 sect.offset = @intCast(seg.fileoff);
3410 sect.size = seg.vmsize;
3411 macho_file.sections.items(.segment_id)[sect_id] = seg_id;
3412 }
3413 }.appendSect;
3414
3415 const allocSect = struct {
3416 fn allocSect(macho_file: *MachO, sect_id: u8, size: u64) !void {
3417 const sect = &macho_file.sections.items(.header)[sect_id];
3418 const alignment = try macho_file.alignPow(sect.@"align");
3419 if (!sect.isZerofill()) {
3420 sect.offset = try macho_file.cast(u32, try macho_file.findFreeSpace(size, alignment));
3421 }
3422 sect.addr = macho_file.findFreeSpaceVirtual(size, alignment);
3423 sect.size = size;
3424 }
3425 }.allocSect;
3426
3427 {
3428 self.zig_text_sect_index = try self.addSection("__TEXT_ZIG", "__text_zig", .{
3429 .alignment = switch (self.getTarget().cpu.arch) {
3430 .aarch64 => 2,
3431 .x86_64 => 0,
3432 else => unreachable,
3433 },
3434 .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
3435 });
3436 if (self.base.isRelocatable()) {
3437 try allocSect(self, self.zig_text_sect_index.?, options.program_code_size_hint);
3438 } else {
3439 appendSect(self, self.zig_text_sect_index.?, self.zig_text_seg_index.?);
3440 }
3441 }
3442
3443 {
3444 self.zig_const_sect_index = try self.addSection("__CONST_ZIG", "__const_zig", .{});
3445 if (self.base.isRelocatable()) {
3446 try allocSect(self, self.zig_const_sect_index.?, 1024);
3447 } else {
3448 appendSect(self, self.zig_const_sect_index.?, self.zig_const_seg_index.?);
3449 }
3450 }
3451
3452 {
3453 self.zig_data_sect_index = try self.addSection("__DATA_ZIG", "__data_zig", .{});
3454 if (self.base.isRelocatable()) {
3455 try allocSect(self, self.zig_data_sect_index.?, 1024);
3456 } else {
3457 appendSect(self, self.zig_data_sect_index.?, self.zig_data_seg_index.?);
3458 }
3459 }
3460
3461 {
3462 self.zig_bss_sect_index = try self.addSection("__BSS_ZIG", "__bss_zig", .{
3463 .flags = macho.S_ZEROFILL,
3464 });
3465 if (self.base.isRelocatable()) {
3466 try allocSect(self, self.zig_bss_sect_index.?, 1024);
3467 } else {
3468 appendSect(self, self.zig_bss_sect_index.?, self.zig_bss_seg_index.?);
3469 }
3470 }
3471
3472 if (self.base.isRelocatable()) if (options.zo.dwarf) |*dwarf| {
3473 self.debug_str_sect_index = try self.addSection("__DWARF", "__debug_str", .{
3474 .flags = macho.S_ATTR_DEBUG,
3475 });
3476 self.debug_info_sect_index = try self.addSection("__DWARF", "__debug_info", .{
3477 .flags = macho.S_ATTR_DEBUG,
3478 });
3479 self.debug_abbrev_sect_index = try self.addSection("__DWARF", "__debug_abbrev", .{
3480 .flags = macho.S_ATTR_DEBUG,
3481 });
3482 self.debug_aranges_sect_index = try self.addSection("__DWARF", "__debug_aranges", .{
3483 .alignment = 4,
3484 .flags = macho.S_ATTR_DEBUG,
3485 });
3486 self.debug_line_sect_index = try self.addSection("__DWARF", "__debug_line", .{
3487 .flags = macho.S_ATTR_DEBUG,
3488 });
3489 self.debug_line_str_sect_index = try self.addSection("__DWARF", "__debug_line_str", .{
3490 .flags = macho.S_ATTR_DEBUG,
3491 });
3492 self.debug_loclists_sect_index = try self.addSection("__DWARF", "__debug_loclists", .{
3493 .flags = macho.S_ATTR_DEBUG,
3494 });
3495 self.debug_rnglists_sect_index = try self.addSection("__DWARF", "__debug_rnglists", .{
3496 .flags = macho.S_ATTR_DEBUG,
3497 });
3498 try dwarf.initMetadata();
3499 };
3500}
3501
3502pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void {
3503 if (self.base.isRelocatable()) {
3504 try self.growSectionRelocatable(sect_index, needed_size);
3505 } else {
3506 try self.growSectionNonRelocatable(sect_index, needed_size);
3507 }
3508}
3509
3510fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void {
3511 const diags = &self.base.comp.link_diags;
3512 const sect = &self.sections.items(.header)[sect_index];
3513
3514 const seg_id = self.sections.items(.segment_id)[sect_index];
3515 const seg = &self.segments.items[seg_id];
3516
3517 const comp = self.base.comp;
3518 const io = comp.io;
3519
3520 if (!sect.isZerofill()) {
3521 const allocated_size = self.allocatedSize(sect.offset);
3522 if (needed_size > allocated_size) {
3523 const existing_size = sect.size;
3524 sect.size = 0;
3525
3526 // Must move the entire section.
3527 const alignment = self.getPageSize();
3528 const new_offset = try self.findFreeSpace(needed_size, alignment);
3529
3530 log.debug("moving '{s},{s}' from 0x{x} to 0x{x}", .{
3531 sect.segName(),
3532 sect.sectName(),
3533 sect.offset,
3534 new_offset,
3535 });
3536
3537 try self.copyRangeAllZeroOut(sect.offset, new_offset, existing_size);
3538
3539 sect.offset = @intCast(new_offset);
3540 } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {
3541 try self.base.file.?.setLength(io, sect.offset + needed_size);
3542 }
3543 seg.filesize = needed_size;
3544 }
3545 sect.size = needed_size;
3546 seg.fileoff = sect.offset;
3547
3548 const mem_capacity = self.allocatedSizeVirtual(seg.vmaddr);
3549 if (needed_size > mem_capacity) {
3550 var err = try diags.addErrorWithNotes(2);
3551 try err.addMsg("fatal linker error: cannot expand segment seg({d})({s}) in virtual memory", .{
3552 seg_id,
3553 seg.segName(),
3554 });
3555 err.addNote("TODO: emit relocations to memory locations in self-hosted backends", .{});
3556 err.addNote("as a workaround, try increasing pre-allocated virtual memory of each segment", .{});
3557 }
3558
3559 seg.vmsize = needed_size;
3560}
3561
3562fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void {
3563 const comp = self.base.comp;
3564 const io = comp.io;
3565 const sect = &self.sections.items(.header)[sect_index];
3566
3567 if (!sect.isZerofill()) {
3568 const allocated_size = self.allocatedSize(sect.offset);
3569 if (needed_size > allocated_size) {
3570 const existing_size = sect.size;
3571 sect.size = 0;
3572
3573 // Must move the entire section.
3574 const alignment = try math.powi(u32, 2, sect.@"align");
3575 const new_offset = try self.findFreeSpace(needed_size, alignment);
3576 const new_addr = self.findFreeSpaceVirtual(needed_size, alignment);
3577
3578 log.debug("new '{s},{s}' file offset 0x{x} to 0x{x} (0x{x} - 0x{x})", .{
3579 sect.segName(),
3580 sect.sectName(),
3581 new_offset,
3582 new_offset + existing_size,
3583 new_addr,
3584 new_addr + existing_size,
3585 });
3586
3587 try self.copyRangeAll(sect.offset, new_offset, existing_size);
3588
3589 sect.offset = @intCast(new_offset);
3590 sect.addr = new_addr;
3591 } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {
3592 try self.base.file.?.setLength(io, sect.offset + needed_size);
3593 }
3594 }
3595 sect.size = needed_size;
3596}
3597
3598pub fn markDirty(self: *MachO, sect_index: u8) void {
3599 if (self.getZigObject()) |zo| {
3600 if (self.debug_info_sect_index.? == sect_index) {
3601 zo.debug_info_header_dirty = true;
3602 } else if (self.debug_line_sect_index.? == sect_index) {
3603 zo.debug_line_header_dirty = true;
3604 } else if (self.debug_abbrev_sect_index.? == sect_index) {
3605 zo.debug_abbrev_dirty = true;
3606 } else if (self.debug_str_sect_index.? == sect_index) {
3607 zo.debug_strtab_dirty = true;
3608 } else if (self.debug_aranges_sect_index.? == sect_index) {
3609 zo.debug_aranges_dirty = true;
3610 }
3611 }
3612}
3613
3614pub fn getTarget(self: *const MachO) *const std.Target {
3615 return &self.base.comp.root_mod.resolved_target.result;
3616}
3617
3618/// XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
3619/// Any change to the binary will effectively invalidate the kernel's cache
3620/// resulting in a SIGKILL on each subsequent run. Since when doing incremental
3621/// linking we're modifying a binary in-place, this will end up with the kernel
3622/// killing it on every subsequent run. To circumvent it, we will copy the file
3623/// into a new inode, remove the original file, and rename the copy to match
3624/// the original file. This is super messy, but there doesn't seem any other
3625/// way to please the XNU.
3626pub fn invalidateKernelCache(io: Io, dir: Io.Dir, sub_path: []const u8) !void {
3627 const tracy = trace(@src());
3628 defer tracy.end();
3629 if (builtin.target.os.tag.isDarwin() and builtin.target.cpu.arch == .aarch64) {
3630 try dir.copyFile(sub_path, dir, sub_path, io, .{});
3631 }
3632}
3633
3634inline fn conformUuid(out: *[Md5.digest_length]u8) void {
3635 // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats
3636 out[6] = (out[6] & 0x0F) | (3 << 4);
3637 out[8] = (out[8] & 0x3F) | 0x80;
3638}
3639
3640pub inline fn getPageSize(self: MachO) u16 {
3641 return switch (self.getTarget().cpu.arch) {
3642 .aarch64 => 0x4000,
3643 .x86_64 => 0x1000,
3644 else => unreachable,
3645 };
3646}
3647
3648pub fn requiresCodeSig(self: MachO) bool {
3649 if (self.entitlements) |_| return true;
3650 // TODO: enable once we support this linker option
3651 // if (self.options.adhoc_codesign) |cs| return cs;
3652 const target = self.getTarget();
3653 return switch (target.cpu.arch) {
3654 .aarch64 => switch (target.os.tag) {
3655 .driverkit, .maccatalyst, .macos => true,
3656 .ios, .tvos, .visionos, .watchos => target.abi == .simulator,
3657 else => false,
3658 },
3659 .x86_64 => false,
3660 else => unreachable,
3661 };
3662}
3663
3664inline fn requiresThunks(self: MachO) bool {
3665 return self.getTarget().cpu.arch == .aarch64;
3666}
3667
3668pub fn isZigSegment(self: MachO, seg_id: u8) bool {
3669 inline for (&[_]?u8{
3670 self.zig_text_seg_index,
3671 self.zig_const_seg_index,
3672 self.zig_data_seg_index,
3673 self.zig_bss_seg_index,
3674 }) |maybe_index| {
3675 if (maybe_index) |index| {
3676 if (index == seg_id) return true;
3677 }
3678 }
3679 return false;
3680}
3681
3682pub fn isZigSection(self: MachO, sect_id: u8) bool {
3683 inline for (&[_]?u8{
3684 self.zig_text_sect_index,
3685 self.zig_const_sect_index,
3686 self.zig_data_sect_index,
3687 self.zig_bss_sect_index,
3688 }) |maybe_index| {
3689 if (maybe_index) |index| {
3690 if (index == sect_id) return true;
3691 }
3692 }
3693 return false;
3694}
3695
3696pub fn isDebugSection(self: MachO, sect_id: u8) bool {
3697 inline for (&[_]?u8{
3698 self.debug_info_sect_index,
3699 self.debug_abbrev_sect_index,
3700 self.debug_str_sect_index,
3701 self.debug_aranges_sect_index,
3702 self.debug_line_sect_index,
3703 }) |maybe_index| {
3704 if (maybe_index) |index| {
3705 if (index == sect_id) return true;
3706 }
3707 }
3708 return false;
3709}
3710
3711pub fn addSegment(self: *MachO, name: []const u8, opts: struct {
3712 vmaddr: u64 = 0,
3713 vmsize: u64 = 0,
3714 fileoff: u64 = 0,
3715 filesize: u64 = 0,
3716 prot: macho.vm_prot_t = .{},
3717}) error{OutOfMemory}!u8 {
3718 const gpa = self.base.comp.gpa;
3719 const index = @as(u8, @intCast(self.segments.items.len));
3720 try self.segments.append(gpa, .{
3721 .segname = makeStaticString(name),
3722 .vmaddr = opts.vmaddr,
3723 .vmsize = opts.vmsize,
3724 .fileoff = opts.fileoff,
3725 .filesize = opts.filesize,
3726 .maxprot = opts.prot,
3727 .initprot = opts.prot,
3728 .nsects = 0,
3729 .cmdsize = @sizeOf(macho.segment_command_64),
3730 });
3731 return index;
3732}
3733
3734const AddSectionOpts = struct {
3735 alignment: u32 = 0,
3736 flags: u32 = macho.S_REGULAR,
3737 reserved1: u32 = 0,
3738 reserved2: u32 = 0,
3739};
3740
3741pub fn addSection(
3742 self: *MachO,
3743 segname: []const u8,
3744 sectname: []const u8,
3745 opts: AddSectionOpts,
3746) !u8 {
3747 const gpa = self.base.comp.gpa;
3748 const index = @as(u8, @intCast(try self.sections.addOne(gpa)));
3749 self.sections.set(index, .{
3750 .segment_id = 0, // Segments will be created automatically later down the pipeline.
3751 .header = .{
3752 .sectname = makeStaticString(sectname),
3753 .segname = makeStaticString(segname),
3754 .@"align" = opts.alignment,
3755 .flags = opts.flags,
3756 .reserved1 = opts.reserved1,
3757 .reserved2 = opts.reserved2,
3758 },
3759 });
3760 return index;
3761}
3762
3763pub fn makeStaticString(bytes: []const u8) [16]u8 {
3764 var buf: [16]u8 = @splat(0);
3765 @memcpy(buf[0..bytes.len], bytes);
3766 return buf;
3767}
3768
3769pub fn getSegmentByName(self: MachO, segname: []const u8) ?u8 {
3770 for (self.segments.items, 0..) |seg, i| {
3771 if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i));
3772 } else return null;
3773}
3774
3775pub fn getSectionByName(self: MachO, segname: []const u8, sectname: []const u8) ?u8 {
3776 for (self.sections.items(.header), 0..) |header, i| {
3777 if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname))
3778 return @as(u8, @intCast(i));
3779 } else return null;
3780}
3781
3782pub fn getTlsAddress(self: MachO) u64 {
3783 for (self.sections.items(.header)) |header| switch (header.type()) {
3784 macho.S_THREAD_LOCAL_REGULAR,
3785 macho.S_THREAD_LOCAL_ZEROFILL,
3786 => return header.addr,
3787 else => {},
3788 };
3789 return 0;
3790}
3791
3792pub inline fn getTextSegment(self: *MachO) *macho.segment_command_64 {
3793 return &self.segments.items[self.text_seg_index.?];
3794}
3795
3796pub inline fn getLinkeditSegment(self: *MachO) *macho.segment_command_64 {
3797 return &self.segments.items[self.linkedit_seg_index.?];
3798}
3799
3800pub fn getFile(self: *MachO, index: File.Index) ?File {
3801 const tag = self.files.items(.tags)[index];
3802 return switch (tag) {
3803 .null => null,
3804 .zig_object => .{ .zig_object = &self.files.items(.data)[index].zig_object },
3805 .internal => .{ .internal = &self.files.items(.data)[index].internal },
3806 .object => .{ .object = &self.files.items(.data)[index].object },
3807 .dylib => .{ .dylib = &self.files.items(.data)[index].dylib },
3808 };
3809}
3810
3811pub fn getZigObject(self: *MachO) ?*ZigObject {
3812 const index = self.zig_object orelse return null;
3813 return self.getFile(index).?.zig_object;
3814}
3815
3816pub fn getInternalObject(self: *MachO) ?*InternalObject {
3817 const index = self.internal_object orelse return null;
3818 return self.getFile(index).?.internal;
3819}
3820
3821pub fn addFileHandle(self: *MachO, file: Io.File) !File.HandleIndex {
3822 const gpa = self.base.comp.gpa;
3823 const index: File.HandleIndex = @intCast(self.file_handles.items.len);
3824 const fh = try self.file_handles.addOne(gpa);
3825 fh.* = file;
3826 return index;
3827}
3828
3829pub fn getFileHandle(self: MachO, index: File.HandleIndex) File.Handle {
3830 assert(index < self.file_handles.items.len);
3831 return self.file_handles.items[index];
3832}
3833
3834pub fn addThunk(self: *MachO) !Thunk.Index {
3835 const index = @as(Thunk.Index, @intCast(self.thunks.items.len));
3836 const thunk = try self.thunks.addOne(self.base.comp.gpa);
3837 thunk.* = .{};
3838 return index;
3839}
3840
3841pub fn getThunk(self: *MachO, index: Thunk.Index) *Thunk {
3842 assert(index < self.thunks.items.len);
3843 return &self.thunks.items[index];
3844}
3845
3846pub fn eatPrefix(path: []const u8, prefix: []const u8) ?[]const u8 {
3847 if (mem.startsWith(u8, path, prefix)) return path[prefix.len..];
3848 return null;
3849}
3850
3851pub fn reportParseError2(
3852 self: *MachO,
3853 file_index: File.Index,
3854 comptime format: []const u8,
3855 args: anytype,
3856) error{OutOfMemory}!void {
3857 const diags = &self.base.comp.link_diags;
3858 var err = try diags.addErrorWithNotes(1);
3859 try err.addMsg(format, args);
3860 err.addNote("while parsing {f}", .{self.getFile(file_index).?.fmtPath()});
3861}
3862
3863fn reportMissingDependencyError(
3864 self: *MachO,
3865 parent: File.Index,
3866 path: []const u8,
3867 checked_paths: []const []const u8,
3868 comptime format: []const u8,
3869 args: anytype,
3870) error{OutOfMemory}!void {
3871 const diags = &self.base.comp.link_diags;
3872 var err = try diags.addErrorWithNotes(2 + checked_paths.len);
3873 try err.addMsg(format, args);
3874 err.addNote("while resolving {s}", .{path});
3875 err.addNote("a dependency of {f}", .{self.getFile(parent).?.fmtPath()});
3876 for (checked_paths) |p| {
3877 err.addNote("tried {s}", .{p});
3878 }
3879}
3880
3881fn reportDependencyError(
3882 self: *MachO,
3883 parent: File.Index,
3884 path: []const u8,
3885 comptime format: []const u8,
3886 args: anytype,
3887) error{OutOfMemory}!void {
3888 const diags = &self.base.comp.link_diags;
3889 var err = try diags.addErrorWithNotes(2);
3890 try err.addMsg(format, args);
3891 err.addNote("while parsing {s}", .{path});
3892 err.addNote("a dependency of {f}", .{self.getFile(parent).?.fmtPath()});
3893}
3894
3895fn reportDuplicates(self: *MachO) error{ HasDuplicates, OutOfMemory }!void {
3896 const tracy = trace(@src());
3897 defer tracy.end();
3898
3899 if (self.dupes.keys().len == 0) return; // Nothing to do
3900
3901 const gpa = self.base.comp.gpa;
3902 const diags = &self.base.comp.link_diags;
3903 const max_notes = 3;
3904
3905 // We will sort by name, and then by file to ensure deterministic output.
3906 var keys = try std.array_list.Managed(SymbolResolver.Index).initCapacity(gpa, self.dupes.keys().len);
3907 defer keys.deinit();
3908 keys.appendSliceAssumeCapacity(self.dupes.keys());
3909 self.sortGlobalSymbolsByName(keys.items);
3910
3911 for (self.dupes.values()) |*refs| {
3912 mem.sort(File.Index, refs.items, {}, std.sort.asc(File.Index));
3913 }
3914
3915 for (keys.items) |key| {
3916 const sym = self.resolver.keys.items[key - 1];
3917 const notes = self.dupes.get(key).?;
3918 const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes);
3919
3920 var err = try diags.addErrorWithNotes(nnotes + 1);
3921 try err.addMsg("duplicate symbol definition: {s}", .{sym.getName(self)});
3922 err.addNote("defined by {f}", .{sym.getFile(self).?.fmtPath()});
3923
3924 var inote: usize = 0;
3925 while (inote < @min(notes.items.len, max_notes)) : (inote += 1) {
3926 const file = self.getFile(notes.items[inote]).?;
3927 err.addNote("defined by {f}", .{file.fmtPath()});
3928 }
3929
3930 if (notes.items.len > max_notes) {
3931 const remaining = notes.items.len - max_notes;
3932 err.addNote("defined {d} more times", .{remaining});
3933 }
3934 }
3935 return error.HasDuplicates;
3936}
3937
3938pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols {
3939 if (self.d_sym) |*ds| return ds;
3940 return null;
3941}
3942
3943pub fn ptraceAttach(self: *MachO, pid: std.posix.pid_t) !void {
3944 if (!is_hot_update_compatible) return;
3945
3946 const mach_task = try machTaskForPid(pid);
3947 log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task });
3948 self.hot_state.mach_task = mach_task;
3949
3950 // TODO start exception handler in another thread
3951
3952 // TODO enable ones we register for exceptions
3953 // try std.os.ptrace(std.os.darwin.PT.ATTACHEXC, pid, 0, 0);
3954}
3955
3956pub fn ptraceDetach(self: *MachO, pid: std.posix.pid_t) !void {
3957 if (!is_hot_update_compatible) return;
3958
3959 _ = pid;
3960
3961 // TODO stop exception handler
3962
3963 // TODO see comment in ptraceAttach
3964 // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0);
3965
3966 self.hot_state.mach_task = null;
3967}
3968
3969pub fn dumpState(self: *MachO) std.fmt.Alt(*MachO, fmtDumpState) {
3970 return .{ .data = self };
3971}
3972
3973fn fmtDumpState(self: *MachO, w: *Writer) Writer.Error!void {
3974 if (self.getZigObject()) |zo| {
3975 try w.print("zig_object({d}) : {s}\n", .{ zo.index, zo.basename });
3976 try w.print("{f}{f}\n", .{
3977 zo.fmtAtoms(self),
3978 zo.fmtSymtab(self),
3979 });
3980 }
3981 for (self.objects.items) |index| {
3982 const object = self.getFile(index).?.object;
3983 try w.print("object({d}) : {f} : has_debug({})", .{
3984 index,
3985 object.fmtPath(),
3986 object.hasDebugInfo(),
3987 });
3988 if (!object.alive) try w.writeAll(" : ([*])");
3989 try w.writeByte('\n');
3990 try w.print("{f}{f}{f}{f}{f}\n", .{
3991 object.fmtAtoms(self),
3992 object.fmtCies(self),
3993 object.fmtFdes(self),
3994 object.fmtUnwindRecords(self),
3995 object.fmtSymtab(self),
3996 });
3997 }
3998 for (self.dylibs.items) |index| {
3999 const dylib = self.getFile(index).?.dylib;
4000 try w.print("dylib({d}) : {f} : needed({}) : weak({})", .{
4001 index,
4002 @as(Path, dylib.path),
4003 dylib.needed,
4004 dylib.weak,
4005 });
4006 if (!dylib.isAlive(self)) try w.writeAll(" : ([*])");
4007 try w.writeByte('\n');
4008 try w.print("{f}\n", .{dylib.fmtSymtab(self)});
4009 }
4010 if (self.getInternalObject()) |internal| {
4011 try w.print("internal({d}) : internal\n", .{internal.index});
4012 try w.print("{f}{f}\n", .{ internal.fmtAtoms(self), internal.fmtSymtab(self) });
4013 }
4014 try w.writeAll("thunks\n");
4015 for (self.thunks.items, 0..) |thunk, index| {
4016 try w.print("thunk({d}) : {f}\n", .{ index, thunk.fmt(self) });
4017 }
4018 try w.print("stubs\n{f}\n", .{self.stubs.fmt(self)});
4019 try w.print("objc_stubs\n{f}\n", .{self.objc_stubs.fmt(self)});
4020 try w.print("got\n{f}\n", .{self.got.fmt(self)});
4021 try w.print("tlv_ptr\n{f}\n", .{self.tlv_ptr.fmt(self)});
4022 try w.writeByte('\n');
4023 try w.print("sections\n{f}\n", .{self.fmtSections()});
4024 try w.print("segments\n{f}\n", .{self.fmtSegments()});
4025}
4026
4027fn fmtSections(self: *MachO) std.fmt.Alt(*MachO, formatSections) {
4028 return .{ .data = self };
4029}
4030
4031fn formatSections(self: *MachO, w: *Writer) Writer.Error!void {
4032 const slice = self.sections.slice();
4033 for (slice.items(.header), slice.items(.segment_id), 0..) |header, seg_id, i| {
4034 try w.print(
4035 "sect({d}) : seg({d}) : {s},{s} : @{x} ({x}) : align({x}) : size({x}) : relocs({x};{d})\n",
4036 .{
4037 i, seg_id, header.segName(), header.sectName(), header.addr, header.offset,
4038 header.@"align", header.size, header.reloff, header.nreloc,
4039 },
4040 );
4041 }
4042}
4043
4044fn fmtSegments(self: *MachO) std.fmt.Alt(*MachO, formatSegments) {
4045 return .{ .data = self };
4046}
4047
4048fn formatSegments(self: *MachO, w: *Writer) Writer.Error!void {
4049 for (self.segments.items, 0..) |seg, i| {
4050 try w.print("seg({d}) : {s} : @{x}-{x} ({x}-{x})\n", .{
4051 i, seg.segName(), seg.vmaddr, seg.vmaddr + seg.vmsize,
4052 seg.fileoff, seg.fileoff + seg.filesize,
4053 });
4054 }
4055}
4056
4057pub fn fmtSectType(tt: u8) std.fmt.Alt(u8, formatSectType) {
4058 return .{ .data = tt };
4059}
4060
4061fn formatSectType(tt: u8, w: *Writer) Writer.Error!void {
4062 const name = switch (tt) {
4063 macho.S_REGULAR => "REGULAR",
4064 macho.S_ZEROFILL => "ZEROFILL",
4065 macho.S_CSTRING_LITERALS => "CSTRING_LITERALS",
4066 macho.S_4BYTE_LITERALS => "4BYTE_LITERALS",
4067 macho.S_8BYTE_LITERALS => "8BYTE_LITERALS",
4068 macho.S_16BYTE_LITERALS => "16BYTE_LITERALS",
4069 macho.S_LITERAL_POINTERS => "LITERAL_POINTERS",
4070 macho.S_NON_LAZY_SYMBOL_POINTERS => "NON_LAZY_SYMBOL_POINTERS",
4071 macho.S_LAZY_SYMBOL_POINTERS => "LAZY_SYMBOL_POINTERS",
4072 macho.S_SYMBOL_STUBS => "SYMBOL_STUBS",
4073 macho.S_MOD_INIT_FUNC_POINTERS => "MOD_INIT_FUNC_POINTERS",
4074 macho.S_MOD_TERM_FUNC_POINTERS => "MOD_TERM_FUNC_POINTERS",
4075 macho.S_COALESCED => "COALESCED",
4076 macho.S_GB_ZEROFILL => "GB_ZEROFILL",
4077 macho.S_INTERPOSING => "INTERPOSING",
4078 macho.S_DTRACE_DOF => "DTRACE_DOF",
4079 macho.S_THREAD_LOCAL_REGULAR => "THREAD_LOCAL_REGULAR",
4080 macho.S_THREAD_LOCAL_ZEROFILL => "THREAD_LOCAL_ZEROFILL",
4081 macho.S_THREAD_LOCAL_VARIABLES => "THREAD_LOCAL_VARIABLES",
4082 macho.S_THREAD_LOCAL_VARIABLE_POINTERS => "THREAD_LOCAL_VARIABLE_POINTERS",
4083 macho.S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => "THREAD_LOCAL_INIT_FUNCTION_POINTERS",
4084 macho.S_INIT_FUNC_OFFSETS => "INIT_FUNC_OFFSETS",
4085 else => |x| return w.print("UNKNOWN({x})", .{x}),
4086 };
4087 try w.print("{s}", .{name});
4088}
4089
4090const is_hot_update_compatible = switch (builtin.target.os.tag) {
4091 .maccatalyst, .macos => true,
4092 else => false,
4093};
4094
4095const default_entry_symbol_name = "_main";
4096
4097const Section = struct {
4098 header: macho.section_64,
4099 segment_id: u8,
4100 atoms: std.ArrayList(Ref) = .empty,
4101 free_list: std.ArrayList(Atom.Index) = .empty,
4102 last_atom_index: Atom.Index = 0,
4103 thunks: std.ArrayList(Thunk.Index) = .empty,
4104 out: std.ArrayList(u8) = .empty,
4105 relocs: std.ArrayList(macho.relocation_info) = .empty,
4106};
4107
4108pub const LiteralPool = struct {
4109 table: std.array_hash_map.Auto(void, void) = .empty,
4110 keys: std.ArrayList(Key) = .empty,
4111 values: std.ArrayList(MachO.Ref) = .empty,
4112 data: std.ArrayList(u8) = .empty,
4113
4114 pub fn deinit(lp: *LiteralPool, allocator: Allocator) void {
4115 lp.table.deinit(allocator);
4116 lp.keys.deinit(allocator);
4117 lp.values.deinit(allocator);
4118 lp.data.deinit(allocator);
4119 }
4120
4121 const InsertResult = struct {
4122 found_existing: bool,
4123 index: Index,
4124 ref: *MachO.Ref,
4125 };
4126
4127 pub fn getSymbolRef(lp: LiteralPool, index: Index) MachO.Ref {
4128 assert(index < lp.values.items.len);
4129 return lp.values.items[index];
4130 }
4131
4132 pub fn getSymbol(lp: LiteralPool, index: Index, macho_file: *MachO) *Symbol {
4133 return lp.getSymbolRef(index).getSymbol(macho_file).?;
4134 }
4135
4136 pub fn insert(lp: *LiteralPool, allocator: Allocator, @"type": u8, string: []const u8) !InsertResult {
4137 const size: u32 = @intCast(string.len);
4138 try lp.data.ensureUnusedCapacity(allocator, size);
4139 const off: u32 = @intCast(lp.data.items.len);
4140 lp.data.appendSliceAssumeCapacity(string);
4141 const adapter = Adapter{ .lp = lp };
4142 const key = Key{ .off = off, .size = size, .seed = @"type" };
4143 const gop = try lp.table.getOrPutAdapted(allocator, key, adapter);
4144 if (!gop.found_existing) {
4145 try lp.keys.append(allocator, key);
4146 _ = try lp.values.addOne(allocator);
4147 }
4148 return .{
4149 .found_existing = gop.found_existing,
4150 .index = @intCast(gop.index),
4151 .ref = &lp.values.items[gop.index],
4152 };
4153 }
4154
4155 const Key = struct {
4156 off: u32,
4157 size: u32,
4158 seed: u8,
4159
4160 fn getData(key: Key, lp: *const LiteralPool) []const u8 {
4161 return lp.data.items[key.off..][0..key.size];
4162 }
4163
4164 fn eql(key: Key, other: Key, lp: *const LiteralPool) bool {
4165 const key_data = key.getData(lp);
4166 const other_data = other.getData(lp);
4167 return mem.eql(u8, key_data, other_data);
4168 }
4169
4170 fn hash(key: Key, lp: *const LiteralPool) u32 {
4171 const data = key.getData(lp);
4172 return @truncate(Hash.hash(key.seed, data));
4173 }
4174 };
4175
4176 const Adapter = struct {
4177 lp: *const LiteralPool,
4178
4179 pub fn eql(ctx: @This(), key: Key, b_void: void, b_map_index: usize) bool {
4180 _ = b_void;
4181 const other = ctx.lp.keys.items[b_map_index];
4182 return key.eql(other, ctx.lp);
4183 }
4184
4185 pub fn hash(ctx: @This(), key: Key) u32 {
4186 return key.hash(ctx.lp);
4187 }
4188 };
4189
4190 pub const Index = u32;
4191};
4192
4193const HotUpdateState = struct {
4194 mach_task: ?MachTask = null,
4195};
4196
4197pub const SymtabCtx = struct {
4198 ilocal: u32 = 0,
4199 istab: u32 = 0,
4200 iexport: u32 = 0,
4201 iimport: u32 = 0,
4202 nlocals: u32 = 0,
4203 nstabs: u32 = 0,
4204 nexports: u32 = 0,
4205 nimports: u32 = 0,
4206 stroff: u32 = 0,
4207 strsize: u32 = 0,
4208};
4209
4210pub const null_sym = macho.nlist_64{
4211 .n_strx = 0,
4212 .n_type = @bitCast(@as(u8, 0)),
4213 .n_sect = 0,
4214 .n_desc = @bitCast(@as(u16, 0)),
4215 .n_value = 0,
4216};
4217
4218pub const Platform = struct {
4219 os_tag: std.Target.Os.Tag,
4220 abi: std.Target.Abi,
4221 version: std.SemanticVersion,
4222
4223 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
4224 /// the extracted minimum platform version.
4225 pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
4226 switch (lc.hdr.cmd) {
4227 .BUILD_VERSION => {
4228 const cmd = lc.cast(macho.build_version_command).?;
4229 return .{
4230 .os_tag = switch (cmd.platform) {
4231 .DRIVERKIT => .driverkit,
4232 .IOS, .IOSSIMULATOR => .ios,
4233 .MACCATALYST => .maccatalyst,
4234 .MACOS => .macos,
4235 .TVOS, .TVOSSIMULATOR => .tvos,
4236 .VISIONOS, .VISIONOSSIMULATOR => .visionos,
4237 .WATCHOS, .WATCHOSSIMULATOR => .watchos,
4238 else => @panic("TODO"),
4239 },
4240 .abi = switch (cmd.platform) {
4241 .IOSSIMULATOR,
4242 .TVOSSIMULATOR,
4243 .VISIONOSSIMULATOR,
4244 .WATCHOSSIMULATOR,
4245 => .simulator,
4246 else => .none,
4247 },
4248 .version = appleVersionToSemanticVersion(cmd.minos),
4249 };
4250 },
4251 .VERSION_MIN_IPHONEOS,
4252 .VERSION_MIN_MACOSX,
4253 .VERSION_MIN_TVOS,
4254 .VERSION_MIN_WATCHOS,
4255 => {
4256 // We can't distinguish Mac Catalyst here, but this is legacy stuff anyway.
4257 const cmd = lc.cast(macho.version_min_command).?;
4258 return .{
4259 .os_tag = switch (lc.hdr.cmd) {
4260 .VERSION_MIN_IPHONEOS => .ios,
4261 .VERSION_MIN_MACOSX => .macos,
4262 .VERSION_MIN_TVOS => .tvos,
4263 .VERSION_MIN_WATCHOS => .watchos,
4264 else => unreachable,
4265 },
4266 .abi = .none,
4267 .version = appleVersionToSemanticVersion(cmd.version),
4268 };
4269 },
4270 else => unreachable,
4271 }
4272 }
4273
4274 pub fn fromTarget(target: *const std.Target) Platform {
4275 return .{
4276 .os_tag = target.os.tag,
4277 .abi = target.abi,
4278 .version = target.os.version_range.semver.min,
4279 };
4280 }
4281
4282 pub fn toAppleVersion(plat: Platform) u32 {
4283 return semanticVersionToAppleVersion(plat.version);
4284 }
4285
4286 pub fn toApplePlatform(plat: Platform) macho.PLATFORM {
4287 return switch (plat.os_tag) {
4288 .driverkit => .DRIVERKIT,
4289 .ios => if (plat.abi == .simulator) .IOSSIMULATOR else .IOS,
4290 .maccatalyst => .MACCATALYST,
4291 .macos => .MACOS,
4292 .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS,
4293 .visionos => if (plat.abi == .simulator) .VISIONOSSIMULATOR else .VISIONOS,
4294 .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS,
4295 else => unreachable,
4296 };
4297 }
4298
4299 pub fn isBuildVersionCompatible(plat: Platform) bool {
4300 inline for (supported_platforms) |sup_plat| {
4301 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
4302 return sup_plat[2] <= plat.toAppleVersion();
4303 }
4304 }
4305 return false;
4306 }
4307
4308 pub fn isVersionMinCompatible(plat: Platform) bool {
4309 inline for (supported_platforms) |sup_plat| {
4310 if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) {
4311 return sup_plat[3] <= plat.toAppleVersion();
4312 }
4313 }
4314 return false;
4315 }
4316
4317 pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Alt(Format, Format.target) {
4318 return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } };
4319 }
4320
4321 const Format = struct {
4322 platform: Platform,
4323 cpu_arch: std.Target.Cpu.Arch,
4324
4325 pub fn target(f: Format, w: *Writer) Writer.Error!void {
4326 try w.print("{s}-{s}", .{ @tagName(f.cpu_arch), @tagName(f.platform.os_tag) });
4327 if (f.platform.abi != .none) {
4328 try w.print("-{s}", .{@tagName(f.platform.abi)});
4329 }
4330 }
4331 };
4332
4333 /// Caller owns the memory.
4334 pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 {
4335 var buffer = std.array_list.Managed(u8).init(gpa);
4336 defer buffer.deinit();
4337 try buffer.writer().print("{f}", .{plat.fmtTarget(cpu_arch)});
4338 return buffer.toOwnedSlice();
4339 }
4340
4341 pub fn eqlTarget(plat: Platform, other: Platform) bool {
4342 return plat.os_tag == other.os_tag and plat.abi == other.abi;
4343 }
4344};
4345
4346const SupportedPlatforms = struct {
4347 std.Target.Os.Tag,
4348 std.Target.Abi,
4349 u32, // Min platform version for which to emit LC_BUILD_VERSION
4350 u32, // Min supported platform version
4351};
4352
4353// Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52
4354// zig fmt: off
4355const supported_platforms = [_]SupportedPlatforms{
4356 .{ .driverkit, .none, 0x130000, 0x130000 },
4357 .{ .ios, .none, 0x0C0000, 0x070000 },
4358 .{ .ios, .simulator, 0x0D0000, 0x080000 },
4359 .{ .maccatalyst, .none, 0x0D0000, 0x0D0000 },
4360 .{ .macos, .none, 0x0A0E00, 0x0A0800 },
4361 .{ .tvos, .none, 0x0C0000, 0x070000 },
4362 .{ .tvos, .simulator, 0x0D0000, 0x080000 },
4363 .{ .visionos, .none, 0x010000, 0x010000 },
4364 .{ .visionos, .simulator, 0x010000, 0x010000 },
4365 .{ .watchos, .none, 0x050000, 0x020000 },
4366 .{ .watchos, .simulator, 0x060000, 0x020000 },
4367};
4368// zig fmt: on
4369
4370pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 {
4371 const major = version.major;
4372 const minor = version.minor;
4373 const patch = version.patch;
4374 return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch));
4375}
4376
4377pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion {
4378 return .{
4379 .major = @as(u16, @truncate(version >> 16)),
4380 .minor = @as(u8, @truncate(version >> 8)),
4381 .patch = @as(u8, @truncate(version)),
4382 };
4383}
4384
4385fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersion {
4386 const gpa = comp.gpa;
4387
4388 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
4389 defer arena_allocator.deinit();
4390 const arena = arena_allocator.allocator();
4391
4392 const io = comp.io;
4393
4394 const sdk_dir = switch (sdk_layout) {
4395 .sdk => comp.sysroot.?,
4396 .vendored => fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "libc", "darwin" }) catch return null,
4397 };
4398 if (readSdkVersionFromSettings(arena, io, sdk_dir)) |ver| {
4399 return parseSdkVersion(ver);
4400 } else |_| {
4401 // Read from settings should always succeed when vendored.
4402 // TODO: convert to fatal linker error
4403 if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version");
4404 }
4405
4406 // infer from pathname
4407 const stem = fs.path.stem(sdk_dir);
4408 const start = for (stem, 0..) |c, i| {
4409 if (std.ascii.isDigit(c)) break i;
4410 } else stem.len;
4411 const end = for (stem[start..], start..) |c, i| {
4412 if (std.ascii.isDigit(c) or c == '.') continue;
4413 break i;
4414 } else stem.len;
4415 return parseSdkVersion(stem[start..end]);
4416}
4417
4418// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout.
4419// Use property `MinimalDisplayName` to determine version.
4420// The file/property is also available with vendored libc.
4421fn readSdkVersionFromSettings(arena: Allocator, io: Io, dir: []const u8) ![]const u8 {
4422 const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" });
4423 const contents = try Io.Dir.cwd().readFileAlloc(io, sdk_path, arena, .limited(std.math.maxInt(u16)));
4424 const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{});
4425 if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string;
4426 return error.SdkVersionFailure;
4427}
4428
4429// Versions reported by Apple aren't exactly semantically valid as they usually omit
4430// the patch component, so we parse SDK value by hand.
4431fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion {
4432 var parsed: std.SemanticVersion = .{
4433 .major = 0,
4434 .minor = 0,
4435 .patch = 0,
4436 };
4437
4438 const parseNext = struct {
4439 fn parseNext(it: anytype) ?u16 {
4440 const nn = it.next() orelse return null;
4441 return std.fmt.parseInt(u16, nn, 10) catch null;
4442 }
4443 }.parseNext;
4444
4445 var it = std.mem.splitAny(u8, raw, ".");
4446 parsed.major = parseNext(&it) orelse return null;
4447 parsed.minor = parseNext(&it) orelse return null;
4448 parsed.patch = parseNext(&it) orelse 0;
4449 return parsed;
4450}
4451
4452/// When allocating, the ideal_capacity is calculated by
4453/// actual_capacity + (actual_capacity / ideal_factor)
4454const ideal_factor = 3;
4455
4456/// In order for a slice of bytes to be considered eligible to keep metadata pointing at
4457/// it as a possible place to put new symbols, it must have enough room for this many bytes
4458/// (plus extra for reserved capacity).
4459const minimum_text_block_size = 64;
4460pub const min_text_capacity = padToIdeal(minimum_text_block_size);
4461
4462/// Default virtual memory offset corresponds to the size of __PAGEZERO segment and
4463/// start of __TEXT segment.
4464pub const default_pagezero_size: u64 = 0x100000000;
4465
4466/// We commit 0x1000 = 4096 bytes of space to the header and
4467/// the table of load commands. This should be plenty for any
4468/// potential future extensions.
4469pub const default_headerpad_size: u32 = 0x1000;
4470
4471const SystemLib = struct {
4472 path: Path,
4473 needed: bool = false,
4474 weak: bool = false,
4475 hidden: bool = false,
4476 reexport: bool = false,
4477 must_link: bool = false,
4478
4479 fn fromLinkInput(link_input: link.Input) SystemLib {
4480 return switch (link_input) {
4481 .dso_exact => unreachable,
4482 .res => unreachable,
4483 .object, .archive => |obj| .{
4484 .path = obj.path,
4485 .must_link = obj.must_link,
4486 .hidden = obj.hidden,
4487 },
4488 .dso => |dso| .{
4489 .path = dso.path,
4490 .needed = dso.needed,
4491 .weak = dso.weak,
4492 .reexport = dso.reexport,
4493 },
4494 };
4495 }
4496};
4497
4498pub const SdkLayout = std.zig.LibCDirs.DarwinSdkLayout;
4499
4500const UndefinedTreatment = enum {
4501 @"error",
4502 warn,
4503 suppress,
4504 dynamic_lookup,
4505};
4506
4507/// A reference to atom or symbol in an input file.
4508/// If file == 0, symbol is an undefined global.
4509pub const Ref = struct {
4510 index: u32,
4511 file: File.Index,
4512
4513 pub fn eql(ref: Ref, other: Ref) bool {
4514 return ref.index == other.index and ref.file == other.file;
4515 }
4516
4517 pub fn lessThan(ref: Ref, other: Ref) bool {
4518 if (ref.file == other.file) {
4519 return ref.index < other.index;
4520 }
4521 return ref.file < other.file;
4522 }
4523
4524 pub fn getFile(ref: Ref, macho_file: *MachO) ?File {
4525 return macho_file.getFile(ref.file);
4526 }
4527
4528 pub fn getAtom(ref: Ref, macho_file: *MachO) ?*Atom {
4529 const file = ref.getFile(macho_file) orelse return null;
4530 return file.getAtom(ref.index);
4531 }
4532
4533 pub fn getSymbol(ref: Ref, macho_file: *MachO) ?*Symbol {
4534 const file = ref.getFile(macho_file) orelse return null;
4535 return switch (file) {
4536 inline else => |x| &x.symbols.items[ref.index],
4537 };
4538 }
4539
4540 pub fn format(ref: Ref, bw: *Writer) Writer.Error!void {
4541 try bw.print("%{d} in file({d})", .{ ref.index, ref.file });
4542 }
4543};
4544
4545pub const SymbolResolver = struct {
4546 keys: std.ArrayList(Key) = .empty,
4547 values: std.ArrayList(Ref) = .empty,
4548 table: std.array_hash_map.Auto(void, void) = .empty,
4549
4550 const Result = struct {
4551 found_existing: bool,
4552 index: Index,
4553 ref: *Ref,
4554 };
4555
4556 pub fn deinit(resolver: *SymbolResolver, allocator: Allocator) void {
4557 resolver.keys.deinit(allocator);
4558 resolver.values.deinit(allocator);
4559 resolver.table.deinit(allocator);
4560 }
4561
4562 pub fn getOrPut(
4563 resolver: *SymbolResolver,
4564 allocator: Allocator,
4565 ref: Ref,
4566 macho_file: *MachO,
4567 ) !Result {
4568 const adapter = Adapter{ .keys = resolver.keys.items, .macho_file = macho_file };
4569 const key = Key{ .index = ref.index, .file = ref.file };
4570 const gop = try resolver.table.getOrPutAdapted(allocator, key, adapter);
4571 if (!gop.found_existing) {
4572 try resolver.keys.append(allocator, key);
4573 _ = try resolver.values.addOne(allocator);
4574 }
4575 return .{
4576 .found_existing = gop.found_existing,
4577 .index = @intCast(gop.index + 1),
4578 .ref = &resolver.values.items[gop.index],
4579 };
4580 }
4581
4582 pub fn get(resolver: SymbolResolver, index: Index) ?Ref {
4583 if (index == 0) return null;
4584 return resolver.values.items[index - 1];
4585 }
4586
4587 pub fn reset(resolver: *SymbolResolver) void {
4588 resolver.keys.clearRetainingCapacity();
4589 resolver.values.clearRetainingCapacity();
4590 resolver.table.clearRetainingCapacity();
4591 }
4592
4593 const Key = struct {
4594 index: Symbol.Index,
4595 file: File.Index,
4596
4597 fn getName(key: Key, macho_file: *MachO) [:0]const u8 {
4598 const ref = Ref{ .index = key.index, .file = key.file };
4599 return ref.getSymbol(macho_file).?.getName(macho_file);
4600 }
4601
4602 pub fn getFile(key: Key, macho_file: *MachO) ?File {
4603 const ref = Ref{ .index = key.index, .file = key.file };
4604 return ref.getFile(macho_file);
4605 }
4606
4607 fn eql(key: Key, other: Key, macho_file: *MachO) bool {
4608 const key_name = key.getName(macho_file);
4609 const other_name = other.getName(macho_file);
4610 return mem.eql(u8, key_name, other_name);
4611 }
4612
4613 fn hash(key: Key, macho_file: *MachO) u32 {
4614 const name = key.getName(macho_file);
4615 return @truncate(Hash.hash(0, name));
4616 }
4617 };
4618
4619 const Adapter = struct {
4620 keys: []const Key,
4621 macho_file: *MachO,
4622
4623 pub fn eql(ctx: @This(), key: Key, b_void: void, b_map_index: usize) bool {
4624 _ = b_void;
4625 const other = ctx.keys[b_map_index];
4626 return key.eql(other, ctx.macho_file);
4627 }
4628
4629 pub fn hash(ctx: @This(), key: Key) u32 {
4630 return key.hash(ctx.macho_file);
4631 }
4632 };
4633
4634 pub const Index = u32;
4635};
4636
4637pub const String = struct {
4638 pos: u32 = 0,
4639 len: u32 = 0,
4640};
4641
4642pub const UndefRefs = union(enum) {
4643 force_undefined,
4644 entry,
4645 dyld_stub_binder,
4646 objc_msgsend,
4647 refs: std.ArrayList(Ref),
4648
4649 pub fn deinit(self: *UndefRefs, allocator: Allocator) void {
4650 switch (self.*) {
4651 .refs => |*refs| refs.deinit(allocator),
4652 else => {},
4653 }
4654 }
4655};
4656
4657pub const MachError = error{
4658 /// Not enough permissions held to perform the requested kernel
4659 /// call.
4660 PermissionDenied,
4661} || std.posix.UnexpectedError;
4662
4663pub const MachTask = extern struct {
4664 port: std.c.mach_port_name_t,
4665
4666 pub fn isValid(self: MachTask) bool {
4667 return self.port != std.c.TASK_NULL;
4668 }
4669
4670 pub fn pidForTask(self: MachTask) MachError!std.c.pid_t {
4671 var pid: std.c.pid_t = undefined;
4672 switch (getKernError(std.c.pid_for_task(self.port, &pid))) {
4673 .SUCCESS => return pid,
4674 .FAILURE => return error.PermissionDenied,
4675 else => |err| return unexpectedKernError(err),
4676 }
4677 }
4678
4679 pub fn allocatePort(self: MachTask, right: std.c.MACH_PORT_RIGHT) MachError!MachTask {
4680 var out_port: std.c.mach_port_name_t = undefined;
4681 switch (getKernError(std.c.mach_port_allocate(
4682 self.port,
4683 @backingInt(right),
4684 &out_port,
4685 ))) {
4686 .SUCCESS => return .{ .port = out_port },
4687 .FAILURE => return error.PermissionDenied,
4688 else => |err| return unexpectedKernError(err),
4689 }
4690 }
4691
4692 pub fn deallocatePort(self: MachTask, port: MachTask) void {
4693 _ = getKernError(std.c.mach_port_deallocate(self.port, port.port));
4694 }
4695
4696 pub fn insertRight(self: MachTask, port: MachTask, msg: std.c.MACH_MSG_TYPE) !void {
4697 switch (getKernError(std.c.mach_port_insert_right(
4698 self.port,
4699 port.port,
4700 port.port,
4701 @backingInt(msg),
4702 ))) {
4703 .SUCCESS => return,
4704 .FAILURE => return error.PermissionDenied,
4705 else => |err| return unexpectedKernError(err),
4706 }
4707 }
4708
4709 pub const PortInfo = struct {
4710 mask: std.c.exception_mask_t,
4711 masks: [std.c.EXC.TYPES_COUNT]std.c.exception_mask_t,
4712 ports: [std.c.EXC.TYPES_COUNT]std.c.mach_port_t,
4713 behaviors: [std.c.EXC.TYPES_COUNT]std.c.exception_behavior_t,
4714 flavors: [std.c.EXC.TYPES_COUNT]std.c.thread_state_flavor_t,
4715 count: std.c.mach_msg_type_number_t,
4716 };
4717
4718 pub fn getExceptionPorts(self: MachTask, mask: std.c.exception_mask_t) !PortInfo {
4719 var info: PortInfo = .{
4720 .mask = mask,
4721 .masks = undefined,
4722 .ports = undefined,
4723 .behaviors = undefined,
4724 .flavors = undefined,
4725 .count = 0,
4726 };
4727 info.count = info.ports.len / @sizeOf(std.c.mach_port_t);
4728
4729 switch (getKernError(std.c.task_get_exception_ports(
4730 self.port,
4731 info.mask,
4732 &info.masks,
4733 &info.count,
4734 &info.ports,
4735 &info.behaviors,
4736 &info.flavors,
4737 ))) {
4738 .SUCCESS => return info,
4739 .FAILURE => return error.PermissionDenied,
4740 else => |err| return unexpectedKernError(err),
4741 }
4742 }
4743
4744 pub fn setExceptionPorts(
4745 self: MachTask,
4746 mask: std.c.exception_mask_t,
4747 new_port: MachTask,
4748 behavior: std.c.exception_behavior_t,
4749 new_flavor: std.c.thread_state_flavor_t,
4750 ) !void {
4751 switch (getKernError(std.c.task_set_exception_ports(
4752 self.port,
4753 mask,
4754 new_port.port,
4755 behavior,
4756 new_flavor,
4757 ))) {
4758 .SUCCESS => return,
4759 .FAILURE => return error.PermissionDenied,
4760 else => |err| return unexpectedKernError(err),
4761 }
4762 }
4763
4764 pub const RegionInfo = struct {
4765 pub const Tag = enum {
4766 basic,
4767 extended,
4768 top,
4769 };
4770
4771 base_addr: u64,
4772 tag: Tag,
4773 info: union {
4774 basic: std.c.vm_region_basic_info_64,
4775 extended: std.c.vm_region_extended_info,
4776 top: std.c.vm_region_top_info,
4777 },
4778 };
4779
4780 pub fn getRegionInfo(
4781 task: MachTask,
4782 address: u64,
4783 len: usize,
4784 tag: RegionInfo.Tag,
4785 ) MachError!RegionInfo {
4786 var info: RegionInfo = .{
4787 .base_addr = address,
4788 .tag = tag,
4789 .info = undefined,
4790 };
4791 switch (tag) {
4792 .basic => info.info = .{ .basic = undefined },
4793 .extended => info.info = .{ .extended = undefined },
4794 .top => info.info = .{ .top = undefined },
4795 }
4796 var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len;
4797 var objname: std.c.mach_port_t = undefined;
4798 var count: std.c.mach_msg_type_number_t = switch (tag) {
4799 .basic => std.c.VM.REGION.BASIC_INFO_COUNT,
4800 .extended => std.c.VM.REGION.EXTENDED_INFO_COUNT,
4801 .top => std.c.VM.REGION.TOP_INFO_COUNT,
4802 };
4803 switch (getKernError(std.c.mach_vm_region(
4804 task.port,
4805 &info.base_addr,
4806 &base_len,
4807 switch (tag) {
4808 .basic => std.c.VM.REGION.BASIC_INFO_64,
4809 .extended => std.c.VM.REGION.EXTENDED_INFO,
4810 .top => std.c.VM.REGION.TOP_INFO,
4811 },
4812 switch (tag) {
4813 .basic => @as(std.c.vm_region_info_t, @ptrCast(&info.info.basic)),
4814 .extended => @as(std.c.vm_region_info_t, @ptrCast(&info.info.extended)),
4815 .top => @as(std.c.vm_region_info_t, @ptrCast(&info.info.top)),
4816 },
4817 &count,
4818 &objname,
4819 ))) {
4820 .SUCCESS => return info,
4821 .FAILURE => return error.PermissionDenied,
4822 else => |err| return unexpectedKernError(err),
4823 }
4824 }
4825
4826 pub const RegionSubmapInfo = struct {
4827 pub const Tag = enum {
4828 short,
4829 full,
4830 };
4831
4832 tag: Tag,
4833 base_addr: u64,
4834 info: union {
4835 short: std.c.vm_region_submap_short_info_64,
4836 full: std.c.vm_region_submap_info_64,
4837 },
4838 };
4839
4840 pub fn getRegionSubmapInfo(
4841 task: MachTask,
4842 address: u64,
4843 len: usize,
4844 nesting_depth: u32,
4845 tag: RegionSubmapInfo.Tag,
4846 ) MachError!RegionSubmapInfo {
4847 var info: RegionSubmapInfo = .{
4848 .base_addr = address,
4849 .tag = tag,
4850 .info = undefined,
4851 };
4852 switch (tag) {
4853 .short => info.info = .{ .short = undefined },
4854 .full => info.info = .{ .full = undefined },
4855 }
4856 var nesting = nesting_depth;
4857 var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len;
4858 var count: std.c.mach_msg_type_number_t = switch (tag) {
4859 .short => std.c.VM.REGION.SUBMAP_SHORT_INFO_COUNT_64,
4860 .full => std.c.VM.REGION.SUBMAP_INFO_COUNT_64,
4861 };
4862 switch (getKernError(std.c.mach_vm_region_recurse(
4863 task.port,
4864 &info.base_addr,
4865 &base_len,
4866 &nesting,
4867 switch (tag) {
4868 .short => @as(std.c.vm_region_recurse_info_t, @ptrCast(&info.info.short)),
4869 .full => @as(std.c.vm_region_recurse_info_t, @ptrCast(&info.info.full)),
4870 },
4871 &count,
4872 ))) {
4873 .SUCCESS => return info,
4874 .FAILURE => return error.PermissionDenied,
4875 else => |err| return unexpectedKernError(err),
4876 }
4877 }
4878
4879 pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!std.c.vm_prot_t {
4880 const info = try task.getRegionSubmapInfo(address, len, 0, .short);
4881 return info.info.short.protection;
4882 }
4883
4884 pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void {
4885 return task.setProtectionImpl(address, len, true, prot);
4886 }
4887
4888 pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void {
4889 return task.setProtectionImpl(address, len, false, prot);
4890 }
4891
4892 fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: std.c.vm_prot_t) MachError!void {
4893 switch (getKernError(std.c.mach_vm_protect(task.port, address, len, @intFromBool(set_max), prot))) {
4894 .SUCCESS => return,
4895 .FAILURE => return error.PermissionDenied,
4896 else => |err| return unexpectedKernError(err),
4897 }
4898 }
4899
4900 /// Will write to VM even if current protection attributes specifically prohibit
4901 /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY
4902 /// variant, and resetting after a successful or unsuccessful write.
4903 pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize {
4904 const curr_prot = try task.getCurrProtection(address, buf.len);
4905 try task.setCurrProtection(
4906 address,
4907 buf.len,
4908 .{ .READ = true, .WRITE = true, .COPY = true },
4909 );
4910 defer {
4911 task.setCurrProtection(address, buf.len, curr_prot) catch {};
4912 }
4913 return task.writeMem(address, buf, arch);
4914 }
4915
4916 pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize {
4917 const count = buf.len;
4918 var total_written: usize = 0;
4919 var curr_addr = address;
4920 const page_size = try MachTask.getPageSize(task); // TODO we probably can assume value here
4921 var out_buf = buf[0..];
4922
4923 while (total_written < count) {
4924 const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written);
4925 switch (getKernError(std.c.mach_vm_write(
4926 task.port,
4927 curr_addr,
4928 @intFromPtr(out_buf.ptr),
4929 @as(std.c.mach_msg_type_number_t, @intCast(curr_size)),
4930 ))) {
4931 .SUCCESS => {},
4932 .FAILURE => return error.PermissionDenied,
4933 else => |err| return unexpectedKernError(err),
4934 }
4935
4936 switch (arch) {
4937 .aarch64 => {
4938 var mattr_value: std.c.vm_machine_attribute_val_t = std.c.MATTR.VAL_CACHE_FLUSH;
4939 switch (getKernError(std.c.vm_machine_attribute(
4940 task.port,
4941 curr_addr,
4942 curr_size,
4943 std.c.MATTR.CACHE,
4944 &mattr_value,
4945 ))) {
4946 .SUCCESS => {},
4947 .FAILURE => return error.PermissionDenied,
4948 else => |err| return unexpectedKernError(err),
4949 }
4950 },
4951 .x86_64 => {},
4952 else => unreachable,
4953 }
4954
4955 out_buf = out_buf[curr_size..];
4956 total_written += curr_size;
4957 curr_addr += curr_size;
4958 }
4959
4960 return total_written;
4961 }
4962
4963 pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize {
4964 const count = buf.len;
4965 var total_read: usize = 0;
4966 var curr_addr = address;
4967 const page_size = try MachTask.getPageSize(task); // TODO we probably can assume value here
4968 var out_buf = buf[0..];
4969
4970 while (total_read < count) {
4971 const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read);
4972 var curr_bytes_read: std.c.mach_msg_type_number_t = 0;
4973 var vm_memory: std.c.vm_offset_t = undefined;
4974 switch (getKernError(std.c.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) {
4975 .SUCCESS => {},
4976 .FAILURE => return error.PermissionDenied,
4977 else => |err| return unexpectedKernError(err),
4978 }
4979
4980 @memcpy(out_buf[0..curr_bytes_read], @as([*]const u8, @ptrFromInt(vm_memory)));
4981 _ = std.c.vm_deallocate(std.c.mach_task_self(), vm_memory, curr_bytes_read);
4982
4983 out_buf = out_buf[curr_bytes_read..];
4984 curr_addr += curr_bytes_read;
4985 total_read += curr_bytes_read;
4986 }
4987
4988 return total_read;
4989 }
4990
4991 fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize {
4992 var left = count;
4993 if (page_size > 0) {
4994 const page_offset = address % page_size;
4995 const bytes_left_in_page = page_size - page_offset;
4996 if (count > bytes_left_in_page) {
4997 left = bytes_left_in_page;
4998 }
4999 }
5000 return left;
5001 }
5002
5003 fn getPageSize(task: MachTask) MachError!usize {
5004 if (task.isValid()) {
5005 var info_count = std.c.TASK_VM_INFO_COUNT;
5006 var vm_info: std.c.task_vm_info_data_t = undefined;
5007 switch (getKernError(std.c.task_info(
5008 task.port,
5009 std.c.TASK_VM_INFO,
5010 @as(std.c.task_info_t, @ptrCast(&vm_info)),
5011 &info_count,
5012 ))) {
5013 .SUCCESS => return @as(usize, @intCast(vm_info.page_size)),
5014 else => {},
5015 }
5016 }
5017 var page_size: std.c.vm_size_t = undefined;
5018 switch (getKernError(std.c._host_page_size(std.c.mach_host_self(), &page_size))) {
5019 .SUCCESS => return page_size,
5020 else => |err| return unexpectedKernError(err),
5021 }
5022 }
5023
5024 pub fn basicTaskInfo(task: MachTask) MachError!std.c.mach_task_basic_info {
5025 var info: std.c.mach_task_basic_info = undefined;
5026 var count = std.c.MACH_TASK_BASIC_INFO_COUNT;
5027 switch (getKernError(std.c.task_info(
5028 task.port,
5029 std.c.MACH_TASK_BASIC_INFO,
5030 @as(std.c.task_info_t, @ptrCast(&info)),
5031 &count,
5032 ))) {
5033 .SUCCESS => return info,
5034 else => |err| return unexpectedKernError(err),
5035 }
5036 }
5037
5038 pub fn @"resume"(task: MachTask) MachError!void {
5039 switch (getKernError(std.c.task_resume(task.port))) {
5040 .SUCCESS => {},
5041 else => |err| return unexpectedKernError(err),
5042 }
5043 }
5044
5045 pub fn @"suspend"(task: MachTask) MachError!void {
5046 switch (getKernError(std.c.task_suspend(task.port))) {
5047 .SUCCESS => {},
5048 else => |err| return unexpectedKernError(err),
5049 }
5050 }
5051
5052 const ThreadList = struct {
5053 buf: []MachThread,
5054
5055 pub fn deinit(list: ThreadList) void {
5056 const self_task = machTaskForSelf();
5057 _ = std.c.vm_deallocate(
5058 self_task.port,
5059 @intFromPtr(list.buf.ptr),
5060 @as(std.c.vm_size_t, @intCast(list.buf.len * @sizeOf(std.c.mach_port_t))),
5061 );
5062 }
5063 };
5064
5065 pub fn getThreads(task: MachTask) MachError!ThreadList {
5066 var thread_list: std.c.mach_port_array_t = undefined;
5067 var thread_count: std.c.mach_msg_type_number_t = undefined;
5068 switch (getKernError(std.c.task_threads(task.port, &thread_list, &thread_count))) {
5069 .SUCCESS => return ThreadList{ .buf = @as([*]MachThread, @ptrCast(thread_list))[0..thread_count] },
5070 else => |err| return unexpectedKernError(err),
5071 }
5072 }
5073};
5074
5075pub const MachThread = extern struct {
5076 port: std.c.mach_port_t,
5077
5078 pub fn isValid(thread: MachThread) bool {
5079 return thread.port != std.c.THREAD_NULL;
5080 }
5081
5082 pub fn getBasicInfo(thread: MachThread) MachError!std.c.thread_basic_info {
5083 var info: std.c.thread_basic_info = undefined;
5084 var count = std.c.THREAD_BASIC_INFO_COUNT;
5085 switch (getKernError(std.c.thread_info(
5086 thread.port,
5087 std.c.THREAD_BASIC_INFO,
5088 @as(std.c.thread_info_t, @ptrCast(&info)),
5089 &count,
5090 ))) {
5091 .SUCCESS => return info,
5092 else => |err| return unexpectedKernError(err),
5093 }
5094 }
5095
5096 pub fn getIdentifierInfo(thread: MachThread) MachError!std.c.thread_identifier_info {
5097 var info: std.c.thread_identifier_info = undefined;
5098 var count = std.c.THREAD_IDENTIFIER_INFO_COUNT;
5099 switch (getKernError(std.c.thread_info(
5100 thread.port,
5101 std.c.THREAD_IDENTIFIER_INFO,
5102 @as(std.c.thread_info_t, @ptrCast(&info)),
5103 &count,
5104 ))) {
5105 .SUCCESS => return info,
5106 else => |err| return unexpectedKernError(err),
5107 }
5108 }
5109};
5110
5111pub fn machTaskForPid(pid: std.c.pid_t) MachError!MachTask {
5112 var port: std.c.mach_port_name_t = undefined;
5113 switch (getKernError(std.c.task_for_pid(std.c.mach_task_self(), pid, &port))) {
5114 .SUCCESS => {},
5115 .FAILURE => return error.PermissionDenied,
5116 else => |err| return unexpectedKernError(err),
5117 }
5118 return MachTask{ .port = port };
5119}
5120
5121pub fn machTaskForSelf() MachTask {
5122 return .{ .port = std.c.mach_task_self() };
5123}
5124
5125pub fn getKernError(err: std.c.kern_return_t) KernE {
5126 return @as(KernE, @fromBackingInt(@intCast(@as(u32, @truncate(@as(usize, @intCast(err)))))));
5127}
5128
5129pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError {
5130 if (std.options.unexpected_error_tracing) {
5131 std.debug.print("unexpected error: {d}\n", .{@backingInt(err)});
5132 std.debug.dumpCurrentStackTrace(.{});
5133 }
5134 return error.Unexpected;
5135}
5136
5137/// Kernel return values
5138pub const KernE = enum(u32) {
5139 SUCCESS = 0,
5140 /// Specified address is not currently valid
5141 INVALID_ADDRESS = 1,
5142 /// Specified memory is valid, but does not permit the
5143 /// required forms of access.
5144 PROTECTION_FAILURE = 2,
5145 /// The address range specified is already in use, or
5146 /// no address range of the size specified could be
5147 /// found.
5148 NO_SPACE = 3,
5149 /// The function requested was not applicable to this
5150 /// type of argument, or an argument is invalid
5151 INVALID_ARGUMENT = 4,
5152 /// The function could not be performed. A catch-all.
5153 FAILURE = 5,
5154 /// A system resource could not be allocated to fulfill
5155 /// this request. This failure may not be permanent.
5156 RESOURCE_SHORTAGE = 6,
5157 /// The task in question does not hold receive rights
5158 /// for the port argument.
5159 NOT_RECEIVER = 7,
5160 /// Bogus access restriction.
5161 NO_ACCESS = 8,
5162 /// During a page fault, the target address refers to a
5163 /// memory object that has been destroyed. This
5164 /// failure is permanent.
5165 MEMORY_FAILURE = 9,
5166 /// During a page fault, the memory object indicated
5167 /// that the data could not be returned. This failure
5168 /// may be temporary; future attempts to access this
5169 /// same data may succeed, as defined by the memory
5170 /// object.
5171 MEMORY_ERROR = 10,
5172 /// The receive right is already a member of the portset.
5173 ALREADY_IN_SET = 11,
5174 /// The receive right is not a member of a port set.
5175 NOT_IN_SET = 12,
5176 /// The name already denotes a right in the task.
5177 NAME_EXISTS = 13,
5178 /// The operation was aborted. Ipc code will
5179 /// catch this and reflect it as a message error.
5180 ABORTED = 14,
5181 /// The name doesn't denote a right in the task.
5182 INVALID_NAME = 15,
5183 /// Target task isn't an active task.
5184 INVALID_TASK = 16,
5185 /// The name denotes a right, but not an appropriate right.
5186 INVALID_RIGHT = 17,
5187 /// A blatant range error.
5188 INVALID_VALUE = 18,
5189 /// Operation would overflow limit on user-references.
5190 UREFS_OVERFLOW = 19,
5191 /// The supplied (port) capability is improper.
5192 INVALID_CAPABILITY = 20,
5193 /// The task already has send or receive rights
5194 /// for the port under another name.
5195 RIGHT_EXISTS = 21,
5196 /// Target host isn't actually a host.
5197 INVALID_HOST = 22,
5198 /// An attempt was made to supply "precious" data
5199 /// for memory that is already present in a
5200 /// memory object.
5201 MEMORY_PRESENT = 23,
5202 /// A page was requested of a memory manager via
5203 /// memory_object_data_request for an object using
5204 /// a MEMORY_OBJECT_COPY_CALL strategy, with the
5205 /// VM_PROT_WANTS_COPY flag being used to specify
5206 /// that the page desired is for a copy of the
5207 /// object, and the memory manager has detected
5208 /// the page was pushed into a copy of the object
5209 /// while the kernel was walking the shadow chain
5210 /// from the copy to the object. This error code
5211 /// is delivered via memory_object_data_error
5212 /// and is handled by the kernel (it forces the
5213 /// kernel to restart the fault). It will not be
5214 /// seen by users.
5215 MEMORY_DATA_MOVED = 24,
5216 /// A strategic copy was attempted of an object
5217 /// upon which a quicker copy is now possible.
5218 /// The caller should retry the copy using
5219 /// vm_object_copy_quickly. This error code
5220 /// is seen only by the kernel.
5221 MEMORY_RESTART_COPY = 25,
5222 /// An argument applied to assert processor set privilege
5223 /// was not a processor set control port.
5224 INVALID_PROCESSOR_SET = 26,
5225 /// The specified scheduling attributes exceed the thread's
5226 /// limits.
5227 POLICY_LIMIT = 27,
5228 /// The specified scheduling policy is not currently
5229 /// enabled for the processor set.
5230 INVALID_POLICY = 28,
5231 /// The external memory manager failed to initialize the
5232 /// memory object.
5233 INVALID_OBJECT = 29,
5234 /// A thread is attempting to wait for an event for which
5235 /// there is already a waiting thread.
5236 ALREADY_WAITING = 30,
5237 /// An attempt was made to destroy the default processor
5238 /// set.
5239 DEFAULT_SET = 31,
5240 /// An attempt was made to fetch an exception port that is
5241 /// protected, or to abort a thread while processing a
5242 /// protected exception.
5243 EXCEPTION_PROTECTED = 32,
5244 /// A ledger was required but not supplied.
5245 INVALID_LEDGER = 33,
5246 /// The port was not a memory cache control port.
5247 INVALID_MEMORY_CONTROL = 34,
5248 /// An argument supplied to assert security privilege
5249 /// was not a host security port.
5250 INVALID_SECURITY = 35,
5251 /// thread_depress_abort was called on a thread which
5252 /// was not currently depressed.
5253 NOT_DEPRESSED = 36,
5254 /// Object has been terminated and is no longer available
5255 TERMINATED = 37,
5256 /// Lock set has been destroyed and is no longer available.
5257 LOCK_SET_DESTROYED = 38,
5258 /// The thread holding the lock terminated before releasing
5259 /// the lock
5260 LOCK_UNSTABLE = 39,
5261 /// The lock is already owned by another thread
5262 LOCK_OWNED = 40,
5263 /// The lock is already owned by the calling thread
5264 LOCK_OWNED_SELF = 41,
5265 /// Semaphore has been destroyed and is no longer available.
5266 SEMAPHORE_DESTROYED = 42,
5267 /// Return from RPC indicating the target server was
5268 /// terminated before it successfully replied
5269 RPC_SERVER_TERMINATED = 43,
5270 /// Terminate an orphaned activation.
5271 RPC_TERMINATE_ORPHAN = 44,
5272 /// Allow an orphaned activation to continue executing.
5273 RPC_CONTINUE_ORPHAN = 45,
5274 /// Empty thread activation (No thread linked to it)
5275 NOT_SUPPORTED = 46,
5276 /// Remote node down or inaccessible.
5277 NODE_DOWN = 47,
5278 /// A signalled thread was not actually waiting.
5279 NOT_WAITING = 48,
5280 /// Some thread-oriented operation (semaphore_wait) timed out
5281 OPERATION_TIMED_OUT = 49,
5282 /// During a page fault, indicates that the page was rejected
5283 /// as a result of a signature check.
5284 CODESIGN_ERROR = 50,
5285 /// The requested property cannot be changed at this time.
5286 POLICY_STATIC = 51,
5287 /// The provided buffer is of insufficient size for the requested data.
5288 INSUFFICIENT_BUFFER_SIZE = 52,
5289 /// Denied by security policy
5290 DENIED = 53,
5291 /// The KC on which the function is operating is missing
5292 MISSING_KC = 54,
5293 /// The KC on which the function is operating is invalid
5294 INVALID_KC = 55,
5295 /// A search or query operation did not return a result
5296 NOT_FOUND = 56,
5297 _,
5298};
5299
5300fn createThunks(macho_file: *MachO, sect_id: u8) !void {
5301 const tracy = trace(@src());
5302 defer tracy.end();
5303
5304 const gpa = macho_file.base.comp.gpa;
5305 const slice = macho_file.sections.slice();
5306 const header = &slice.items(.header)[sect_id];
5307 const thnks = &slice.items(.thunks)[sect_id];
5308 const atoms = slice.items(.atoms)[sect_id].items;
5309 assert(atoms.len > 0);
5310
5311 for (atoms) |ref| {
5312 ref.getAtom(macho_file).?.value = @bitCast(@as(i64, -1));
5313 }
5314
5315 var i: usize = 0;
5316 while (i < atoms.len) {
5317 const start = i;
5318 const start_atom = atoms[start].getAtom(macho_file).?;
5319 assert(start_atom.isAlive());
5320 start_atom.value = advanceSection(header, start_atom.size, start_atom.alignment);
5321 i += 1;
5322
5323 while (i < atoms.len and
5324 header.size - start_atom.value < max_allowed_distance) : (i += 1)
5325 {
5326 const atom = atoms[i].getAtom(macho_file).?;
5327 assert(atom.isAlive());
5328 atom.value = advanceSection(header, atom.size, atom.alignment);
5329 }
5330
5331 // Insert a thunk at the group end
5332 const thunk_index = try macho_file.addThunk();
5333 const thunk = macho_file.getThunk(thunk_index);
5334 thunk.out_n_sect = sect_id;
5335 try thnks.append(gpa, thunk_index);
5336
5337 // Scan relocs in the group and create trampolines for any unreachable callsite
5338 try scanThunkRelocs(thunk_index, gpa, atoms[start..i], macho_file);
5339 thunk.value = advanceSection(header, thunk.size(), .@"4");
5340
5341 log.debug("thunk({d}) : {f}", .{ thunk_index, thunk.fmt(macho_file) });
5342 }
5343}
5344
5345fn advanceSection(sect: *macho.section_64, adv_size: u64, alignment: Atom.Alignment) u64 {
5346 const offset = alignment.forward(sect.size);
5347 const padding = offset - sect.size;
5348 sect.size += padding + adv_size;
5349 sect.@"align" = @max(sect.@"align", alignment.toLog2Units());
5350 return offset;
5351}
5352
5353fn scanThunkRelocs(thunk_index: Thunk.Index, gpa: Allocator, atoms: []const MachO.Ref, macho_file: *MachO) !void {
5354 const tracy = trace(@src());
5355 defer tracy.end();
5356
5357 const thunk = macho_file.getThunk(thunk_index);
5358
5359 for (atoms) |ref| {
5360 const atom = ref.getAtom(macho_file).?;
5361 log.debug("atom({d}) {s}", .{ atom.atom_index, atom.getName(macho_file) });
5362 for (atom.getRelocs(macho_file)) |rel| {
5363 if (rel.type != .branch) continue;
5364 if (isReachable(atom, rel, macho_file)) continue;
5365 try thunk.symbols.put(gpa, rel.getTargetSymbolRef(atom.*, macho_file), {});
5366 }
5367 atom.addExtra(.{ .thunk = thunk_index }, macho_file);
5368 }
5369}
5370
5371fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool {
5372 const target = rel.getTargetSymbol(atom.*, macho_file);
5373 if (target.getSectionFlags().stubs or target.getSectionFlags().objc_stubs) return false;
5374 if (atom.out_n_sect != target.getOutputSectionIndex(macho_file)) return false;
5375 const target_atom = target.getAtom(macho_file).?;
5376 if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false;
5377 const saddr = @as(i64, @intCast(atom.getAddress(macho_file))) + @as(i64, @intCast(rel.offset - atom.off));
5378 const taddr: i64 = @intCast(rel.getTargetAddress(atom.*, macho_file));
5379 _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false;
5380 return true;
5381}
5382
5383pub fn pwriteAll(macho_file: *MachO, bytes: []const u8, offset: u64) error{AlreadyReported}!void {
5384 const comp = macho_file.base.comp;
5385 const io = comp.io;
5386 const diags = &comp.link_diags;
5387 macho_file.base.file.?.writePositionalAll(io, bytes, offset) catch |err|
5388 return diags.fail("failed to write: {t}", .{err});
5389}
5390
5391pub fn setLength(macho_file: *MachO, length: u64) error{AlreadyReported}!void {
5392 const comp = macho_file.base.comp;
5393 const io = comp.io;
5394 const diags = &comp.link_diags;
5395 macho_file.base.file.?.setLength(io, length) catch |err|
5396 return diags.fail("failed to set file end pos: {t}", .{err});
5397}
5398
5399pub fn cast(macho_file: *MachO, comptime T: type, x: anytype) error{AlreadyReported}!T {
5400 return std.math.cast(T, x) orelse {
5401 const comp = macho_file.base.comp;
5402 const diags = &comp.link_diags;
5403 return diags.fail("encountered {d}, overflowing {d}-bit value", .{ x, @bitSizeOf(T) });
5404 };
5405}
5406
5407pub fn alignPow(macho_file: *MachO, x: u32) error{AlreadyReported}!u32 {
5408 const result, const ov = @shlWithOverflow(@as(u32, 1), try cast(macho_file, u5, x));
5409 if (ov != 0) {
5410 const comp = macho_file.base.comp;
5411 const diags = &comp.link_diags;
5412 return diags.fail("alignment overflow", .{});
5413 }
5414 return result;
5415}
5416
5417pub fn needsEncryptionInfo(macho_file: *MachO) bool {
5418 const target = macho_file.getTarget();
5419 return switch (target.os.tag) {
5420 .ios,
5421 .tvos,
5422 .visionos,
5423 .watchos,
5424 => target.abi != .simulator,
5425 else => false,
5426 };
5427}
5428
5429/// Branch instruction has 26 bits immediate but is 4 byte aligned.
5430const jump_bits = @bitSizeOf(i28);
5431const max_distance = (1 << (jump_bits - 1));
5432
5433/// A branch will need an extender if its target is larger than
5434/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number.
5435/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold
5436/// and assume margin to be 5MiB.
5437const max_allowed_distance = max_distance - 0x500_000;
5438
5439const MachO = @This();
5440const build_options = @import("build_options");
5441const builtin = @import("builtin");
5442
5443const std = @import("std");
5444const Io = std.Io;
5445const assert = std.debug.assert;
5446const fs = std.fs;
5447const log = std.log.scoped(.link);
5448const state_log = std.log.scoped(.link_state);
5449const macho = std.macho;
5450const math = std.math;
5451const mem = std.mem;
5452const meta = std.meta;
5453const Writer = std.Io.Writer;
5454const AtomicBool = std.atomic.Value(bool);
5455const Cache = std.Build.Cache;
5456const Hash = std.hash.Wyhash;
5457const Md5 = std.crypto.hash.Md5;
5458const Allocator = std.mem.Allocator;
5459
5460const aarch64 = codegen.aarch64.encoding;
5461const bind = @import("MachO/dyld_info/bind.zig");
5462const calcUuid = @import("MachO/uuid.zig").calcUuid;
5463const codegen = @import("../codegen.zig");
5464const dead_strip = @import("MachO/dead_strip.zig");
5465const eh_frame = @import("MachO/eh_frame.zig");
5466const fat = @import("MachO/fat.zig");
5467const link = @import("../link.zig");
5468const load_commands = @import("MachO/load_commands.zig");
5469const relocatable = @import("MachO/relocatable.zig");
5470const tapi = @import("tapi.zig");
5471const target_util = @import("../target.zig");
5472const trace = @import("../tracy.zig").trace;
5473const synthetic = @import("MachO/synthetic.zig");
5474
5475const Alignment = Atom.Alignment;
5476const Archive = @import("MachO/Archive.zig");
5477const Bind = bind.Bind;
5478const CodeSignature = @import("MachO/CodeSignature.zig");
5479const Compilation = @import("../Compilation.zig");
5480const DataInCode = synthetic.DataInCode;
5481const Directory = Cache.Directory;
5482const Dylib = @import("MachO/Dylib.zig");
5483const ExportTrie = @import("MachO/dyld_info/Trie.zig");
5484const Path = Cache.Path;
5485const File = @import("MachO/file.zig").File;
5486const GotSection = synthetic.GotSection;
5487const Indsymtab = synthetic.Indsymtab;
5488const InternalObject = @import("MachO/InternalObject.zig");
5489const ObjcStubsSection = synthetic.ObjcStubsSection;
5490const Object = @import("MachO/Object.zig");
5491const LazyBind = bind.LazyBind;
5492const LaSymbolPtrSection = synthetic.LaSymbolPtrSection;
5493const Zcu = @import("../Zcu.zig");
5494const InternPool = @import("../InternPool.zig");
5495const Rebase = @import("MachO/dyld_info/Rebase.zig");
5496const StringTable = @import("StringTable.zig");
5497const StubsSection = synthetic.StubsSection;
5498const StubsHelperSection = synthetic.StubsHelperSection;
5499const Symbol = @import("MachO/Symbol.zig");
5500const Thunk = @import("MachO/Thunk.zig");
5501const TlvPtrSection = synthetic.TlvPtrSection;
5502const Value = @import("../Value.zig");
5503const UnwindInfo = @import("MachO/UnwindInfo.zig");
5504const WeakBind = bind.WeakBind;
5505const ZigObject = @import("MachO/ZigObject.zig");