Commit 3968aea8ec
Changed files (9)
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;
};
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];
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,
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,
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,
}));
}
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 {
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;
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;
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;