diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c87f2f7fd5..af5d165665 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -540,7 +540,63 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node state_log.debug("{}", .{self.dumpState()}); - @panic("TODO"); + try self.initDyldInfoSections(); + self.writeAtoms() catch |err| switch (err) { + error.ResolveFailed => return error.FlushFailure, + else => |e| { + try self.reportUnexpectedError("unexpected error while resolving relocations", .{}); + return e; + }, + }; + try self.writeUnwindInfo(); + try self.finalizeDyldInfoSections(); + try self.writeSyntheticSections(); + + var off = math.cast(u32, self.getLinkeditSegment().fileoff) orelse return error.Overflow; + off = try self.writeDyldInfoSections(off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try self.writeFunctionStarts(off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try self.writeDataInCode(self.getTextSegment().vmaddr, off); + try self.calcSymtabSize(); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try self.writeSymtab(off); + off = mem.alignForward(u32, off, @alignOf(u32)); + off = try self.writeIndsymtab(off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try self.writeStrtab(off); + + self.getLinkeditSegment().filesize = off - self.getLinkeditSegment().fileoff; + + 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 = self.base.emit.sub_path; + if (self.entitlements) |path| try codesig.addEntitlements(gpa, path); + 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 = try self.writeLoadCommands(); + try self.writeHeader(ncmds, sizeofcmds); + try self.writeUuid(uuid_cmd_offset, self.requiresCodeSig()); + + if (codesig) |*csig| { + try self.writeCodeSignature(csig); // code signing always comes last + const emit = self.base.emit; + try invalidateKernelCache(emit.directory.handle, emit.sub_path); + } } /// --verbose-link output @@ -2186,6 +2242,646 @@ fn allocateSyntheticSymbols(self: *MachO) void { } } +fn initDyldInfoSections(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.comp.gpa; + + if (self.got_sect_index != null) try self.got.addDyldRelocs(self); + if (self.tlv_ptr_sect_index != null) try self.tlv_ptr.addDyldRelocs(self); + if (self.la_symbol_ptr_sect_index != null) try self.la_symbol_ptr.addDyldRelocs(self); + try self.initExportTrie(); + + var nrebases: usize = 0; + var nbinds: usize = 0; + var nweak_binds: usize = 0; + for (self.objects.items) |index| { + const object = self.getFile(index).?.object; + nrebases += object.num_rebase_relocs; + nbinds += object.num_bind_relocs; + nweak_binds += object.num_weak_bind_relocs; + } + try self.rebase.entries.ensureUnusedCapacity(gpa, nrebases); + try self.bind.entries.ensureUnusedCapacity(gpa, nbinds); + try self.weak_bind.entries.ensureUnusedCapacity(gpa, nweak_binds); +} + +fn initExportTrie(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.comp.gpa; + try self.export_trie.init(gpa); + + const seg = self.getTextSegment(); + for (self.objects.items) |index| { + for (self.getFile(index).?.getSymbols()) |sym_index| { + const sym = self.getSymbol(sym_index); + if (!sym.flags.@"export") continue; + if (sym.getAtom(self)) |atom| if (!atom.flags.alive) continue; + if (sym.getFile(self).?.getIndex() != index) continue; + var flags: u64 = if (sym.flags.abs) + macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE + else if (sym.flags.tlv) + macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL + else + macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR; + if (sym.flags.weak) { + flags |= macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION; + self.weak_defines = true; + self.binds_to_weak = true; + } + try self.export_trie.put(gpa, .{ + .name = sym.getName(self), + .vmaddr_offset = sym.getAddress(.{ .stubs = false }, self) - seg.vmaddr, + .export_flags = flags, + }); + } + } + + if (self.mh_execute_header_index) |index| { + const sym = self.getSymbol(index); + try self.export_trie.put(gpa, .{ + .name = sym.getName(self), + .vmaddr_offset = sym.getAddress(.{}, self) - seg.vmaddr, + .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR, + }); + } +} + +fn writeAtoms(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.comp.gpa; + const cpu_arch = self.getTarget().cpu.arch; + const slice = self.sections.slice(); + + var has_resolve_error = false; + for (slice.items(.header), slice.items(.atoms)) |header, atoms| { + if (atoms.items.len == 0) continue; + if (header.isZerofill()) continue; + + const buffer = try gpa.alloc(u8, header.size); + defer gpa.free(buffer); + const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0; + @memset(buffer, padding_byte); + + for (atoms.items) |atom_index| { + const atom = self.getAtom(atom_index).?; + assert(atom.flags.alive); + const off = atom.value - header.addr; + atom.resolveRelocs(self, buffer[off..][0..atom.size]) catch |err| switch (err) { + error.ResolveFailed => has_resolve_error = true, + else => |e| return e, + }; + } + + try self.base.file.?.pwriteAll(buffer, header.offset); + } + + for (self.thunks.items) |thunk| { + const header = slice.items(.header)[thunk.out_n_sect]; + const offset = thunk.value - header.addr + header.offset; + const buffer = try gpa.alloc(u8, thunk.size()); + defer gpa.free(buffer); + var stream = std.io.fixedBufferStream(buffer); + try thunk.write(self, stream.writer()); + try self.base.file.?.pwriteAll(buffer, offset); + } + + if (has_resolve_error) return error.ResolveFailed; +} + +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 buffer = try gpa.alloc(u8, header.size); + defer gpa.free(buffer); + eh_frame.write(self, buffer); + try self.base.file.?.pwriteAll(buffer, header.offset); + } + + if (self.unwind_info_sect_index) |index| { + const header = self.sections.items(.header)[index]; + const buffer = try gpa.alloc(u8, header.size); + defer gpa.free(buffer); + try self.unwind_info.write(self, buffer); + try self.base.file.?.pwriteAll(buffer, header.offset); + } +} + +fn finalizeDyldInfoSections(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + const gpa = self.base.comp.gpa; + + try self.rebase.finalize(gpa); + try self.bind.finalize(gpa, self); + try self.weak_bind.finalize(gpa, self); + try self.lazy_bind.finalize(gpa, self); + try self.export_trie.finalize(gpa); +} + +fn writeSyntheticSections(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.comp.gpa; + + if (self.got_sect_index) |sect_id| { + const header = self.sections.items(.header)[sect_id]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, header.size); + defer buffer.deinit(); + try self.got.write(self, buffer.writer()); + assert(buffer.items.len == header.size); + try self.base.file.?.pwriteAll(buffer.items, header.offset); + } + + if (self.stubs_sect_index) |sect_id| { + const header = self.sections.items(.header)[sect_id]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, header.size); + defer buffer.deinit(); + try self.stubs.write(self, buffer.writer()); + assert(buffer.items.len == header.size); + try self.base.file.?.pwriteAll(buffer.items, header.offset); + } + + if (self.stubs_helper_sect_index) |sect_id| { + const header = self.sections.items(.header)[sect_id]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, header.size); + defer buffer.deinit(); + try self.stubs_helper.write(self, buffer.writer()); + assert(buffer.items.len == header.size); + try self.base.file.?.pwriteAll(buffer.items, header.offset); + } + + if (self.la_symbol_ptr_sect_index) |sect_id| { + const header = self.sections.items(.header)[sect_id]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, header.size); + defer buffer.deinit(); + try self.la_symbol_ptr.write(self, buffer.writer()); + assert(buffer.items.len == header.size); + try self.base.file.?.pwriteAll(buffer.items, header.offset); + } + + if (self.tlv_ptr_sect_index) |sect_id| { + const header = self.sections.items(.header)[sect_id]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, header.size); + defer buffer.deinit(); + try self.tlv_ptr.write(self, buffer.writer()); + assert(buffer.items.len == header.size); + try self.base.file.?.pwriteAll(buffer.items, header.offset); + } + + if (self.objc_stubs_sect_index) |sect_id| { + const header = self.sections.items(.header)[sect_id]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, header.size); + defer buffer.deinit(); + try self.objc_stubs.write(self, buffer.writer()); + assert(buffer.items.len == header.size); + try self.base.file.?.pwriteAll(buffer.items, header.offset); + } +} + +fn writeDyldInfoSections(self: *MachO, off: u32) !u32 { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.comp.gpa; + const cmd = &self.dyld_info_cmd; + var needed_size: u32 = 0; + + cmd.rebase_off = needed_size; + cmd.rebase_size = mem.alignForward(u32, @intCast(self.rebase.size()), @alignOf(u64)); + needed_size += cmd.rebase_size; + + cmd.bind_off = needed_size; + cmd.bind_size = mem.alignForward(u32, @intCast(self.bind.size()), @alignOf(u64)); + needed_size += cmd.bind_size; + + cmd.weak_bind_off = needed_size; + cmd.weak_bind_size = mem.alignForward(u32, @intCast(self.weak_bind.size()), @alignOf(u64)); + needed_size += cmd.weak_bind_size; + + cmd.lazy_bind_off = needed_size; + cmd.lazy_bind_size = mem.alignForward(u32, @intCast(self.lazy_bind.size()), @alignOf(u64)); + needed_size += cmd.lazy_bind_size; + + cmd.export_off = needed_size; + cmd.export_size = mem.alignForward(u32, @intCast(self.export_trie.size), @alignOf(u64)); + 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.write(writer); + try stream.seekTo(cmd.bind_off); + try self.bind.write(writer); + try stream.seekTo(cmd.weak_bind_off); + try self.weak_bind.write(writer); + try stream.seekTo(cmd.lazy_bind_off); + try self.lazy_bind.write(writer); + try stream.seekTo(cmd.export_off); + try self.export_trie.write(writer); + + cmd.rebase_off += off; + cmd.bind_off += off; + cmd.weak_bind_off += off; + cmd.lazy_bind_off += off; + cmd.export_off += off; + + try self.base.file.?.pwriteAll(buffer, off); + + return off + needed_size; +} + +fn writeFunctionStarts(self: *MachO, off: u32) !u32 { + // TODO actually write it out + const cmd = &self.function_starts_cmd; + cmd.dataoff = off; + return off; +} + +pub fn writeDataInCode(self: *MachO, base_address: u64, off: u32) !u32 { + const cmd = &self.data_in_code_cmd; + cmd.dataoff = off; + + const gpa = self.base.comp.gpa; + var dices = std.ArrayList(macho.data_in_code_entry).init(gpa); + defer dices.deinit(); + + for (self.objects.items) |index| { + const object = self.getFile(index).?.object; + const in_dices = object.getDataInCode(); + + try dices.ensureUnusedCapacity(in_dices.len); + + var next_dice: usize = 0; + for (object.atoms.items) |atom_index| { + if (next_dice >= in_dices.len) break; + const atom = self.getAtom(atom_index) orelse continue; + const start_off = atom.getInputAddress(self); + const end_off = start_off + atom.size; + const start_dice = next_dice; + + if (end_off < in_dices[next_dice].offset) continue; + + while (next_dice < in_dices.len and + in_dices[next_dice].offset < end_off) : (next_dice += 1) + {} + + if (atom.flags.alive) for (in_dices[start_dice..next_dice]) |dice| { + dices.appendAssumeCapacity(.{ + .offset = @intCast(atom.value + dice.offset - start_off - base_address), + .length = dice.length, + .kind = dice.kind, + }); + }; + } + } + + const needed_size = math.cast(u32, dices.items.len * @sizeOf(macho.data_in_code_entry)) orelse return error.Overflow; + cmd.datasize = needed_size; + + try self.base.file.?.pwriteAll(mem.sliceAsBytes(dices.items), cmd.dataoff); + + return off + needed_size; +} + +pub fn calcSymtabSize(self: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + const gpa = self.base.comp.gpa; + + var nlocals: u32 = 0; + var nstabs: u32 = 0; + var nexports: u32 = 0; + var nimports: u32 = 0; + var strsize: u32 = 0; + + var files = std.ArrayList(File.Index).init(gpa); + defer files.deinit(); + try files.ensureTotalCapacityPrecise(self.objects.items.len + self.dylibs.items.len + 1); + for (self.objects.items) |index| files.appendAssumeCapacity(index); + for (self.dylibs.items) |index| files.appendAssumeCapacity(index); + if (self.internal_object) |index| files.appendAssumeCapacity(index); + + 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; + try file.calcSymtabSize(self); + 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; + } + + { + const cmd = &self.symtab_cmd; + cmd.nsyms = nlocals + nstabs + nexports + nimports; + cmd.strsize = strsize + 1; + } + + { + 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; + } +} + +pub fn writeSymtab(self: *MachO, off: u32) !u32 { + const tracy = trace(@src()); + defer tracy.end(); + const gpa = self.base.comp.gpa; + const cmd = &self.symtab_cmd; + cmd.symoff = off; + + try self.symtab.resize(gpa, cmd.nsyms); + try self.strtab.ensureUnusedCapacity(gpa, cmd.strsize - 1); + + for (self.objects.items) |index| { + self.getFile(index).?.writeSymtab(self); + } + for (self.dylibs.items) |index| { + self.getFile(index).?.writeSymtab(self); + } + if (self.getInternalObject()) |internal| { + internal.writeSymtab(self); + } + + assert(self.strtab.items.len == cmd.strsize); + + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff); + + return off + cmd.nsyms * @sizeOf(macho.nlist_64); +} + +fn writeIndsymtab(self: *MachO, off: u32) !u32 { + const gpa = self.base.comp.gpa; + const cmd = &self.dysymtab_cmd; + cmd.indirectsymoff = off; + cmd.nindirectsyms = self.indsymtab.nsyms(self); + + 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.base.file.?.pwriteAll(buffer.items, cmd.indirectsymoff); + assert(buffer.items.len == needed_size); + + return off + needed_size; +} + +pub fn writeStrtab(self: *MachO, off: u32) !u32 { + const cmd = &self.symtab_cmd; + cmd.stroff = off; + try self.base.file.?.pwriteAll(self.strtab.items, cmd.stroff); + return off + cmd.strsize; +} + +fn writeLoadCommands(self: *MachO) !struct { usize, usize, usize } { + const gpa = self.base.comp.gpa; + const needed_size = load_commands.calcLoadCommandsSize(self, false); + const buffer = try gpa.alloc(u8, needed_size); + defer gpa.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + var cwriter = std.io.countingWriter(stream.writer()); + const writer = cwriter.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.entry_index) |global_index| { + const sym = self.getSymbol(global_index); + 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; + } + + try load_commands.writeRpathLCs(self.rpath_table.keys(), writer); + ncmds += self.rpath_table.keys().len; + + 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) + cwriter.bytes_written; + 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(cwriter.bytes_written == needed_size); + + try self.base.file.?.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) { + header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; + } + if (self.binds_to_weak) { + header.flags |= macho.MH_BINDS_TO_WEAK; + } + if (self.weak_defines) { + header.flags |= macho.MH_WEAK_DEFINES; + } + + header.ncmds = @intCast(ncmds); + header.sizeofcmds = @intCast(sizeofcmds); + + log.debug("writing Mach-O header {}", .{header}); + + try self.base.file.?.pwriteAll(mem.asBytes(&header), 0); +} + +fn writeUuid(self: *MachO, uuid_cmd_offset: usize, 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.base.file.?.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.base.file.?.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.base.file.?.pwriteAll(buffer.items, offset); +} + fn shrinkAtom(self: *MachO, atom_index: Atom.Index, new_block_size: u64) void { _ = self; _ = atom_index; @@ -3194,7 +3890,7 @@ const supported_platforms = [_]SupportedPlatforms{ }; // zig fmt: on -inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 { +pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 { const major = version.major; const minor = version.minor; const patch = version.patch; diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index cb5feef263..1e723d337b 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -311,13 +311,16 @@ pub fn resolveRelocs(self: Atom, macho_file: *MachO, buffer: []u8) !void { try stream.seekTo(rel_offset); self.resolveRelocInner(rel, subtractor, buffer, macho_file, stream.writer()) catch |err| { switch (err) { - error.RelaxFail => macho_file.base.fatal( - "{}: {s}: 0x{x}: failed to relax relocation: in {s}", - .{ file.fmtPath(), name, rel.offset, @tagName(rel.type) }, - ), + error.RelaxFail => { + try macho_file.reportParseError2( + file.getIndex(), + "{s}: 0x{x}: failed to relax relocation: in {s}", + .{ name, rel.offset, @tagName(rel.type) }, + ); + return error.ResolveFailed; + }, else => |e| return e, } - return error.ResolveFailed; }; } } @@ -338,7 +341,7 @@ fn resolveRelocInner( macho_file: *MachO, writer: anytype, ) ResolveError!void { - const cpu_arch = macho_file.options.cpu_arch.?; + const cpu_arch = macho_file.getTarget().cpu.arch; const rel_offset = rel.offset - self.off; const seg_id = macho_file.sections.items(.segment_id)[self.out_n_sect]; const seg = macho_file.segments.items[seg_id]; diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 54d83d4530..045bad712b 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -264,7 +264,7 @@ pub fn writeAdhocSignature( opts: WriteOpts, writer: anytype, ) !void { - const allocator = macho_file.base.allocator; + const allocator = macho_file.base.comp.gpa; var header: macho.SuperBlob = .{ .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, @@ -287,7 +287,7 @@ pub fn writeAdhocSignature( self.code_directory.inner.nCodeSlots = total_pages; // Calculate hash for each page (in file) and write it to the buffer - var hasher = Hasher(Sha256){ .allocator = allocator, .thread_pool = macho_file.base.thread_pool }; + var hasher = Hasher(Sha256){ .allocator = allocator, .thread_pool = macho_file.base.comp.thread_pool }; try hasher.hash(opts.file, self.code_directory.code_slots.items, .{ .chunk_size = self.page_size, .max_file_size = opts.file_size, diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index a6f865901e..a89e75d533 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -1184,7 +1184,8 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void { self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1)); } - if (!macho_file.options.strip and self.hasDebugInfo()) self.calcStabsSize(macho_file); + if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo()) + self.calcStabsSize(macho_file); } pub fn calcStabsSize(self: *Object, macho_file: *MachO) void { @@ -1264,7 +1265,8 @@ pub fn writeSymtab(self: Object, macho_file: *MachO) void { sym.setOutputSym(macho_file, out_sym); } - if (!macho_file.options.strip and self.hasDebugInfo()) self.writeStabs(macho_file); + if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo()) + self.writeStabs(macho_file); } pub fn writeStabs(self: *const Object, macho_file: *MachO) void { diff --git a/src/link/MachO/Symbol.zig b/src/link/MachO/Symbol.zig index c3a6d7b54e..9355c0db2c 100644 --- a/src/link/MachO/Symbol.zig +++ b/src/link/MachO/Symbol.zig @@ -230,9 +230,14 @@ pub fn setOutputSym(symbol: Symbol, macho_file: *MachO, out: *macho.nlist_64) vo out.n_value = 0; out.n_desc = 0; - const ord: u16 = if (macho_file.options.namespace == .flat) - @as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP)) - else if (symbol.getDylibOrdinal(macho_file)) |ord| + // TODO: + // const ord: u16 = if (macho_file.options.namespace == .flat) + // @as(u8, @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP)) + // else if (symbol.getDylibOrdinal(macho_file)) |ord| + // ord + // else + // macho.BIND_SPECIAL_DYLIB_SELF; + const ord: u16 = if (symbol.getDylibOrdinal(macho_file)) |ord| ord else macho.BIND_SPECIAL_DYLIB_SELF; diff --git a/src/link/MachO/dyld_info/bind.zig b/src/link/MachO/dyld_info/bind.zig index 5bc872e277..cee57e1edf 100644 --- a/src/link/MachO/dyld_info/bind.zig +++ b/src/link/MachO/dyld_info/bind.zig @@ -99,10 +99,10 @@ pub const Bind = struct { const ordinal: i16 = ord: { if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; if (sym.flags.import) { - if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; + // TODO: if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord); } - if (ctx.options.undefined_treatment == .dynamic_lookup) + if (ctx.undefined_treatment == .dynamic_lookup) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; break :ord macho.BIND_SPECIAL_DYLIB_SELF; }; @@ -359,10 +359,10 @@ pub const LazyBind = struct { const ordinal: i16 = ord: { if (sym.flags.interposable) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; if (sym.flags.import) { - if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; + // TODO: if (ctx.options.namespace == .flat) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; if (sym.getDylibOrdinal(ctx)) |ord| break :ord @bitCast(ord); } - if (ctx.options.undefined_treatment == .dynamic_lookup) + if (ctx.undefined_treatment == .dynamic_lookup) break :ord macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP; break :ord macho.BIND_SPECIAL_DYLIB_SELF; }; diff --git a/src/link/MachO/eh_frame.zig b/src/link/MachO/eh_frame.zig index 91a9cafb54..56d81ba93a 100644 --- a/src/link/MachO/eh_frame.zig +++ b/src/link/MachO/eh_frame.zig @@ -374,7 +374,7 @@ pub fn write(macho_file: *MachO, buffer: []u8) void { defer tracy.end(); const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; - const addend: i64 = switch (macho_file.options.cpu_arch.?) { + const addend: i64 = switch (macho_file.getTarget().cpu.arch) { .x86_64 => 4, else => 0, }; @@ -452,7 +452,7 @@ pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: *std.ArrayList(macho. const tracy = trace(@src()); defer tracy.end(); - const cpu_arch = macho_file.options.cpu_arch.?; + const cpu_arch = macho_file.getTarget().cpu.arch; const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; const addend: i64 = switch (cpu_arch) { .x86_64 => 4, diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig index 66b838f95c..3c3c53f637 100644 --- a/src/link/MachO/load_commands.zig +++ b/src/link/MachO/load_commands.zig @@ -7,7 +7,6 @@ const mem = std.mem; const Allocator = mem.Allocator; const Dylib = @import("Dylib.zig"); const MachO = @import("../MachO.zig"); -const Options = @import("../MachO.zig").Options; pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; @@ -200,17 +199,29 @@ pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: anytype) !void { } } -pub fn writeDylibIdLC(options: *const Options, writer: anytype) !void { - assert(options.dylib); - const emit = options.emit; - const install_name = options.install_name orelse emit.sub_path; - const curr = options.current_version orelse Options.Version.new(1, 0, 0); - const compat = options.compatibility_version orelse Options.Version.new(1, 0, 0); +pub fn writeDylibIdLC(macho_file: *MachO, writer: anytype) !void { + const comp = macho_file.base.comp; + const gpa = comp.gpa; + assert(comp.config.output_mode == .Lib and comp.config.link_mode == .Dynamic); + const emit = macho_file.base.emit; + const install_name = macho_file.install_name orelse + try emit.directory.join(gpa, &.{emit.sub_path}); + defer if (macho_file.install_name == null) gpa.free(install_name); + const curr = comp.version orelse std.SemanticVersion{ + .major = 1, + .minor = 0, + .patch = 0, + }; + const compat = macho_file.compatibility_version orelse std.SemanticVersion{ + .major = 1, + .minor = 0, + .patch = 0, + }; try writeDylibLC(.{ .cmd = .ID_DYLIB, .name = install_name, - .current_version = curr.value, - .compatibility_version = compat.value, + .current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)), + .compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)), }, writer); } @@ -235,32 +246,38 @@ pub fn writeRpathLCs(rpaths: []const []const u8, writer: anytype) !void { } } -pub fn writeVersionMinLC(platform: Options.Platform, sdk_version: ?Options.Version, writer: anytype) !void { - const cmd: macho.LC = switch (platform.platform) { - .MACOS => .VERSION_MIN_MACOSX, - .IOS, .IOSSIMULATOR => .VERSION_MIN_IPHONEOS, - .TVOS, .TVOSSIMULATOR => .VERSION_MIN_TVOS, - .WATCHOS, .WATCHOSSIMULATOR => .VERSION_MIN_WATCHOS, +pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: anytype) !void { + const cmd: macho.LC = switch (platform.os_tag) { + .macos => .VERSION_MIN_MACOSX, + .ios => .VERSION_MIN_IPHONEOS, + .tvos => .VERSION_MIN_TVOS, + .watchos => .VERSION_MIN_WATCHOS, else => unreachable, }; try writer.writeAll(mem.asBytes(&macho.version_min_command{ .cmd = cmd, - .version = platform.version.value, - .sdk = if (sdk_version) |ver| ver.value else platform.version.value, + .version = platform.toAppleVersion(), + .sdk = if (sdk_version) |ver| + MachO.semanticVersionToAppleVersion(ver) + else + platform.toAppleVersion(), })); } -pub fn writeBuildVersionLC(platform: Options.Platform, sdk_version: ?Options.Version, writer: anytype) !void { +pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: anytype) !void { const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); try writer.writeStruct(macho.build_version_command{ .cmdsize = cmdsize, - .platform = platform.platform, - .minos = platform.version.value, - .sdk = if (sdk_version) |ver| ver.value else platform.version.value, + .platform = platform.toApplePlatform(), + .minos = platform.toAppleVersion(), + .sdk = if (sdk_version) |ver| + MachO.semanticVersionToAppleVersion(ver) + else + platform.toAppleVersion(), .ntools = 1, }); try writer.writeAll(mem.asBytes(&macho.build_tool_version{ - .tool = @as(macho.TOOL, @enumFromInt(0x6)), + .tool = .ZIG, .version = 0x0, })); } diff --git a/src/link/MachO/uuid.zig b/src/link/MachO/uuid.zig index ca66e129d2..565ae80b22 100644 --- a/src/link/MachO/uuid.zig +++ b/src/link/MachO/uuid.zig @@ -4,13 +4,7 @@ /// and we will use it too as it seems accepted by Apple OSes. /// TODO LLD also hashes the output filename to disambiguate between same builds with different /// output files. Should we also do that? -pub fn calcUuid( - allocator: Allocator, - thread_pool: *ThreadPool, - file: fs.File, - file_size: u64, - out: *[Md5.digest_length]u8, -) !void { +pub fn calcUuid(comp: *const Compilation, file: fs.File, file_size: u64, out: *[Md5.digest_length]u8) !void { const tracy = trace(@src()); defer tracy.end(); @@ -18,17 +12,17 @@ pub fn calcUuid( const num_chunks: usize = std.math.cast(usize, @divTrunc(file_size, chunk_size)) orelse return error.Overflow; const actual_num_chunks = if (@rem(file_size, chunk_size) > 0) num_chunks + 1 else num_chunks; - const hashes = try allocator.alloc([Md5.digest_length]u8, actual_num_chunks); - defer allocator.free(hashes); + const hashes = try comp.gpa.alloc([Md5.digest_length]u8, actual_num_chunks); + defer comp.gpa.free(hashes); - var hasher = Hasher(Md5){ .allocator = allocator, .thread_pool = thread_pool }; + var hasher = Hasher(Md5){ .allocator = comp.gpa, .thread_pool = comp.thread_pool }; try hasher.hash(file, hashes, .{ .chunk_size = chunk_size, .max_file_size = file_size, }); - const final_buffer = try allocator.alloc(u8, actual_num_chunks * Md5.digest_length); - defer allocator.free(final_buffer); + const final_buffer = try comp.gpa.alloc(u8, actual_num_chunks * Md5.digest_length); + defer comp.gpa.free(final_buffer); for (hashes, 0..) |hash, i| { @memcpy(final_buffer[i * Md5.digest_length ..][0..Md5.digest_length], &hash); @@ -49,7 +43,7 @@ const mem = std.mem; const std = @import("std"); const trace = @import("../../tracy.zig").trace; -const Allocator = mem.Allocator; +const Compilation = @import("../../Compilation.zig"); const Md5 = std.crypto.hash.Md5; const Hasher = @import("hasher.zig").ParallelHasher; const ThreadPool = std.Thread.Pool;