pub const Atom = @import("MachO/Atom.zig"); pub const DebugSymbols = @import("MachO/DebugSymbols.zig"); pub const Relocation = @import("MachO/Relocation.zig"); base: link.File, rpath_list: []const []const u8, /// Debug symbols bundle (or dSym). d_sym: ?DebugSymbols = null, /// A list of all input files. /// Index of each input file also encodes the priority or precedence of one input file /// over another. files: std.MultiArrayList(File.Entry) = .{}, /// Long-lived list of all file descriptors. /// We store them globally rather than per actual File so that we can re-use /// one file handle per every object file within an archive. file_handles: std.ArrayListUnmanaged(File.Handle) = .empty, zig_object: ?File.Index = null, internal_object: ?File.Index = null, objects: std.ArrayListUnmanaged(File.Index) = .empty, dylibs: std.ArrayListUnmanaged(File.Index) = .empty, segments: std.ArrayListUnmanaged(macho.segment_command_64) = .empty, sections: std.MultiArrayList(Section) = .{}, resolver: SymbolResolver = .{}, /// This table will be populated after `scanRelocs` has run. /// Key is symbol index. undefs: std.AutoArrayHashMapUnmanaged(SymbolResolver.Index, UndefRefs) = .empty, undefs_mutex: std.Thread.Mutex = .{}, dupes: std.AutoArrayHashMapUnmanaged(SymbolResolver.Index, std.ArrayListUnmanaged(File.Index)) = .empty, dupes_mutex: std.Thread.Mutex = .{}, dyld_info_cmd: macho.dyld_info_command = .{}, symtab_cmd: macho.symtab_command = .{}, dysymtab_cmd: macho.dysymtab_command = .{}, function_starts_cmd: macho.linkedit_data_command = .{ .cmd = .FUNCTION_STARTS }, data_in_code_cmd: macho.linkedit_data_command = .{ .cmd = .DATA_IN_CODE }, uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 }, codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE }, pagezero_seg_index: ?u8 = null, text_seg_index: ?u8 = null, linkedit_seg_index: ?u8 = null, text_sect_index: ?u8 = null, data_sect_index: ?u8 = null, got_sect_index: ?u8 = null, stubs_sect_index: ?u8 = null, stubs_helper_sect_index: ?u8 = null, la_symbol_ptr_sect_index: ?u8 = null, tlv_ptr_sect_index: ?u8 = null, eh_frame_sect_index: ?u8 = null, unwind_info_sect_index: ?u8 = null, objc_stubs_sect_index: ?u8 = null, thunks: std.ArrayListUnmanaged(Thunk) = .empty, /// Output synthetic sections symtab: std.ArrayListUnmanaged(macho.nlist_64) = .empty, strtab: std.ArrayListUnmanaged(u8) = .empty, indsymtab: Indsymtab = .{}, got: GotSection = .{}, stubs: StubsSection = .{}, stubs_helper: StubsHelperSection = .{}, objc_stubs: ObjcStubsSection = .{}, la_symbol_ptr: LaSymbolPtrSection = .{}, tlv_ptr: TlvPtrSection = .{}, rebase_section: Rebase = .{}, bind_section: Bind = .{}, weak_bind_section: WeakBind = .{}, lazy_bind_section: LazyBind = .{}, export_trie: ExportTrie = .{}, unwind_info: UnwindInfo = .{}, data_in_code: DataInCode = .{}, /// Tracked loadable segments during incremental linking. zig_text_seg_index: ?u8 = null, zig_const_seg_index: ?u8 = null, zig_data_seg_index: ?u8 = null, zig_bss_seg_index: ?u8 = null, /// Tracked section headers with incremental updates to Zig object. zig_text_sect_index: ?u8 = null, zig_const_sect_index: ?u8 = null, zig_data_sect_index: ?u8 = null, zig_bss_sect_index: ?u8 = null, /// Tracked DWARF section headers that apply only when we emit relocatable. /// For executable and loadable images, DWARF is tracked directly by dSYM bundle object. debug_info_sect_index: ?u8 = null, debug_abbrev_sect_index: ?u8 = null, debug_str_sect_index: ?u8 = null, debug_aranges_sect_index: ?u8 = null, debug_line_sect_index: ?u8 = null, debug_line_str_sect_index: ?u8 = null, debug_loclists_sect_index: ?u8 = null, debug_rnglists_sect_index: ?u8 = null, has_tlv: AtomicBool = AtomicBool.init(false), binds_to_weak: AtomicBool = AtomicBool.init(false), weak_defines: AtomicBool = AtomicBool.init(false), /// Options /// SDK layout sdk_layout: ?SdkLayout, /// Size of the __PAGEZERO segment. pagezero_size: ?u64, /// Minimum space for future expansion of the load commands. headerpad_size: ?u32, /// Set enough space as if all paths were MATPATHLEN. headerpad_max_install_names: bool, /// Remove dylibs that are unreachable by the entry point or exported symbols. dead_strip_dylibs: bool, /// Treatment of undefined symbols undefined_treatment: UndefinedTreatment, /// TODO: delete this, libraries need to be resolved by the frontend instead lib_directories: []const Directory, /// Resolved list of framework search directories framework_dirs: []const []const u8, /// List of input frameworks frameworks: []const Framework, /// Install name for the dylib. /// TODO: unify with soname install_name: ?[]const u8, /// Path to entitlements file. entitlements: ?[]const u8, compatibility_version: ?std.SemanticVersion, /// Entry name entry_name: ?[]const u8, platform: Platform, sdk_version: ?std.SemanticVersion, /// When set to true, the linker will hoist all dylibs including system dependent dylibs. no_implicit_dylibs: bool = false, /// Whether the linker should parse and always force load objects containing ObjC in archives. // TODO: in Zig we currently take -ObjC as always on force_load_objc: bool = true, /// Whether local symbols should be discarded from the symbol table. discard_local_symbols: bool = false, /// Hot-code swapping state. hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{}, /// When adding a new field, remember to update `hashAddFrameworks`. pub const Framework = struct { needed: bool = false, weak: bool = false, path: Path, }; pub fn hashAddFrameworks(man: *Cache.Manifest, hm: []const Framework) !void { for (hm) |value| { man.hash.add(value.needed); man.hash.add(value.weak); _ = try man.addFilePath(value.path, null); } } pub fn createEmpty( arena: Allocator, comp: *Compilation, emit: Path, options: link.File.OpenOptions, ) !*MachO { const target = &comp.root_mod.resolved_target.result; assert(target.ofmt == .macho); const gpa = comp.gpa; const use_llvm = comp.config.use_llvm; const opt_zcu = comp.zcu; const optimize_mode = comp.root_mod.optimize_mode; const output_mode = comp.config.output_mode; const link_mode = comp.config.link_mode; const allow_shlib_undefined = options.allow_shlib_undefined orelse false; const self = try arena.create(MachO); self.* = .{ .base = .{ .tag = .macho, .comp = comp, .emit = emit, .zcu_object_basename = if (use_llvm) try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)}) else null, .gc_sections = options.gc_sections orelse (optimize_mode != .Debug), .print_gc_sections = options.print_gc_sections, .stack_size = options.stack_size orelse 16777216, .allow_shlib_undefined = allow_shlib_undefined, .file = null, .build_id = options.build_id, }, .rpath_list = options.rpath_list, .pagezero_size = options.pagezero_size, .headerpad_size = options.headerpad_size, .headerpad_max_install_names = options.headerpad_max_install_names, .dead_strip_dylibs = options.dead_strip_dylibs, .sdk_layout = options.darwin_sdk_layout, .frameworks = options.frameworks, .install_name = options.install_name, .entitlements = options.entitlements, .compatibility_version = options.compatibility_version, .entry_name = switch (options.entry) { .disabled => null, .default => if (output_mode != .Exe) null else default_entry_symbol_name, .enabled => default_entry_symbol_name, .named => |name| name, }, .platform = Platform.fromTarget(target), .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null, .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error", // TODO delete this, directories must instead be resolved by the frontend .lib_directories = options.lib_directories, .framework_dirs = options.framework_dirs, .force_load_objc = options.force_load_objc, .discard_local_symbols = options.discard_local_symbols, }; errdefer self.base.destroy(); self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{ .truncate = true, .read = true, .mode = link.File.determineMode(output_mode, link_mode), }); // Append null file try self.files.append(gpa, .null); // Append empty string to string tables try self.strtab.append(gpa, 0); if (opt_zcu) |zcu| { if (!use_llvm) { const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .zig_object = .{ .index = index, .basename = try std.fmt.allocPrint(arena, "{s}.o", .{ fs.path.stem(zcu.main_mod.root_src_path), }), } }); self.zig_object = index; const zo = self.getZigObject().?; try zo.init(self); try self.initMetadata(.{ .emit = emit, .zo = zo, .symbol_count_hint = options.symbol_count_hint, .program_code_size_hint = options.program_code_size_hint, }); } } return self; } pub fn open( arena: Allocator, comp: *Compilation, emit: Path, options: link.File.OpenOptions, ) !*MachO { // TODO: restore saved linker state, don't truncate the file, and // participate in incremental compilation. return createEmpty(arena, comp, emit, options); } pub fn deinit(self: *MachO) void { const gpa = self.base.comp.gpa; if (self.d_sym) |*d_sym| { d_sym.deinit(); } for (self.file_handles.items) |handle| { handle.close(); } self.file_handles.deinit(gpa); for (self.files.items(.tags), self.files.items(.data)) |tag, *data| switch (tag) { .null => {}, .zig_object => data.zig_object.deinit(gpa), .internal => data.internal.deinit(gpa), .object => data.object.deinit(gpa), .dylib => data.dylib.deinit(gpa), }; self.files.deinit(gpa); self.objects.deinit(gpa); self.dylibs.deinit(gpa); self.segments.deinit(gpa); for ( self.sections.items(.atoms), self.sections.items(.out), self.sections.items(.thunks), self.sections.items(.relocs), ) |*atoms, *out, *thnks, *relocs| { atoms.deinit(gpa); out.deinit(gpa); thnks.deinit(gpa); relocs.deinit(gpa); } self.sections.deinit(gpa); self.resolver.deinit(gpa); for (self.undefs.values()) |*val| { val.deinit(gpa); } self.undefs.deinit(gpa); for (self.dupes.values()) |*val| { val.deinit(gpa); } self.dupes.deinit(gpa); self.symtab.deinit(gpa); self.strtab.deinit(gpa); self.got.deinit(gpa); self.stubs.deinit(gpa); self.objc_stubs.deinit(gpa); self.tlv_ptr.deinit(gpa); self.rebase_section.deinit(gpa); self.bind_section.deinit(gpa); self.weak_bind_section.deinit(gpa); self.lazy_bind_section.deinit(gpa); self.export_trie.deinit(gpa); self.unwind_info.deinit(gpa); self.data_in_code.deinit(gpa); self.thunks.deinit(gpa); } pub fn flush( self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) link.File.FlushError!void { const tracy = trace(@src()); defer tracy.end(); const comp = self.base.comp; const gpa = comp.gpa; const diags = &self.base.comp.link_diags; const sub_prog_node = prog_node.start("MachO Flush", 0); defer sub_prog_node.end(); const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: { break :p try comp.resolveEmitPathFlush(arena, .temp, raw); } else null; // --verbose-link if (comp.verbose_link) try self.dumpArgv(comp); if (self.getZigObject()) |zo| try zo.flush(self, tid); if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path); if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path); var positionals = std.ArrayList(link.Input).init(gpa); defer positionals.deinit(); try positionals.ensureUnusedCapacity(comp.link_inputs.len); for (comp.link_inputs) |link_input| switch (link_input) { .dso => continue, // handled below .object, .archive => positionals.appendAssumeCapacity(link_input), .dso_exact => @panic("TODO"), .res => unreachable, }; // This is a set of object files emitted by clang in a single `build-exe` invocation. // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up // in this set. try positionals.ensureUnusedCapacity(comp.c_object_table.keys().len); for (comp.c_object_table.keys()) |key| { positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path)); } if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path)); if (comp.config.any_sanitize_thread) { try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path)); } if (comp.config.any_fuzz) { try positionals.append(try link.openArchiveInput(diags, comp.fuzzer_lib.?.full_object_path, false, false)); } if (comp.ubsan_rt_lib) |crt_file| { const path = crt_file.full_object_path; self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } else if (comp.ubsan_rt_obj) |crt_file| { const path = crt_file.full_object_path; self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } for (positionals.items) |link_input| { self.classifyInputFile(link_input) catch |err| diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); } var system_libs = std.ArrayList(SystemLib).init(gpa); defer system_libs.deinit(); // frameworks try system_libs.ensureUnusedCapacity(self.frameworks.len); for (self.frameworks) |info| { system_libs.appendAssumeCapacity(.{ .needed = info.needed, .weak = info.weak, .path = info.path, }); } // libc++ dep if (comp.config.link_libcpp) { try system_libs.ensureUnusedCapacity(2); system_libs.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path }); system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path }); } const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); if (comp.config.link_libc and is_exe_or_dyn_lib) { if (comp.zigc_static_lib) |zigc| { const path = zigc.full_object_path; self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } } // libc/libSystem dep self.resolveLibSystem(arena, comp, &system_libs) catch |err| switch (err) { error.MissingLibSystem => {}, // already reported else => |e| return diags.fail("failed to resolve libSystem: {s}", .{@errorName(e)}), }; for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive, .dso_exact => continue, .res => unreachable, .dso => { self.classifyInputFile(link_input) catch |err| diags.addParseError(link_input.path().?, "failed to parse input file: {s}", .{@errorName(err)}); }, }; for (system_libs.items) |lib| { switch (Compilation.classifyFileExt(lib.path.sub_path)) { .shared_library => { const dso_input = try link.openDsoInput(diags, lib.path, lib.needed, lib.weak, lib.reexport); self.classifyInputFile(dso_input) catch |err| diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); }, .static_library => { const archive_input = try link.openArchiveInput(diags, lib.path, lib.must_link, lib.hidden); self.classifyInputFile(archive_input) catch |err| diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); }, else => unreachable, } } // Finally, link against compiler_rt. if (comp.compiler_rt_lib) |crt_file| { const path = crt_file.full_object_path; self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } else if (comp.compiler_rt_obj) |crt_file| { const path = crt_file.full_object_path; self.classifyInputFile(try link.openObjectInput(diags, path)) catch |err| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); } try self.parseInputFiles(); self.parseDependentDylibs() catch |err| { switch (err) { error.MissingLibraryDependencies => {}, else => |e| return diags.fail("failed to parse dependent libraries: {s}", .{@errorName(e)}), } }; if (diags.hasErrors()) return error.LinkFailure; { const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .internal = .{ .index = index } }); self.internal_object = index; const object = self.getInternalObject().?; try object.init(gpa); try object.initSymbols(self); } try self.resolveSymbols(); try self.convertTentativeDefsAndResolveSpecialSymbols(); self.dedupLiterals() catch |err| switch (err) { error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("failed to deduplicate literals: {s}", .{@errorName(e)}), }; if (self.base.gc_sections) { try dead_strip.gcAtoms(self); } self.checkDuplicates() catch |err| switch (err) { error.HasDuplicates => return error.LinkFailure, else => |e| return diags.fail("failed to check for duplicate symbol definitions: {s}", .{@errorName(e)}), }; self.markImportsAndExports(); self.deadStripDylibs(); for (self.dylibs.items, 1..) |index, ord| { const dylib = self.getFile(index).?.dylib; dylib.ordinal = @intCast(ord); } self.claimUnresolved(); self.scanRelocs() catch |err| switch (err) { error.HasUndefinedSymbols => return error.LinkFailure, else => |e| return diags.fail("failed to scan relocations: {s}", .{@errorName(e)}), }; try self.initOutputSections(); try self.initSyntheticSections(); try self.sortSections(); try self.addAtomsToSections(); try self.calcSectionSizes(); try self.generateUnwindInfo(); try self.initSegments(); self.allocateSections() catch |err| switch (err) { error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("failed to allocate sections: {s}", .{@errorName(e)}), }; self.allocateSegments(); self.allocateSyntheticSymbols(); if (build_options.enable_logging) { state_log.debug("{f}", .{self.dumpState()}); } // Beyond this point, everything has been allocated a virtual address and we can resolve // the relocations, and commit objects to file. try self.resizeSections(); if (self.getZigObject()) |zo| { zo.resolveRelocs(self) catch |err| switch (err) { error.ResolveFailed => return error.LinkFailure, else => |e| return e, }; } try self.writeSectionsAndUpdateLinkeditSizes(); try self.writeSectionsToFile(); try self.allocateLinkeditSegment(); self.writeLinkeditSectionsToFile() catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("failed to write linkedit sections to file: {s}", .{@errorName(e)}), }; var codesig: ?CodeSignature = if (self.requiresCodeSig()) blk: { // Preallocate space for the code signature. // We need to do this at this stage so that we have the load commands with proper values // written out to the file. // The most important here is to have the correct vm and filesize of the __LINKEDIT segment // where the code signature goes into. var codesig = CodeSignature.init(self.getPageSize()); codesig.code_directory.ident = fs.path.basename(self.base.emit.sub_path); if (self.entitlements) |path| codesig.addEntitlements(gpa, path) catch |err| return diags.fail("failed to add entitlements from {s}: {s}", .{ path, @errorName(err) }); try self.writeCodeSignaturePadding(&codesig); break :blk codesig; } else null; defer if (codesig) |*csig| csig.deinit(gpa); self.getLinkeditSegment().vmsize = mem.alignForward( u64, self.getLinkeditSegment().filesize, self.getPageSize(), ); const ncmds, const sizeofcmds, const uuid_cmd_offset = self.writeLoadCommands() catch |err| switch (err) { error.NoSpaceLeft => unreachable, error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, }; try self.writeHeader(ncmds, sizeofcmds); self.writeUuid(uuid_cmd_offset, self.requiresCodeSig()) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("failed to calculate and write uuid: {s}", .{@errorName(e)}), }; if (self.getDebugSymbols()) |dsym| dsym.flush(self) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return diags.fail("failed to get debug symbols: {s}", .{@errorName(e)}), }; // Code signing always comes last. if (codesig) |*csig| { self.writeCodeSignature(csig) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.LinkFailure => return error.LinkFailure, else => |e| return diags.fail("failed to write code signature: {s}", .{@errorName(e)}), }; const emit = self.base.emit; invalidateKernelCache(emit.root_dir.handle, emit.sub_path) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => |e| return diags.fail("failed to invalidate kernel cache: {s}", .{@errorName(e)}), }; } } /// --verbose-link output fn dumpArgv(self: *MachO, comp: *Compilation) !void { const gpa = self.base.comp.gpa; var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); const directory = self.base.emit.root_dir; const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path}); const zcu_obj_path: ?[]const u8 = if (self.base.zcu_object_basename) |raw| p: { const p = try comp.resolveEmitPathFlush(arena, .temp, raw); break :p try p.toString(arena); } else null; var argv = std.ArrayList([]const u8).init(arena); try argv.append("zig"); if (self.base.isStaticLib()) { try argv.append("ar"); } else { try argv.append("ld"); } if (self.base.isObject()) { try argv.append("-r"); } if (self.base.isRelocatable()) { for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive => |obj| try argv.append(try obj.path.toString(arena)), .res => |res| try argv.append(try res.path.toString(arena)), .dso => |dso| try argv.append(try dso.path.toString(arena)), .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }), }; for (comp.c_object_table.keys()) |key| { try argv.append(try key.status.success.object_path.toString(arena)); } if (zcu_obj_path) |p| { try argv.append(p); } } else { if (!self.base.isStatic()) { try argv.append("-dynamic"); } if (self.base.isDynLib()) { try argv.append("-dylib"); if (self.install_name) |install_name| { try argv.append("-install_name"); try argv.append(install_name); } } try argv.append("-platform_version"); try argv.append(@tagName(self.platform.os_tag)); try argv.append(try std.fmt.allocPrint(arena, "{f}", .{self.platform.version})); if (self.sdk_version) |ver| { try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor })); } else { try argv.append(try std.fmt.allocPrint(arena, "{f}", .{self.platform.version})); } if (comp.sysroot) |syslibroot| { try argv.append("-syslibroot"); try argv.append(syslibroot); } for (self.rpath_list) |rpath| { try argv.appendSlice(&.{ "-rpath", rpath }); } if (self.pagezero_size) |size| { try argv.append("-pagezero_size"); try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size})); } if (self.headerpad_size) |size| { try argv.append("-headerpad_size"); try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{size})); } if (self.headerpad_max_install_names) { try argv.append("-headerpad_max_install_names"); } if (self.base.gc_sections) { try argv.append("-dead_strip"); } if (self.dead_strip_dylibs) { try argv.append("-dead_strip_dylibs"); } if (self.force_load_objc) { try argv.append("-ObjC"); } if (self.discard_local_symbols) { try argv.append("-x"); } if (self.entry_name) |entry_name| { try argv.appendSlice(&.{ "-e", entry_name }); } try argv.append("-o"); try argv.append(full_out_path); if (self.base.isDynLib() and self.base.allow_shlib_undefined) { try argv.append("-undefined"); try argv.append("dynamic_lookup"); } for (comp.link_inputs) |link_input| switch (link_input) { .dso => continue, // handled below .res => unreachable, // windows only .object, .archive => |obj| { if (obj.must_link) try argv.append("-force_load"); // TODO: verify this try argv.append(try obj.path.toString(arena)); }, .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }), }; for (comp.c_object_table.keys()) |key| { try argv.append(try key.status.success.object_path.toString(arena)); } if (zcu_obj_path) |p| { try argv.append(p); } if (comp.config.any_sanitize_thread) { const path = try comp.tsan_lib.?.full_object_path.toString(arena); try argv.appendSlice(&.{ path, "-rpath", std.fs.path.dirname(path) orelse "." }); } if (comp.config.any_fuzz) { try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena)); } for (self.lib_directories) |lib_directory| { // TODO delete this, directories must instead be resolved by the frontend const arg = try std.fmt.allocPrint(arena, "-L{s}", .{lib_directory.path orelse "."}); try argv.append(arg); } for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive, .dso_exact => continue, // handled above .res => unreachable, // windows only .dso => |dso| { if (dso.needed) { try argv.appendSlice(&.{ "-needed-l", try dso.path.toString(arena) }); } else if (dso.weak) { try argv.appendSlice(&.{ "-weak-l", try dso.path.toString(arena) }); } else { try argv.appendSlice(&.{ "-l", try dso.path.toString(arena) }); } }, }; for (self.framework_dirs) |f_dir| { try argv.append("-F"); try argv.append(f_dir); } for (self.frameworks) |framework| { const name = framework.path.stem(); const arg = if (framework.needed) try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name}) else if (framework.weak) try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name}) else try std.fmt.allocPrint(arena, "-framework {s}", .{name}); try argv.append(arg); } if (comp.config.link_libcpp) { try argv.appendSlice(&.{ try comp.libcxxabi_static_lib.?.full_object_path.toString(arena), try comp.libcxx_static_lib.?.full_object_path.toString(arena), }); } try argv.append("-lSystem"); if (comp.zigc_static_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); if (comp.ubsan_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena)); if (comp.ubsan_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena)); } Compilation.dump_argv(argv.items); } /// TODO delete this, libsystem must be resolved when setting up the compilation pipeline pub fn resolveLibSystem( self: *MachO, arena: Allocator, comp: *Compilation, out_libs: anytype, ) !void { const diags = &self.base.comp.link_diags; var test_path = std.ArrayList(u8).init(arena); var checked_paths = std.ArrayList([]const u8).init(arena); success: { if (self.sdk_layout) |sdk_layout| switch (sdk_layout) { .sdk => { const dir = try fs.path.join(arena, &.{ comp.sysroot.?, "usr", "lib" }); if (try accessLibPath(arena, &test_path, &checked_paths, dir, "System")) break :success; }, .vendored => { const dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "darwin" }); if (try accessLibPath(arena, &test_path, &checked_paths, dir, "System")) break :success; }, }; for (self.lib_directories) |directory| { if (try accessLibPath(arena, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success; } diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{}); return error.MissingLibSystem; } const libsystem_path = Path.initCwd(try arena.dupe(u8, test_path.items)); try out_libs.append(.{ .needed = true, .path = libsystem_path, }); } pub fn classifyInputFile(self: *MachO, input: link.Input) !void { const tracy = trace(@src()); defer tracy.end(); const path, const file = input.pathAndFile().?; // TODO don't classify now, it's too late. The input file has already been classified log.debug("classifying input file {f}", .{path}); const fh = try self.addFileHandle(file); var buffer: [Archive.SARMAG]u8 = undefined; const fat_arch: ?fat.Arch = try self.parseFatFile(file, path); const offset = if (fat_arch) |fa| fa.offset else 0; if (readMachHeader(file, offset) catch null) |h| blk: { if (h.magic != macho.MH_MAGIC_64) break :blk; switch (h.filetype) { macho.MH_OBJECT => try self.addObject(path, fh, offset), macho.MH_DYLIB => _ = try self.addDylib(.fromLinkInput(input), true, fh, offset), else => return error.UnknownFileType, } return; } if (readArMagic(file, offset, &buffer) catch null) |ar_magic| blk: { if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk; try self.addArchive(input.archive, fh, fat_arch); return; } _ = try self.addTbd(.fromLinkInput(input), true, fh); } fn parseFatFile(self: *MachO, file: std.fs.File, path: Path) !?fat.Arch { const diags = &self.base.comp.link_diags; const fat_h = fat.readFatHeader(file) catch return null; if (fat_h.magic != macho.FAT_MAGIC and fat_h.magic != macho.FAT_MAGIC_64) return null; var fat_archs_buffer: [2]fat.Arch = undefined; const fat_archs = try fat.parseArchs(file, fat_h, &fat_archs_buffer); const cpu_arch = self.getTarget().cpu.arch; for (fat_archs) |arch| { if (arch.tag == cpu_arch) return arch; } return diags.failParse(path, "missing arch in universal file: expected {s}", .{@tagName(cpu_arch)}); } pub fn readMachHeader(file: std.fs.File, offset: usize) !macho.mach_header_64 { var buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; const nread = try file.preadAll(&buffer, offset); if (nread != buffer.len) return error.InputOutput; const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(&buffer)).*; return hdr; } pub fn readArMagic(file: std.fs.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { const nread = try file.preadAll(buffer, offset); if (nread != buffer.len) return error.InputOutput; return buffer[0..Archive.SARMAG]; } fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; const mtime: u64 = mtime: { const file = self.getFileHandle(handle); const stat = file.stat() catch break :mtime 0; break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); }; const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .object = .{ .offset = offset, .path = .{ .root_dir = path.root_dir, .sub_path = try gpa.dupe(u8, path.sub_path), }, .file_handle = handle, .mtime = mtime, .index = index, } }); try self.objects.append(gpa, index); } pub fn parseInputFiles(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; { for (self.objects.items) |index| { parseInputFileWorker(self, self.getFile(index).?); } for (self.dylibs.items) |index| { parseInputFileWorker(self, self.getFile(index).?); } } if (diags.hasErrors()) return error.LinkFailure; } fn parseInputFileWorker(self: *MachO, file: File) void { file.parse(self) catch |err| { switch (err) { error.MalformedObject, error.MalformedDylib, error.MalformedTbd, error.InvalidMachineType, error.InvalidTarget, => {}, // already reported else => |e| self.reportParseError2(file.getIndex(), "unexpected error: parsing input file failed with error {s}", .{@errorName(e)}) catch {}, } }; } fn addArchive(self: *MachO, lib: link.Input.Object, handle: File.HandleIndex, fat_arch: ?fat.Arch) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; var archive: Archive = .{}; defer archive.deinit(gpa); try archive.unpack(self, lib.path, handle, fat_arch); for (archive.objects.items) |unpacked| { const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .object = unpacked }); const object = &self.files.items(.data)[index].object; object.index = index; object.alive = lib.must_link; // TODO: or self.options.all_load; object.hidden = lib.hidden; try self.objects.append(gpa, index); } } fn addDylib(self: *MachO, lib: SystemLib, explicit: bool, handle: File.HandleIndex, offset: u64) !File.Index { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .dylib = .{ .offset = offset, .file_handle = handle, .tag = .dylib, .path = .{ .root_dir = lib.path.root_dir, .sub_path = try gpa.dupe(u8, lib.path.sub_path), }, .index = index, .needed = lib.needed, .weak = lib.weak, .reexport = lib.reexport, .explicit = explicit, .umbrella = index, } }); try self.dylibs.append(gpa, index); return index; } fn addTbd(self: *MachO, lib: SystemLib, explicit: bool, handle: File.HandleIndex) !File.Index { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; const index: File.Index = @intCast(try self.files.addOne(gpa)); self.files.set(index, .{ .dylib = .{ .offset = 0, .file_handle = handle, .tag = .tbd, .path = .{ .root_dir = lib.path.root_dir, .sub_path = try gpa.dupe(u8, lib.path.sub_path), }, .index = index, .needed = lib.needed, .weak = lib.weak, .reexport = lib.reexport, .explicit = explicit, .umbrella = index, } }); try self.dylibs.append(gpa, index); return index; } /// According to ld64's manual, public (i.e., system) dylibs/frameworks are hoisted into the final /// image unless overriden by -no_implicit_dylibs. fn isHoisted(self: *MachO, install_name: []const u8) bool { if (self.no_implicit_dylibs) return true; if (fs.path.dirname(install_name)) |dirname| { if (mem.startsWith(u8, dirname, "/usr/lib")) return true; if (eatPrefix(dirname, "/System/Library/Frameworks/")) |path| { const basename = fs.path.basename(install_name); if (mem.indexOfScalar(u8, path, '.')) |index| { if (mem.eql(u8, basename, path[0..index])) return true; } } } return false; } /// TODO delete this, libraries must be instead resolved when instantiating the compilation pipeline fn accessLibPath( arena: Allocator, test_path: *std.ArrayList(u8), checked_paths: *std.ArrayList([]const u8), search_dir: []const u8, name: []const u8, ) !bool { const sep = fs.path.sep_str; for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { test_path.clearRetainingCapacity(); try test_path.writer().print("{s}" ++ sep ++ "lib{s}{s}", .{ search_dir, name, ext }); try checked_paths.append(try arena.dupe(u8, test_path.items)); fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; return true; } return false; } fn accessFrameworkPath( arena: Allocator, test_path: *std.ArrayList(u8), checked_paths: *std.ArrayList([]const u8), search_dir: []const u8, name: []const u8, ) !bool { const sep = fs.path.sep_str; for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { test_path.clearRetainingCapacity(); try test_path.writer().print("{s}" ++ sep ++ "{s}.framework" ++ sep ++ "{s}{s}", .{ search_dir, name, name, ext, }); try checked_paths.append(try arena.dupe(u8, test_path.items)); fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; return true; } return false; } fn parseDependentDylibs(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); if (self.dylibs.items.len == 0) return; const gpa = self.base.comp.gpa; const framework_dirs = self.framework_dirs; // TODO delete this, directories must instead be resolved by the frontend const lib_directories = self.lib_directories; var arena_alloc = std.heap.ArenaAllocator.init(gpa); defer arena_alloc.deinit(); const arena = arena_alloc.allocator(); // TODO handle duplicate dylibs - it is not uncommon to have the same dylib loaded multiple times // in which case we should track that and return File.Index immediately instead re-parsing paths. var has_errors = false; var index: usize = 0; while (index < self.dylibs.items.len) : (index += 1) { const dylib_index = self.dylibs.items[index]; var dependents = std.ArrayList(File.Index).init(gpa); defer dependents.deinit(); try dependents.ensureTotalCapacityPrecise(self.getFile(dylib_index).?.dylib.dependents.items.len); const is_weak = self.getFile(dylib_index).?.dylib.weak; for (self.getFile(dylib_index).?.dylib.dependents.items) |id| { // We will search for the dependent dylibs in the following order: // 1. Basename is in search lib directories or framework directories // 2. If name is an absolute path, search as-is optionally prepending a syslibroot // if specified. // 3. If name is a relative path, substitute @rpath, @loader_path, @executable_path with // dependees list of rpaths, and search there. // 4. Finally, just search the provided relative path directly in CWD. var test_path = std.ArrayList(u8).init(arena); var checked_paths = std.ArrayList([]const u8).init(arena); const full_path = full_path: { { const stem = fs.path.stem(id.name); // Framework for (framework_dirs) |dir| { test_path.clearRetainingCapacity(); if (try accessFrameworkPath(arena, &test_path, &checked_paths, dir, stem)) break :full_path test_path.items; } // Library const lib_name = eatPrefix(stem, "lib") orelse stem; for (lib_directories) |lib_directory| { test_path.clearRetainingCapacity(); if (try accessLibPath(arena, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items; } } if (fs.path.isAbsolute(id.name)) { const existing_ext = fs.path.extension(id.name); const path = if (existing_ext.len > 0) id.name[0 .. id.name.len - existing_ext.len] else id.name; for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { test_path.clearRetainingCapacity(); if (self.base.comp.sysroot) |root| { try test_path.writer().print("{s}" ++ fs.path.sep_str ++ "{s}{s}", .{ root, path, ext }); } else { try test_path.writer().print("{s}{s}", .{ path, ext }); } try checked_paths.append(try arena.dupe(u8, test_path.items)); fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, }; break :full_path test_path.items; } } if (eatPrefix(id.name, "@rpath/")) |path| { const dylib = self.getFile(dylib_index).?.dylib; for (self.getFile(dylib.umbrella).?.dylib.rpaths.keys()) |rpath| { const prefix = eatPrefix(rpath, "@loader_path/") orelse rpath; const rel_path = try fs.path.join(arena, &.{ prefix, path }); try checked_paths.append(rel_path); var buffer: [fs.max_path_bytes]u8 = undefined; const full_path = fs.realpath(rel_path, &buffer) catch continue; break :full_path try arena.dupe(u8, full_path); } } else if (eatPrefix(id.name, "@loader_path/")) |_| { try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name}); return error.Unhandled; } else if (eatPrefix(id.name, "@executable_path/")) |_| { try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name}); return error.Unhandled; } try checked_paths.append(try arena.dupe(u8, id.name)); var buffer: [fs.max_path_bytes]u8 = undefined; if (fs.realpath(id.name, &buffer)) |full_path| { break :full_path try arena.dupe(u8, full_path); } else |_| { try self.reportMissingDependencyError( self.getFile(dylib_index).?.dylib.getUmbrella(self).index, id.name, checked_paths.items, "unable to resolve dependency", .{}, ); has_errors = true; continue; } }; const lib: SystemLib = .{ .path = Path.initCwd(full_path), .weak = is_weak, }; const file = try lib.path.root_dir.handle.openFile(lib.path.sub_path, .{}); const fh = try self.addFileHandle(file); const fat_arch = try self.parseFatFile(file, lib.path); const offset = if (fat_arch) |fa| fa.offset else 0; const file_index = file_index: { if (readMachHeader(file, offset) catch null) |h| blk: { if (h.magic != macho.MH_MAGIC_64) break :blk; switch (h.filetype) { macho.MH_DYLIB => break :file_index try self.addDylib(lib, false, fh, offset), else => break :file_index @as(File.Index, 0), } } break :file_index try self.addTbd(lib, false, fh); }; dependents.appendAssumeCapacity(file_index); } const dylib = self.getFile(dylib_index).?.dylib; for (dylib.dependents.items, dependents.items) |id, file_index| { if (self.getFile(file_index)) |file| { const dep_dylib = file.dylib; try dep_dylib.parse(self); // TODO in parallel dep_dylib.hoisted = self.isHoisted(id.name); dep_dylib.umbrella = dylib.umbrella; if (!dep_dylib.hoisted) { const umbrella = dep_dylib.getUmbrella(self); for (dep_dylib.exports.items(.name), dep_dylib.exports.items(.flags)) |off, flags| { // TODO rethink this entire algorithm try umbrella.addExport(gpa, dep_dylib.getString(off), flags); } try umbrella.rpaths.ensureUnusedCapacity(gpa, dep_dylib.rpaths.keys().len); for (dep_dylib.rpaths.keys()) |rpath| { umbrella.rpaths.putAssumeCapacity(try gpa.dupe(u8, rpath), {}); } } } else try self.reportDependencyError( dylib.getUmbrella(self).index, id.name, "unable to resolve dependency", .{}, ); has_errors = true; } } if (has_errors) return error.MissingLibraryDependencies; } /// When resolving symbols, we approach the problem similarly to `mold`. /// 1. Resolve symbols across all objects (including those preemptively extracted archives). /// 2. Resolve symbols across all shared objects. /// 3. Mark live objects (see `MachO.markLive`) /// 4. Reset state of all resolved globals since we will redo this bit on the pruned set. /// 5. Remove references to dead objects/shared objects /// 6. Re-run symbol resolution on pruned objects and shared objects sets. pub fn resolveSymbols(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); // Resolve symbols in the ZigObject. For now, we assume that it's always live. if (self.getZigObject()) |zo| try zo.asFile().resolveSymbols(self); // Resolve symbols on the set of all objects and shared objects (even if some are unneeded). for (self.objects.items) |index| try self.getFile(index).?.resolveSymbols(self); for (self.dylibs.items) |index| try self.getFile(index).?.resolveSymbols(self); if (self.getInternalObject()) |obj| try obj.resolveSymbols(self); // Mark live objects. self.markLive(); // Reset state of all globals after marking live objects. self.resolver.reset(); // Prune dead objects. var i: usize = 0; while (i < self.objects.items.len) { const index = self.objects.items[i]; if (!self.getFile(index).?.object.alive) { _ = self.objects.orderedRemove(i); self.files.items(.data)[index].object.deinit(self.base.comp.gpa); self.files.set(index, .null); } else i += 1; } // Re-resolve the symbols. if (self.getZigObject()) |zo| try zo.resolveSymbols(self); for (self.objects.items) |index| try self.getFile(index).?.resolveSymbols(self); for (self.dylibs.items) |index| try self.getFile(index).?.resolveSymbols(self); if (self.getInternalObject()) |obj| try obj.resolveSymbols(self); // Merge symbol visibility if (self.getZigObject()) |zo| zo.mergeSymbolVisibility(self); for (self.objects.items) |index| self.getFile(index).?.object.mergeSymbolVisibility(self); } fn markLive(self: *MachO) void { const tracy = trace(@src()); defer tracy.end(); if (self.getZigObject()) |zo| zo.markLive(self); for (self.objects.items) |index| { const object = self.getFile(index).?.object; if (object.alive) object.markLive(self); } if (self.getInternalObject()) |obj| obj.markLive(self); } fn convertTentativeDefsAndResolveSpecialSymbols(self: *MachO) !void { const diags = &self.base.comp.link_diags; { for (self.objects.items) |index| { convertTentativeDefinitionsWorker(self, self.getFile(index).?.object); } if (self.getInternalObject()) |obj| { resolveSpecialSymbolsWorker(self, obj); } } if (diags.hasErrors()) return error.LinkFailure; } fn convertTentativeDefinitionsWorker(self: *MachO, object: *Object) void { const tracy = trace(@src()); defer tracy.end(); object.convertTentativeDefinitions(self) catch |err| { self.reportParseError2( object.index, "unexpected error occurred while converting tentative symbols into defined symbols: {s}", .{@errorName(err)}, ) catch {}; }; } fn resolveSpecialSymbolsWorker(self: *MachO, obj: *InternalObject) void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; obj.resolveBoundarySymbols(self) catch |err| return diags.addError("failed to resolve boundary symbols: {s}", .{@errorName(err)}); obj.resolveObjcMsgSendSymbols(self) catch |err| return diags.addError("failed to resolve ObjC msgsend stubs: {s}", .{@errorName(err)}); } pub fn dedupLiterals(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; var lp: LiteralPool = .{}; defer lp.deinit(gpa); if (self.getZigObject()) |zo| { try zo.resolveLiterals(&lp, self); } for (self.objects.items) |index| { try self.getFile(index).?.object.resolveLiterals(&lp, self); } if (self.getInternalObject()) |object| { try object.resolveLiterals(&lp, self); } { if (self.getZigObject()) |zo| { File.dedupLiterals(zo.asFile(), lp, self); } for (self.objects.items) |index| { File.dedupLiterals(self.getFile(index).?, lp, self); } if (self.getInternalObject()) |object| { File.dedupLiterals(object.asFile(), lp, self); } } } fn claimUnresolved(self: *MachO) void { if (self.getZigObject()) |zo| { zo.asFile().claimUnresolved(self); } for (self.objects.items) |index| { self.getFile(index).?.claimUnresolved(self); } } fn checkDuplicates(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; { if (self.getZigObject()) |zo| { checkDuplicatesWorker(self, zo.asFile()); } for (self.objects.items) |index| { checkDuplicatesWorker(self, self.getFile(index).?); } if (self.getInternalObject()) |obj| { checkDuplicatesWorker(self, obj.asFile()); } } if (diags.hasErrors()) return error.LinkFailure; try self.reportDuplicates(); } fn checkDuplicatesWorker(self: *MachO, file: File) void { const tracy = trace(@src()); defer tracy.end(); file.checkDuplicates(self) catch |err| { self.reportParseError2(file.getIndex(), "failed to check for duplicate definitions: {s}", .{ @errorName(err), }) catch {}; }; } fn markImportsAndExports(self: *MachO) void { const tracy = trace(@src()); defer tracy.end(); if (self.getZigObject()) |zo| { zo.asFile().markImportsExports(self); } for (self.objects.items) |index| { self.getFile(index).?.markImportsExports(self); } if (self.getInternalObject()) |obj| { obj.asFile().markImportsExports(self); } } fn deadStripDylibs(self: *MachO) void { const tracy = trace(@src()); defer tracy.end(); for (self.dylibs.items) |index| { self.getFile(index).?.dylib.markReferenced(self); } var i: usize = 0; while (i < self.dylibs.items.len) { const index = self.dylibs.items[i]; if (!self.getFile(index).?.dylib.isAlive(self)) { _ = self.dylibs.orderedRemove(i); self.files.items(.data)[index].dylib.deinit(self.base.comp.gpa); self.files.set(index, .null); } else i += 1; } } fn scanRelocs(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; { if (self.getZigObject()) |zo| { scanRelocsWorker(self, zo.asFile()); } for (self.objects.items) |index| { scanRelocsWorker(self, self.getFile(index).?); } if (self.getInternalObject()) |obj| { scanRelocsWorker(self, obj.asFile()); } } if (diags.hasErrors()) return error.LinkFailure; if (self.getInternalObject()) |obj| { try obj.checkUndefs(self); } try self.reportUndefs(); if (self.getZigObject()) |zo| { try zo.asFile().createSymbolIndirection(self); } for (self.objects.items) |index| { try self.getFile(index).?.createSymbolIndirection(self); } for (self.dylibs.items) |index| { try self.getFile(index).?.createSymbolIndirection(self); } if (self.getInternalObject()) |obj| { try obj.asFile().createSymbolIndirection(self); } } fn scanRelocsWorker(self: *MachO, file: File) void { file.scanRelocs(self) catch |err| { self.reportParseError2(file.getIndex(), "failed to scan relocations: {s}", .{ @errorName(err), }) catch {}; }; } fn sortGlobalSymbolsByName(self: *MachO, symbols: []SymbolResolver.Index) void { const lessThan = struct { fn lessThan(ctx: *MachO, lhs: SymbolResolver.Index, rhs: SymbolResolver.Index) bool { const lhs_name = ctx.resolver.keys.items[lhs - 1].getName(ctx); const rhs_name = ctx.resolver.keys.items[rhs - 1].getName(ctx); return mem.order(u8, lhs_name, rhs_name) == .lt; } }.lessThan; mem.sort(SymbolResolver.Index, symbols, self, lessThan); } fn reportUndefs(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); if (self.undefined_treatment == .suppress or self.undefined_treatment == .dynamic_lookup) return; if (self.undefs.keys().len == 0) return; // Nothing to do const gpa = self.base.comp.gpa; const diags = &self.base.comp.link_diags; const max_notes = 4; // We will sort by name, and then by file to ensure deterministic output. var keys = try std.ArrayList(SymbolResolver.Index).initCapacity(gpa, self.undefs.keys().len); defer keys.deinit(); keys.appendSliceAssumeCapacity(self.undefs.keys()); self.sortGlobalSymbolsByName(keys.items); const refLessThan = struct { fn lessThan(ctx: void, lhs: Ref, rhs: Ref) bool { _ = ctx; return lhs.lessThan(rhs); } }.lessThan; for (self.undefs.values()) |*undefs| switch (undefs.*) { .refs => |refs| mem.sort(Ref, refs.items, {}, refLessThan), else => {}, }; for (keys.items) |key| { const undef_sym = self.resolver.keys.items[key - 1]; const notes = self.undefs.get(key).?; const nnotes = nnotes: { const nnotes = switch (notes) { .refs => |refs| refs.items.len, else => 1, }; break :nnotes @min(nnotes, max_notes) + @intFromBool(nnotes > max_notes); }; var err = try diags.addErrorWithNotes(nnotes); try err.addMsg("undefined symbol: {s}", .{undef_sym.getName(self)}); switch (notes) { .force_undefined => err.addNote("referenced with linker flag -u", .{}), .entry => err.addNote("referenced with linker flag -e", .{}), .dyld_stub_binder, .objc_msgsend => err.addNote("referenced implicitly", .{}), .refs => |refs| { var inote: usize = 0; while (inote < @min(refs.items.len, max_notes)) : (inote += 1) { const ref = refs.items[inote]; const file = self.getFile(ref.file).?; const atom = ref.getAtom(self).?; err.addNote("referenced by {f}:{s}", .{ file.fmtPath(), atom.getName(self) }); } if (refs.items.len > max_notes) { const remaining = refs.items.len - max_notes; err.addNote("referenced {d} more times", .{remaining}); } }, } } return error.HasUndefinedSymbols; } fn initOutputSections(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); for (self.objects.items) |index| { try self.getFile(index).?.initOutputSections(self); } if (self.getInternalObject()) |obj| { try obj.asFile().initOutputSections(self); } self.text_sect_index = self.getSectionByName("__TEXT", "__text") orelse try self.addSection("__TEXT", "__text", .{ .alignment = switch (self.getTarget().cpu.arch) { .x86_64 => 0, .aarch64 => 2, else => unreachable, }, .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, }); self.data_sect_index = self.getSectionByName("__DATA", "__data") orelse try self.addSection("__DATA", "__data", .{}); } fn initSyntheticSections(self: *MachO) !void { const cpu_arch = self.getTarget().cpu.arch; if (self.got.symbols.items.len > 0) { self.got_sect_index = try self.addSection("__DATA_CONST", "__got", .{ .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, .reserved1 = @intCast(self.stubs.symbols.items.len), }); } if (self.stubs.symbols.items.len > 0) { self.stubs_sect_index = try self.addSection("__TEXT", "__stubs", .{ .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, .reserved1 = 0, .reserved2 = switch (cpu_arch) { .x86_64 => 6, .aarch64 => 3 * @sizeOf(u32), else => 0, }, }); self.stubs_helper_sect_index = try self.addSection("__TEXT", "__stub_helper", .{ .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, }); self.la_symbol_ptr_sect_index = try self.addSection("__DATA", "__la_symbol_ptr", .{ .flags = macho.S_LAZY_SYMBOL_POINTERS, .reserved1 = @intCast(self.stubs.symbols.items.len + self.got.symbols.items.len), }); } if (self.objc_stubs.symbols.items.len > 0) { self.objc_stubs_sect_index = try self.addSection("__TEXT", "__objc_stubs", .{ .flags = macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, }); } if (self.tlv_ptr.symbols.items.len > 0) { self.tlv_ptr_sect_index = try self.addSection("__DATA", "__thread_ptrs", .{ .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS, }); } const needs_unwind_info = for (self.objects.items) |index| { if (self.getFile(index).?.object.hasUnwindRecords()) break true; } else false; if (needs_unwind_info) { self.unwind_info_sect_index = try self.addSection("__TEXT", "__unwind_info", .{}); } const needs_eh_frame = for (self.objects.items) |index| { if (self.getFile(index).?.object.hasEhFrameRecords()) break true; } else false; if (needs_eh_frame) { assert(needs_unwind_info); self.eh_frame_sect_index = try self.addSection("__TEXT", "__eh_frame", .{}); } if (self.getInternalObject()) |obj| { const gpa = self.base.comp.gpa; for (obj.boundary_symbols.items) |sym_index| { const ref = obj.getSymbolRef(sym_index, self); const sym = ref.getSymbol(self).?; const name = sym.getName(self); if (eatPrefix(name, "segment$start$")) |segname| { if (self.getSegmentByName(segname) == null) { // TODO check segname is valid const prot = getSegmentProt(segname); _ = try self.segments.append(gpa, .{ .cmdsize = @sizeOf(macho.segment_command_64), .segname = makeStaticString(segname), .initprot = prot, .maxprot = prot, }); } } else if (eatPrefix(name, "segment$end$")) |segname| { if (self.getSegmentByName(segname) == null) { // TODO check segname is valid const prot = getSegmentProt(segname); _ = try self.segments.append(gpa, .{ .cmdsize = @sizeOf(macho.segment_command_64), .segname = makeStaticString(segname), .initprot = prot, .maxprot = prot, }); } } else if (eatPrefix(name, "section$start$")) |actual_name| { const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic const segname = actual_name[0..sep]; // TODO check segname is valid const sectname = actual_name[sep + 1 ..]; // TODO check sectname is valid if (self.getSectionByName(segname, sectname) == null) { _ = try self.addSection(segname, sectname, .{}); } } else if (eatPrefix(name, "section$end$")) |actual_name| { const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic const segname = actual_name[0..sep]; // TODO check segname is valid const sectname = actual_name[sep + 1 ..]; // TODO check sectname is valid if (self.getSectionByName(segname, sectname) == null) { _ = try self.addSection(segname, sectname, .{}); } } else unreachable; } } } fn getSegmentProt(segname: []const u8) macho.vm_prot_t { if (mem.eql(u8, segname, "__PAGEZERO")) return macho.PROT.NONE; if (mem.eql(u8, segname, "__TEXT")) return macho.PROT.READ | macho.PROT.EXEC; if (mem.eql(u8, segname, "__LINKEDIT")) return macho.PROT.READ; return macho.PROT.READ | macho.PROT.WRITE; } fn getSegmentRank(segname: []const u8) u8 { if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0; if (mem.eql(u8, segname, "__LINKEDIT")) return 0xf; if (mem.indexOf(u8, segname, "ZIG")) |_| return 0xe; if (mem.startsWith(u8, segname, "__TEXT")) return 0x1; if (mem.startsWith(u8, segname, "__DATA_CONST")) return 0x2; if (mem.startsWith(u8, segname, "__DATA")) return 0x3; return 0x4; } fn segmentLessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool { _ = ctx; const lhs_rank = getSegmentRank(lhs); const rhs_rank = getSegmentRank(rhs); if (lhs_rank == rhs_rank) { return mem.order(u8, lhs, rhs) == .lt; } return lhs_rank < rhs_rank; } fn getSectionRank(section: macho.section_64) u8 { if (section.isCode()) { if (mem.eql(u8, "__text", section.sectName())) return 0x0; if (section.type() == macho.S_SYMBOL_STUBS) return 0x1; return 0x2; } switch (section.type()) { macho.S_NON_LAZY_SYMBOL_POINTERS, macho.S_LAZY_SYMBOL_POINTERS, => return 0x0, macho.S_MOD_INIT_FUNC_POINTERS => return 0x1, macho.S_MOD_TERM_FUNC_POINTERS => return 0x2, macho.S_ZEROFILL => return 0xf, macho.S_THREAD_LOCAL_REGULAR => return 0xd, macho.S_THREAD_LOCAL_ZEROFILL => return 0xe, else => { if (mem.eql(u8, "__unwind_info", section.sectName())) return 0xe; if (mem.eql(u8, "__compact_unwind", section.sectName())) return 0xe; if (mem.eql(u8, "__eh_frame", section.sectName())) return 0xf; return 0x3; }, } } fn sectionLessThan(ctx: void, lhs: macho.section_64, rhs: macho.section_64) bool { if (mem.eql(u8, lhs.segName(), rhs.segName())) { const lhs_rank = getSectionRank(lhs); const rhs_rank = getSectionRank(rhs); if (lhs_rank == rhs_rank) { return mem.order(u8, lhs.sectName(), rhs.sectName()) == .lt; } return lhs_rank < rhs_rank; } return segmentLessThan(ctx, lhs.segName(), rhs.segName()); } pub fn sortSections(self: *MachO) !void { const Entry = struct { index: u8, pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool { return sectionLessThan( {}, macho_file.sections.items(.header)[lhs.index], macho_file.sections.items(.header)[rhs.index], ); } }; const gpa = self.base.comp.gpa; var entries = try std.ArrayList(Entry).initCapacity(gpa, self.sections.slice().len); defer entries.deinit(); for (0..self.sections.slice().len) |index| { entries.appendAssumeCapacity(.{ .index = @intCast(index) }); } mem.sort(Entry, entries.items, self, Entry.lessThan); const backlinks = try gpa.alloc(u8, entries.items.len); defer gpa.free(backlinks); for (entries.items, 0..) |entry, i| { backlinks[entry.index] = @intCast(i); } var slice = self.sections.toOwnedSlice(); defer slice.deinit(gpa); try self.sections.ensureTotalCapacity(gpa, slice.len); for (entries.items) |sorted| { self.sections.appendAssumeCapacity(slice.get(sorted.index)); } for (&[_]*?u8{ &self.data_sect_index, &self.got_sect_index, &self.zig_text_sect_index, &self.zig_const_sect_index, &self.zig_data_sect_index, &self.zig_bss_sect_index, &self.stubs_sect_index, &self.stubs_helper_sect_index, &self.la_symbol_ptr_sect_index, &self.tlv_ptr_sect_index, &self.eh_frame_sect_index, &self.unwind_info_sect_index, &self.objc_stubs_sect_index, &self.debug_str_sect_index, &self.debug_info_sect_index, &self.debug_abbrev_sect_index, &self.debug_aranges_sect_index, &self.debug_line_sect_index, &self.debug_line_str_sect_index, &self.debug_loclists_sect_index, &self.debug_rnglists_sect_index, }) |maybe_index| { if (maybe_index.*) |*index| { index.* = backlinks[index.*]; } } if (self.getZigObject()) |zo| { for (zo.getAtoms()) |atom_index| { const atom = zo.getAtom(atom_index) orelse continue; if (!atom.isAlive()) continue; atom.out_n_sect = backlinks[atom.out_n_sect]; } if (zo.dwarf) |*dwarf| dwarf.reloadSectionMetadata(); } for (self.objects.items) |index| { const file = self.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; if (!atom.isAlive()) continue; atom.out_n_sect = backlinks[atom.out_n_sect]; } } if (self.getInternalObject()) |object| { for (object.getAtoms()) |atom_index| { const atom = object.getAtom(atom_index) orelse continue; if (!atom.isAlive()) continue; atom.out_n_sect = backlinks[atom.out_n_sect]; } } } pub fn addAtomsToSections(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; if (self.getZigObject()) |zo| { for (zo.getAtoms()) |atom_index| { const atom = zo.getAtom(atom_index) orelse continue; if (!atom.isAlive()) continue; if (self.isZigSection(atom.out_n_sect)) continue; const atoms = &self.sections.items(.atoms)[atom.out_n_sect]; try atoms.append(gpa, .{ .index = atom_index, .file = zo.index }); } } for (self.objects.items) |index| { const file = self.getFile(index).?; for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; if (!atom.isAlive()) continue; const atoms = &self.sections.items(.atoms)[atom.out_n_sect]; try atoms.append(gpa, .{ .index = atom_index, .file = index }); } } if (self.getInternalObject()) |object| { for (object.getAtoms()) |atom_index| { const atom = object.getAtom(atom_index) orelse continue; if (!atom.isAlive()) continue; const atoms = &self.sections.items(.atoms)[atom.out_n_sect]; try atoms.append(gpa, .{ .index = atom_index, .file = object.index }); } } } fn calcSectionSizes(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; const cpu_arch = self.getTarget().cpu.arch; if (self.data_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; header.size += @sizeOf(u64); header.@"align" = 3; } { const slice = self.sections.slice(); for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| { if (atoms.items.len == 0) continue; if (self.requiresThunks() and header.isCode()) continue; calcSectionSizeWorker(self, @as(u8, @intCast(i))); } if (self.requiresThunks()) { for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| { if (!header.isCode()) continue; if (atoms.items.len == 0) continue; createThunksWorker(self, @as(u8, @intCast(i))); } } // At this point, we can also calculate most of the symtab and data-in-code linkedit section sizes if (self.getZigObject()) |zo| { File.calcSymtabSize(zo.asFile(), self); } for (self.objects.items) |index| { File.calcSymtabSize(self.getFile(index).?, self); } for (self.dylibs.items) |index| { File.calcSymtabSize(self.getFile(index).?, self); } if (self.getInternalObject()) |obj| { File.calcSymtabSize(obj.asFile(), self); } } if (diags.hasErrors()) return error.LinkFailure; try self.calcSymtabSize(); if (self.got_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; header.size = self.got.size(); header.@"align" = 3; } if (self.stubs_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; header.size = self.stubs.size(self); header.@"align" = switch (cpu_arch) { .x86_64 => 1, .aarch64 => 2, else => 0, }; } if (self.stubs_helper_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; header.size = self.stubs_helper.size(self); header.@"align" = 2; } if (self.la_symbol_ptr_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; header.size = self.la_symbol_ptr.size(self); header.@"align" = 3; } if (self.tlv_ptr_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; header.size = self.tlv_ptr.size(); header.@"align" = 3; } if (self.objc_stubs_sect_index) |idx| { const header = &self.sections.items(.header)[idx]; header.size = self.objc_stubs.size(self); header.@"align" = switch (cpu_arch) { .x86_64 => 0, .aarch64 => 2, else => 0, }; } } fn calcSectionSizeWorker(self: *MachO, sect_id: u8) void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; const doWork = struct { fn doWork(macho_file: *MachO, header: *macho.section_64, atoms: []const Ref) !void { for (atoms) |ref| { const atom = ref.getAtom(macho_file).?; const atom_alignment = atom.alignment.toByteUnits() orelse 1; const offset = mem.alignForward(u64, header.size, atom_alignment); const padding = offset - header.size; atom.value = offset; header.size += padding + atom.size; header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); } } }.doWork; const slice = self.sections.slice(); const header = &slice.items(.header)[sect_id]; const atoms = slice.items(.atoms)[sect_id].items; doWork(self, header, atoms) catch |err| { try diags.addError("failed to calculate size of section '{s},{s}': {s}", .{ header.segName(), header.sectName(), @errorName(err), }); }; } fn createThunksWorker(self: *MachO, sect_id: u8) void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; self.createThunks(sect_id) catch |err| { const header = self.sections.items(.header)[sect_id]; diags.addError("failed to create thunks and calculate size of section '{s},{s}': {s}", .{ header.segName(), header.sectName(), @errorName(err), }); }; } fn generateUnwindInfo(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; if (self.eh_frame_sect_index) |index| { const sect = &self.sections.items(.header)[index]; sect.size = try eh_frame.calcSize(self); sect.@"align" = 3; } if (self.unwind_info_sect_index) |index| { const sect = &self.sections.items(.header)[index]; self.unwind_info.generate(self) catch |err| switch (err) { error.TooManyPersonalities => return diags.fail("too many personalities in unwind info", .{}), else => |e| return e, }; sect.size = self.unwind_info.calcSize(); sect.@"align" = 2; } } fn initSegments(self: *MachO) !void { const gpa = self.base.comp.gpa; const slice = self.sections.slice(); // Add __PAGEZERO if required const pagezero_size = self.pagezero_size orelse default_pagezero_size; const aligned_pagezero_size = mem.alignBackward(u64, pagezero_size, self.getPageSize()); if (!self.base.isDynLib() and aligned_pagezero_size > 0) { if (aligned_pagezero_size != pagezero_size) { // TODO convert into a warning log.warn("requested __PAGEZERO size (0x{x}) is not page aligned", .{pagezero_size}); log.warn(" rounding down to 0x{x}", .{aligned_pagezero_size}); } self.pagezero_seg_index = try self.addSegment("__PAGEZERO", .{ .vmsize = aligned_pagezero_size }); } // __TEXT segment is non-optional self.text_seg_index = try self.addSegment("__TEXT", .{ .prot = getSegmentProt("__TEXT") }); // Next, create segments required by sections for (slice.items(.header)) |header| { const segname = header.segName(); if (self.getSegmentByName(segname) == null) { _ = try self.addSegment(segname, .{ .prot = getSegmentProt(segname) }); } } // Add __LINKEDIT self.linkedit_seg_index = try self.addSegment("__LINKEDIT", .{ .prot = getSegmentProt("__LINKEDIT") }); // Sort segments const Entry = struct { index: u8, pub fn lessThan(macho_file: *MachO, lhs: @This(), rhs: @This()) bool { return segmentLessThan( {}, macho_file.segments.items[lhs.index].segName(), macho_file.segments.items[rhs.index].segName(), ); } }; var entries = try std.ArrayList(Entry).initCapacity(gpa, self.segments.items.len); defer entries.deinit(); for (0..self.segments.items.len) |index| { entries.appendAssumeCapacity(.{ .index = @intCast(index) }); } mem.sort(Entry, entries.items, self, Entry.lessThan); const backlinks = try gpa.alloc(u8, entries.items.len); defer gpa.free(backlinks); for (entries.items, 0..) |entry, i| { backlinks[entry.index] = @intCast(i); } const segments = try self.segments.toOwnedSlice(gpa); defer gpa.free(segments); try self.segments.ensureTotalCapacityPrecise(gpa, segments.len); for (entries.items) |sorted| { self.segments.appendAssumeCapacity(segments[sorted.index]); } for (&[_]*?u8{ &self.pagezero_seg_index, &self.text_seg_index, &self.linkedit_seg_index, &self.zig_text_seg_index, &self.zig_const_seg_index, &self.zig_data_seg_index, &self.zig_bss_seg_index, }) |maybe_index| { if (maybe_index.*) |*index| { index.* = backlinks[index.*]; } } // Attach sections to segments for (slice.items(.header), slice.items(.segment_id)) |header, *seg_id| { const segname = header.segName(); const segment_id = self.getSegmentByName(segname) orelse blk: { const segment_id = @as(u8, @intCast(self.segments.items.len)); const protection = getSegmentProt(segname); try self.segments.append(gpa, .{ .cmdsize = @sizeOf(macho.segment_command_64), .segname = makeStaticString(segname), .maxprot = protection, .initprot = protection, }); break :blk segment_id; }; const segment = &self.segments.items[segment_id]; segment.cmdsize += @sizeOf(macho.section_64); segment.nsects += 1; seg_id.* = segment_id; } // Set __DATA_CONST as READ_ONLY if (self.getSegmentByName("__DATA_CONST")) |seg_id| { const seg = &self.segments.items[seg_id]; seg.flags |= macho.SG_READ_ONLY; } } fn allocateSections(self: *MachO) !void { const headerpad = try load_commands.calcMinHeaderPadSize(self); var vmaddr: u64 = if (self.pagezero_seg_index) |index| self.segments.items[index].vmaddr + self.segments.items[index].vmsize else 0; vmaddr += headerpad; var fileoff = headerpad; var prev_seg_id: u8 = if (self.pagezero_seg_index) |index| index + 1 else 0; const page_size = self.getPageSize(); const slice = self.sections.slice(); const last_index = for (0..slice.items(.header).len) |i| { if (self.isZigSection(@intCast(i))) break i; } else slice.items(.header).len; for (slice.items(.header)[0..last_index], slice.items(.segment_id)[0..last_index]) |*header, curr_seg_id| { if (prev_seg_id != curr_seg_id) { vmaddr = mem.alignForward(u64, vmaddr, page_size); fileoff = mem.alignForward(u32, fileoff, page_size); } const alignment = try self.alignPow(header.@"align"); vmaddr = mem.alignForward(u64, vmaddr, alignment); header.addr = vmaddr; vmaddr += header.size; if (!header.isZerofill()) { fileoff = mem.alignForward(u32, fileoff, alignment); header.offset = fileoff; fileoff += @intCast(header.size); } prev_seg_id = curr_seg_id; } fileoff = mem.alignForward(u32, fileoff, page_size); for (slice.items(.header)[last_index..], slice.items(.segment_id)[last_index..]) |*header, seg_id| { if (header.isZerofill()) continue; if (header.offset < fileoff) { const existing_size = header.size; header.size = 0; // Must move the entire section. const new_offset = try self.findFreeSpace(existing_size, page_size); log.debug("moving '{s},{s}' from 0x{x} to 0x{x}", .{ header.segName(), header.sectName(), header.offset, new_offset, }); try self.copyRangeAllZeroOut(header.offset, new_offset, existing_size); header.offset = @intCast(new_offset); header.size = existing_size; self.segments.items[seg_id].fileoff = new_offset; } } } /// We allocate segments in a separate step to also consider segments that have no sections. fn allocateSegments(self: *MachO) void { const first_index = if (self.pagezero_seg_index) |index| index + 1 else 0; const last_index = for (0..self.segments.items.len) |i| { if (self.isZigSegment(@intCast(i))) break i; } else self.segments.items.len; var vmaddr: u64 = if (self.pagezero_seg_index) |index| self.segments.items[index].vmaddr + self.segments.items[index].vmsize else 0; var fileoff: u64 = 0; const page_size = self.getPageSize(); const slice = self.sections.slice(); var next_sect_id: u8 = 0; for (self.segments.items[first_index..last_index], first_index..last_index) |*seg, seg_id| { seg.vmaddr = vmaddr; seg.fileoff = fileoff; while (next_sect_id < slice.items(.header).len) : (next_sect_id += 1) { const header = slice.items(.header)[next_sect_id]; const sid = slice.items(.segment_id)[next_sect_id]; if (seg_id != sid) break; vmaddr = header.addr + header.size; if (!header.isZerofill()) { fileoff = header.offset + header.size; } } seg.vmsize = vmaddr - seg.vmaddr; seg.filesize = fileoff - seg.fileoff; vmaddr = mem.alignForward(u64, vmaddr, page_size); fileoff = mem.alignForward(u64, fileoff, page_size); } } fn allocateSyntheticSymbols(self: *MachO) void { if (self.getInternalObject()) |obj| { obj.allocateSyntheticSymbols(self); const text_seg = self.getTextSegment(); for (obj.boundary_symbols.items) |sym_index| { const ref = obj.getSymbolRef(sym_index, self); const sym = ref.getSymbol(self).?; const name = sym.getName(self); sym.value = text_seg.vmaddr; if (mem.startsWith(u8, name, "segment$start$")) { const segname = name["segment$start$".len..]; if (self.getSegmentByName(segname)) |seg_id| { const seg = self.segments.items[seg_id]; sym.value = seg.vmaddr; } } else if (mem.startsWith(u8, name, "segment$end$")) { const segname = name["segment$end$".len..]; if (self.getSegmentByName(segname)) |seg_id| { const seg = self.segments.items[seg_id]; sym.value = seg.vmaddr + seg.vmsize; } } else if (mem.startsWith(u8, name, "section$start$")) { const actual_name = name["section$start$".len..]; const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic const segname = actual_name[0..sep]; const sectname = actual_name[sep + 1 ..]; if (self.getSectionByName(segname, sectname)) |sect_id| { const sect = self.sections.items(.header)[sect_id]; sym.value = sect.addr; sym.out_n_sect = sect_id; } } else if (mem.startsWith(u8, name, "section$end$")) { const actual_name = name["section$end$".len..]; const sep = mem.indexOfScalar(u8, actual_name, '$').?; // TODO error rather than a panic const segname = actual_name[0..sep]; const sectname = actual_name[sep + 1 ..]; if (self.getSectionByName(segname, sectname)) |sect_id| { const sect = self.sections.items(.header)[sect_id]; sym.value = sect.addr + sect.size; sym.out_n_sect = sect_id; } } else unreachable; } if (self.objc_stubs.symbols.items.len > 0) { const addr = self.sections.items(.header)[self.objc_stubs_sect_index.?].addr; for (self.objc_stubs.symbols.items, 0..) |ref, idx| { const sym = ref.getSymbol(self).?; sym.value = addr + idx * ObjcStubsSection.entrySize(self.getTarget().cpu.arch); sym.out_n_sect = self.objc_stubs_sect_index.?; } } } } fn allocateLinkeditSegment(self: *MachO) !void { var fileoff: u64 = 0; var vmaddr: u64 = 0; for (self.segments.items) |seg| { if (fileoff < seg.fileoff + seg.filesize) fileoff = seg.fileoff + seg.filesize; if (vmaddr < seg.vmaddr + seg.vmsize) vmaddr = seg.vmaddr + seg.vmsize; } const page_size = self.getPageSize(); const seg = self.getLinkeditSegment(); seg.vmaddr = mem.alignForward(u64, vmaddr, page_size); seg.fileoff = mem.alignForward(u64, fileoff, page_size); var off = try self.cast(u32, seg.fileoff); // DYLD_INFO_ONLY { const cmd = &self.dyld_info_cmd; cmd.rebase_off = off; off += cmd.rebase_size; cmd.bind_off = off; off += cmd.bind_size; cmd.weak_bind_off = off; off += cmd.weak_bind_size; cmd.lazy_bind_off = off; off += cmd.lazy_bind_size; cmd.export_off = off; off += cmd.export_size; off = mem.alignForward(u32, off, @alignOf(u64)); } // FUNCTION_STARTS { const cmd = &self.function_starts_cmd; cmd.dataoff = off; off += cmd.datasize; off = mem.alignForward(u32, off, @alignOf(u64)); } // DATA_IN_CODE { const cmd = &self.data_in_code_cmd; cmd.dataoff = off; off += cmd.datasize; off = mem.alignForward(u32, off, @alignOf(u64)); } // SYMTAB (symtab) { const cmd = &self.symtab_cmd; cmd.symoff = off; off += cmd.nsyms * @sizeOf(macho.nlist_64); off = mem.alignForward(u32, off, @alignOf(u32)); } // DYSYMTAB { const cmd = &self.dysymtab_cmd; cmd.indirectsymoff = off; off += cmd.nindirectsyms * @sizeOf(u32); off = mem.alignForward(u32, off, @alignOf(u64)); } // SYMTAB (strtab) { const cmd = &self.symtab_cmd; cmd.stroff = off; off += cmd.strsize; } seg.filesize = off - seg.fileoff; } fn resizeSections(self: *MachO) !void { const slice = self.sections.slice(); for (slice.items(.header), slice.items(.out), 0..) |header, *out, n_sect| { if (header.isZerofill()) continue; if (self.isZigSection(@intCast(n_sect))) continue; // TODO this is horrible const cpu_arch = self.getTarget().cpu.arch; const size = try self.cast(usize, header.size); try out.resize(self.base.comp.gpa, size); const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0; @memset(out.items, padding_byte); } } fn writeSectionsAndUpdateLinkeditSizes(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; const diags = &self.base.comp.link_diags; const cmd = self.symtab_cmd; try self.symtab.resize(gpa, cmd.nsyms); try self.strtab.resize(gpa, cmd.strsize); self.strtab.items[0] = 0; { for (self.objects.items) |index| { writeAtomsWorker(self, self.getFile(index).?); } if (self.getZigObject()) |zo| { writeAtomsWorker(self, zo.asFile()); } if (self.getInternalObject()) |obj| { writeAtomsWorker(self, obj.asFile()); } for (self.thunks.items) |thunk| { writeThunkWorker(self, thunk); } const slice = self.sections.slice(); for (&[_]?u8{ self.eh_frame_sect_index, self.unwind_info_sect_index, self.got_sect_index, self.stubs_sect_index, self.la_symbol_ptr_sect_index, self.tlv_ptr_sect_index, self.objc_stubs_sect_index, }) |maybe_sect_id| { if (maybe_sect_id) |sect_id| { const out = slice.items(.out)[sect_id].items; writeSyntheticSectionWorker(self, sect_id, out); } } if (self.la_symbol_ptr_sect_index) |_| { updateLazyBindSizeWorker(self); } updateLinkeditSizeWorker(self, .rebase); updateLinkeditSizeWorker(self, .bind); updateLinkeditSizeWorker(self, .weak_bind); updateLinkeditSizeWorker(self, .export_trie); updateLinkeditSizeWorker(self, .data_in_code); if (self.getZigObject()) |zo| { File.writeSymtab(zo.asFile(), self, self); } for (self.objects.items) |index| { File.writeSymtab(self.getFile(index).?, self, self); } for (self.dylibs.items) |index| { File.writeSymtab(self.getFile(index).?, self, self); } if (self.getInternalObject()) |obj| { File.writeSymtab(obj.asFile(), self, self); } if (self.requiresThunks()) for (self.thunks.items) |th| { Thunk.writeSymtab(th, self, self); }; } if (diags.hasErrors()) return error.LinkFailure; } fn writeAtomsWorker(self: *MachO, file: File) void { const tracy = trace(@src()); defer tracy.end(); file.writeAtoms(self) catch |err| { self.reportParseError2(file.getIndex(), "failed to resolve relocations and write atoms: {s}", .{ @errorName(err), }) catch {}; }; } fn writeThunkWorker(self: *MachO, thunk: Thunk) void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; const doWork = struct { fn doWork(th: Thunk, buffer: []u8, macho_file: *MachO) !void { const off = try macho_file.cast(usize, th.value); const size = th.size(); var stream = std.io.fixedBufferStream(buffer[off..][0..size]); try th.write(macho_file, stream.writer()); } }.doWork; const out = self.sections.items(.out)[thunk.out_n_sect].items; doWork(thunk, out, self) catch |err| { diags.addError("failed to write contents of thunk: {s}", .{@errorName(err)}); }; } fn writeSyntheticSectionWorker(self: *MachO, sect_id: u8, out: []u8) void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; const Tag = enum { eh_frame, unwind_info, got, stubs, la_symbol_ptr, tlv_ptr, objc_stubs, }; const doWork = struct { fn doWork(macho_file: *MachO, tag: Tag, buffer: []u8) !void { var stream = std.io.fixedBufferStream(buffer); switch (tag) { .eh_frame => eh_frame.write(macho_file, buffer), .unwind_info => try macho_file.unwind_info.write(macho_file, buffer), .got => try macho_file.got.write(macho_file, stream.writer()), .stubs => try macho_file.stubs.write(macho_file, stream.writer()), .la_symbol_ptr => try macho_file.la_symbol_ptr.write(macho_file, stream.writer()), .tlv_ptr => try macho_file.tlv_ptr.write(macho_file, stream.writer()), .objc_stubs => try macho_file.objc_stubs.write(macho_file, stream.writer()), } } }.doWork; const header = self.sections.items(.header)[sect_id]; const tag: Tag = tag: { if (self.eh_frame_sect_index != null and self.eh_frame_sect_index.? == sect_id) break :tag .eh_frame; if (self.unwind_info_sect_index != null and self.unwind_info_sect_index.? == sect_id) break :tag .unwind_info; if (self.got_sect_index != null and self.got_sect_index.? == sect_id) break :tag .got; if (self.stubs_sect_index != null and self.stubs_sect_index.? == sect_id) break :tag .stubs; if (self.la_symbol_ptr_sect_index != null and self.la_symbol_ptr_sect_index.? == sect_id) break :tag .la_symbol_ptr; if (self.tlv_ptr_sect_index != null and self.tlv_ptr_sect_index.? == sect_id) break :tag .tlv_ptr; if (self.objc_stubs_sect_index != null and self.objc_stubs_sect_index.? == sect_id) break :tag .objc_stubs; unreachable; }; doWork(self, tag, out) catch |err| { diags.addError("could not write section '{s},{s}': {s}", .{ header.segName(), header.sectName(), @errorName(err), }); }; } fn updateLazyBindSizeWorker(self: *MachO) void { const tracy = trace(@src()); defer tracy.end(); const diags = &self.base.comp.link_diags; const doWork = struct { fn doWork(macho_file: *MachO) !void { try macho_file.lazy_bind_section.updateSize(macho_file); const sect_id = macho_file.stubs_helper_sect_index.?; const out = &macho_file.sections.items(.out)[sect_id]; var stream = std.io.fixedBufferStream(out.items); try macho_file.stubs_helper.write(macho_file, stream.writer()); } }.doWork; doWork(self) catch |err| diags.addError("could not calculate size of lazy binding section: {s}", .{@errorName(err)}); } pub fn updateLinkeditSizeWorker(self: *MachO, tag: enum { rebase, bind, weak_bind, export_trie, data_in_code, }) void { const diags = &self.base.comp.link_diags; const res = switch (tag) { .rebase => self.rebase_section.updateSize(self), .bind => self.bind_section.updateSize(self), .weak_bind => self.weak_bind_section.updateSize(self), .export_trie => self.export_trie.updateSize(self), .data_in_code => self.data_in_code.updateSize(self), }; res catch |err| diags.addError("could not calculate size of {s} section: {s}", .{ @tagName(tag), @errorName(err) }); } fn writeSectionsToFile(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const slice = self.sections.slice(); for (slice.items(.header), slice.items(.out)) |header, out| { try self.pwriteAll(out.items, header.offset); } } fn writeLinkeditSectionsToFile(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); try self.writeDyldInfo(); try self.writeDataInCode(); try self.writeSymtabToFile(); try self.writeIndsymtab(); } fn writeDyldInfo(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; const base_off = self.getLinkeditSegment().fileoff; const cmd = self.dyld_info_cmd; var needed_size: u32 = 0; needed_size += cmd.rebase_size; needed_size += cmd.bind_size; needed_size += cmd.weak_bind_size; needed_size += cmd.lazy_bind_size; needed_size += cmd.export_size; const buffer = try gpa.alloc(u8, needed_size); defer gpa.free(buffer); @memset(buffer, 0); var stream = std.io.fixedBufferStream(buffer); const writer = stream.writer(); try self.rebase_section.write(writer); try stream.seekTo(cmd.bind_off - base_off); try self.bind_section.write(writer); try stream.seekTo(cmd.weak_bind_off - base_off); try self.weak_bind_section.write(writer); try stream.seekTo(cmd.lazy_bind_off - base_off); try self.lazy_bind_section.write(writer); try stream.seekTo(cmd.export_off - base_off); try self.export_trie.write(writer); try self.pwriteAll(buffer, cmd.rebase_off); } pub fn writeDataInCode(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; const cmd = self.data_in_code_cmd; var buffer = try std.ArrayList(u8).initCapacity(gpa, self.data_in_code.size()); defer buffer.deinit(); try self.data_in_code.write(self, buffer.writer()); try self.pwriteAll(buffer.items, cmd.dataoff); } fn writeIndsymtab(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; const cmd = self.dysymtab_cmd; const needed_size = cmd.nindirectsyms * @sizeOf(u32); var buffer = try std.ArrayList(u8).initCapacity(gpa, needed_size); defer buffer.deinit(); try self.indsymtab.write(self, buffer.writer()); try self.pwriteAll(buffer.items, cmd.indirectsymoff); } pub fn writeSymtabToFile(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const cmd = self.symtab_cmd; try self.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff); try self.pwriteAll(self.strtab.items, cmd.stroff); } fn writeUnwindInfo(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; if (self.eh_frame_sect_index) |index| { const header = self.sections.items(.header)[index]; const size = try self.cast(usize, header.size); const buffer = try gpa.alloc(u8, size); defer gpa.free(buffer); eh_frame.write(self, buffer); try self.pwriteAll(buffer, header.offset); } if (self.unwind_info_sect_index) |index| { const header = self.sections.items(.header)[index]; const size = try self.cast(usize, header.size); const buffer = try gpa.alloc(u8, size); defer gpa.free(buffer); try self.unwind_info.write(self, buffer); try self.pwriteAll(buffer, header.offset); } } fn calcSymtabSize(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = self.base.comp.gpa; var files = std.ArrayList(File.Index).init(gpa); defer files.deinit(); try files.ensureTotalCapacityPrecise(self.objects.items.len + self.dylibs.items.len + 2); if (self.zig_object) |index| files.appendAssumeCapacity(index); for (self.objects.items) |index| files.appendAssumeCapacity(index); for (self.dylibs.items) |index| files.appendAssumeCapacity(index); if (self.internal_object) |index| files.appendAssumeCapacity(index); var nlocals: u32 = 0; var nstabs: u32 = 0; var nexports: u32 = 0; var nimports: u32 = 0; var strsize: u32 = 1; if (self.requiresThunks()) for (self.thunks.items) |*th| { th.output_symtab_ctx.ilocal = nlocals; th.output_symtab_ctx.stroff = strsize; th.calcSymtabSize(self); nlocals += th.output_symtab_ctx.nlocals; strsize += th.output_symtab_ctx.strsize; }; for (files.items) |index| { const file = self.getFile(index).?; const ctx = switch (file) { inline else => |x| &x.output_symtab_ctx, }; ctx.ilocal = nlocals; ctx.istab = nstabs; ctx.iexport = nexports; ctx.iimport = nimports; ctx.stroff = strsize; nlocals += ctx.nlocals; nstabs += ctx.nstabs; nexports += ctx.nexports; nimports += ctx.nimports; strsize += ctx.strsize; } for (files.items) |index| { const file = self.getFile(index).?; const ctx = switch (file) { inline else => |x| &x.output_symtab_ctx, }; ctx.istab += nlocals; ctx.iexport += nlocals + nstabs; ctx.iimport += nlocals + nstabs + nexports; } try self.indsymtab.updateSize(self); { const cmd = &self.symtab_cmd; cmd.nsyms = nlocals + nstabs + nexports + nimports; cmd.strsize = strsize; } { const cmd = &self.dysymtab_cmd; cmd.ilocalsym = 0; cmd.nlocalsym = nlocals + nstabs; cmd.iextdefsym = nlocals + nstabs; cmd.nextdefsym = nexports; cmd.iundefsym = nlocals + nstabs + nexports; cmd.nundefsym = nimports; } } fn writeLoadCommands(self: *MachO) !struct { usize, usize, u64 } { const comp = self.base.comp; const gpa = comp.gpa; const needed_size = try load_commands.calcLoadCommandsSize(self, false); const buffer = try gpa.alloc(u8, needed_size); defer gpa.free(buffer); var stream = std.io.fixedBufferStream(buffer); const writer = stream.writer(); var ncmds: usize = 0; // Segment and section load commands { const slice = self.sections.slice(); var sect_id: usize = 0; for (self.segments.items) |seg| { try writer.writeStruct(seg); for (slice.items(.header)[sect_id..][0..seg.nsects]) |header| { try writer.writeStruct(header); } sect_id += seg.nsects; } ncmds += self.segments.items.len; } try writer.writeStruct(self.dyld_info_cmd); ncmds += 1; try writer.writeStruct(self.function_starts_cmd); ncmds += 1; try writer.writeStruct(self.data_in_code_cmd); ncmds += 1; try writer.writeStruct(self.symtab_cmd); ncmds += 1; try writer.writeStruct(self.dysymtab_cmd); ncmds += 1; try load_commands.writeDylinkerLC(writer); ncmds += 1; if (self.getInternalObject()) |obj| { if (obj.getEntryRef(self)) |ref| { const sym = ref.getSymbol(self).?; const seg = self.getTextSegment(); const entryoff: u32 = if (sym.getFile(self) == null) 0 else @as(u32, @intCast(sym.getAddress(.{ .stubs = true }, self) - seg.vmaddr)); try writer.writeStruct(macho.entry_point_command{ .entryoff = entryoff, .stacksize = self.base.stack_size, }); ncmds += 1; } } if (self.base.isDynLib()) { try load_commands.writeDylibIdLC(self, writer); ncmds += 1; } for (self.rpath_list) |rpath| { try load_commands.writeRpathLC(rpath, writer); ncmds += 1; } if (comp.config.any_sanitize_thread) { const path = try comp.tsan_lib.?.full_object_path.toString(gpa); defer gpa.free(path); const rpath = std.fs.path.dirname(path) orelse "."; try load_commands.writeRpathLC(rpath, writer); ncmds += 1; } try writer.writeStruct(macho.source_version_command{ .version = 0 }); ncmds += 1; if (self.platform.isBuildVersionCompatible()) { try load_commands.writeBuildVersionLC(self.platform, self.sdk_version, writer); ncmds += 1; } else { try load_commands.writeVersionMinLC(self.platform, self.sdk_version, writer); ncmds += 1; } const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + stream.pos; try writer.writeStruct(self.uuid_cmd); ncmds += 1; for (self.dylibs.items) |index| { const dylib = self.getFile(index).?.dylib; assert(dylib.isAlive(self)); const dylib_id = dylib.id.?; try load_commands.writeDylibLC(.{ .cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else if (dylib.reexport) .REEXPORT_DYLIB else .LOAD_DYLIB, .name = dylib_id.name, .timestamp = dylib_id.timestamp, .current_version = dylib_id.current_version, .compatibility_version = dylib_id.compatibility_version, }, writer); ncmds += 1; } if (self.requiresCodeSig()) { try writer.writeStruct(self.codesig_cmd); ncmds += 1; } assert(stream.pos == needed_size); try self.pwriteAll(buffer, @sizeOf(macho.mach_header_64)); return .{ ncmds, buffer.len, uuid_cmd_offset }; } fn writeHeader(self: *MachO, ncmds: usize, sizeofcmds: usize) !void { var header: macho.mach_header_64 = .{}; header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK; // TODO: if (self.options.namespace == .two_level) { header.flags |= macho.MH_TWOLEVEL; // } switch (self.getTarget().cpu.arch) { .aarch64 => { header.cputype = macho.CPU_TYPE_ARM64; header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL; }, .x86_64 => { header.cputype = macho.CPU_TYPE_X86_64; header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL; }, else => {}, } if (self.base.isDynLib()) { header.filetype = macho.MH_DYLIB; } else { header.filetype = macho.MH_EXECUTE; header.flags |= macho.MH_PIE; } const has_reexports = for (self.dylibs.items) |index| { if (self.getFile(index).?.dylib.reexport) break true; } else false; if (!has_reexports) { header.flags |= macho.MH_NO_REEXPORTED_DYLIBS; } if (self.has_tlv.load(.seq_cst)) { header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; } if (self.binds_to_weak.load(.seq_cst)) { header.flags |= macho.MH_BINDS_TO_WEAK; } if (self.weak_defines.load(.seq_cst)) { header.flags |= macho.MH_WEAK_DEFINES; } header.ncmds = @intCast(ncmds); header.sizeofcmds = @intCast(sizeofcmds); log.debug("writing Mach-O header {}", .{header}); try self.pwriteAll(mem.asBytes(&header), 0); } fn writeUuid(self: *MachO, uuid_cmd_offset: u64, has_codesig: bool) !void { const file_size = if (!has_codesig) blk: { const seg = self.getLinkeditSegment(); break :blk seg.fileoff + seg.filesize; } else self.codesig_cmd.dataoff; try calcUuid(self.base.comp, self.base.file.?, file_size, &self.uuid_cmd.uuid); const offset = uuid_cmd_offset + @sizeOf(macho.load_command); try self.pwriteAll(&self.uuid_cmd.uuid, offset); } pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { const seg = self.getLinkeditSegment(); // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271 const offset = mem.alignForward(u64, seg.fileoff + seg.filesize, 16); const needed_size = code_sig.estimateSize(offset); seg.filesize = offset + needed_size - seg.fileoff; seg.vmsize = mem.alignForward(u64, seg.filesize, self.getPageSize()); log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ offset, offset + needed_size }); // Pad out the space. We need to do this to calculate valid hashes for everything in the file // except for code signature data. try self.pwriteAll(&[_]u8{0}, offset + needed_size - 1); self.codesig_cmd.dataoff = @as(u32, @intCast(offset)); self.codesig_cmd.datasize = @as(u32, @intCast(needed_size)); } pub fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void { const seg = self.getTextSegment(); const offset = self.codesig_cmd.dataoff; var buffer = std.ArrayList(u8).init(self.base.comp.gpa); defer buffer.deinit(); try buffer.ensureTotalCapacityPrecise(code_sig.size()); try code_sig.writeAdhocSignature(self, .{ .file = self.base.file.?, .exec_seg_base = seg.fileoff, .exec_seg_limit = seg.filesize, .file_size = offset, .dylib = self.base.isDynLib(), }, buffer.writer()); assert(buffer.items.len == code_sig.size()); log.debug("writing code signature from 0x{x} to 0x{x}", .{ offset, offset + buffer.items.len, }); try self.pwriteAll(buffer.items, offset); } pub fn updateFunc( self: *MachO, pt: Zcu.PerThread, func_index: InternPool.Index, mir: *const codegen.AnyMir, ) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } return self.getZigObject().?.updateFunc(self, pt, func_index, mir); } pub fn updateNav(self: *MachO, pt: Zcu.PerThread, nav: InternPool.Nav.Index) link.File.UpdateNavError!void { if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } return self.getZigObject().?.updateNav(self, pt, nav); } pub fn updateLineNumber(self: *MachO, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void { return self.getZigObject().?.updateLineNumber(pt, ti_id); } pub fn updateExports( self: *MachO, pt: Zcu.PerThread, exported: Zcu.Exported, export_indices: []const Zcu.Export.Index, ) link.File.UpdateExportsError!void { if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } return self.getZigObject().?.updateExports(self, pt, exported, export_indices); } pub fn deleteExport( self: *MachO, exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { return self.getZigObject().?.deleteExport(self, exported, name); } pub fn freeNav(self: *MachO, nav: InternPool.Nav.Index) void { return self.getZigObject().?.freeNav(nav); } pub fn getNavVAddr(self: *MachO, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: link.File.RelocInfo) !u64 { return self.getZigObject().?.getNavVAddr(self, pt, nav_index, reloc_info); } pub fn lowerUav( self: *MachO, pt: Zcu.PerThread, uav: InternPool.Index, explicit_alignment: InternPool.Alignment, src_loc: Zcu.LazySrcLoc, ) !codegen.SymbolResult { return self.getZigObject().?.lowerUav(self, pt, uav, explicit_alignment, src_loc); } pub fn getUavVAddr(self: *MachO, uav: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 { return self.getZigObject().?.getUavVAddr(self, uav, reloc_info); } pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 { return self.getZigObject().?.getGlobalSymbol(self, name, lib_name); } pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { return actual_size +| (actual_size / ideal_factor); } fn detectAllocCollision(self: *MachO, start: u64, size: u64) !?u64 { // Conservatively commit one page size as reserved space for the headers as we // expect it to grow and everything else be moved in flush anyhow. const header_size = self.getPageSize(); if (start < header_size) return header_size; var at_end = true; const end = start + padToIdeal(size); for (self.sections.items(.header)) |header| { if (header.isZerofill()) continue; const increased_size = padToIdeal(header.size); const test_end = header.offset +| increased_size; if (start < test_end) { if (end > header.offset) return test_end; if (test_end < std.math.maxInt(u64)) at_end = false; } } for (self.segments.items) |seg| { const increased_size = padToIdeal(seg.filesize); const test_end = seg.fileoff +| increased_size; if (start < test_end) { if (end > seg.fileoff) return test_end; if (test_end < std.math.maxInt(u64)) at_end = false; } } if (at_end) try self.base.file.?.setEndPos(end); return null; } fn detectAllocCollisionVirtual(self: *MachO, start: u64, size: u64) ?u64 { // Conservatively commit one page size as reserved space for the headers as we // expect it to grow and everything else be moved in flush anyhow. const header_size = self.getPageSize(); if (start < header_size) return header_size; const end = start + padToIdeal(size); for (self.sections.items(.header)) |header| { const increased_size = padToIdeal(header.size); const test_end = header.addr +| increased_size; if (end > header.addr and start < test_end) { return test_end; } } for (self.segments.items) |seg| { const increased_size = padToIdeal(seg.vmsize); const test_end = seg.vmaddr +| increased_size; if (end > seg.vmaddr and start < test_end) { return test_end; } } return null; } pub fn allocatedSize(self: *MachO, start: u64) u64 { if (start == 0) return 0; var min_pos: u64 = std.math.maxInt(u64); for (self.sections.items(.header)) |header| { if (header.offset <= start) continue; if (header.offset < min_pos) min_pos = header.offset; } for (self.segments.items) |seg| { if (seg.fileoff <= start) continue; if (seg.fileoff < min_pos) min_pos = seg.fileoff; } return min_pos - start; } pub fn allocatedSizeVirtual(self: *MachO, start: u64) u64 { if (start == 0) return 0; var min_pos: u64 = std.math.maxInt(u64); for (self.sections.items(.header)) |header| { if (header.addr <= start) continue; if (header.addr < min_pos) min_pos = header.addr; } for (self.segments.items) |seg| { if (seg.vmaddr <= start) continue; if (seg.vmaddr < min_pos) min_pos = seg.vmaddr; } return min_pos - start; } pub fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) !u64 { var start: u64 = 0; while (try self.detectAllocCollision(start, object_size)) |item_end| { start = mem.alignForward(u64, item_end, min_alignment); } return start; } pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32) u64 { var start: u64 = 0; while (self.detectAllocCollisionVirtual(start, object_size)) |item_end| { start = mem.alignForward(u64, item_end, min_alignment); } return start; } pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { const file = self.base.file.?; const amt = try file.copyRangeAll(old_offset, file, new_offset, size); if (amt != size) return error.InputOutput; } /// Like File.copyRangeAll but also ensures the source region is zeroed out after copy. /// This is so that we guarantee zeroed out regions for mapping of zerofill sections by the loader. fn copyRangeAllZeroOut(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void { const gpa = self.base.comp.gpa; try self.copyRangeAll(old_offset, new_offset, size); const size_u = math.cast(usize, size) orelse return error.Overflow; const zeroes = try gpa.alloc(u8, size_u); // TODO no need to allocate here. defer gpa.free(zeroes); @memset(zeroes, 0); try self.base.file.?.pwriteAll(zeroes, old_offset); } const InitMetadataOptions = struct { emit: Path, zo: *ZigObject, symbol_count_hint: u64, program_code_size_hint: u64, }; pub fn closeDebugInfo(self: *MachO) bool { const d_sym = &(self.d_sym orelse return false); d_sym.file.?.close(); d_sym.file = null; return true; } pub fn reopenDebugInfo(self: *MachO) !void { assert(self.d_sym.?.file == null); assert(!self.base.comp.config.use_llvm); assert(self.base.comp.config.debug_format == .dwarf); const gpa = self.base.comp.gpa; const sep = fs.path.sep_str; const d_sym_path = try std.fmt.allocPrint( gpa, "{s}.dSYM" ++ sep ++ "Contents" ++ sep ++ "Resources" ++ sep ++ "DWARF", .{self.base.emit.sub_path}, ); defer gpa.free(d_sym_path); var d_sym_bundle = try self.base.emit.root_dir.handle.makeOpenPath(d_sym_path, .{}); defer d_sym_bundle.close(); self.d_sym.?.file = try d_sym_bundle.createFile(fs.path.basename(self.base.emit.sub_path), .{ .truncate = false, .read = true, }); } // TODO: move to ZigObject fn initMetadata(self: *MachO, options: InitMetadataOptions) !void { if (!self.base.isRelocatable()) { const base_vmaddr = blk: { const pagezero_size = self.pagezero_size orelse default_pagezero_size; break :blk mem.alignBackward(u64, pagezero_size, self.getPageSize()); }; { const filesize = options.program_code_size_hint; const off = try self.findFreeSpace(filesize, self.getPageSize()); self.zig_text_seg_index = try self.addSegment("__TEXT_ZIG", .{ .fileoff = off, .filesize = filesize, .vmaddr = base_vmaddr + 0x4000000, .vmsize = filesize, .prot = macho.PROT.READ | macho.PROT.EXEC, }); } { const filesize: u64 = 1024; const off = try self.findFreeSpace(filesize, self.getPageSize()); self.zig_const_seg_index = try self.addSegment("__CONST_ZIG", .{ .fileoff = off, .filesize = filesize, .vmaddr = base_vmaddr + 0xc000000, .vmsize = filesize, .prot = macho.PROT.READ | macho.PROT.WRITE, }); } { const filesize: u64 = 1024; const off = try self.findFreeSpace(filesize, self.getPageSize()); self.zig_data_seg_index = try self.addSegment("__DATA_ZIG", .{ .fileoff = off, .filesize = filesize, .vmaddr = base_vmaddr + 0x10000000, .vmsize = filesize, .prot = macho.PROT.READ | macho.PROT.WRITE, }); } { const memsize: u64 = 1024; self.zig_bss_seg_index = try self.addSegment("__BSS_ZIG", .{ .vmaddr = base_vmaddr + 0x14000000, .vmsize = memsize, .prot = macho.PROT.READ | macho.PROT.WRITE, }); } if (options.zo.dwarf) |*dwarf| { // Create dSYM bundle. log.debug("creating {s}.dSYM bundle", .{options.emit.sub_path}); self.d_sym = .{ .allocator = self.base.comp.gpa, .file = null }; try self.reopenDebugInfo(); try self.d_sym.?.initMetadata(self); try dwarf.initMetadata(); } } const appendSect = struct { fn appendSect(macho_file: *MachO, sect_id: u8, seg_id: u8) void { const sect = &macho_file.sections.items(.header)[sect_id]; const seg = macho_file.segments.items[seg_id]; sect.addr = seg.vmaddr; sect.offset = @intCast(seg.fileoff); sect.size = seg.vmsize; macho_file.sections.items(.segment_id)[sect_id] = seg_id; } }.appendSect; const allocSect = struct { fn allocSect(macho_file: *MachO, sect_id: u8, size: u64) !void { const sect = &macho_file.sections.items(.header)[sect_id]; const alignment = try macho_file.alignPow(sect.@"align"); if (!sect.isZerofill()) { sect.offset = try macho_file.cast(u32, try macho_file.findFreeSpace(size, alignment)); } sect.addr = macho_file.findFreeSpaceVirtual(size, alignment); sect.size = size; } }.allocSect; { self.zig_text_sect_index = try self.addSection("__TEXT_ZIG", "__text_zig", .{ .alignment = switch (self.getTarget().cpu.arch) { .aarch64 => 2, .x86_64 => 0, else => unreachable, }, .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, }); if (self.base.isRelocatable()) { try allocSect(self, self.zig_text_sect_index.?, options.program_code_size_hint); } else { appendSect(self, self.zig_text_sect_index.?, self.zig_text_seg_index.?); } } { self.zig_const_sect_index = try self.addSection("__CONST_ZIG", "__const_zig", .{}); if (self.base.isRelocatable()) { try allocSect(self, self.zig_const_sect_index.?, 1024); } else { appendSect(self, self.zig_const_sect_index.?, self.zig_const_seg_index.?); } } { self.zig_data_sect_index = try self.addSection("__DATA_ZIG", "__data_zig", .{}); if (self.base.isRelocatable()) { try allocSect(self, self.zig_data_sect_index.?, 1024); } else { appendSect(self, self.zig_data_sect_index.?, self.zig_data_seg_index.?); } } { self.zig_bss_sect_index = try self.addSection("__BSS_ZIG", "__bss_zig", .{ .flags = macho.S_ZEROFILL, }); if (self.base.isRelocatable()) { try allocSect(self, self.zig_bss_sect_index.?, 1024); } else { appendSect(self, self.zig_bss_sect_index.?, self.zig_bss_seg_index.?); } } if (self.base.isRelocatable()) if (options.zo.dwarf) |*dwarf| { self.debug_str_sect_index = try self.addSection("__DWARF", "__debug_str", .{ .flags = macho.S_ATTR_DEBUG, }); self.debug_info_sect_index = try self.addSection("__DWARF", "__debug_info", .{ .flags = macho.S_ATTR_DEBUG, }); self.debug_abbrev_sect_index = try self.addSection("__DWARF", "__debug_abbrev", .{ .flags = macho.S_ATTR_DEBUG, }); self.debug_aranges_sect_index = try self.addSection("__DWARF", "__debug_aranges", .{ .alignment = 4, .flags = macho.S_ATTR_DEBUG, }); self.debug_line_sect_index = try self.addSection("__DWARF", "__debug_line", .{ .flags = macho.S_ATTR_DEBUG, }); self.debug_line_str_sect_index = try self.addSection("__DWARF", "__debug_line_str", .{ .flags = macho.S_ATTR_DEBUG, }); self.debug_loclists_sect_index = try self.addSection("__DWARF", "__debug_loclists", .{ .flags = macho.S_ATTR_DEBUG, }); self.debug_rnglists_sect_index = try self.addSection("__DWARF", "__debug_rnglists", .{ .flags = macho.S_ATTR_DEBUG, }); try dwarf.initMetadata(); }; } pub fn growSection(self: *MachO, sect_index: u8, needed_size: u64) !void { if (self.base.isRelocatable()) { try self.growSectionRelocatable(sect_index, needed_size); } else { try self.growSectionNonRelocatable(sect_index, needed_size); } } fn growSectionNonRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void { const diags = &self.base.comp.link_diags; const sect = &self.sections.items(.header)[sect_index]; const seg_id = self.sections.items(.segment_id)[sect_index]; const seg = &self.segments.items[seg_id]; if (!sect.isZerofill()) { const allocated_size = self.allocatedSize(sect.offset); if (needed_size > allocated_size) { const existing_size = sect.size; sect.size = 0; // Must move the entire section. const alignment = self.getPageSize(); const new_offset = try self.findFreeSpace(needed_size, alignment); log.debug("moving '{s},{s}' from 0x{x} to 0x{x}", .{ sect.segName(), sect.sectName(), sect.offset, new_offset, }); try self.copyRangeAllZeroOut(sect.offset, new_offset, existing_size); sect.offset = @intCast(new_offset); } else if (sect.offset + allocated_size == std.math.maxInt(u64)) { try self.base.file.?.setEndPos(sect.offset + needed_size); } seg.filesize = needed_size; } sect.size = needed_size; seg.fileoff = sect.offset; const mem_capacity = self.allocatedSizeVirtual(seg.vmaddr); if (needed_size > mem_capacity) { var err = try diags.addErrorWithNotes(2); try err.addMsg("fatal linker error: cannot expand segment seg({d})({s}) in virtual memory", .{ seg_id, seg.segName(), }); err.addNote("TODO: emit relocations to memory locations in self-hosted backends", .{}); err.addNote("as a workaround, try increasing pre-allocated virtual memory of each segment", .{}); } seg.vmsize = needed_size; } fn growSectionRelocatable(self: *MachO, sect_index: u8, needed_size: u64) !void { const sect = &self.sections.items(.header)[sect_index]; if (!sect.isZerofill()) { const allocated_size = self.allocatedSize(sect.offset); if (needed_size > allocated_size) { const existing_size = sect.size; sect.size = 0; // Must move the entire section. const alignment = try math.powi(u32, 2, sect.@"align"); const new_offset = try self.findFreeSpace(needed_size, alignment); const new_addr = self.findFreeSpaceVirtual(needed_size, alignment); log.debug("new '{s},{s}' file offset 0x{x} to 0x{x} (0x{x} - 0x{x})", .{ sect.segName(), sect.sectName(), new_offset, new_offset + existing_size, new_addr, new_addr + existing_size, }); try self.copyRangeAll(sect.offset, new_offset, existing_size); sect.offset = @intCast(new_offset); sect.addr = new_addr; } else if (sect.offset + allocated_size == std.math.maxInt(u64)) { try self.base.file.?.setEndPos(sect.offset + needed_size); } } sect.size = needed_size; } pub fn markDirty(self: *MachO, sect_index: u8) void { if (self.getZigObject()) |zo| { if (self.debug_info_sect_index.? == sect_index) { zo.debug_info_header_dirty = true; } else if (self.debug_line_sect_index.? == sect_index) { zo.debug_line_header_dirty = true; } else if (self.debug_abbrev_sect_index.? == sect_index) { zo.debug_abbrev_dirty = true; } else if (self.debug_str_sect_index.? == sect_index) { zo.debug_strtab_dirty = true; } else if (self.debug_aranges_sect_index.? == sect_index) { zo.debug_aranges_dirty = true; } } } pub fn getTarget(self: *const MachO) *const std.Target { return &self.base.comp.root_mod.resolved_target.result; } /// XNU starting with Big Sur running on arm64 is caching inodes of running binaries. /// Any change to the binary will effectively invalidate the kernel's cache /// resulting in a SIGKILL on each subsequent run. Since when doing incremental /// linking we're modifying a binary in-place, this will end up with the kernel /// killing it on every subsequent run. To circumvent it, we will copy the file /// into a new inode, remove the original file, and rename the copy to match /// the original file. This is super messy, but there doesn't seem any other /// way to please the XNU. pub fn invalidateKernelCache(dir: fs.Dir, sub_path: []const u8) !void { const tracy = trace(@src()); defer tracy.end(); if (builtin.target.os.tag.isDarwin() and builtin.target.cpu.arch == .aarch64) { try dir.copyFile(sub_path, dir, sub_path, .{}); } } inline fn conformUuid(out: *[Md5.digest_length]u8) void { // LC_UUID uuids should conform to RFC 4122 UUID version 4 & UUID version 5 formats out[6] = (out[6] & 0x0F) | (3 << 4); out[8] = (out[8] & 0x3F) | 0x80; } pub inline fn getPageSize(self: MachO) u16 { return switch (self.getTarget().cpu.arch) { .aarch64 => 0x4000, .x86_64 => 0x1000, else => unreachable, }; } pub fn requiresCodeSig(self: MachO) bool { if (self.entitlements) |_| return true; // TODO: enable once we support this linker option // if (self.options.adhoc_codesign) |cs| return cs; const target = self.getTarget(); return switch (target.cpu.arch) { .aarch64 => switch (target.os.tag) { .driverkit, .macos => true, .ios, .tvos, .visionos, .watchos => target.abi == .simulator, else => false, }, .x86_64 => false, else => unreachable, }; } inline fn requiresThunks(self: MachO) bool { return self.getTarget().cpu.arch == .aarch64; } pub fn isZigSegment(self: MachO, seg_id: u8) bool { inline for (&[_]?u8{ self.zig_text_seg_index, self.zig_const_seg_index, self.zig_data_seg_index, self.zig_bss_seg_index, }) |maybe_index| { if (maybe_index) |index| { if (index == seg_id) return true; } } return false; } pub fn isZigSection(self: MachO, sect_id: u8) bool { inline for (&[_]?u8{ self.zig_text_sect_index, self.zig_const_sect_index, self.zig_data_sect_index, self.zig_bss_sect_index, }) |maybe_index| { if (maybe_index) |index| { if (index == sect_id) return true; } } return false; } pub fn isDebugSection(self: MachO, sect_id: u8) bool { inline for (&[_]?u8{ self.debug_info_sect_index, self.debug_abbrev_sect_index, self.debug_str_sect_index, self.debug_aranges_sect_index, self.debug_line_sect_index, }) |maybe_index| { if (maybe_index) |index| { if (index == sect_id) return true; } } return false; } pub fn addSegment(self: *MachO, name: []const u8, opts: struct { vmaddr: u64 = 0, vmsize: u64 = 0, fileoff: u64 = 0, filesize: u64 = 0, prot: macho.vm_prot_t = macho.PROT.NONE, }) error{OutOfMemory}!u8 { const gpa = self.base.comp.gpa; const index = @as(u8, @intCast(self.segments.items.len)); try self.segments.append(gpa, .{ .segname = makeStaticString(name), .vmaddr = opts.vmaddr, .vmsize = opts.vmsize, .fileoff = opts.fileoff, .filesize = opts.filesize, .maxprot = opts.prot, .initprot = opts.prot, .nsects = 0, .cmdsize = @sizeOf(macho.segment_command_64), }); return index; } const AddSectionOpts = struct { alignment: u32 = 0, flags: u32 = macho.S_REGULAR, reserved1: u32 = 0, reserved2: u32 = 0, }; pub fn addSection( self: *MachO, segname: []const u8, sectname: []const u8, opts: AddSectionOpts, ) !u8 { const gpa = self.base.comp.gpa; const index = @as(u8, @intCast(try self.sections.addOne(gpa))); self.sections.set(index, .{ .segment_id = 0, // Segments will be created automatically later down the pipeline. .header = .{ .sectname = makeStaticString(sectname), .segname = makeStaticString(segname), .@"align" = opts.alignment, .flags = opts.flags, .reserved1 = opts.reserved1, .reserved2 = opts.reserved2, }, }); return index; } pub fn makeStaticString(bytes: []const u8) [16]u8 { var buf = [_]u8{0} ** 16; @memcpy(buf[0..bytes.len], bytes); return buf; } pub fn getSegmentByName(self: MachO, segname: []const u8) ?u8 { for (self.segments.items, 0..) |seg, i| { if (mem.eql(u8, segname, seg.segName())) return @as(u8, @intCast(i)); } else return null; } pub fn getSectionByName(self: MachO, segname: []const u8, sectname: []const u8) ?u8 { for (self.sections.items(.header), 0..) |header, i| { if (mem.eql(u8, header.segName(), segname) and mem.eql(u8, header.sectName(), sectname)) return @as(u8, @intCast(i)); } else return null; } pub fn getTlsAddress(self: MachO) u64 { for (self.sections.items(.header)) |header| switch (header.type()) { macho.S_THREAD_LOCAL_REGULAR, macho.S_THREAD_LOCAL_ZEROFILL, => return header.addr, else => {}, }; return 0; } pub inline fn getTextSegment(self: *MachO) *macho.segment_command_64 { return &self.segments.items[self.text_seg_index.?]; } pub inline fn getLinkeditSegment(self: *MachO) *macho.segment_command_64 { return &self.segments.items[self.linkedit_seg_index.?]; } pub fn getFile(self: *MachO, index: File.Index) ?File { const tag = self.files.items(.tags)[index]; return switch (tag) { .null => null, .zig_object => .{ .zig_object = &self.files.items(.data)[index].zig_object }, .internal => .{ .internal = &self.files.items(.data)[index].internal }, .object => .{ .object = &self.files.items(.data)[index].object }, .dylib => .{ .dylib = &self.files.items(.data)[index].dylib }, }; } pub fn getZigObject(self: *MachO) ?*ZigObject { const index = self.zig_object orelse return null; return self.getFile(index).?.zig_object; } pub fn getInternalObject(self: *MachO) ?*InternalObject { const index = self.internal_object orelse return null; return self.getFile(index).?.internal; } pub fn addFileHandle(self: *MachO, file: fs.File) !File.HandleIndex { const gpa = self.base.comp.gpa; const index: File.HandleIndex = @intCast(self.file_handles.items.len); const fh = try self.file_handles.addOne(gpa); fh.* = file; return index; } pub fn getFileHandle(self: MachO, index: File.HandleIndex) File.Handle { assert(index < self.file_handles.items.len); return self.file_handles.items[index]; } pub fn addThunk(self: *MachO) !Thunk.Index { const index = @as(Thunk.Index, @intCast(self.thunks.items.len)); const thunk = try self.thunks.addOne(self.base.comp.gpa); thunk.* = .{}; return index; } pub fn getThunk(self: *MachO, index: Thunk.Index) *Thunk { assert(index < self.thunks.items.len); return &self.thunks.items[index]; } pub fn eatPrefix(path: []const u8, prefix: []const u8) ?[]const u8 { if (mem.startsWith(u8, path, prefix)) return path[prefix.len..]; return null; } pub fn reportParseError2( self: *MachO, file_index: File.Index, comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { const diags = &self.base.comp.link_diags; var err = try diags.addErrorWithNotes(1); try err.addMsg(format, args); err.addNote("while parsing {f}", .{self.getFile(file_index).?.fmtPath()}); } fn reportMissingDependencyError( self: *MachO, parent: File.Index, path: []const u8, checked_paths: []const []const u8, comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { const diags = &self.base.comp.link_diags; var err = try diags.addErrorWithNotes(2 + checked_paths.len); try err.addMsg(format, args); err.addNote("while resolving {s}", .{path}); err.addNote("a dependency of {f}", .{self.getFile(parent).?.fmtPath()}); for (checked_paths) |p| { err.addNote("tried {s}", .{p}); } } fn reportDependencyError( self: *MachO, parent: File.Index, path: []const u8, comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { const diags = &self.base.comp.link_diags; var err = try diags.addErrorWithNotes(2); try err.addMsg(format, args); err.addNote("while parsing {s}", .{path}); err.addNote("a dependency of {f}", .{self.getFile(parent).?.fmtPath()}); } fn reportDuplicates(self: *MachO) error{ HasDuplicates, OutOfMemory }!void { const tracy = trace(@src()); defer tracy.end(); if (self.dupes.keys().len == 0) return; // Nothing to do const gpa = self.base.comp.gpa; const diags = &self.base.comp.link_diags; const max_notes = 3; // We will sort by name, and then by file to ensure deterministic output. var keys = try std.ArrayList(SymbolResolver.Index).initCapacity(gpa, self.dupes.keys().len); defer keys.deinit(); keys.appendSliceAssumeCapacity(self.dupes.keys()); self.sortGlobalSymbolsByName(keys.items); for (self.dupes.values()) |*refs| { mem.sort(File.Index, refs.items, {}, std.sort.asc(File.Index)); } for (keys.items) |key| { const sym = self.resolver.keys.items[key - 1]; const notes = self.dupes.get(key).?; const nnotes = @min(notes.items.len, max_notes) + @intFromBool(notes.items.len > max_notes); var err = try diags.addErrorWithNotes(nnotes + 1); try err.addMsg("duplicate symbol definition: {s}", .{sym.getName(self)}); err.addNote("defined by {f}", .{sym.getFile(self).?.fmtPath()}); var inote: usize = 0; while (inote < @min(notes.items.len, max_notes)) : (inote += 1) { const file = self.getFile(notes.items[inote]).?; err.addNote("defined by {f}", .{file.fmtPath()}); } if (notes.items.len > max_notes) { const remaining = notes.items.len - max_notes; err.addNote("defined {d} more times", .{remaining}); } } return error.HasDuplicates; } pub fn getDebugSymbols(self: *MachO) ?*DebugSymbols { if (self.d_sym) |*ds| return ds; return null; } pub fn ptraceAttach(self: *MachO, pid: std.posix.pid_t) !void { if (!is_hot_update_compatible) return; const mach_task = try machTaskForPid(pid); log.debug("Mach task for pid {d}: {any}", .{ pid, mach_task }); self.hot_state.mach_task = mach_task; // TODO start exception handler in another thread // TODO enable ones we register for exceptions // try std.os.ptrace(std.os.darwin.PT.ATTACHEXC, pid, 0, 0); } pub fn ptraceDetach(self: *MachO, pid: std.posix.pid_t) !void { if (!is_hot_update_compatible) return; _ = pid; // TODO stop exception handler // TODO see comment in ptraceAttach // try std.os.ptrace(std.os.darwin.PT.DETACH, pid, 0, 0); self.hot_state.mach_task = null; } pub fn dumpState(self: *MachO) std.fmt.Formatter(*MachO, fmtDumpState) { return .{ .data = self }; } fn fmtDumpState(self: *MachO, w: *Writer) Writer.Error!void { if (self.getZigObject()) |zo| { try w.print("zig_object({d}) : {s}\n", .{ zo.index, zo.basename }); try w.print("{f}{f}\n", .{ zo.fmtAtoms(self), zo.fmtSymtab(self), }); } for (self.objects.items) |index| { const object = self.getFile(index).?.object; try w.print("object({d}) : {f} : has_debug({})", .{ index, object.fmtPath(), object.hasDebugInfo(), }); if (!object.alive) try w.writeAll(" : ([*])"); try w.writeByte('\n'); try w.print("{f}{f}{f}{f}{f}\n", .{ object.fmtAtoms(self), object.fmtCies(self), object.fmtFdes(self), object.fmtUnwindRecords(self), object.fmtSymtab(self), }); } for (self.dylibs.items) |index| { const dylib = self.getFile(index).?.dylib; try w.print("dylib({d}) : {f} : needed({}) : weak({})", .{ index, @as(Path, dylib.path), dylib.needed, dylib.weak, }); if (!dylib.isAlive(self)) try w.writeAll(" : ([*])"); try w.writeByte('\n'); try w.print("{f}\n", .{dylib.fmtSymtab(self)}); } if (self.getInternalObject()) |internal| { try w.print("internal({d}) : internal\n", .{internal.index}); try w.print("{f}{f}\n", .{ internal.fmtAtoms(self), internal.fmtSymtab(self) }); } try w.writeAll("thunks\n"); for (self.thunks.items, 0..) |thunk, index| { try w.print("thunk({d}) : {f}\n", .{ index, thunk.fmt(self) }); } try w.print("stubs\n{f}\n", .{self.stubs.fmt(self)}); try w.print("objc_stubs\n{f}\n", .{self.objc_stubs.fmt(self)}); try w.print("got\n{f}\n", .{self.got.fmt(self)}); try w.print("tlv_ptr\n{f}\n", .{self.tlv_ptr.fmt(self)}); try w.writeByte('\n'); try w.print("sections\n{f}\n", .{self.fmtSections()}); try w.print("segments\n{f}\n", .{self.fmtSegments()}); } fn fmtSections(self: *MachO) std.fmt.Formatter(*MachO, formatSections) { return .{ .data = self }; } fn formatSections(self: *MachO, w: *Writer) Writer.Error!void { const slice = self.sections.slice(); for (slice.items(.header), slice.items(.segment_id), 0..) |header, seg_id, i| { try w.print( "sect({d}) : seg({d}) : {s},{s} : @{x} ({x}) : align({x}) : size({x}) : relocs({x};{d})\n", .{ i, seg_id, header.segName(), header.sectName(), header.addr, header.offset, header.@"align", header.size, header.reloff, header.nreloc, }, ); } } fn fmtSegments(self: *MachO) std.fmt.Formatter(*MachO, formatSegments) { return .{ .data = self }; } fn formatSegments(self: *MachO, w: *Writer) Writer.Error!void { for (self.segments.items, 0..) |seg, i| { try w.print("seg({d}) : {s} : @{x}-{x} ({x}-{x})\n", .{ i, seg.segName(), seg.vmaddr, seg.vmaddr + seg.vmsize, seg.fileoff, seg.fileoff + seg.filesize, }); } } pub fn fmtSectType(tt: u8) std.fmt.Formatter(u8, formatSectType) { return .{ .data = tt }; } fn formatSectType(tt: u8, w: *Writer) Writer.Error!void { const name = switch (tt) { macho.S_REGULAR => "REGULAR", macho.S_ZEROFILL => "ZEROFILL", macho.S_CSTRING_LITERALS => "CSTRING_LITERALS", macho.S_4BYTE_LITERALS => "4BYTE_LITERALS", macho.S_8BYTE_LITERALS => "8BYTE_LITERALS", macho.S_16BYTE_LITERALS => "16BYTE_LITERALS", macho.S_LITERAL_POINTERS => "LITERAL_POINTERS", macho.S_NON_LAZY_SYMBOL_POINTERS => "NON_LAZY_SYMBOL_POINTERS", macho.S_LAZY_SYMBOL_POINTERS => "LAZY_SYMBOL_POINTERS", macho.S_SYMBOL_STUBS => "SYMBOL_STUBS", macho.S_MOD_INIT_FUNC_POINTERS => "MOD_INIT_FUNC_POINTERS", macho.S_MOD_TERM_FUNC_POINTERS => "MOD_TERM_FUNC_POINTERS", macho.S_COALESCED => "COALESCED", macho.S_GB_ZEROFILL => "GB_ZEROFILL", macho.S_INTERPOSING => "INTERPOSING", macho.S_DTRACE_DOF => "DTRACE_DOF", macho.S_THREAD_LOCAL_REGULAR => "THREAD_LOCAL_REGULAR", macho.S_THREAD_LOCAL_ZEROFILL => "THREAD_LOCAL_ZEROFILL", macho.S_THREAD_LOCAL_VARIABLES => "THREAD_LOCAL_VARIABLES", macho.S_THREAD_LOCAL_VARIABLE_POINTERS => "THREAD_LOCAL_VARIABLE_POINTERS", macho.S_THREAD_LOCAL_INIT_FUNCTION_POINTERS => "THREAD_LOCAL_INIT_FUNCTION_POINTERS", macho.S_INIT_FUNC_OFFSETS => "INIT_FUNC_OFFSETS", else => |x| return w.print("UNKNOWN({x})", .{x}), }; try w.print("{s}", .{name}); } const is_hot_update_compatible = switch (builtin.target.os.tag) { .macos => true, else => false, }; const default_entry_symbol_name = "_main"; const Section = struct { header: macho.section_64, segment_id: u8, atoms: std.ArrayListUnmanaged(Ref) = .empty, free_list: std.ArrayListUnmanaged(Atom.Index) = .empty, last_atom_index: Atom.Index = 0, thunks: std.ArrayListUnmanaged(Thunk.Index) = .empty, out: std.ArrayListUnmanaged(u8) = .empty, relocs: std.ArrayListUnmanaged(macho.relocation_info) = .empty, }; pub const LiteralPool = struct { table: std.AutoArrayHashMapUnmanaged(void, void) = .empty, keys: std.ArrayListUnmanaged(Key) = .empty, values: std.ArrayListUnmanaged(MachO.Ref) = .empty, data: std.ArrayListUnmanaged(u8) = .empty, pub fn deinit(lp: *LiteralPool, allocator: Allocator) void { lp.table.deinit(allocator); lp.keys.deinit(allocator); lp.values.deinit(allocator); lp.data.deinit(allocator); } const InsertResult = struct { found_existing: bool, index: Index, ref: *MachO.Ref, }; pub fn getSymbolRef(lp: LiteralPool, index: Index) MachO.Ref { assert(index < lp.values.items.len); return lp.values.items[index]; } pub fn getSymbol(lp: LiteralPool, index: Index, macho_file: *MachO) *Symbol { return lp.getSymbolRef(index).getSymbol(macho_file).?; } pub fn insert(lp: *LiteralPool, allocator: Allocator, @"type": u8, string: []const u8) !InsertResult { const size: u32 = @intCast(string.len); try lp.data.ensureUnusedCapacity(allocator, size); const off: u32 = @intCast(lp.data.items.len); lp.data.appendSliceAssumeCapacity(string); const adapter = Adapter{ .lp = lp }; const key = Key{ .off = off, .size = size, .seed = @"type" }; const gop = try lp.table.getOrPutAdapted(allocator, key, adapter); if (!gop.found_existing) { try lp.keys.append(allocator, key); _ = try lp.values.addOne(allocator); } return .{ .found_existing = gop.found_existing, .index = @intCast(gop.index), .ref = &lp.values.items[gop.index], }; } const Key = struct { off: u32, size: u32, seed: u8, fn getData(key: Key, lp: *const LiteralPool) []const u8 { return lp.data.items[key.off..][0..key.size]; } fn eql(key: Key, other: Key, lp: *const LiteralPool) bool { const key_data = key.getData(lp); const other_data = other.getData(lp); return mem.eql(u8, key_data, other_data); } fn hash(key: Key, lp: *const LiteralPool) u32 { const data = key.getData(lp); return @truncate(Hash.hash(key.seed, data)); } }; const Adapter = struct { lp: *const LiteralPool, pub fn eql(ctx: @This(), key: Key, b_void: void, b_map_index: usize) bool { _ = b_void; const other = ctx.lp.keys.items[b_map_index]; return key.eql(other, ctx.lp); } pub fn hash(ctx: @This(), key: Key) u32 { return key.hash(ctx.lp); } }; pub const Index = u32; }; const HotUpdateState = struct { mach_task: ?MachTask = null, }; pub const SymtabCtx = struct { ilocal: u32 = 0, istab: u32 = 0, iexport: u32 = 0, iimport: u32 = 0, nlocals: u32 = 0, nstabs: u32 = 0, nexports: u32 = 0, nimports: u32 = 0, stroff: u32 = 0, strsize: u32 = 0, }; pub const null_sym = macho.nlist_64{ .n_strx = 0, .n_type = 0, .n_sect = 0, .n_desc = 0, .n_value = 0, }; pub const Platform = struct { os_tag: std.Target.Os.Tag, abi: std.Target.Abi, version: std.SemanticVersion, /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to /// the extracted minimum platform version. pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform { switch (lc.cmd()) { .BUILD_VERSION => { const cmd = lc.cast(macho.build_version_command).?; return .{ .os_tag = switch (cmd.platform) { .DRIVERKIT => .driverkit, .IOS, .IOSSIMULATOR => .ios, .MACCATALYST => .ios, .MACOS => .macos, .TVOS, .TVOSSIMULATOR => .tvos, .VISIONOS, .VISIONOSSIMULATOR => .visionos, .WATCHOS, .WATCHOSSIMULATOR => .watchos, else => @panic("TODO"), }, .abi = switch (cmd.platform) { .MACCATALYST => .macabi, .IOSSIMULATOR, .TVOSSIMULATOR, .VISIONOSSIMULATOR, .WATCHOSSIMULATOR, => .simulator, else => .none, }, .version = appleVersionToSemanticVersion(cmd.minos), }; }, .VERSION_MIN_IPHONEOS, .VERSION_MIN_MACOSX, .VERSION_MIN_TVOS, .VERSION_MIN_WATCHOS, => { const cmd = lc.cast(macho.version_min_command).?; return .{ .os_tag = switch (lc.cmd()) { .VERSION_MIN_IPHONEOS => .ios, .VERSION_MIN_MACOSX => .macos, .VERSION_MIN_TVOS => .tvos, .VERSION_MIN_WATCHOS => .watchos, else => unreachable, }, .abi = .none, .version = appleVersionToSemanticVersion(cmd.version), }; }, else => unreachable, } } pub fn fromTarget(target: *const std.Target) Platform { return .{ .os_tag = target.os.tag, .abi = target.abi, .version = target.os.version_range.semver.min, }; } pub fn toAppleVersion(plat: Platform) u32 { return semanticVersionToAppleVersion(plat.version); } pub fn toApplePlatform(plat: Platform) macho.PLATFORM { return switch (plat.os_tag) { .driverkit => .DRIVERKIT, .ios => switch (plat.abi) { .macabi => .MACCATALYST, .simulator => .IOSSIMULATOR, else => .IOS, }, .macos => .MACOS, .tvos => if (plat.abi == .simulator) .TVOSSIMULATOR else .TVOS, .visionos => if (plat.abi == .simulator) .VISIONOSSIMULATOR else .VISIONOS, .watchos => if (plat.abi == .simulator) .WATCHOSSIMULATOR else .WATCHOS, else => unreachable, }; } pub fn isBuildVersionCompatible(plat: Platform) bool { inline for (supported_platforms) |sup_plat| { if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { return sup_plat[2] <= plat.toAppleVersion(); } } return false; } pub fn isVersionMinCompatible(plat: Platform) bool { inline for (supported_platforms) |sup_plat| { if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { return sup_plat[3] <= plat.toAppleVersion(); } } return false; } pub fn fmtTarget(plat: Platform, cpu_arch: std.Target.Cpu.Arch) std.fmt.Formatter(Format, Format.target) { return .{ .data = .{ .platform = plat, .cpu_arch = cpu_arch } }; } const Format = struct { platform: Platform, cpu_arch: std.Target.Cpu.Arch, pub fn target(f: Format, w: *Writer) Writer.Error!void { try w.print("{s}-{s}", .{ @tagName(f.cpu_arch), @tagName(f.platform.os_tag) }); if (f.platform.abi != .none) { try w.print("-{s}", .{@tagName(f.platform.abi)}); } } }; /// Caller owns the memory. pub fn allocPrintTarget(plat: Platform, gpa: Allocator, cpu_arch: std.Target.Cpu.Arch) error{OutOfMemory}![]u8 { var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); try buffer.writer().print("{f}", .{plat.fmtTarget(cpu_arch)}); return buffer.toOwnedSlice(); } pub fn eqlTarget(plat: Platform, other: Platform) bool { return plat.os_tag == other.os_tag and plat.abi == other.abi; } }; const SupportedPlatforms = struct { std.Target.Os.Tag, std.Target.Abi, u32, // Min platform version for which to emit LC_BUILD_VERSION u32, // Min supported platform version }; // Source: https://github.com/apple-oss-distributions/ld64/blob/59a99ab60399c5e6c49e6945a9e1049c42b71135/src/ld/PlatformSupport.cpp#L52 // zig fmt: off const supported_platforms = [_]SupportedPlatforms{ .{ .driverkit, .none, 0x130000, 0x130000 }, .{ .ios, .none, 0x0C0000, 0x070000 }, .{ .ios, .macabi, 0x0D0000, 0x0D0000 }, .{ .ios, .simulator, 0x0D0000, 0x080000 }, .{ .macos, .none, 0x0A0E00, 0x0A0800 }, .{ .tvos, .none, 0x0C0000, 0x070000 }, .{ .tvos, .simulator, 0x0D0000, 0x080000 }, .{ .visionos, .none, 0x010000, 0x010000 }, .{ .visionos, .simulator, 0x010000, 0x010000 }, .{ .watchos, .none, 0x050000, 0x020000 }, .{ .watchos, .simulator, 0x060000, 0x020000 }, }; // zig fmt: on pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 { const major = version.major; const minor = version.minor; const patch = version.patch; return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch)); } pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion { return .{ .major = @as(u16, @truncate(version >> 16)), .minor = @as(u8, @truncate(version >> 8)), .patch = @as(u8, @truncate(version)), }; } fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersion { const gpa = comp.gpa; var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); const sdk_dir = switch (sdk_layout) { .sdk => comp.sysroot.?, .vendored => fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "libc", "darwin" }) catch return null, }; if (readSdkVersionFromSettings(arena, sdk_dir)) |ver| { return parseSdkVersion(ver); } else |_| { // Read from settings should always succeed when vendored. // TODO: convert to fatal linker error if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version"); } // infer from pathname const stem = fs.path.stem(sdk_dir); const start = for (stem, 0..) |c, i| { if (std.ascii.isDigit(c)) break i; } else stem.len; const end = for (stem[start..], start..) |c, i| { if (std.ascii.isDigit(c) or c == '.') continue; break i; } else stem.len; return parseSdkVersion(stem[start..end]); } // Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout. // Use property `MinimalDisplayName` to determine version. // The file/property is also available with vendored libc. fn readSdkVersionFromSettings(arena: Allocator, dir: []const u8) ![]const u8 { const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" }); const contents = try fs.cwd().readFileAlloc(arena, sdk_path, std.math.maxInt(u16)); const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{}); if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string; return error.SdkVersionFailure; } // Versions reported by Apple aren't exactly semantically valid as they usually omit // the patch component, so we parse SDK value by hand. fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion { var parsed: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0, }; const parseNext = struct { fn parseNext(it: anytype) ?u16 { const nn = it.next() orelse return null; return std.fmt.parseInt(u16, nn, 10) catch null; } }.parseNext; var it = std.mem.splitAny(u8, raw, "."); parsed.major = parseNext(&it) orelse return null; parsed.minor = parseNext(&it) orelse return null; parsed.patch = parseNext(&it) orelse 0; return parsed; } /// When allocating, the ideal_capacity is calculated by /// actual_capacity + (actual_capacity / ideal_factor) const ideal_factor = 3; /// In order for a slice of bytes to be considered eligible to keep metadata pointing at /// it as a possible place to put new symbols, it must have enough room for this many bytes /// (plus extra for reserved capacity). const minimum_text_block_size = 64; pub const min_text_capacity = padToIdeal(minimum_text_block_size); /// Default virtual memory offset corresponds to the size of __PAGEZERO segment and /// start of __TEXT segment. pub const default_pagezero_size: u64 = 0x100000000; /// We commit 0x1000 = 4096 bytes of space to the header and /// the table of load commands. This should be plenty for any /// potential future extensions. pub const default_headerpad_size: u32 = 0x1000; const SystemLib = struct { path: Path, needed: bool = false, weak: bool = false, hidden: bool = false, reexport: bool = false, must_link: bool = false, fn fromLinkInput(link_input: link.Input) SystemLib { return switch (link_input) { .dso_exact => unreachable, .res => unreachable, .object, .archive => |obj| .{ .path = obj.path, .must_link = obj.must_link, .hidden = obj.hidden, }, .dso => |dso| .{ .path = dso.path, .needed = dso.needed, .weak = dso.weak, .reexport = dso.reexport, }, }; } }; pub const SdkLayout = std.zig.LibCDirs.DarwinSdkLayout; const UndefinedTreatment = enum { @"error", warn, suppress, dynamic_lookup, }; /// A reference to atom or symbol in an input file. /// If file == 0, symbol is an undefined global. pub const Ref = struct { index: u32, file: File.Index, pub fn eql(ref: Ref, other: Ref) bool { return ref.index == other.index and ref.file == other.file; } pub fn lessThan(ref: Ref, other: Ref) bool { if (ref.file == other.file) { return ref.index < other.index; } return ref.file < other.file; } pub fn getFile(ref: Ref, macho_file: *MachO) ?File { return macho_file.getFile(ref.file); } pub fn getAtom(ref: Ref, macho_file: *MachO) ?*Atom { const file = ref.getFile(macho_file) orelse return null; return file.getAtom(ref.index); } pub fn getSymbol(ref: Ref, macho_file: *MachO) ?*Symbol { const file = ref.getFile(macho_file) orelse return null; return switch (file) { inline else => |x| &x.symbols.items[ref.index], }; } pub fn format(ref: Ref, bw: *Writer) Writer.Error!void { try bw.print("%{d} in file({d})", .{ ref.index, ref.file }); } }; pub const SymbolResolver = struct { keys: std.ArrayListUnmanaged(Key) = .empty, values: std.ArrayListUnmanaged(Ref) = .empty, table: std.AutoArrayHashMapUnmanaged(void, void) = .empty, const Result = struct { found_existing: bool, index: Index, ref: *Ref, }; pub fn deinit(resolver: *SymbolResolver, allocator: Allocator) void { resolver.keys.deinit(allocator); resolver.values.deinit(allocator); resolver.table.deinit(allocator); } pub fn getOrPut( resolver: *SymbolResolver, allocator: Allocator, ref: Ref, macho_file: *MachO, ) !Result { const adapter = Adapter{ .keys = resolver.keys.items, .macho_file = macho_file }; const key = Key{ .index = ref.index, .file = ref.file }; const gop = try resolver.table.getOrPutAdapted(allocator, key, adapter); if (!gop.found_existing) { try resolver.keys.append(allocator, key); _ = try resolver.values.addOne(allocator); } return .{ .found_existing = gop.found_existing, .index = @intCast(gop.index + 1), .ref = &resolver.values.items[gop.index], }; } pub fn get(resolver: SymbolResolver, index: Index) ?Ref { if (index == 0) return null; return resolver.values.items[index - 1]; } pub fn reset(resolver: *SymbolResolver) void { resolver.keys.clearRetainingCapacity(); resolver.values.clearRetainingCapacity(); resolver.table.clearRetainingCapacity(); } const Key = struct { index: Symbol.Index, file: File.Index, fn getName(key: Key, macho_file: *MachO) [:0]const u8 { const ref = Ref{ .index = key.index, .file = key.file }; return ref.getSymbol(macho_file).?.getName(macho_file); } pub fn getFile(key: Key, macho_file: *MachO) ?File { const ref = Ref{ .index = key.index, .file = key.file }; return ref.getFile(macho_file); } fn eql(key: Key, other: Key, macho_file: *MachO) bool { const key_name = key.getName(macho_file); const other_name = other.getName(macho_file); return mem.eql(u8, key_name, other_name); } fn hash(key: Key, macho_file: *MachO) u32 { const name = key.getName(macho_file); return @truncate(Hash.hash(0, name)); } }; const Adapter = struct { keys: []const Key, macho_file: *MachO, pub fn eql(ctx: @This(), key: Key, b_void: void, b_map_index: usize) bool { _ = b_void; const other = ctx.keys[b_map_index]; return key.eql(other, ctx.macho_file); } pub fn hash(ctx: @This(), key: Key) u32 { return key.hash(ctx.macho_file); } }; pub const Index = u32; }; pub const String = struct { pos: u32 = 0, len: u32 = 0, }; pub const UndefRefs = union(enum) { force_undefined, entry, dyld_stub_binder, objc_msgsend, refs: std.ArrayListUnmanaged(Ref), pub fn deinit(self: *UndefRefs, allocator: Allocator) void { switch (self.*) { .refs => |*refs| refs.deinit(allocator), else => {}, } } }; pub const MachError = error{ /// Not enough permissions held to perform the requested kernel /// call. PermissionDenied, } || std.posix.UnexpectedError; pub const MachTask = extern struct { port: std.c.mach_port_name_t, pub fn isValid(self: MachTask) bool { return self.port != std.c.TASK_NULL; } pub fn pidForTask(self: MachTask) MachError!std.c.pid_t { var pid: std.c.pid_t = undefined; switch (getKernError(std.c.pid_for_task(self.port, &pid))) { .SUCCESS => return pid, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } } pub fn allocatePort(self: MachTask, right: std.c.MACH_PORT_RIGHT) MachError!MachTask { var out_port: std.c.mach_port_name_t = undefined; switch (getKernError(std.c.mach_port_allocate( self.port, @intFromEnum(right), &out_port, ))) { .SUCCESS => return .{ .port = out_port }, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } } pub fn deallocatePort(self: MachTask, port: MachTask) void { _ = getKernError(std.c.mach_port_deallocate(self.port, port.port)); } pub fn insertRight(self: MachTask, port: MachTask, msg: std.c.MACH_MSG_TYPE) !void { switch (getKernError(std.c.mach_port_insert_right( self.port, port.port, port.port, @intFromEnum(msg), ))) { .SUCCESS => return, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } } pub const PortInfo = struct { mask: std.c.exception_mask_t, masks: [std.c.EXC.TYPES_COUNT]std.c.exception_mask_t, ports: [std.c.EXC.TYPES_COUNT]std.c.mach_port_t, behaviors: [std.c.EXC.TYPES_COUNT]std.c.exception_behavior_t, flavors: [std.c.EXC.TYPES_COUNT]std.c.thread_state_flavor_t, count: std.c.mach_msg_type_number_t, }; pub fn getExceptionPorts(self: MachTask, mask: std.c.exception_mask_t) !PortInfo { var info: PortInfo = .{ .mask = mask, .masks = undefined, .ports = undefined, .behaviors = undefined, .flavors = undefined, .count = 0, }; info.count = info.ports.len / @sizeOf(std.c.mach_port_t); switch (getKernError(std.c.task_get_exception_ports( self.port, info.mask, &info.masks, &info.count, &info.ports, &info.behaviors, &info.flavors, ))) { .SUCCESS => return info, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } } pub fn setExceptionPorts( self: MachTask, mask: std.c.exception_mask_t, new_port: MachTask, behavior: std.c.exception_behavior_t, new_flavor: std.c.thread_state_flavor_t, ) !void { switch (getKernError(std.c.task_set_exception_ports( self.port, mask, new_port.port, behavior, new_flavor, ))) { .SUCCESS => return, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } } pub const RegionInfo = struct { pub const Tag = enum { basic, extended, top, }; base_addr: u64, tag: Tag, info: union { basic: std.c.vm_region_basic_info_64, extended: std.c.vm_region_extended_info, top: std.c.vm_region_top_info, }, }; pub fn getRegionInfo( task: MachTask, address: u64, len: usize, tag: RegionInfo.Tag, ) MachError!RegionInfo { var info: RegionInfo = .{ .base_addr = address, .tag = tag, .info = undefined, }; switch (tag) { .basic => info.info = .{ .basic = undefined }, .extended => info.info = .{ .extended = undefined }, .top => info.info = .{ .top = undefined }, } var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len; var objname: std.c.mach_port_t = undefined; var count: std.c.mach_msg_type_number_t = switch (tag) { .basic => std.c.VM.REGION.BASIC_INFO_COUNT, .extended => std.c.VM.REGION.EXTENDED_INFO_COUNT, .top => std.c.VM.REGION.TOP_INFO_COUNT, }; switch (getKernError(std.c.mach_vm_region( task.port, &info.base_addr, &base_len, switch (tag) { .basic => std.c.VM.REGION.BASIC_INFO_64, .extended => std.c.VM.REGION.EXTENDED_INFO, .top => std.c.VM.REGION.TOP_INFO, }, switch (tag) { .basic => @as(std.c.vm_region_info_t, @ptrCast(&info.info.basic)), .extended => @as(std.c.vm_region_info_t, @ptrCast(&info.info.extended)), .top => @as(std.c.vm_region_info_t, @ptrCast(&info.info.top)), }, &count, &objname, ))) { .SUCCESS => return info, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } } pub const RegionSubmapInfo = struct { pub const Tag = enum { short, full, }; tag: Tag, base_addr: u64, info: union { short: std.c.vm_region_submap_short_info_64, full: std.c.vm_region_submap_info_64, }, }; pub fn getRegionSubmapInfo( task: MachTask, address: u64, len: usize, nesting_depth: u32, tag: RegionSubmapInfo.Tag, ) MachError!RegionSubmapInfo { var info: RegionSubmapInfo = .{ .base_addr = address, .tag = tag, .info = undefined, }; switch (tag) { .short => info.info = .{ .short = undefined }, .full => info.info = .{ .full = undefined }, } var nesting = nesting_depth; var base_len: std.c.mach_vm_size_t = if (len == 1) 2 else len; var count: std.c.mach_msg_type_number_t = switch (tag) { .short => std.c.VM.REGION.SUBMAP_SHORT_INFO_COUNT_64, .full => std.c.VM.REGION.SUBMAP_INFO_COUNT_64, }; switch (getKernError(std.c.mach_vm_region_recurse( task.port, &info.base_addr, &base_len, &nesting, switch (tag) { .short => @as(std.c.vm_region_recurse_info_t, @ptrCast(&info.info.short)), .full => @as(std.c.vm_region_recurse_info_t, @ptrCast(&info.info.full)), }, &count, ))) { .SUCCESS => return info, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } } pub fn getCurrProtection(task: MachTask, address: u64, len: usize) MachError!std.c.vm_prot_t { const info = try task.getRegionSubmapInfo(address, len, 0, .short); return info.info.short.protection; } pub fn setMaxProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { return task.setProtectionImpl(address, len, true, prot); } pub fn setCurrProtection(task: MachTask, address: u64, len: usize, prot: std.c.vm_prot_t) MachError!void { return task.setProtectionImpl(address, len, false, prot); } fn setProtectionImpl(task: MachTask, address: u64, len: usize, set_max: bool, prot: std.c.vm_prot_t) MachError!void { switch (getKernError(std.c.mach_vm_protect(task.port, address, len, @intFromBool(set_max), prot))) { .SUCCESS => return, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } } /// Will write to VM even if current protection attributes specifically prohibit /// us from doing so, by temporarily setting protection level to a level with VM_PROT_COPY /// variant, and resetting after a successful or unsuccessful write. pub fn writeMemProtected(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { const curr_prot = try task.getCurrProtection(address, buf.len); try task.setCurrProtection( address, buf.len, std.c.PROT.READ | std.c.PROT.WRITE | std.c.PROT.COPY, ); defer { task.setCurrProtection(address, buf.len, curr_prot) catch {}; } return task.writeMem(address, buf, arch); } pub fn writeMem(task: MachTask, address: u64, buf: []const u8, arch: std.Target.Cpu.Arch) MachError!usize { const count = buf.len; var total_written: usize = 0; var curr_addr = address; const page_size = try MachTask.getPageSize(task); // TODO we probably can assume value here var out_buf = buf[0..]; while (total_written < count) { const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_written); switch (getKernError(std.c.mach_vm_write( task.port, curr_addr, @intFromPtr(out_buf.ptr), @as(std.c.mach_msg_type_number_t, @intCast(curr_size)), ))) { .SUCCESS => {}, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } switch (arch) { .aarch64 => { var mattr_value: std.c.vm_machine_attribute_val_t = std.c.MATTR.VAL_CACHE_FLUSH; switch (getKernError(std.c.vm_machine_attribute( task.port, curr_addr, curr_size, std.c.MATTR.CACHE, &mattr_value, ))) { .SUCCESS => {}, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } }, .x86_64 => {}, else => unreachable, } out_buf = out_buf[curr_size..]; total_written += curr_size; curr_addr += curr_size; } return total_written; } pub fn readMem(task: MachTask, address: u64, buf: []u8) MachError!usize { const count = buf.len; var total_read: usize = 0; var curr_addr = address; const page_size = try MachTask.getPageSize(task); // TODO we probably can assume value here var out_buf = buf[0..]; while (total_read < count) { const curr_size = maxBytesLeftInPage(page_size, curr_addr, count - total_read); var curr_bytes_read: std.c.mach_msg_type_number_t = 0; var vm_memory: std.c.vm_offset_t = undefined; switch (getKernError(std.c.mach_vm_read(task.port, curr_addr, curr_size, &vm_memory, &curr_bytes_read))) { .SUCCESS => {}, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } @memcpy(out_buf[0..curr_bytes_read], @as([*]const u8, @ptrFromInt(vm_memory))); _ = std.c.vm_deallocate(std.c.mach_task_self(), vm_memory, curr_bytes_read); out_buf = out_buf[curr_bytes_read..]; curr_addr += curr_bytes_read; total_read += curr_bytes_read; } return total_read; } fn maxBytesLeftInPage(page_size: usize, address: u64, count: usize) usize { var left = count; if (page_size > 0) { const page_offset = address % page_size; const bytes_left_in_page = page_size - page_offset; if (count > bytes_left_in_page) { left = bytes_left_in_page; } } return left; } fn getPageSize(task: MachTask) MachError!usize { if (task.isValid()) { var info_count = std.c.TASK_VM_INFO_COUNT; var vm_info: std.c.task_vm_info_data_t = undefined; switch (getKernError(std.c.task_info( task.port, std.c.TASK_VM_INFO, @as(std.c.task_info_t, @ptrCast(&vm_info)), &info_count, ))) { .SUCCESS => return @as(usize, @intCast(vm_info.page_size)), else => {}, } } var page_size: std.c.vm_size_t = undefined; switch (getKernError(std.c._host_page_size(std.c.mach_host_self(), &page_size))) { .SUCCESS => return page_size, else => |err| return unexpectedKernError(err), } } pub fn basicTaskInfo(task: MachTask) MachError!std.c.mach_task_basic_info { var info: std.c.mach_task_basic_info = undefined; var count = std.c.MACH_TASK_BASIC_INFO_COUNT; switch (getKernError(std.c.task_info( task.port, std.c.MACH_TASK_BASIC_INFO, @as(std.c.task_info_t, @ptrCast(&info)), &count, ))) { .SUCCESS => return info, else => |err| return unexpectedKernError(err), } } pub fn @"resume"(task: MachTask) MachError!void { switch (getKernError(std.c.task_resume(task.port))) { .SUCCESS => {}, else => |err| return unexpectedKernError(err), } } pub fn @"suspend"(task: MachTask) MachError!void { switch (getKernError(std.c.task_suspend(task.port))) { .SUCCESS => {}, else => |err| return unexpectedKernError(err), } } const ThreadList = struct { buf: []MachThread, pub fn deinit(list: ThreadList) void { const self_task = machTaskForSelf(); _ = std.c.vm_deallocate( self_task.port, @intFromPtr(list.buf.ptr), @as(std.c.vm_size_t, @intCast(list.buf.len * @sizeOf(std.c.mach_port_t))), ); } }; pub fn getThreads(task: MachTask) MachError!ThreadList { var thread_list: std.c.mach_port_array_t = undefined; var thread_count: std.c.mach_msg_type_number_t = undefined; switch (getKernError(std.c.task_threads(task.port, &thread_list, &thread_count))) { .SUCCESS => return ThreadList{ .buf = @as([*]MachThread, @ptrCast(thread_list))[0..thread_count] }, else => |err| return unexpectedKernError(err), } } }; pub const MachThread = extern struct { port: std.c.mach_port_t, pub fn isValid(thread: MachThread) bool { return thread.port != std.c.THREAD_NULL; } pub fn getBasicInfo(thread: MachThread) MachError!std.c.thread_basic_info { var info: std.c.thread_basic_info = undefined; var count = std.c.THREAD_BASIC_INFO_COUNT; switch (getKernError(std.c.thread_info( thread.port, std.c.THREAD_BASIC_INFO, @as(std.c.thread_info_t, @ptrCast(&info)), &count, ))) { .SUCCESS => return info, else => |err| return unexpectedKernError(err), } } pub fn getIdentifierInfo(thread: MachThread) MachError!std.c.thread_identifier_info { var info: std.c.thread_identifier_info = undefined; var count = std.c.THREAD_IDENTIFIER_INFO_COUNT; switch (getKernError(std.c.thread_info( thread.port, std.c.THREAD_IDENTIFIER_INFO, @as(std.c.thread_info_t, @ptrCast(&info)), &count, ))) { .SUCCESS => return info, else => |err| return unexpectedKernError(err), } } }; pub fn machTaskForPid(pid: std.c.pid_t) MachError!MachTask { var port: std.c.mach_port_name_t = undefined; switch (getKernError(std.c.task_for_pid(std.c.mach_task_self(), pid, &port))) { .SUCCESS => {}, .FAILURE => return error.PermissionDenied, else => |err| return unexpectedKernError(err), } return MachTask{ .port = port }; } pub fn machTaskForSelf() MachTask { return .{ .port = std.c.mach_task_self() }; } pub fn getKernError(err: std.c.kern_return_t) KernE { return @as(KernE, @enumFromInt(@as(u32, @truncate(@as(usize, @intCast(err)))))); } pub fn unexpectedKernError(err: KernE) std.posix.UnexpectedError { if (std.posix.unexpected_error_tracing) { std.debug.print("unexpected error: {d}\n", .{@intFromEnum(err)}); std.debug.dumpCurrentStackTrace(null); } return error.Unexpected; } /// Kernel return values pub const KernE = enum(u32) { SUCCESS = 0, /// Specified address is not currently valid INVALID_ADDRESS = 1, /// Specified memory is valid, but does not permit the /// required forms of access. PROTECTION_FAILURE = 2, /// The address range specified is already in use, or /// no address range of the size specified could be /// found. NO_SPACE = 3, /// The function requested was not applicable to this /// type of argument, or an argument is invalid INVALID_ARGUMENT = 4, /// The function could not be performed. A catch-all. FAILURE = 5, /// A system resource could not be allocated to fulfill /// this request. This failure may not be permanent. RESOURCE_SHORTAGE = 6, /// The task in question does not hold receive rights /// for the port argument. NOT_RECEIVER = 7, /// Bogus access restriction. NO_ACCESS = 8, /// During a page fault, the target address refers to a /// memory object that has been destroyed. This /// failure is permanent. MEMORY_FAILURE = 9, /// During a page fault, the memory object indicated /// that the data could not be returned. This failure /// may be temporary; future attempts to access this /// same data may succeed, as defined by the memory /// object. MEMORY_ERROR = 10, /// The receive right is already a member of the portset. ALREADY_IN_SET = 11, /// The receive right is not a member of a port set. NOT_IN_SET = 12, /// The name already denotes a right in the task. NAME_EXISTS = 13, /// The operation was aborted. Ipc code will /// catch this and reflect it as a message error. ABORTED = 14, /// The name doesn't denote a right in the task. INVALID_NAME = 15, /// Target task isn't an active task. INVALID_TASK = 16, /// The name denotes a right, but not an appropriate right. INVALID_RIGHT = 17, /// A blatant range error. INVALID_VALUE = 18, /// Operation would overflow limit on user-references. UREFS_OVERFLOW = 19, /// The supplied (port) capability is improper. INVALID_CAPABILITY = 20, /// The task already has send or receive rights /// for the port under another name. RIGHT_EXISTS = 21, /// Target host isn't actually a host. INVALID_HOST = 22, /// An attempt was made to supply "precious" data /// for memory that is already present in a /// memory object. MEMORY_PRESENT = 23, /// A page was requested of a memory manager via /// memory_object_data_request for an object using /// a MEMORY_OBJECT_COPY_CALL strategy, with the /// VM_PROT_WANTS_COPY flag being used to specify /// that the page desired is for a copy of the /// object, and the memory manager has detected /// the page was pushed into a copy of the object /// while the kernel was walking the shadow chain /// from the copy to the object. This error code /// is delivered via memory_object_data_error /// and is handled by the kernel (it forces the /// kernel to restart the fault). It will not be /// seen by users. MEMORY_DATA_MOVED = 24, /// A strategic copy was attempted of an object /// upon which a quicker copy is now possible. /// The caller should retry the copy using /// vm_object_copy_quickly. This error code /// is seen only by the kernel. MEMORY_RESTART_COPY = 25, /// An argument applied to assert processor set privilege /// was not a processor set control port. INVALID_PROCESSOR_SET = 26, /// The specified scheduling attributes exceed the thread's /// limits. POLICY_LIMIT = 27, /// The specified scheduling policy is not currently /// enabled for the processor set. INVALID_POLICY = 28, /// The external memory manager failed to initialize the /// memory object. INVALID_OBJECT = 29, /// A thread is attempting to wait for an event for which /// there is already a waiting thread. ALREADY_WAITING = 30, /// An attempt was made to destroy the default processor /// set. DEFAULT_SET = 31, /// An attempt was made to fetch an exception port that is /// protected, or to abort a thread while processing a /// protected exception. EXCEPTION_PROTECTED = 32, /// A ledger was required but not supplied. INVALID_LEDGER = 33, /// The port was not a memory cache control port. INVALID_MEMORY_CONTROL = 34, /// An argument supplied to assert security privilege /// was not a host security port. INVALID_SECURITY = 35, /// thread_depress_abort was called on a thread which /// was not currently depressed. NOT_DEPRESSED = 36, /// Object has been terminated and is no longer available TERMINATED = 37, /// Lock set has been destroyed and is no longer available. LOCK_SET_DESTROYED = 38, /// The thread holding the lock terminated before releasing /// the lock LOCK_UNSTABLE = 39, /// The lock is already owned by another thread LOCK_OWNED = 40, /// The lock is already owned by the calling thread LOCK_OWNED_SELF = 41, /// Semaphore has been destroyed and is no longer available. SEMAPHORE_DESTROYED = 42, /// Return from RPC indicating the target server was /// terminated before it successfully replied RPC_SERVER_TERMINATED = 43, /// Terminate an orphaned activation. RPC_TERMINATE_ORPHAN = 44, /// Allow an orphaned activation to continue executing. RPC_CONTINUE_ORPHAN = 45, /// Empty thread activation (No thread linked to it) NOT_SUPPORTED = 46, /// Remote node down or inaccessible. NODE_DOWN = 47, /// A signalled thread was not actually waiting. NOT_WAITING = 48, /// Some thread-oriented operation (semaphore_wait) timed out OPERATION_TIMED_OUT = 49, /// During a page fault, indicates that the page was rejected /// as a result of a signature check. CODESIGN_ERROR = 50, /// The requested property cannot be changed at this time. POLICY_STATIC = 51, /// The provided buffer is of insufficient size for the requested data. INSUFFICIENT_BUFFER_SIZE = 52, /// Denied by security policy DENIED = 53, /// The KC on which the function is operating is missing MISSING_KC = 54, /// The KC on which the function is operating is invalid INVALID_KC = 55, /// A search or query operation did not return a result NOT_FOUND = 56, _, }; fn createThunks(macho_file: *MachO, sect_id: u8) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = macho_file.base.comp.gpa; const slice = macho_file.sections.slice(); const header = &slice.items(.header)[sect_id]; const thnks = &slice.items(.thunks)[sect_id]; const atoms = slice.items(.atoms)[sect_id].items; assert(atoms.len > 0); for (atoms) |ref| { ref.getAtom(macho_file).?.value = @bitCast(@as(i64, -1)); } var i: usize = 0; while (i < atoms.len) { const start = i; const start_atom = atoms[start].getAtom(macho_file).?; assert(start_atom.isAlive()); start_atom.value = advanceSection(header, start_atom.size, start_atom.alignment); i += 1; while (i < atoms.len and header.size - start_atom.value < max_allowed_distance) : (i += 1) { const atom = atoms[i].getAtom(macho_file).?; assert(atom.isAlive()); atom.value = advanceSection(header, atom.size, atom.alignment); } // Insert a thunk at the group end const thunk_index = try macho_file.addThunk(); const thunk = macho_file.getThunk(thunk_index); thunk.out_n_sect = sect_id; try thnks.append(gpa, thunk_index); // Scan relocs in the group and create trampolines for any unreachable callsite try scanThunkRelocs(thunk_index, gpa, atoms[start..i], macho_file); thunk.value = advanceSection(header, thunk.size(), .@"4"); log.debug("thunk({d}) : {f}", .{ thunk_index, thunk.fmt(macho_file) }); } } fn advanceSection(sect: *macho.section_64, adv_size: u64, alignment: Atom.Alignment) u64 { const offset = alignment.forward(sect.size); const padding = offset - sect.size; sect.size += padding + adv_size; sect.@"align" = @max(sect.@"align", alignment.toLog2Units()); return offset; } fn scanThunkRelocs(thunk_index: Thunk.Index, gpa: Allocator, atoms: []const MachO.Ref, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const thunk = macho_file.getThunk(thunk_index); for (atoms) |ref| { const atom = ref.getAtom(macho_file).?; log.debug("atom({d}) {s}", .{ atom.atom_index, atom.getName(macho_file) }); for (atom.getRelocs(macho_file)) |rel| { if (rel.type != .branch) continue; if (isReachable(atom, rel, macho_file)) continue; try thunk.symbols.put(gpa, rel.getTargetSymbolRef(atom.*, macho_file), {}); } atom.addExtra(.{ .thunk = thunk_index }, macho_file); } } fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { const target = rel.getTargetSymbol(atom.*, macho_file); if (target.getSectionFlags().stubs or target.getSectionFlags().objc_stubs) return false; if (atom.out_n_sect != target.getOutputSectionIndex(macho_file)) return false; const target_atom = target.getAtom(macho_file).?; if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; const saddr = @as(i64, @intCast(atom.getAddress(macho_file))) + @as(i64, @intCast(rel.offset - atom.off)); const taddr: i64 = @intCast(rel.getTargetAddress(atom.*, macho_file)); _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false; return true; } pub fn pwriteAll(macho_file: *MachO, bytes: []const u8, offset: u64) error{LinkFailure}!void { const comp = macho_file.base.comp; const diags = &comp.link_diags; macho_file.base.file.?.pwriteAll(bytes, offset) catch |err| { return diags.fail("failed to write: {s}", .{@errorName(err)}); }; } pub fn setEndPos(macho_file: *MachO, length: u64) error{LinkFailure}!void { const comp = macho_file.base.comp; const diags = &comp.link_diags; macho_file.base.file.?.setEndPos(length) catch |err| { return diags.fail("failed to set file end pos: {s}", .{@errorName(err)}); }; } pub fn cast(macho_file: *MachO, comptime T: type, x: anytype) error{LinkFailure}!T { return std.math.cast(T, x) orelse { const comp = macho_file.base.comp; const diags = &comp.link_diags; return diags.fail("encountered {d}, overflowing {d}-bit value", .{ x, @bitSizeOf(T) }); }; } pub fn alignPow(macho_file: *MachO, x: u32) error{LinkFailure}!u32 { const result, const ov = @shlWithOverflow(@as(u32, 1), try cast(macho_file, u5, x)); if (ov != 0) { const comp = macho_file.base.comp; const diags = &comp.link_diags; return diags.fail("alignment overflow", .{}); } return result; } /// Branch instruction has 26 bits immediate but is 4 byte aligned. const jump_bits = @bitSizeOf(i28); const max_distance = (1 << (jump_bits - 1)); /// A branch will need an extender if its target is larger than /// `2^(jump_bits - 1) - margin` where margin is some arbitrary number. /// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold /// and assume margin to be 5MiB. const max_allowed_distance = max_distance - 0x500_000; const MachO = @This(); const std = @import("std"); const build_options = @import("build_options"); const builtin = @import("builtin"); const assert = std.debug.assert; const fs = std.fs; const log = std.log.scoped(.link); const state_log = std.log.scoped(.link_state); const macho = std.macho; const math = std.math; const mem = std.mem; const meta = std.meta; const Writer = std.io.Writer; const aarch64 = @import("../arch/aarch64/bits.zig"); const bind = @import("MachO/dyld_info/bind.zig"); const calcUuid = @import("MachO/uuid.zig").calcUuid; const codegen = @import("../codegen.zig"); const dead_strip = @import("MachO/dead_strip.zig"); const eh_frame = @import("MachO/eh_frame.zig"); const fat = @import("MachO/fat.zig"); const link = @import("../link.zig"); const load_commands = @import("MachO/load_commands.zig"); const relocatable = @import("MachO/relocatable.zig"); const tapi = @import("tapi.zig"); const target_util = @import("../target.zig"); const trace = @import("../tracy.zig").trace; const synthetic = @import("MachO/synthetic.zig"); const Alignment = Atom.Alignment; const Allocator = mem.Allocator; const Archive = @import("MachO/Archive.zig"); const AtomicBool = std.atomic.Value(bool); const Bind = bind.Bind; const Cache = std.Build.Cache; const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); const DataInCode = synthetic.DataInCode; const Directory = Cache.Directory; const Dylib = @import("MachO/Dylib.zig"); const ExportTrie = @import("MachO/dyld_info/Trie.zig"); const Path = Cache.Path; const File = @import("MachO/file.zig").File; const GotSection = synthetic.GotSection; const Hash = std.hash.Wyhash; const Indsymtab = synthetic.Indsymtab; const InternalObject = @import("MachO/InternalObject.zig"); const ObjcStubsSection = synthetic.ObjcStubsSection; const Object = @import("MachO/Object.zig"); const LazyBind = bind.LazyBind; const LaSymbolPtrSection = synthetic.LaSymbolPtrSection; const Md5 = std.crypto.hash.Md5; const Zcu = @import("../Zcu.zig"); const InternPool = @import("../InternPool.zig"); const Rebase = @import("MachO/dyld_info/Rebase.zig"); const StringTable = @import("StringTable.zig"); const StubsSection = synthetic.StubsSection; const StubsHelperSection = synthetic.StubsHelperSection; const Symbol = @import("MachO/Symbol.zig"); const Thunk = @import("MachO/Thunk.zig"); const TlvPtrSection = synthetic.TlvPtrSection; const Value = @import("../Value.zig"); const UnwindInfo = @import("MachO/UnwindInfo.zig"); const WeakBind = bind.WeakBind; const ZigObject = @import("MachO/ZigObject.zig"); const dev = @import("../dev.zig");