diff --git a/CMakeLists.txt b/CMakeLists.txt index fa900dbe93..8e8c66d374 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -585,10 +585,13 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/MachO/DwarfInfo.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/Relocation.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/ZldAtom.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/dead_strip.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/fat.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/load_commands.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/thunks.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/zld.zig" "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" diff --git a/ci/aarch64-macos.sh b/ci/aarch64-macos.sh index 337c2bfa8e..2fe38f21a4 100755 --- a/ci/aarch64-macos.sh +++ b/ci/aarch64-macos.sh @@ -52,3 +52,19 @@ stage3-release/bin/zig build test docs \ # Produce the experimental std lib documentation. stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib + +# Ensure that stage3 and stage4 are byte-for-byte identical. +stage3-release/bin/zig build \ + --prefix stage4-release \ + -Denable-llvm \ + -Dno-lib \ + -Drelease \ + -Dstrip \ + -Dtarget=$TARGET \ + -Duse-zig-libcxx \ + -Dversion-string="$(stage3-release/bin/zig version)" + +# diff returns an error code if the files differ. +echo "If the following command fails, it means nondeterminism has been" +echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." +diff stage3-release/bin/zig stage4-release/bin/zig diff --git a/ci/x86_64-macos.sh b/ci/x86_64-macos.sh index f09121ccd0..9d9cdb9d6f 100755 --- a/ci/x86_64-macos.sh +++ b/ci/x86_64-macos.sh @@ -60,3 +60,19 @@ stage3-release/bin/zig build test docs \ # Produce the experimental std lib documentation. stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib + +# Ensure that stage3 and stage4 are byte-for-byte identical. +stage3-release/bin/zig build \ + --prefix stage4-release \ + -Denable-llvm \ + -Dno-lib \ + -Drelease \ + -Dstrip \ + -Dtarget=$TARGET \ + -Duse-zig-libcxx \ + -Dversion-string="$(stage3-release/bin/zig version)" + +# diff returns an error code if the files differ. +echo "If the following command fails, it means nondeterminism has been" +echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." +diff stage3-release/bin/zig stage4-release/bin/zig diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 7bebea54a0..41a275c38f 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -571,6 +571,12 @@ const MachODumper = struct { }); }, + .UUID => { + const uuid = lc.cast(macho.uuid_command).?; + try writer.writeByte('\n'); + try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)}); + }, + else => {}, } } diff --git a/lib/std/macho.zig b/lib/std/macho.zig index cb1fca20b2..24dd1749ea 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -58,10 +58,10 @@ pub const uuid_command = extern struct { cmd: LC = .UUID, /// sizeof(struct uuid_command) - cmdsize: u32, + cmdsize: u32 = @sizeOf(uuid_command), /// the 128-bit uuid - uuid: [16]u8, + uuid: [16]u8 = undefined, }; /// The version_min_command contains the min OS version on which this @@ -71,7 +71,7 @@ pub const version_min_command = extern struct { cmd: LC, /// sizeof(struct version_min_command) - cmdsize: u32, + cmdsize: u32 = @sizeOf(version_min_command), /// X.Y.Z is encoded in nibbles xxxx.yy.zz version: u32, @@ -87,7 +87,7 @@ pub const source_version_command = extern struct { cmd: LC = .SOURCE_VERSION, /// sizeof(source_version_command) - cmdsize: u32, + cmdsize: u32 = @sizeOf(source_version_command), /// A.B.C.D.E packed as a24.b10.c10.d10.e10 version: u64, @@ -155,13 +155,13 @@ pub const entry_point_command = extern struct { cmd: LC = .MAIN, /// sizeof(struct entry_point_command) - cmdsize: u32, + cmdsize: u32 = @sizeOf(entry_point_command), /// file (__TEXT) offset of main() - entryoff: u64, + entryoff: u64 = 0, /// if not zero, initial stack size - stacksize: u64, + stacksize: u64 = 0, }; /// The symtab_command contains the offsets and sizes of the link-edit 4.3BSD @@ -172,19 +172,19 @@ pub const symtab_command = extern struct { cmd: LC = .SYMTAB, /// sizeof(struct symtab_command) - cmdsize: u32, + cmdsize: u32 = @sizeOf(symtab_command), /// symbol table offset - symoff: u32, + symoff: u32 = 0, /// number of symbol table entries - nsyms: u32, + nsyms: u32 = 0, /// string table offset - stroff: u32, + stroff: u32 = 0, /// string table size in bytes - strsize: u32, + strsize: u32 = 0, }; /// This is the second set of the symbolic information which is used to support @@ -230,7 +230,7 @@ pub const dysymtab_command = extern struct { cmd: LC = .DYSYMTAB, /// sizeof(struct dysymtab_command) - cmdsize: u32, + cmdsize: u32 = @sizeOf(dysymtab_command), // The symbols indicated by symoff and nsyms of the LC_SYMTAB load command // are grouped into the following three groups: @@ -247,22 +247,22 @@ pub const dysymtab_command = extern struct { // table when this is a dynamically linked shared library file). /// index of local symbols - ilocalsym: u32, + ilocalsym: u32 = 0, /// number of local symbols - nlocalsym: u32, + nlocalsym: u32 = 0, /// index to externally defined symbols - iextdefsym: u32, + iextdefsym: u32 = 0, /// number of externally defined symbols - nextdefsym: u32, + nextdefsym: u32 = 0, /// index to undefined symbols - iundefsym: u32, + iundefsym: u32 = 0, /// number of undefined symbols - nundefsym: u32, + nundefsym: u32 = 0, // For the for the dynamic binding process to find which module a symbol // is defined in the table of contents is used (analogous to the ranlib @@ -272,10 +272,10 @@ pub const dysymtab_command = extern struct { // symbols are sorted by name and is use as the table of contents. /// file offset to table of contents - tocoff: u32, + tocoff: u32 = 0, /// number of entries in table of contents - ntoc: u32, + ntoc: u32 = 0, // To support dynamic binding of "modules" (whole object files) the symbol // table must reflect the modules that the file was created from. This is @@ -286,10 +286,10 @@ pub const dysymtab_command = extern struct { // contains one module so everything in the file belongs to the module. /// file offset to module table - modtaboff: u32, + modtaboff: u32 = 0, /// number of module table entries - nmodtab: u32, + nmodtab: u32 = 0, // To support dynamic module binding the module structure for each module // indicates the external references (defined and undefined) each module @@ -300,10 +300,10 @@ pub const dysymtab_command = extern struct { // undefined external symbols indicates the external references. /// offset to referenced symbol table - extrefsymoff: u32, + extrefsymoff: u32 = 0, /// number of referenced symbol table entries - nextrefsyms: u32, + nextrefsyms: u32 = 0, // The sections that contain "symbol pointers" and "routine stubs" have // indexes and (implied counts based on the size of the section and fixed @@ -315,10 +315,10 @@ pub const dysymtab_command = extern struct { // The indirect symbol table is ordered to match the entries in the section. /// file offset to the indirect symbol table - indirectsymoff: u32, + indirectsymoff: u32 = 0, /// number of indirect symbol table entries - nindirectsyms: u32, + nindirectsyms: u32 = 0, // To support relocating an individual module in a library file quickly the // external relocation entries for each module in the library need to be @@ -347,20 +347,20 @@ pub const dysymtab_command = extern struct { // remaining relocation entries must be local). /// offset to external relocation entries - extreloff: u32, + extreloff: u32 = 0, /// number of external relocation entries - nextrel: u32, + nextrel: u32 = 0, // All the local relocation entries are grouped together (they are not // grouped by their module since they are only used if the object is moved // from it staticly link edited address). /// offset to local relocation entries - locreloff: u32, + locreloff: u32 = 0, /// number of local relocation entries - nlocrel: u32, + nlocrel: u32 = 0, }; /// The linkedit_data_command contains the offsets and sizes of a blob @@ -370,13 +370,13 @@ pub const linkedit_data_command = extern struct { cmd: LC, /// sizeof(struct linkedit_data_command) - cmdsize: u32, + cmdsize: u32 = @sizeOf(linkedit_data_command), /// file offset of data in __LINKEDIT segment - dataoff: u32, + dataoff: u32 = 0, /// file size of data in __LINKEDIT segment - datasize: u32, + datasize: u32 = 0, }; /// The dyld_info_command contains the file offsets and sizes of @@ -387,10 +387,10 @@ pub const linkedit_data_command = extern struct { /// to interpret it. pub const dyld_info_command = extern struct { /// LC_DYLD_INFO or LC_DYLD_INFO_ONLY - cmd: LC, + cmd: LC = .DYLD_INFO_ONLY, /// sizeof(struct dyld_info_command) - cmdsize: u32, + cmdsize: u32 = @sizeOf(dyld_info_command), // Dyld rebases an image whenever dyld loads it at an address different // from its preferred address. The rebase information is a stream @@ -403,10 +403,10 @@ pub const dyld_info_command = extern struct { // bytes. /// file offset to rebase info - rebase_off: u32, + rebase_off: u32 = 0, /// size of rebase info - rebase_size: u32, + rebase_size: u32 = 0, // Dyld binds an image during the loading process, if the image // requires any pointers to be initialized to symbols in other images. @@ -420,10 +420,10 @@ pub const dyld_info_command = extern struct { // encoded in a few bytes. /// file offset to binding info - bind_off: u32, + bind_off: u32 = 0, /// size of binding info - bind_size: u32, + bind_size: u32 = 0, // Some C++ programs require dyld to unique symbols so that all // images in the process use the same copy of some code/data. @@ -440,10 +440,10 @@ pub const dyld_info_command = extern struct { // and the call to operator new is then rebound. /// file offset to weak binding info - weak_bind_off: u32, + weak_bind_off: u32 = 0, /// size of weak binding info - weak_bind_size: u32, + weak_bind_size: u32 = 0, // Some uses of external symbols do not need to be bound immediately. // Instead they can be lazily bound on first use. The lazy_bind @@ -457,10 +457,10 @@ pub const dyld_info_command = extern struct { // to bind. /// file offset to lazy binding info - lazy_bind_off: u32, + lazy_bind_off: u32 = 0, /// size of lazy binding info - lazy_bind_size: u32, + lazy_bind_size: u32 = 0, // The symbols exported by a dylib are encoded in a trie. This // is a compact representation that factors out common prefixes. @@ -494,10 +494,10 @@ pub const dyld_info_command = extern struct { // edge points to. /// file offset to lazy binding info - export_off: u32, + export_off: u32 = 0, /// size of lazy binding info - export_size: u32, + export_size: u32 = 0, }; /// A program that uses a dynamic linker contains a dylinker_command to identify diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 4a1ca9a357..72a24b0ac6 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -20,6 +20,7 @@ const dead_strip = @import("MachO/dead_strip.zig"); const fat = @import("MachO/fat.zig"); const link = @import("../link.zig"); const llvm_backend = @import("../codegen/llvm.zig"); +const load_commands = @import("MachO/load_commands.zig"); const target_util = @import("../target.zig"); const trace = @import("../tracy.zig").trace; const zld = @import("MachO/zld.zig"); @@ -38,6 +39,7 @@ const Object = @import("MachO/Object.zig"); const LibStub = @import("tapi.zig").LibStub; const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; +const Md5 = std.crypto.hash.Md5; const Module = @import("../Module.zig"); const Relocation = @import("MachO/Relocation.zig"); const StringTable = @import("strtab.zig").StringTable; @@ -98,10 +100,11 @@ page_size: u16, /// fashion (default for LLVM backend). mode: enum { incremental, one_shot }, -uuid: macho.uuid_command = .{ - .cmdsize = @sizeOf(macho.uuid_command), - .uuid = undefined, -}, +dyld_info_cmd: macho.dyld_info_command = .{}, +symtab_cmd: macho.symtab_command = .{}, +dysymtab_cmd: macho.dysymtab_command = .{}, +uuid_cmd: macho.uuid_command = .{}, +codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE }, dylibs: std.ArrayListUnmanaged(Dylib) = .{}, dylibs_map: std.StringHashMapUnmanaged(u16) = .{}, @@ -265,9 +268,6 @@ pub const SymbolWithLoc = struct { /// actual_capacity + (actual_capacity / ideal_factor) const ideal_factor = 3; -/// Default path to dyld -pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; - /// 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). @@ -556,40 +556,55 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No self.logAtoms(); } + try self.writeLinkeditSegmentData(); + + // Write load commands var lc_buffer = std.ArrayList(u8).init(arena); const lc_writer = lc_buffer.writer(); - var ncmds: u32 = 0; - try self.writeLinkeditSegmentData(&ncmds, lc_writer); - try writeDylinkerLC(&ncmds, lc_writer); + try self.writeSegmentHeaders(lc_writer); + try lc_writer.writeStruct(self.dyld_info_cmd); + try lc_writer.writeStruct(self.symtab_cmd); + try lc_writer.writeStruct(self.dysymtab_cmd); + try load_commands.writeDylinkerLC(lc_writer); - self.writeMainLC(&ncmds, lc_writer) catch |err| switch (err) { - error.MissingMainEntrypoint => { - self.error_flags.no_entry_point_found = true; + switch (self.base.options.output_mode) { + .Exe => blk: { + const seg_id = self.header_segment_cmd_index.?; + const seg = self.segments.items[seg_id]; + const global = self.getEntryPoint() catch |err| switch (err) { + error.MissingMainEntrypoint => { + self.error_flags.no_entry_point_found = true; + break :blk; + }, + else => |e| return e, + }; + const sym = self.getSymbol(global); + try lc_writer.writeStruct(macho.entry_point_command{ + .entryoff = @intCast(u32, sym.n_value - seg.vmaddr), + .stacksize = self.base.options.stack_size_override orelse 0, + }); }, - else => |e| return e, - }; - - try self.writeDylibIdLC(&ncmds, lc_writer); - try self.writeRpathLCs(&ncmds, lc_writer); - - { - try lc_writer.writeStruct(macho.source_version_command{ - .cmdsize = @sizeOf(macho.source_version_command), - .version = 0x0, - }); - ncmds += 1; + .Lib => if (self.base.options.link_mode == .Dynamic) { + try load_commands.writeDylibIdLC(self.base.allocator, &self.base.options, lc_writer); + }, + else => {}, } - try self.writeBuildVersionLC(&ncmds, lc_writer); + try load_commands.writeRpathLCs(self.base.allocator, &self.base.options, lc_writer); + try lc_writer.writeStruct(macho.source_version_command{ + .version = 0, + }); + try load_commands.writeBuildVersionLC(&self.base.options, lc_writer); - { - std.crypto.random.bytes(&self.uuid.uuid); - try lc_writer.writeStruct(self.uuid); - ncmds += 1; + if (self.cold_start) { + std.crypto.random.bytes(&self.uuid_cmd.uuid); + Md5.hash(&self.uuid_cmd.uuid, &self.uuid_cmd.uuid, .{}); + conformUuid(&self.uuid_cmd.uuid); } + try lc_writer.writeStruct(self.uuid_cmd); - try self.writeLoadDylibLCs(&ncmds, lc_writer); + try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), lc_writer); const target = self.base.options.target; const requires_codesig = blk: { @@ -598,7 +613,6 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No break :blk true; break :blk false; }; - var codesig_offset: ?u32 = null; var codesig: ?CodeSignature = if (requires_codesig) 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 @@ -610,20 +624,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No if (self.base.options.entitlements) |path| { try codesig.addEntitlements(arena, path); } - codesig_offset = try self.writeCodeSignaturePadding(&codesig, &ncmds, lc_writer); + try self.writeCodeSignaturePadding(&codesig); + try lc_writer.writeStruct(self.codesig_cmd); break :blk codesig; } else null; - var headers_buf = std.ArrayList(u8).init(arena); - try self.writeSegmentHeaders(&ncmds, headers_buf.writer()); + try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64)); - try self.base.file.?.pwriteAll(headers_buf.items, @sizeOf(macho.mach_header_64)); - try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64) + headers_buf.items.len); - - try self.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len + headers_buf.items.len)); + const ncmds = load_commands.calcNumOfLCs(lc_buffer.items); + try self.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len)); if (codesig) |*csig| { - try self.writeCodeSignature(comp, csig, codesig_offset.?); // code signing always comes last + try self.writeCodeSignature(comp, csig); // code signing always comes last } if (self.d_sym) |*d_sym| { @@ -653,6 +665,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No self.cold_start = false; } +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 fn resolveLibSystem( arena: Allocator, @@ -1702,195 +1719,6 @@ pub fn resolveDyldStubBinder(self: *MachO) !void { try self.writePtrWidthAtom(got_atom); } -pub fn writeDylinkerLC(ncmds: *u32, lc_writer: anytype) !void { - const name_len = mem.sliceTo(default_dyld_path, 0).len; - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylinker_command) + name_len, - @sizeOf(u64), - )); - try lc_writer.writeStruct(macho.dylinker_command{ - .cmd = .LOAD_DYLINKER, - .cmdsize = cmdsize, - .name = @sizeOf(macho.dylinker_command), - }); - try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0)); - const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len; - if (padding > 0) { - try lc_writer.writeByteNTimes(0, padding); - } - ncmds.* += 1; -} - -pub fn writeMainLC(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { - if (self.base.options.output_mode != .Exe) return; - const seg_id = self.header_segment_cmd_index.?; - const seg = self.segments.items[seg_id]; - const global = try self.getEntryPoint(); - const sym = self.getSymbol(global); - try lc_writer.writeStruct(macho.entry_point_command{ - .cmd = .MAIN, - .cmdsize = @sizeOf(macho.entry_point_command), - .entryoff = @intCast(u32, sym.n_value - seg.vmaddr), - .stacksize = self.base.options.stack_size_override orelse 0, - }); - ncmds.* += 1; -} - -const WriteDylibLCCtx = struct { - cmd: macho.LC, - name: []const u8, - timestamp: u32 = 2, - current_version: u32 = 0x10000, - compatibility_version: u32 = 0x10000, -}; - -pub fn writeDylibLC(ctx: WriteDylibLCCtx, ncmds: *u32, lc_writer: anytype) !void { - const name_len = ctx.name.len + 1; - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylib_command) + name_len, - @sizeOf(u64), - )); - try lc_writer.writeStruct(macho.dylib_command{ - .cmd = ctx.cmd, - .cmdsize = cmdsize, - .dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = ctx.timestamp, - .current_version = ctx.current_version, - .compatibility_version = ctx.compatibility_version, - }, - }); - try lc_writer.writeAll(ctx.name); - try lc_writer.writeByte(0); - const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len; - if (padding > 0) { - try lc_writer.writeByteNTimes(0, padding); - } - ncmds.* += 1; -} - -pub fn writeDylibIdLC(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { - if (self.base.options.output_mode != .Lib) return; - const install_name = self.base.options.install_name orelse self.base.options.emit.?.sub_path; - const curr = self.base.options.version orelse std.builtin.Version{ - .major = 1, - .minor = 0, - .patch = 0, - }; - const compat = self.base.options.compatibility_version orelse std.builtin.Version{ - .major = 1, - .minor = 0, - .patch = 0, - }; - try writeDylibLC(.{ - .cmd = .ID_DYLIB, - .name = install_name, - .current_version = curr.major << 16 | curr.minor << 8 | curr.patch, - .compatibility_version = compat.major << 16 | compat.minor << 8 | compat.patch, - }, ncmds, lc_writer); -} - -const RpathIterator = struct { - buffer: []const []const u8, - table: std.StringHashMap(void), - count: usize = 0, - - fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator { - return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) }; - } - - fn deinit(it: *RpathIterator) void { - it.table.deinit(); - } - - fn next(it: *RpathIterator) !?[]const u8 { - while (true) { - if (it.count >= it.buffer.len) return null; - const rpath = it.buffer[it.count]; - it.count += 1; - const gop = try it.table.getOrPut(rpath); - if (gop.found_existing) continue; - return rpath; - } - } -}; - -pub fn writeRpathLCs(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { - const gpa = self.base.allocator; - - var it = RpathIterator.init(gpa, self.base.options.rpath_list); - defer it.deinit(); - - while (try it.next()) |rpath| { - const rpath_len = rpath.len + 1; - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.rpath_command) + rpath_len, - @sizeOf(u64), - )); - try lc_writer.writeStruct(macho.rpath_command{ - .cmdsize = cmdsize, - .path = @sizeOf(macho.rpath_command), - }); - try lc_writer.writeAll(rpath); - try lc_writer.writeByte(0); - const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len; - if (padding > 0) { - try lc_writer.writeByteNTimes(0, padding); - } - ncmds.* += 1; - } -} - -pub fn writeBuildVersionLC(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { - const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); - const platform_version = blk: { - const ver = self.base.options.target.os.version_range.semver.min; - const platform_version = ver.major << 16 | ver.minor << 8; - break :blk platform_version; - }; - const sdk_version = if (self.base.options.native_darwin_sdk) |sdk| blk: { - const ver = sdk.version; - const sdk_version = ver.major << 16 | ver.minor << 8; - break :blk sdk_version; - } else platform_version; - const is_simulator_abi = self.base.options.target.abi == .simulator; - try lc_writer.writeStruct(macho.build_version_command{ - .cmdsize = cmdsize, - .platform = switch (self.base.options.target.os.tag) { - .macos => .MACOS, - .ios => if (is_simulator_abi) macho.PLATFORM.IOSSIMULATOR else macho.PLATFORM.IOS, - .watchos => if (is_simulator_abi) macho.PLATFORM.WATCHOSSIMULATOR else macho.PLATFORM.WATCHOS, - .tvos => if (is_simulator_abi) macho.PLATFORM.TVOSSIMULATOR else macho.PLATFORM.TVOS, - else => unreachable, - }, - .minos = platform_version, - .sdk = sdk_version, - .ntools = 1, - }); - try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{ - .tool = .LD, - .version = 0x0, - })); - ncmds.* += 1; -} - -pub fn writeLoadDylibLCs(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { - for (self.referenced_dylibs.keys()) |id| { - const dylib = self.dylibs.items[id]; - const dylib_id = dylib.id orelse unreachable; - try writeDylibLC(.{ - .cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB, - .name = dylib_id.name, - .timestamp = dylib_id.timestamp, - .current_version = dylib_id.current_version, - .compatibility_version = dylib_id.compatibility_version, - }, ncmds, lc_writer); - } -} - pub fn deinit(self: *MachO) void { const gpa = self.base.allocator; @@ -2976,98 +2804,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { } } -pub inline fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 { - const darwin_path_max = 1024; - const name_len = if (assume_max_path_len) darwin_path_max else std.mem.len(name) + 1; - return mem.alignForwardGeneric(u64, cmd_size + name_len, @alignOf(u64)); -} - -fn calcLCsSize(self: *MachO, assume_max_path_len: bool) !u32 { - const gpa = self.base.allocator; - var sizeofcmds: u64 = 0; - for (self.segments.items) |seg| { - sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64); - } - - // LC_DYLD_INFO_ONLY - sizeofcmds += @sizeOf(macho.dyld_info_command); - // LC_FUNCTION_STARTS - if (self.text_section_index != null) { - sizeofcmds += @sizeOf(macho.linkedit_data_command); - } - // LC_DATA_IN_CODE - sizeofcmds += @sizeOf(macho.linkedit_data_command); - // LC_SYMTAB - sizeofcmds += @sizeOf(macho.symtab_command); - // LC_DYSYMTAB - sizeofcmds += @sizeOf(macho.dysymtab_command); - // LC_LOAD_DYLINKER - sizeofcmds += calcInstallNameLen( - @sizeOf(macho.dylinker_command), - mem.sliceTo(default_dyld_path, 0), - false, - ); - // LC_MAIN - if (self.base.options.output_mode == .Exe) { - sizeofcmds += @sizeOf(macho.entry_point_command); - } - // LC_ID_DYLIB - if (self.base.options.output_mode == .Lib) { - sizeofcmds += blk: { - const install_name = self.base.options.install_name orelse self.base.options.emit.?.sub_path; - break :blk calcInstallNameLen( - @sizeOf(macho.dylib_command), - install_name, - assume_max_path_len, - ); - }; - } - // LC_RPATH - { - var it = RpathIterator.init(gpa, self.base.options.rpath_list); - defer it.deinit(); - while (try it.next()) |rpath| { - sizeofcmds += calcInstallNameLen( - @sizeOf(macho.rpath_command), - rpath, - assume_max_path_len, - ); - } - } - // LC_SOURCE_VERSION - sizeofcmds += @sizeOf(macho.source_version_command); - // LC_BUILD_VERSION - sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); - // LC_UUID - sizeofcmds += @sizeOf(macho.uuid_command); - // LC_LOAD_DYLIB - for (self.referenced_dylibs.keys()) |id| { - const dylib = self.dylibs.items[id]; - const dylib_id = dylib.id orelse unreachable; - sizeofcmds += calcInstallNameLen( - @sizeOf(macho.dylib_command), - dylib_id.name, - assume_max_path_len, - ); - } - // LC_CODE_SIGNATURE - { - const target = self.base.options.target; - const requires_codesig = blk: { - if (self.base.options.entitlements) |_| break :blk true; - if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator)) - break :blk true; - break :blk false; - }; - if (requires_codesig) { - sizeofcmds += @sizeOf(macho.linkedit_data_command); - } - } - - return @intCast(u32, sizeofcmds); -} - -pub fn calcPagezeroSize(self: *MachO) u64 { +fn calcPagezeroSize(self: *MachO) u64 { const pagezero_vmsize = self.base.options.pagezero_size orelse default_pagezero_vmsize; const aligned_pagezero_vmsize = mem.alignBackwardGeneric(u64, pagezero_vmsize, self.page_size); if (self.base.options.output_mode == .Lib) return 0; @@ -3079,23 +2816,6 @@ pub fn calcPagezeroSize(self: *MachO) u64 { return aligned_pagezero_vmsize; } -pub fn calcMinHeaderPad(self: *MachO) !u64 { - var padding: u32 = (try self.calcLCsSize(false)) + (self.base.options.headerpad_size orelse 0); - log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); - - if (self.base.options.headerpad_max_install_names) { - var min_headerpad_size: u32 = try self.calcLCsSize(true); - log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ - min_headerpad_size + @sizeOf(macho.mach_header_64), - }); - padding = @max(padding, min_headerpad_size); - } - const offset = @sizeOf(macho.mach_header_64) + padding; - log.debug("actual headerpad size 0x{x}", .{offset}); - - return offset; -} - fn allocateSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: struct { size: u64 = 0, alignment: u32 = 0, @@ -3433,18 +3153,17 @@ pub fn getGlobalSymbol(self: *MachO, name: []const u8) !u32 { return global_index; } -fn writeSegmentHeaders(self: *MachO, ncmds: *u32, writer: anytype) !void { +fn writeSegmentHeaders(self: *MachO, writer: anytype) !void { for (self.segments.items) |seg, i| { const indexes = self.getSectionIndexes(@intCast(u8, i)); try writer.writeStruct(seg); for (self.sections.items(.header)[indexes.start..indexes.end]) |header| { try writer.writeStruct(header); } - ncmds.* += 1; } } -fn writeLinkeditSegmentData(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { +fn writeLinkeditSegmentData(self: *MachO) !void { const seg = self.getLinkeditSegmentPtr(); seg.filesize = 0; seg.vmsize = 0; @@ -3459,8 +3178,8 @@ fn writeLinkeditSegmentData(self: *MachO, ncmds: *u32, lc_writer: anytype) !void } } - try self.writeDyldInfoData(ncmds, lc_writer); - try self.writeSymtabs(ncmds, lc_writer); + try self.writeDyldInfoData(); + try self.writeSymtabs(); seg.vmsize = mem.alignForwardGeneric(u64, seg.filesize, self.page_size); } @@ -3612,7 +3331,7 @@ fn collectExportData(self: *MachO, trie: *Trie) !void { try trie.finalize(gpa); } -fn writeDyldInfoData(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { +fn writeDyldInfoData(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -3683,21 +3402,14 @@ fn writeDyldInfoData(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { const end = start + (math.cast(usize, lazy_bind_size) orelse return error.Overflow); try self.populateLazyBindOffsetsInStubHelper(buffer[start..end]); - try lc_writer.writeStruct(macho.dyld_info_command{ - .cmd = .DYLD_INFO_ONLY, - .cmdsize = @sizeOf(macho.dyld_info_command), - .rebase_off = @intCast(u32, rebase_off), - .rebase_size = @intCast(u32, rebase_size), - .bind_off = @intCast(u32, bind_off), - .bind_size = @intCast(u32, bind_size), - .weak_bind_off = 0, - .weak_bind_size = 0, - .lazy_bind_off = @intCast(u32, lazy_bind_off), - .lazy_bind_size = @intCast(u32, lazy_bind_size), - .export_off = @intCast(u32, export_off), - .export_size = @intCast(u32, export_size), - }); - ncmds.* += 1; + self.dyld_info_cmd.rebase_off = @intCast(u32, rebase_off); + self.dyld_info_cmd.rebase_size = @intCast(u32, rebase_size); + self.dyld_info_cmd.bind_off = @intCast(u32, bind_off); + self.dyld_info_cmd.bind_size = @intCast(u32, bind_size); + self.dyld_info_cmd.lazy_bind_off = @intCast(u32, lazy_bind_off); + self.dyld_info_cmd.lazy_bind_size = @intCast(u32, lazy_bind_size); + self.dyld_info_cmd.export_off = @intCast(u32, export_off); + self.dyld_info_cmd.export_size = @intCast(u32, export_size); } fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { @@ -3799,45 +3511,14 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { } } -fn writeSymtabs(self: *MachO, ncmds: *u32, lc_writer: anytype) !void { - var symtab_cmd = macho.symtab_command{ - .cmdsize = @sizeOf(macho.symtab_command), - .symoff = 0, - .nsyms = 0, - .stroff = 0, - .strsize = 0, - }; - var dysymtab_cmd = macho.dysymtab_command{ - .cmdsize = @sizeOf(macho.dysymtab_command), - .ilocalsym = 0, - .nlocalsym = 0, - .iextdefsym = 0, - .nextdefsym = 0, - .iundefsym = 0, - .nundefsym = 0, - .tocoff = 0, - .ntoc = 0, - .modtaboff = 0, - .nmodtab = 0, - .extrefsymoff = 0, - .nextrefsyms = 0, - .indirectsymoff = 0, - .nindirectsyms = 0, - .extreloff = 0, - .nextrel = 0, - .locreloff = 0, - .nlocrel = 0, - }; - var ctx = try self.writeSymtab(&symtab_cmd); +fn writeSymtabs(self: *MachO) !void { + var ctx = try self.writeSymtab(); defer ctx.imports_table.deinit(); - try self.writeDysymtab(ctx, &dysymtab_cmd); - try self.writeStrtab(&symtab_cmd); - try lc_writer.writeStruct(symtab_cmd); - try lc_writer.writeStruct(dysymtab_cmd); - ncmds.* += 2; + try self.writeDysymtab(ctx); + try self.writeStrtab(); } -fn writeSymtab(self: *MachO, lc: *macho.symtab_command) !SymtabCtx { +fn writeSymtab(self: *MachO) !SymtabCtx { const gpa = self.base.allocator; var locals = std.ArrayList(macho.nlist_64).init(gpa); @@ -3902,8 +3583,8 @@ fn writeSymtab(self: *MachO, lc: *macho.symtab_command) !SymtabCtx { log.debug("writing symtab from 0x{x} to 0x{x}", .{ offset, offset + needed_size }); try self.base.file.?.pwriteAll(buffer.items, offset); - lc.symoff = @intCast(u32, offset); - lc.nsyms = nsyms; + self.symtab_cmd.symoff = @intCast(u32, offset); + self.symtab_cmd.nsyms = nsyms; return SymtabCtx{ .nlocalsym = nlocals, @@ -3913,7 +3594,7 @@ fn writeSymtab(self: *MachO, lc: *macho.symtab_command) !SymtabCtx { }; } -fn writeStrtab(self: *MachO, lc: *macho.symtab_command) !void { +fn writeStrtab(self: *MachO) !void { const seg = self.getLinkeditSegmentPtr(); const offset = mem.alignForwardGeneric(u64, seg.fileoff + seg.filesize, @alignOf(u64)); const needed_size = self.strtab.buffer.items.len; @@ -3923,8 +3604,8 @@ fn writeStrtab(self: *MachO, lc: *macho.symtab_command) !void { try self.base.file.?.pwriteAll(self.strtab.buffer.items, offset); - lc.stroff = @intCast(u32, offset); - lc.strsize = @intCast(u32, needed_size); + self.symtab_cmd.stroff = @intCast(u32, offset); + self.symtab_cmd.strsize = @intCast(u32, needed_size); } const SymtabCtx = struct { @@ -3934,7 +3615,7 @@ const SymtabCtx = struct { imports_table: std.AutoHashMap(SymbolWithLoc, u32), }; -fn writeDysymtab(self: *MachO, ctx: SymtabCtx, lc: *macho.dysymtab_command) !void { +fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void { const gpa = self.base.allocator; const nstubs = @intCast(u32, self.stubs_table.count()); const ngot_entries = @intCast(u32, self.got_entries_table.count()); @@ -3993,21 +3674,16 @@ fn writeDysymtab(self: *MachO, ctx: SymtabCtx, lc: *macho.dysymtab_command) !voi assert(buf.items.len == needed_size); try self.base.file.?.pwriteAll(buf.items, offset); - lc.nlocalsym = ctx.nlocalsym; - lc.iextdefsym = iextdefsym; - lc.nextdefsym = ctx.nextdefsym; - lc.iundefsym = iundefsym; - lc.nundefsym = ctx.nundefsym; - lc.indirectsymoff = @intCast(u32, offset); - lc.nindirectsyms = nindirectsyms; + self.dysymtab_cmd.nlocalsym = ctx.nlocalsym; + self.dysymtab_cmd.iextdefsym = iextdefsym; + self.dysymtab_cmd.nextdefsym = ctx.nextdefsym; + self.dysymtab_cmd.iundefsym = iundefsym; + self.dysymtab_cmd.nundefsym = ctx.nundefsym; + self.dysymtab_cmd.indirectsymoff = @intCast(u32, offset); + self.dysymtab_cmd.nindirectsyms = nindirectsyms; } -fn writeCodeSignaturePadding( - self: *MachO, - code_sig: *CodeSignature, - ncmds: *u32, - lc_writer: anytype, -) !u32 { +fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { const seg = self.getLinkeditSegmentPtr(); // 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 @@ -4020,19 +3696,13 @@ fn writeCodeSignaturePadding( // except for code signature data. try self.base.file.?.pwriteAll(&[_]u8{0}, offset + needed_size - 1); - try lc_writer.writeStruct(macho.linkedit_data_command{ - .cmd = .CODE_SIGNATURE, - .cmdsize = @sizeOf(macho.linkedit_data_command), - .dataoff = @intCast(u32, offset), - .datasize = @intCast(u32, needed_size), - }); - ncmds.* += 1; - - return @intCast(u32, offset); + self.codesig_cmd.dataoff = @intCast(u32, offset); + self.codesig_cmd.datasize = @intCast(u32, needed_size); } -fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature, offset: u32) !void { +fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void { const seg = self.getSegment(self.text_section_index.?); + const offset = self.codesig_cmd.dataoff; var buffer = std.ArrayList(u8).init(self.base.allocator); defer buffer.deinit(); diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index e3c362e941..8bc00d9181 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -1,6 +1,4 @@ const CodeSignature = @This(); -const Compilation = @import("../../Compilation.zig"); -const WaitGroup = @import("../../WaitGroup.zig"); const std = @import("std"); const assert = std.debug.assert; @@ -9,10 +7,14 @@ const log = std.log.scoped(.link); const macho = std.macho; const mem = std.mem; const testing = std.testing; -const Allocator = mem.Allocator; -const Sha256 = std.crypto.hash.sha2.Sha256; -const hash_size: u8 = 32; +const Allocator = mem.Allocator; +const Compilation = @import("../../Compilation.zig"); +const Sha256 = std.crypto.hash.sha2.Sha256; +const ThreadPool = @import("../../ThreadPool.zig"); +const WaitGroup = @import("../../WaitGroup.zig"); + +const hash_size = Sha256.digest_length; const Blob = union(enum) { code_directory: *CodeDirectory, @@ -109,7 +111,7 @@ const CodeDirectory = struct { fn size(self: CodeDirectory) u32 { const code_slots = self.inner.nCodeSlots * hash_size; const special_slots = self.inner.nSpecialSlots * hash_size; - return @sizeOf(macho.CodeDirectory) + @intCast(u32, self.ident.len + 1) + special_slots + code_slots; + return @sizeOf(macho.CodeDirectory) + @intCast(u32, self.ident.len + 1 + special_slots + code_slots); } fn write(self: CodeDirectory, writer: anytype) !void { @@ -287,33 +289,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 wg: WaitGroup = .{}; - { - const buffer = try gpa.alloc(u8, self.page_size * total_pages); - defer gpa.free(buffer); - - const results = try gpa.alloc(fs.File.PReadError!usize, total_pages); - defer gpa.free(results); - { - wg.reset(); - defer wg.wait(); - - var i: usize = 0; - while (i < total_pages) : (i += 1) { - const fstart = i * self.page_size; - const fsize = if (fstart + self.page_size > opts.file_size) - opts.file_size - fstart - else - self.page_size; - const out_hash = &self.code_directory.code_slots.items[i]; - wg.start(); - try comp.thread_pool.spawn(workerSha256Hash, .{ - opts.file, fstart, buffer[fstart..][0..fsize], out_hash, &results[i], &wg, - }); - } - } - for (results) |result| _ = try result; - } + try self.parallelHash(gpa, comp.thread_pool, opts.file, opts.file_size); try blobs.append(.{ .code_directory = &self.code_directory }); header.length += @sizeOf(macho.BlobIndex); @@ -352,7 +328,7 @@ pub fn writeAdhocSignature( } self.code_directory.inner.hashOffset = - @sizeOf(macho.CodeDirectory) + @intCast(u32, self.code_directory.ident.len + 1) + self.code_directory.inner.nSpecialSlots * hash_size; + @sizeOf(macho.CodeDirectory) + @intCast(u32, self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size); self.code_directory.inner.length = self.code_directory.size(); header.length += self.code_directory.size(); @@ -372,17 +348,60 @@ pub fn writeAdhocSignature( } } -fn workerSha256Hash( +fn parallelHash( + self: *CodeSignature, + gpa: Allocator, + pool: *ThreadPool, + file: fs.File, + file_size: u32, +) !void { + var wg: WaitGroup = .{}; + + const total_num_chunks = mem.alignForward(file_size, self.page_size) / self.page_size; + assert(self.code_directory.code_slots.items.len >= total_num_chunks); + + const buffer = try gpa.alloc(u8, self.page_size * total_num_chunks); + defer gpa.free(buffer); + + const results = try gpa.alloc(fs.File.PReadError!usize, total_num_chunks); + defer gpa.free(results); + + { + wg.reset(); + defer wg.wait(); + + var i: usize = 0; + while (i < total_num_chunks) : (i += 1) { + const fstart = i * self.page_size; + const fsize = if (fstart + self.page_size > file_size) + file_size - fstart + else + self.page_size; + wg.start(); + try pool.spawn(worker, .{ + file, + fstart, + buffer[fstart..][0..fsize], + &self.code_directory.code_slots.items[i], + &results[i], + &wg, + }); + } + } + for (results) |result| _ = try result; +} + +fn worker( file: fs.File, fstart: usize, buffer: []u8, - hash: *[hash_size]u8, + out: *[hash_size]u8, err: *fs.File.PReadError!usize, wg: *WaitGroup, ) void { defer wg.finish(); err.* = file.preadAll(buffer, fstart); - Sha256.hash(buffer, hash, .{}); + Sha256.hash(buffer, out, .{}); } pub fn size(self: CodeSignature) u32 { diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 655ba7162f..a13ad9c9f4 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -5,6 +5,7 @@ const build_options = @import("build_options"); const assert = std.debug.assert; const fs = std.fs; const link = @import("../../link.zig"); +const load_commands = @import("load_commands.zig"); const log = std.log.scoped(.dsym); const macho = std.macho; const makeStaticString = MachO.makeStaticString; @@ -25,6 +26,8 @@ dwarf: Dwarf, file: fs.File, page_size: u16, +symtab_cmd: macho.symtab_command = .{}, + segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{}, sections: std.ArrayListUnmanaged(macho.section_64) = .{}, @@ -295,31 +298,21 @@ pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void { } } + self.finalizeDwarfSegment(macho_file); + try self.writeLinkeditSegmentData(macho_file); + + // Write load commands var lc_buffer = std.ArrayList(u8).init(self.allocator); defer lc_buffer.deinit(); const lc_writer = lc_buffer.writer(); - var ncmds: u32 = 0; - self.finalizeDwarfSegment(macho_file); - try self.writeLinkeditSegmentData(macho_file, &ncmds, lc_writer); + try self.writeSegmentHeaders(macho_file, lc_writer); + try lc_writer.writeStruct(self.symtab_cmd); + try lc_writer.writeStruct(macho_file.uuid_cmd); - { - try lc_writer.writeStruct(macho_file.uuid); - ncmds += 1; - } - - var headers_buf = std.ArrayList(u8).init(self.allocator); - defer headers_buf.deinit(); - try self.writeSegmentHeaders(macho_file, &ncmds, headers_buf.writer()); - - try self.file.pwriteAll(headers_buf.items, @sizeOf(macho.mach_header_64)); - try self.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64) + headers_buf.items.len); - - try self.writeHeader( - macho_file, - ncmds, - @intCast(u32, lc_buffer.items.len + headers_buf.items.len), - ); + const ncmds = load_commands.calcNumOfLCs(lc_buffer.items); + try self.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64)); + try self.writeHeader(macho_file, ncmds, @intCast(u32, lc_buffer.items.len)); assert(!self.debug_abbrev_section_dirty); assert(!self.debug_aranges_section_dirty); @@ -386,7 +379,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void { log.debug("found __LINKEDIT segment free space at 0x{x}", .{linkedit.fileoff}); } -fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, ncmds: *u32, writer: anytype) !void { +fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, writer: anytype) !void { // Write segment/section headers from the binary file first. const end = macho_file.linkedit_segment_cmd_index.?; for (macho_file.segments.items[0..end]) |seg, i| { @@ -416,8 +409,6 @@ fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, ncmds: *u32, wri out_header.offset = 0; try writer.writeStruct(out_header); } - - ncmds.* += 1; } // Next, commit DSYM's __LINKEDIT and __DWARF segments headers. for (self.segments.items) |seg, i| { @@ -426,7 +417,6 @@ fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, ncmds: *u32, wri for (self.sections.items[indexes.start..indexes.end]) |header| { try writer.writeStruct(header); } - ncmds.* += 1; } } @@ -465,33 +455,19 @@ fn allocatedSize(self: *DebugSymbols, start: u64) u64 { return min_pos - start; } -fn writeLinkeditSegmentData( - self: *DebugSymbols, - macho_file: *MachO, - ncmds: *u32, - lc_writer: anytype, -) !void { +fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - var symtab_cmd = macho.symtab_command{ - .cmdsize = @sizeOf(macho.symtab_command), - .symoff = 0, - .nsyms = 0, - .stroff = 0, - .strsize = 0, - }; - try self.writeSymtab(macho_file, &symtab_cmd); - try self.writeStrtab(&symtab_cmd); - try lc_writer.writeStruct(symtab_cmd); - ncmds.* += 1; + try self.writeSymtab(macho_file); + try self.writeStrtab(); const seg = &self.segments.items[self.linkedit_segment_cmd_index.?]; const aligned_size = mem.alignForwardGeneric(u64, seg.filesize, self.page_size); seg.vmsize = aligned_size; } -fn writeSymtab(self: *DebugSymbols, macho_file: *MachO, lc: *macho.symtab_command) !void { +fn writeSymtab(self: *DebugSymbols, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -530,10 +506,10 @@ fn writeSymtab(self: *DebugSymbols, macho_file: *MachO, lc: *macho.symtab_comman const needed_size = nsyms * @sizeOf(macho.nlist_64); seg.filesize = offset + needed_size - seg.fileoff; - lc.symoff = @intCast(u32, offset); - lc.nsyms = @intCast(u32, nsyms); + self.symtab_cmd.symoff = @intCast(u32, offset); + self.symtab_cmd.nsyms = @intCast(u32, nsyms); - const locals_off = lc.symoff; + const locals_off = @intCast(u32, offset); const locals_size = nlocals * @sizeOf(macho.nlist_64); const exports_off = locals_off + locals_size; const exports_size = nexports * @sizeOf(macho.nlist_64); @@ -545,26 +521,26 @@ fn writeSymtab(self: *DebugSymbols, macho_file: *MachO, lc: *macho.symtab_comman try self.file.pwriteAll(mem.sliceAsBytes(exports.items), exports_off); } -fn writeStrtab(self: *DebugSymbols, lc: *macho.symtab_command) !void { +fn writeStrtab(self: *DebugSymbols) !void { const tracy = trace(@src()); defer tracy.end(); const seg = &self.segments.items[self.linkedit_segment_cmd_index.?]; - const symtab_size = @intCast(u32, lc.nsyms * @sizeOf(macho.nlist_64)); - const offset = mem.alignForwardGeneric(u64, lc.symoff + symtab_size, @alignOf(u64)); + const symtab_size = @intCast(u32, self.symtab_cmd.nsyms * @sizeOf(macho.nlist_64)); + const offset = mem.alignForwardGeneric(u64, self.symtab_cmd.symoff + symtab_size, @alignOf(u64)); const needed_size = mem.alignForwardGeneric(u64, self.strtab.buffer.items.len, @alignOf(u64)); seg.filesize = offset + needed_size - seg.fileoff; - lc.stroff = @intCast(u32, offset); - lc.strsize = @intCast(u32, needed_size); + self.symtab_cmd.stroff = @intCast(u32, offset); + self.symtab_cmd.strsize = @intCast(u32, needed_size); - log.debug("writing string table from 0x{x} to 0x{x}", .{ lc.stroff, lc.stroff + lc.strsize }); + log.debug("writing string table from 0x{x} to 0x{x}", .{ offset, offset + needed_size }); - try self.file.pwriteAll(self.strtab.buffer.items, lc.stroff); + try self.file.pwriteAll(self.strtab.buffer.items, offset); if (self.strtab.buffer.items.len < needed_size) { // Ensure we are always padded to the actual length of the file. - try self.file.pwriteAll(&[_]u8{0}, lc.stroff + lc.strsize); + try self.file.pwriteAll(&[_]u8{0}, offset + needed_size); } } diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig new file mode 100644 index 0000000000..73c28965ff --- /dev/null +++ b/src/link/MachO/load_commands.zig @@ -0,0 +1,314 @@ +const std = @import("std"); +const assert = std.debug.assert; +const link = @import("../../link.zig"); +const log = std.log.scoped(.link); +const macho = std.macho; +const mem = std.mem; + +const Allocator = mem.Allocator; +const Dylib = @import("Dylib.zig"); + +pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; + +fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 { + const darwin_path_max = 1024; + const name_len = if (assume_max_path_len) darwin_path_max else std.mem.len(name) + 1; + return mem.alignForwardGeneric(u64, cmd_size + name_len, @alignOf(u64)); +} + +const CalcLCsSizeCtx = struct { + segments: []const macho.segment_command_64, + dylibs: []const Dylib, + referenced_dylibs: []u16, + wants_function_starts: bool = true, +}; + +fn calcLCsSize(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx, assume_max_path_len: bool) !u32 { + var has_text_segment: bool = false; + var sizeofcmds: u64 = 0; + for (ctx.segments) |seg| { + sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64); + if (mem.eql(u8, seg.segName(), "__TEXT")) { + has_text_segment = true; + } + } + + // LC_DYLD_INFO_ONLY + sizeofcmds += @sizeOf(macho.dyld_info_command); + // LC_FUNCTION_STARTS + if (has_text_segment and ctx.wants_function_starts) |_| { + sizeofcmds += @sizeOf(macho.linkedit_data_command); + } + // LC_DATA_IN_CODE + sizeofcmds += @sizeOf(macho.linkedit_data_command); + // LC_SYMTAB + sizeofcmds += @sizeOf(macho.symtab_command); + // LC_DYSYMTAB + sizeofcmds += @sizeOf(macho.dysymtab_command); + // LC_LOAD_DYLINKER + sizeofcmds += calcInstallNameLen( + @sizeOf(macho.dylinker_command), + mem.sliceTo(default_dyld_path, 0), + false, + ); + // LC_MAIN + if (options.output_mode == .Exe) { + sizeofcmds += @sizeOf(macho.entry_point_command); + } + // LC_ID_DYLIB + if (options.output_mode == .Lib and options.link_mode == .Dynamic) { + sizeofcmds += blk: { + const emit = options.emit.?; + const install_name = options.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path}); + defer if (options.install_name == null) gpa.free(install_name); + break :blk calcInstallNameLen( + @sizeOf(macho.dylib_command), + install_name, + assume_max_path_len, + ); + }; + } + // LC_RPATH + { + var it = RpathIterator.init(gpa, options.rpath_list); + defer it.deinit(); + while (try it.next()) |rpath| { + sizeofcmds += calcInstallNameLen( + @sizeOf(macho.rpath_command), + rpath, + assume_max_path_len, + ); + } + } + // LC_SOURCE_VERSION + sizeofcmds += @sizeOf(macho.source_version_command); + // LC_BUILD_VERSION + sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); + // LC_UUID + sizeofcmds += @sizeOf(macho.uuid_command); + // LC_LOAD_DYLIB + for (ctx.referenced_dylibs) |id| { + const dylib = ctx.dylibs[id]; + const dylib_id = dylib.id orelse unreachable; + sizeofcmds += calcInstallNameLen( + @sizeOf(macho.dylib_command), + dylib_id.name, + assume_max_path_len, + ); + } + // LC_CODE_SIGNATURE + { + const target = options.target; + const requires_codesig = blk: { + if (options.entitlements) |_| break :blk true; + if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator)) + break :blk true; + break :blk false; + }; + if (requires_codesig) { + sizeofcmds += @sizeOf(macho.linkedit_data_command); + } + } + + return @intCast(u32, sizeofcmds); +} + +pub fn calcMinHeaderPad(gpa: Allocator, options: *const link.Options, ctx: CalcLCsSizeCtx) !u64 { + var padding: u32 = (try calcLCsSize(gpa, options, ctx, false)) + (options.headerpad_size orelse 0); + log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); + + if (options.headerpad_max_install_names) { + var min_headerpad_size: u32 = try calcLCsSize(gpa, options, ctx, true); + log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ + min_headerpad_size + @sizeOf(macho.mach_header_64), + }); + padding = @max(padding, min_headerpad_size); + } + + const offset = @sizeOf(macho.mach_header_64) + padding; + log.debug("actual headerpad size 0x{x}", .{offset}); + + return offset; +} + +pub fn calcNumOfLCs(lc_buffer: []const u8) u32 { + var ncmds: u32 = 0; + var pos: usize = 0; + while (true) { + if (pos >= lc_buffer.len) break; + const cmd = @ptrCast(*align(1) const macho.load_command, lc_buffer.ptr + pos).*; + ncmds += 1; + pos += cmd.cmdsize; + } + return ncmds; +} + +pub fn writeDylinkerLC(lc_writer: anytype) !void { + const name_len = mem.sliceTo(default_dyld_path, 0).len; + const cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.dylinker_command) + name_len, + @sizeOf(u64), + )); + try lc_writer.writeStruct(macho.dylinker_command{ + .cmd = .LOAD_DYLINKER, + .cmdsize = cmdsize, + .name = @sizeOf(macho.dylinker_command), + }); + try lc_writer.writeAll(mem.sliceTo(default_dyld_path, 0)); + const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len; + if (padding > 0) { + try lc_writer.writeByteNTimes(0, padding); + } +} + +const WriteDylibLCCtx = struct { + cmd: macho.LC, + name: []const u8, + timestamp: u32 = 2, + current_version: u32 = 0x10000, + compatibility_version: u32 = 0x10000, +}; + +fn writeDylibLC(ctx: WriteDylibLCCtx, lc_writer: anytype) !void { + const name_len = ctx.name.len + 1; + const cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.dylib_command) + name_len, + @sizeOf(u64), + )); + try lc_writer.writeStruct(macho.dylib_command{ + .cmd = ctx.cmd, + .cmdsize = cmdsize, + .dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = ctx.timestamp, + .current_version = ctx.current_version, + .compatibility_version = ctx.compatibility_version, + }, + }); + try lc_writer.writeAll(ctx.name); + try lc_writer.writeByte(0); + const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len; + if (padding > 0) { + try lc_writer.writeByteNTimes(0, padding); + } +} + +pub fn writeDylibIdLC(gpa: Allocator, options: *const link.Options, lc_writer: anytype) !void { + assert(options.output_mode == .Lib and options.link_mode == .Dynamic); + const emit = options.emit.?; + const install_name = options.install_name orelse try emit.directory.join(gpa, &.{emit.sub_path}); + defer if (options.install_name == null) gpa.free(install_name); + const curr = options.version orelse std.builtin.Version{ + .major = 1, + .minor = 0, + .patch = 0, + }; + const compat = options.compatibility_version orelse std.builtin.Version{ + .major = 1, + .minor = 0, + .patch = 0, + }; + try writeDylibLC(.{ + .cmd = .ID_DYLIB, + .name = install_name, + .current_version = curr.major << 16 | curr.minor << 8 | curr.patch, + .compatibility_version = compat.major << 16 | compat.minor << 8 | compat.patch, + }, lc_writer); +} + +const RpathIterator = struct { + buffer: []const []const u8, + table: std.StringHashMap(void), + count: usize = 0, + + fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator { + return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) }; + } + + fn deinit(it: *RpathIterator) void { + it.table.deinit(); + } + + fn next(it: *RpathIterator) !?[]const u8 { + while (true) { + if (it.count >= it.buffer.len) return null; + const rpath = it.buffer[it.count]; + it.count += 1; + const gop = try it.table.getOrPut(rpath); + if (gop.found_existing) continue; + return rpath; + } + } +}; + +pub fn writeRpathLCs(gpa: Allocator, options: *const link.Options, lc_writer: anytype) !void { + var it = RpathIterator.init(gpa, options.rpath_list); + defer it.deinit(); + + while (try it.next()) |rpath| { + const rpath_len = rpath.len + 1; + const cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.rpath_command) + rpath_len, + @sizeOf(u64), + )); + try lc_writer.writeStruct(macho.rpath_command{ + .cmdsize = cmdsize, + .path = @sizeOf(macho.rpath_command), + }); + try lc_writer.writeAll(rpath); + try lc_writer.writeByte(0); + const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len; + if (padding > 0) { + try lc_writer.writeByteNTimes(0, padding); + } + } +} + +pub fn writeBuildVersionLC(options: *const link.Options, lc_writer: anytype) !void { + const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); + const platform_version = blk: { + const ver = options.target.os.version_range.semver.min; + const platform_version = ver.major << 16 | ver.minor << 8; + break :blk platform_version; + }; + const sdk_version = if (options.native_darwin_sdk) |sdk| blk: { + const ver = sdk.version; + const sdk_version = ver.major << 16 | ver.minor << 8; + break :blk sdk_version; + } else platform_version; + const is_simulator_abi = options.target.abi == .simulator; + try lc_writer.writeStruct(macho.build_version_command{ + .cmdsize = cmdsize, + .platform = switch (options.target.os.tag) { + .macos => .MACOS, + .ios => if (is_simulator_abi) macho.PLATFORM.IOSSIMULATOR else macho.PLATFORM.IOS, + .watchos => if (is_simulator_abi) macho.PLATFORM.WATCHOSSIMULATOR else macho.PLATFORM.WATCHOS, + .tvos => if (is_simulator_abi) macho.PLATFORM.TVOSSIMULATOR else macho.PLATFORM.TVOS, + else => unreachable, + }, + .minos = platform_version, + .sdk = sdk_version, + .ntools = 1, + }); + try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{ + .tool = .LD, + .version = 0x0, + })); +} + +pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, lc_writer: anytype) !void { + for (referenced) |index| { + const dylib = dylibs[index]; + const dylib_id = dylib.id orelse unreachable; + try writeDylibLC(.{ + .cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB, + .name = dylib_id.name, + .timestamp = dylib_id.timestamp, + .current_version = dylib_id.current_version, + .compatibility_version = dylib_id.compatibility_version, + }, lc_writer); + } +} diff --git a/src/link/MachO/zld.zig b/src/link/MachO/zld.zig index 9baecd326a..272c8ad094 100644 --- a/src/link/MachO/zld.zig +++ b/src/link/MachO/zld.zig @@ -13,6 +13,7 @@ const bind = @import("bind.zig"); const dead_strip = @import("dead_strip.zig"); const fat = @import("fat.zig"); const link = @import("../../link.zig"); +const load_commands = @import("load_commands.zig"); const thunks = @import("thunks.zig"); const trace = @import("../../tracy.zig").trace; @@ -25,6 +26,7 @@ const Compilation = @import("../../Compilation.zig"); const DwarfInfo = @import("DwarfInfo.zig"); const Dylib = @import("Dylib.zig"); const MachO = @import("../MachO.zig"); +const Md5 = std.crypto.hash.Md5; const LibStub = @import("../tapi.zig").LibStub; const Object = @import("Object.zig"); const StringTable = @import("../strtab.zig").StringTable; @@ -34,7 +36,17 @@ pub const Zld = struct { gpa: Allocator, file: fs.File, page_size: u16, - options: link.Options, + options: *const link.Options, + + 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 }, objects: std.ArrayListUnmanaged(Object) = .{}, archives: std.ArrayListUnmanaged(Archive) = .{}, @@ -1227,195 +1239,6 @@ pub const Zld = struct { } } - fn writeDylinkerLC(ncmds: *u32, lc_writer: anytype) !void { - const name_len = mem.sliceTo(MachO.default_dyld_path, 0).len; - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylinker_command) + name_len, - @sizeOf(u64), - )); - try lc_writer.writeStruct(macho.dylinker_command{ - .cmd = .LOAD_DYLINKER, - .cmdsize = cmdsize, - .name = @sizeOf(macho.dylinker_command), - }); - try lc_writer.writeAll(mem.sliceTo(MachO.default_dyld_path, 0)); - const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len; - if (padding > 0) { - try lc_writer.writeByteNTimes(0, padding); - } - ncmds.* += 1; - } - - fn writeMainLC(self: *Zld, ncmds: *u32, lc_writer: anytype) !void { - if (self.options.output_mode != .Exe) return; - const seg_id = self.getSegmentByName("__TEXT").?; - const seg = self.segments.items[seg_id]; - const global = self.getEntryPoint(); - const sym = self.getSymbol(global); - try lc_writer.writeStruct(macho.entry_point_command{ - .cmd = .MAIN, - .cmdsize = @sizeOf(macho.entry_point_command), - .entryoff = @intCast(u32, sym.n_value - seg.vmaddr), - .stacksize = self.options.stack_size_override orelse 0, - }); - ncmds.* += 1; - } - - const WriteDylibLCCtx = struct { - cmd: macho.LC, - name: []const u8, - timestamp: u32 = 2, - current_version: u32 = 0x10000, - compatibility_version: u32 = 0x10000, - }; - - fn writeDylibLC(ctx: WriteDylibLCCtx, ncmds: *u32, lc_writer: anytype) !void { - const name_len = ctx.name.len + 1; - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylib_command) + name_len, - @sizeOf(u64), - )); - try lc_writer.writeStruct(macho.dylib_command{ - .cmd = ctx.cmd, - .cmdsize = cmdsize, - .dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = ctx.timestamp, - .current_version = ctx.current_version, - .compatibility_version = ctx.compatibility_version, - }, - }); - try lc_writer.writeAll(ctx.name); - try lc_writer.writeByte(0); - const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len; - if (padding > 0) { - try lc_writer.writeByteNTimes(0, padding); - } - ncmds.* += 1; - } - - fn writeDylibIdLC(self: *Zld, ncmds: *u32, lc_writer: anytype) !void { - if (self.options.output_mode != .Lib) return; - const install_name = self.options.install_name orelse self.options.emit.?.sub_path; - const curr = self.options.version orelse std.builtin.Version{ - .major = 1, - .minor = 0, - .patch = 0, - }; - const compat = self.options.compatibility_version orelse std.builtin.Version{ - .major = 1, - .minor = 0, - .patch = 0, - }; - try writeDylibLC(.{ - .cmd = .ID_DYLIB, - .name = install_name, - .current_version = curr.major << 16 | curr.minor << 8 | curr.patch, - .compatibility_version = compat.major << 16 | compat.minor << 8 | compat.patch, - }, ncmds, lc_writer); - } - - const RpathIterator = struct { - buffer: []const []const u8, - table: std.StringHashMap(void), - count: usize = 0, - - fn init(gpa: Allocator, rpaths: []const []const u8) RpathIterator { - return .{ .buffer = rpaths, .table = std.StringHashMap(void).init(gpa) }; - } - - fn deinit(it: *RpathIterator) void { - it.table.deinit(); - } - - fn next(it: *RpathIterator) !?[]const u8 { - while (true) { - if (it.count >= it.buffer.len) return null; - const rpath = it.buffer[it.count]; - it.count += 1; - const gop = try it.table.getOrPut(rpath); - if (gop.found_existing) continue; - return rpath; - } - } - }; - - fn writeRpathLCs(self: *Zld, ncmds: *u32, lc_writer: anytype) !void { - const gpa = self.gpa; - - var it = RpathIterator.init(gpa, self.options.rpath_list); - defer it.deinit(); - - while (try it.next()) |rpath| { - const rpath_len = rpath.len + 1; - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.rpath_command) + rpath_len, - @sizeOf(u64), - )); - try lc_writer.writeStruct(macho.rpath_command{ - .cmdsize = cmdsize, - .path = @sizeOf(macho.rpath_command), - }); - try lc_writer.writeAll(rpath); - try lc_writer.writeByte(0); - const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len; - if (padding > 0) { - try lc_writer.writeByteNTimes(0, padding); - } - ncmds.* += 1; - } - } - - fn writeBuildVersionLC(self: *Zld, ncmds: *u32, lc_writer: anytype) !void { - const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); - const platform_version = blk: { - const ver = self.options.target.os.version_range.semver.min; - const platform_version = ver.major << 16 | ver.minor << 8; - break :blk platform_version; - }; - const sdk_version = if (self.options.native_darwin_sdk) |sdk| blk: { - const ver = sdk.version; - const sdk_version = ver.major << 16 | ver.minor << 8; - break :blk sdk_version; - } else platform_version; - const is_simulator_abi = self.options.target.abi == .simulator; - try lc_writer.writeStruct(macho.build_version_command{ - .cmdsize = cmdsize, - .platform = switch (self.options.target.os.tag) { - .macos => .MACOS, - .ios => if (is_simulator_abi) macho.PLATFORM.IOSSIMULATOR else macho.PLATFORM.IOS, - .watchos => if (is_simulator_abi) macho.PLATFORM.WATCHOSSIMULATOR else macho.PLATFORM.WATCHOS, - .tvos => if (is_simulator_abi) macho.PLATFORM.TVOSSIMULATOR else macho.PLATFORM.TVOS, - else => unreachable, - }, - .minos = platform_version, - .sdk = sdk_version, - .ntools = 1, - }); - try lc_writer.writeAll(mem.asBytes(&macho.build_tool_version{ - .tool = .LD, - .version = 0x0, - })); - ncmds.* += 1; - } - - fn writeLoadDylibLCs(self: *Zld, ncmds: *u32, lc_writer: anytype) !void { - for (self.referenced_dylibs.keys()) |id| { - const dylib = self.dylibs.items[id]; - const dylib_id = dylib.id orelse unreachable; - try writeDylibLC(.{ - .cmd = if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB, - .name = dylib_id.name, - .timestamp = dylib_id.timestamp, - .current_version = dylib_id.current_version, - .compatibility_version = dylib_id.compatibility_version, - }, ncmds, lc_writer); - } - } - pub fn deinit(self: *Zld) void { const gpa = self.gpa; @@ -1516,110 +1339,6 @@ pub const Zld = struct { } } - fn calcLCsSize(self: *Zld, assume_max_path_len: bool) !u32 { - const gpa = self.gpa; - - var sizeofcmds: u64 = 0; - for (self.segments.items) |seg| { - sizeofcmds += seg.nsects * @sizeOf(macho.section_64) + @sizeOf(macho.segment_command_64); - } - - // LC_DYLD_INFO_ONLY - sizeofcmds += @sizeOf(macho.dyld_info_command); - // LC_FUNCTION_STARTS - if (self.getSectionByName("__TEXT", "__text")) |_| { - sizeofcmds += @sizeOf(macho.linkedit_data_command); - } - // LC_DATA_IN_CODE - sizeofcmds += @sizeOf(macho.linkedit_data_command); - // LC_SYMTAB - sizeofcmds += @sizeOf(macho.symtab_command); - // LC_DYSYMTAB - sizeofcmds += @sizeOf(macho.dysymtab_command); - // LC_LOAD_DYLINKER - sizeofcmds += MachO.calcInstallNameLen( - @sizeOf(macho.dylinker_command), - mem.sliceTo(MachO.default_dyld_path, 0), - false, - ); - // LC_MAIN - if (self.options.output_mode == .Exe) { - sizeofcmds += @sizeOf(macho.entry_point_command); - } - // LC_ID_DYLIB - if (self.options.output_mode == .Lib) { - sizeofcmds += blk: { - const install_name = self.options.install_name orelse self.options.emit.?.sub_path; - break :blk MachO.calcInstallNameLen( - @sizeOf(macho.dylib_command), - install_name, - assume_max_path_len, - ); - }; - } - // LC_RPATH - { - var it = RpathIterator.init(gpa, self.options.rpath_list); - defer it.deinit(); - while (try it.next()) |rpath| { - sizeofcmds += MachO.calcInstallNameLen( - @sizeOf(macho.rpath_command), - rpath, - assume_max_path_len, - ); - } - } - // LC_SOURCE_VERSION - sizeofcmds += @sizeOf(macho.source_version_command); - // LC_BUILD_VERSION - sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); - // LC_UUID - sizeofcmds += @sizeOf(macho.uuid_command); - // LC_LOAD_DYLIB - for (self.referenced_dylibs.keys()) |id| { - const dylib = self.dylibs.items[id]; - const dylib_id = dylib.id orelse unreachable; - sizeofcmds += MachO.calcInstallNameLen( - @sizeOf(macho.dylib_command), - dylib_id.name, - assume_max_path_len, - ); - } - // LC_CODE_SIGNATURE - { - const target = self.options.target; - const requires_codesig = blk: { - if (self.options.entitlements) |_| break :blk true; - if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator)) - break :blk true; - break :blk false; - }; - if (requires_codesig) { - sizeofcmds += @sizeOf(macho.linkedit_data_command); - } - } - - return @intCast(u32, sizeofcmds); - } - - fn calcMinHeaderPad(self: *Zld) !u64 { - var padding: u32 = (try self.calcLCsSize(false)) + (self.options.headerpad_size orelse 0); - log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)}); - - if (self.options.headerpad_max_install_names) { - var min_headerpad_size: u32 = try self.calcLCsSize(true); - log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ - min_headerpad_size + @sizeOf(macho.mach_header_64), - }); - padding = @max(padding, min_headerpad_size); - } - - const offset = @sizeOf(macho.mach_header_64) + padding; - log.debug("actual headerpad size 0x{x}", .{offset}); - - return offset; - } - pub fn allocateSymbol(self: *Zld) !u32 { try self.locals.ensureUnusedCapacity(self.gpa, 1); log.debug(" (allocating symbol index {d})", .{self.locals.items.len}); @@ -1842,7 +1561,11 @@ pub const Zld = struct { fn allocateSegments(self: *Zld) !void { for (self.segments.items) |*segment, segment_index| { const is_text_segment = mem.eql(u8, segment.segName(), "__TEXT"); - const base_size = if (is_text_segment) try self.calcMinHeaderPad() else 0; + const base_size = if (is_text_segment) try load_commands.calcMinHeaderPad(self.gpa, self.options, .{ + .segments = self.segments.items, + .dylibs = self.dylibs.items, + .referenced_dylibs = self.referenced_dylibs.keys(), + }) else 0; try self.allocateSegment(@intCast(u8, segment_index), base_size); } } @@ -2015,7 +1738,7 @@ pub const Zld = struct { return (@intCast(u8, segment_precedence) << 4) + section_precedence; } - fn writeSegmentHeaders(self: *Zld, ncmds: *u32, writer: anytype) !void { + fn writeSegmentHeaders(self: *Zld, writer: anytype) !void { for (self.segments.items) |seg, i| { const indexes = self.getSectionIndexes(@intCast(u8, i)); var out_seg = seg; @@ -2039,16 +1762,14 @@ pub const Zld = struct { if (header.size == 0) continue; try writer.writeStruct(header); } - - ncmds.* += 1; } } - fn writeLinkeditSegmentData(self: *Zld, ncmds: *u32, lc_writer: anytype, reverse_lookups: [][]u32) !void { - try self.writeDyldInfoData(ncmds, lc_writer, reverse_lookups); - try self.writeFunctionStarts(ncmds, lc_writer); - try self.writeDataInCode(ncmds, lc_writer); - try self.writeSymtabs(ncmds, lc_writer); + fn writeLinkeditSegmentData(self: *Zld, reverse_lookups: [][]u32) !void { + try self.writeDyldInfoData(reverse_lookups); + try self.writeFunctionStarts(); + try self.writeDataInCode(); + try self.writeSymtabs(); const seg = self.getLinkeditSegmentPtr(); seg.vmsize = mem.alignForwardGeneric(u64, seg.filesize, self.page_size); @@ -2437,7 +2158,7 @@ pub const Zld = struct { try trie.finalize(gpa); } - fn writeDyldInfoData(self: *Zld, ncmds: *u32, lc_writer: anytype, reverse_lookups: [][]u32) !void { + fn writeDyldInfoData(self: *Zld, reverse_lookups: [][]u32) !void { const gpa = self.gpa; var rebase_pointers = std.ArrayList(bind.Pointer).init(gpa); @@ -2506,21 +2227,14 @@ pub const Zld = struct { const size = math.cast(usize, lazy_bind_size) orelse return error.Overflow; try self.populateLazyBindOffsetsInStubHelper(buffer[offset..][0..size]); - try lc_writer.writeStruct(macho.dyld_info_command{ - .cmd = .DYLD_INFO_ONLY, - .cmdsize = @sizeOf(macho.dyld_info_command), - .rebase_off = @intCast(u32, rebase_off), - .rebase_size = @intCast(u32, rebase_size), - .bind_off = @intCast(u32, bind_off), - .bind_size = @intCast(u32, bind_size), - .weak_bind_off = 0, - .weak_bind_size = 0, - .lazy_bind_off = @intCast(u32, lazy_bind_off), - .lazy_bind_size = @intCast(u32, lazy_bind_size), - .export_off = @intCast(u32, export_off), - .export_size = @intCast(u32, export_size), - }); - ncmds.* += 1; + self.dyld_info_cmd.rebase_off = @intCast(u32, rebase_off); + self.dyld_info_cmd.rebase_size = @intCast(u32, rebase_size); + self.dyld_info_cmd.bind_off = @intCast(u32, bind_off); + self.dyld_info_cmd.bind_size = @intCast(u32, bind_size); + self.dyld_info_cmd.lazy_bind_off = @intCast(u32, lazy_bind_off); + self.dyld_info_cmd.lazy_bind_size = @intCast(u32, lazy_bind_size); + self.dyld_info_cmd.export_off = @intCast(u32, export_off); + self.dyld_info_cmd.export_size = @intCast(u32, export_size); } fn populateLazyBindOffsetsInStubHelper(self: *Zld, buffer: []const u8) !void { @@ -2638,7 +2352,7 @@ pub const Zld = struct { const asc_u64 = std.sort.asc(u64); - fn writeFunctionStarts(self: *Zld, ncmds: *u32, lc_writer: anytype) !void { + fn writeFunctionStarts(self: *Zld) !void { const text_seg_index = self.getSegmentByName("__TEXT") orelse return; const text_sect_index = self.getSectionByName("__TEXT", "__text") orelse return; const text_seg = self.segments.items[text_seg_index]; @@ -2697,13 +2411,8 @@ pub const Zld = struct { try self.file.pwriteAll(buffer.items, offset); - try lc_writer.writeStruct(macho.linkedit_data_command{ - .cmd = .FUNCTION_STARTS, - .cmdsize = @sizeOf(macho.linkedit_data_command), - .dataoff = @intCast(u32, offset), - .datasize = @intCast(u32, needed_size), - }); - ncmds.* += 1; + self.function_starts_cmd.dataoff = @intCast(u32, offset); + self.function_starts_cmd.datasize = @intCast(u32, needed_size); } fn filterDataInCode( @@ -2725,7 +2434,7 @@ pub const Zld = struct { return dices[start..end]; } - fn writeDataInCode(self: *Zld, ncmds: *u32, lc_writer: anytype) !void { + fn writeDataInCode(self: *Zld) !void { var out_dice = std.ArrayList(macho.data_in_code_entry).init(self.gpa); defer out_dice.deinit(); @@ -2775,54 +2484,19 @@ pub const Zld = struct { log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ offset, offset + needed_size }); try self.file.pwriteAll(mem.sliceAsBytes(out_dice.items), offset); - try lc_writer.writeStruct(macho.linkedit_data_command{ - .cmd = .DATA_IN_CODE, - .cmdsize = @sizeOf(macho.linkedit_data_command), - .dataoff = @intCast(u32, offset), - .datasize = @intCast(u32, needed_size), - }); - ncmds.* += 1; + + self.data_in_code_cmd.dataoff = @intCast(u32, offset); + self.data_in_code_cmd.datasize = @intCast(u32, needed_size); } - fn writeSymtabs(self: *Zld, ncmds: *u32, lc_writer: anytype) !void { - var symtab_cmd = macho.symtab_command{ - .cmdsize = @sizeOf(macho.symtab_command), - .symoff = 0, - .nsyms = 0, - .stroff = 0, - .strsize = 0, - }; - var dysymtab_cmd = macho.dysymtab_command{ - .cmdsize = @sizeOf(macho.dysymtab_command), - .ilocalsym = 0, - .nlocalsym = 0, - .iextdefsym = 0, - .nextdefsym = 0, - .iundefsym = 0, - .nundefsym = 0, - .tocoff = 0, - .ntoc = 0, - .modtaboff = 0, - .nmodtab = 0, - .extrefsymoff = 0, - .nextrefsyms = 0, - .indirectsymoff = 0, - .nindirectsyms = 0, - .extreloff = 0, - .nextrel = 0, - .locreloff = 0, - .nlocrel = 0, - }; - var ctx = try self.writeSymtab(&symtab_cmd); + fn writeSymtabs(self: *Zld) !void { + var ctx = try self.writeSymtab(); defer ctx.imports_table.deinit(); - try self.writeDysymtab(ctx, &dysymtab_cmd); - try self.writeStrtab(&symtab_cmd); - try lc_writer.writeStruct(symtab_cmd); - try lc_writer.writeStruct(dysymtab_cmd); - ncmds.* += 2; + try self.writeDysymtab(ctx); + try self.writeStrtab(); } - fn writeSymtab(self: *Zld, lc: *macho.symtab_command) !SymtabCtx { + fn writeSymtab(self: *Zld) !SymtabCtx { const gpa = self.gpa; var locals = std.ArrayList(macho.nlist_64).init(gpa); @@ -2843,12 +2517,6 @@ pub const Zld = struct { } } - if (!self.options.strip) { - for (self.objects.items) |object| { - try self.generateSymbolStabs(object, &locals); - } - } - var exports = std.ArrayList(macho.nlist_64).init(gpa); defer exports.deinit(); @@ -2879,6 +2547,14 @@ pub const Zld = struct { try imports_table.putNoClobber(global, new_index); } + // We generate stabs last in order to ensure that the strtab always has debug info + // strings trailing + if (!self.options.strip) { + for (self.objects.items) |object| { + try self.generateSymbolStabs(object, &locals); + } + } + const nlocals = @intCast(u32, locals.items.len); const nexports = @intCast(u32, exports.items.len); const nimports = @intCast(u32, imports.items.len); @@ -2903,8 +2579,8 @@ pub const Zld = struct { log.debug("writing symtab from 0x{x} to 0x{x}", .{ offset, offset + needed_size }); try self.file.pwriteAll(buffer.items, offset); - lc.symoff = @intCast(u32, offset); - lc.nsyms = nsyms; + self.symtab_cmd.symoff = @intCast(u32, offset); + self.symtab_cmd.nsyms = nsyms; return SymtabCtx{ .nlocalsym = nlocals, @@ -2914,7 +2590,7 @@ pub const Zld = struct { }; } - fn writeStrtab(self: *Zld, lc: *macho.symtab_command) !void { + fn writeStrtab(self: *Zld) !void { const seg = self.getLinkeditSegmentPtr(); const offset = mem.alignForwardGeneric(u64, seg.fileoff + seg.filesize, @alignOf(u64)); const needed_size = self.strtab.buffer.items.len; @@ -2924,8 +2600,8 @@ pub const Zld = struct { try self.file.pwriteAll(self.strtab.buffer.items, offset); - lc.stroff = @intCast(u32, offset); - lc.strsize = @intCast(u32, needed_size); + self.symtab_cmd.stroff = @intCast(u32, offset); + self.symtab_cmd.strsize = @intCast(u32, needed_size); } const SymtabCtx = struct { @@ -2935,7 +2611,7 @@ pub const Zld = struct { imports_table: std.AutoHashMap(SymbolWithLoc, u32), }; - fn writeDysymtab(self: *Zld, ctx: SymtabCtx, lc: *macho.dysymtab_command) !void { + fn writeDysymtab(self: *Zld, ctx: SymtabCtx) !void { const gpa = self.gpa; const nstubs = @intCast(u32, self.stubs.items.len); const ngot_entries = @intCast(u32, self.got_entries.items.len); @@ -2991,21 +2667,161 @@ pub const Zld = struct { assert(buf.items.len == needed_size); try self.file.pwriteAll(buf.items, offset); - lc.nlocalsym = ctx.nlocalsym; - lc.iextdefsym = iextdefsym; - lc.nextdefsym = ctx.nextdefsym; - lc.iundefsym = iundefsym; - lc.nundefsym = ctx.nundefsym; - lc.indirectsymoff = @intCast(u32, offset); - lc.nindirectsyms = nindirectsyms; + self.dysymtab_cmd.nlocalsym = ctx.nlocalsym; + self.dysymtab_cmd.iextdefsym = iextdefsym; + self.dysymtab_cmd.nextdefsym = ctx.nextdefsym; + self.dysymtab_cmd.iundefsym = iundefsym; + self.dysymtab_cmd.nundefsym = ctx.nundefsym; + self.dysymtab_cmd.indirectsymoff = @intCast(u32, offset); + self.dysymtab_cmd.nindirectsyms = nindirectsyms; } - fn writeCodeSignaturePadding( - self: *Zld, - code_sig: *CodeSignature, - ncmds: *u32, - lc_writer: anytype, - ) !u32 { + fn writeUuid(self: *Zld, comp: *const Compilation, args: struct { + linkedit_cmd_offset: u32, + symtab_cmd_offset: u32, + uuid_cmd_offset: u32, + codesig_cmd_offset: ?u32, + }) !void { + _ = comp; + switch (self.options.optimize_mode) { + .Debug => { + // In Debug we don't really care about reproducibility, so put in a random value + // and be done with it. + std.crypto.random.bytes(&self.uuid_cmd.uuid); + Md5.hash(&self.uuid_cmd.uuid, &self.uuid_cmd.uuid, .{}); + conformUuid(&self.uuid_cmd.uuid); + }, + else => { + const max_file_end = self.symtab_cmd.stroff + self.symtab_cmd.strsize; + + const FileSubsection = struct { + start: u32, + end: u32, + }; + + var subsections: [5]FileSubsection = undefined; + var count: usize = 0; + + // Exclude LINKEDIT segment command as it contains file size that includes stabs contribution + // and code signature. + subsections[count] = .{ + .start = 0, + .end = args.linkedit_cmd_offset, + }; + count += 1; + + // Exclude SYMTAB and DYSYMTAB commands for the same reason. + subsections[count] = .{ + .start = subsections[count - 1].end + @sizeOf(macho.segment_command_64), + .end = args.symtab_cmd_offset, + }; + count += 1; + + // Exclude CODE_SIGNATURE command (if present). + if (args.codesig_cmd_offset) |offset| { + subsections[count] = .{ + .start = subsections[count - 1].end + @sizeOf(macho.symtab_command) + @sizeOf(macho.dysymtab_command), + .end = offset, + }; + count += 1; + } + + if (!self.options.strip) { + // Exclude region comprising all symbol stabs. + const nlocals = self.dysymtab_cmd.nlocalsym; + + const locals_buf = try self.gpa.alloc(u8, nlocals * @sizeOf(macho.nlist_64)); + defer self.gpa.free(locals_buf); + + const amt = try self.file.preadAll(locals_buf, self.symtab_cmd.symoff); + if (amt != locals_buf.len) return error.InputOutput; + const locals = @ptrCast([*]macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), locals_buf))[0..nlocals]; + + const istab: usize = for (locals) |local, i| { + if (local.stab()) break i; + } else locals.len; + const nstabs = locals.len - istab; + + if (nstabs == 0) { + subsections[count] = .{ + .start = subsections[count - 1].end + if (args.codesig_cmd_offset == null) + @as(u32, @sizeOf(macho.symtab_command) + @sizeOf(macho.dysymtab_command)) + else + @sizeOf(macho.linkedit_data_command), + .end = max_file_end, + }; + count += 1; + } else { + // Exclude a subsection of the strtab with names of the stabs. + // We do not care about anything succeeding strtab as it is the code signature data which is + // not part of the UUID calculation anyway. + const stab_stroff = locals[istab].n_strx; + + subsections[count] = .{ + .start = subsections[count - 1].end + if (args.codesig_cmd_offset == null) + @as(u32, @sizeOf(macho.symtab_command) + @sizeOf(macho.dysymtab_command)) + else + @sizeOf(macho.linkedit_data_command), + .end = @intCast(u32, self.symtab_cmd.symoff + istab * @sizeOf(macho.nlist_64)), + }; + count += 1; + + subsections[count] = .{ + .start = subsections[count - 1].end + @intCast(u32, nstabs * @sizeOf(macho.nlist_64)), + .end = self.symtab_cmd.stroff + stab_stroff, + }; + count += 1; + } + } else { + subsections[count] = .{ + .start = subsections[count - 1].end + if (args.codesig_cmd_offset == null) + @as(u32, @sizeOf(macho.symtab_command) + @sizeOf(macho.dysymtab_command)) + else + @sizeOf(macho.linkedit_data_command), + .end = max_file_end, + }; + count += 1; + } + + const chunk_size = 0x4000; + + var hasher = Md5.init(.{}); + var buffer: [chunk_size]u8 = undefined; + + for (subsections[0..count]) |cut| { + const size = cut.end - cut.start; + const num_chunks = mem.alignForward(size, chunk_size) / chunk_size; + + var i: usize = 0; + while (i < num_chunks) : (i += 1) { + const fstart = cut.start + i * chunk_size; + const fsize = if (fstart + chunk_size > cut.end) + cut.end - fstart + else + chunk_size; + const amt = try self.file.preadAll(buffer[0..fsize], fstart); + if (amt != fsize) return error.InputOutput; + + hasher.update(buffer[0..fsize]); + } + } + + hasher.final(&self.uuid_cmd.uuid); + conformUuid(&self.uuid_cmd.uuid); + }, + } + + const in_file = args.uuid_cmd_offset + @sizeOf(macho.load_command); + try self.file.pwriteAll(&self.uuid_cmd.uuid, in_file); + } + + 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; + } + + fn writeCodeSignaturePadding(self: *Zld, code_sig: *CodeSignature) !void { const seg = self.getLinkeditSegmentPtr(); // 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 @@ -3018,23 +2834,11 @@ pub const Zld = struct { // except for code signature data. try self.file.pwriteAll(&[_]u8{0}, offset + needed_size - 1); - try lc_writer.writeStruct(macho.linkedit_data_command{ - .cmd = .CODE_SIGNATURE, - .cmdsize = @sizeOf(macho.linkedit_data_command), - .dataoff = @intCast(u32, offset), - .datasize = @intCast(u32, needed_size), - }); - ncmds.* += 1; - - return @intCast(u32, offset); + self.codesig_cmd.dataoff = @intCast(u32, offset); + self.codesig_cmd.datasize = @intCast(u32, needed_size); } - fn writeCodeSignature( - self: *Zld, - comp: *const Compilation, - code_sig: *CodeSignature, - offset: u32, - ) !void { + fn writeCodeSignature(self: *Zld, comp: *const Compilation, code_sig: *CodeSignature) !void { const seg_id = self.getSegmentByName("__TEXT").?; const seg = self.segments.items[seg_id]; @@ -3045,17 +2849,17 @@ pub const Zld = struct { .file = self.file, .exec_seg_base = seg.fileoff, .exec_seg_limit = seg.filesize, - .file_size = offset, + .file_size = self.codesig_cmd.dataoff, .output_mode = self.options.output_mode, }, 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, + self.codesig_cmd.dataoff, + self.codesig_cmd.dataoff + buffer.items.len, }); - try self.file.pwriteAll(buffer.items, offset); + try self.file.pwriteAll(buffer.items, self.codesig_cmd.dataoff); } /// Writes Mach-O file header. @@ -3734,7 +3538,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr defer tracy.end(); const gpa = macho_file.base.allocator; - const options = macho_file.base.options; + const options = &macho_file.base.options; const target = options.target; var arena_allocator = std.heap.ArenaAllocator.init(gpa); @@ -3884,7 +3688,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr macho_file.base.file = try directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, - .mode = link.determineMode(options), + .mode = link.determineMode(options.*), }); } var zld = Zld{ @@ -4271,12 +4075,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr } try zld.writeAtoms(reverse_lookups); - - var lc_buffer = std.ArrayList(u8).init(arena); - const lc_writer = lc_buffer.writer(); - var ncmds: u32 = 0; - - try zld.writeLinkeditSegmentData(&ncmds, lc_writer, reverse_lookups); + try zld.writeLinkeditSegmentData(reverse_lookups); // If the last section of __DATA segment is zerofill section, we need to ensure // that the free space between the end of the last non-zerofill section of __DATA @@ -4301,39 +4100,54 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr } } - try Zld.writeDylinkerLC(&ncmds, lc_writer); - try zld.writeMainLC(&ncmds, lc_writer); - try zld.writeDylibIdLC(&ncmds, lc_writer); - try zld.writeRpathLCs(&ncmds, lc_writer); + // Write load commands + var lc_buffer = std.ArrayList(u8).init(arena); + const lc_writer = lc_buffer.writer(); - { - try lc_writer.writeStruct(macho.source_version_command{ - .cmdsize = @sizeOf(macho.source_version_command), - .version = 0x0, + try zld.writeSegmentHeaders(lc_writer); + const linkedit_cmd_offset = @sizeOf(macho.mach_header_64) + @intCast(u32, lc_buffer.items.len - @sizeOf(macho.segment_command_64)); + + try lc_writer.writeStruct(zld.dyld_info_cmd); + try lc_writer.writeStruct(zld.function_starts_cmd); + try lc_writer.writeStruct(zld.data_in_code_cmd); + + const symtab_cmd_offset = @sizeOf(macho.mach_header_64) + @intCast(u32, lc_buffer.items.len); + try lc_writer.writeStruct(zld.symtab_cmd); + try lc_writer.writeStruct(zld.dysymtab_cmd); + + try load_commands.writeDylinkerLC(lc_writer); + + if (zld.options.output_mode == .Exe) { + const seg_id = zld.getSegmentByName("__TEXT").?; + const seg = zld.segments.items[seg_id]; + const global = zld.getEntryPoint(); + const sym = zld.getSymbol(global); + try lc_writer.writeStruct(macho.entry_point_command{ + .entryoff = @intCast(u32, sym.n_value - seg.vmaddr), + .stacksize = options.stack_size_override orelse 0, }); - ncmds += 1; + } else { + assert(zld.options.output_mode == .Lib); + try load_commands.writeDylibIdLC(zld.gpa, zld.options, lc_writer); } - try zld.writeBuildVersionLC(&ncmds, lc_writer); + try load_commands.writeRpathLCs(zld.gpa, zld.options, lc_writer); + try lc_writer.writeStruct(macho.source_version_command{ + .version = 0, + }); + try load_commands.writeBuildVersionLC(zld.options, lc_writer); - { - var uuid_lc = macho.uuid_command{ - .cmdsize = @sizeOf(macho.uuid_command), - .uuid = undefined, - }; - std.crypto.random.bytes(&uuid_lc.uuid); - try lc_writer.writeStruct(uuid_lc); - ncmds += 1; - } + const uuid_cmd_offset = @sizeOf(macho.mach_header_64) + @intCast(u32, lc_buffer.items.len); + try lc_writer.writeStruct(zld.uuid_cmd); - try zld.writeLoadDylibLCs(&ncmds, lc_writer); + try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), lc_writer); const requires_codesig = blk: { if (options.entitlements) |_| break :blk true; if (cpu_arch == .aarch64 and (os_tag == .macos or abi == .simulator)) break :blk true; break :blk false; }; - var codesig_offset: ?u32 = null; + var codesig_cmd_offset: ?u32 = null; var codesig: ?CodeSignature = if (requires_codesig) 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 @@ -4341,24 +4155,30 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr // 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(page_size); - codesig.code_directory.ident = options.emit.?.sub_path; + codesig.code_directory.ident = fs.path.basename(full_out_path); if (options.entitlements) |path| { try codesig.addEntitlements(gpa, path); } - codesig_offset = try zld.writeCodeSignaturePadding(&codesig, &ncmds, lc_writer); + try zld.writeCodeSignaturePadding(&codesig); + codesig_cmd_offset = @sizeOf(macho.mach_header_64) + @intCast(u32, lc_buffer.items.len); + try lc_writer.writeStruct(zld.codesig_cmd); break :blk codesig; } else null; defer if (codesig) |*csig| csig.deinit(gpa); - var headers_buf = std.ArrayList(u8).init(arena); - try zld.writeSegmentHeaders(&ncmds, headers_buf.writer()); + const ncmds = load_commands.calcNumOfLCs(lc_buffer.items); + try zld.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64)); + try zld.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len)); - try zld.file.pwriteAll(headers_buf.items, @sizeOf(macho.mach_header_64)); - try zld.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64) + headers_buf.items.len); - try zld.writeHeader(ncmds, @intCast(u32, lc_buffer.items.len + headers_buf.items.len)); + try zld.writeUuid(comp, .{ + .linkedit_cmd_offset = linkedit_cmd_offset, + .symtab_cmd_offset = symtab_cmd_offset, + .uuid_cmd_offset = uuid_cmd_offset, + .codesig_cmd_offset = codesig_cmd_offset, + }); if (codesig) |*csig| { - try zld.writeCodeSignature(comp, csig, codesig_offset.?); // code signing always comes last + try zld.writeCodeSignature(comp, csig); // code signing always comes last } } diff --git a/test/link.zig b/test/link.zig index 7eec02e53a..5620ac95a0 100644 --- a/test/link.zig +++ b/test/link.zig @@ -170,6 +170,11 @@ fn addMachOCases(cases: *tests.StandaloneContext) void { .requires_symlinks = true, }); + cases.addBuildFile("test/link/macho/uuid/build.zig", .{ + .build_modes = false, + .requires_symlinks = true, + }); + cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ .build_modes = true, .requires_symlinks = true, diff --git a/test/link/macho/uuid/build.zig b/test/link/macho/uuid/build.zig new file mode 100644 index 0000000000..9276d8a401 --- /dev/null +++ b/test/link/macho/uuid/build.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const Builder = std.build.Builder; +const LibExeObjectStep = std.build.LibExeObjStep; + +pub fn build(b: *Builder) void { + const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); + testUuid(b, test_step, .ReleaseSafe, "eb1203019e453d808d4f1e71053af9af"); + testUuid(b, test_step, .ReleaseFast, "eb1203019e453d808d4f1e71053af9af"); + testUuid(b, test_step, .ReleaseSmall, "eb1203019e453d808d4f1e71053af9af"); +} + +fn testUuid(b: *Builder, test_step: *std.build.Step, mode: std.builtin.Mode, comptime exp: []const u8) void { + // The calculated UUID value is independent of debug info and so it should + // stay the same across builds. + { + const dylib = simpleDylib(b, mode); + const check_dylib = dylib.checkObject(.macho); + check_dylib.checkStart("cmd UUID"); + check_dylib.checkNext("uuid " ++ exp); + test_step.dependOn(&check_dylib.step); + } + { + const dylib = simpleDylib(b, mode); + dylib.strip = true; + const check_dylib = dylib.checkObject(.macho); + check_dylib.checkStart("cmd UUID"); + check_dylib.checkNext("uuid " ++ exp); + test_step.dependOn(&check_dylib.step); + } +} + +fn simpleDylib(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep { + const dylib = b.addSharedLibrary("test", null, b.version(1, 0, 0)); + dylib.setBuildMode(mode); + dylib.setTarget(.{ .cpu_arch = .aarch64, .os_tag = .macos }); + dylib.addCSourceFile("test.c", &.{}); + dylib.linkLibC(); + return dylib; +} diff --git a/test/link/macho/uuid/test.c b/test/link/macho/uuid/test.c new file mode 100644 index 0000000000..6f23a1a926 --- /dev/null +++ b/test/link/macho/uuid/test.c @@ -0,0 +1,2 @@ +void test() {} +