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/src/link/MachO.zig b/src/link/MachO.zig index 4a1ca9a357..f83338f48b 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"); @@ -265,9 +266,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). @@ -561,17 +559,24 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No var ncmds: u32 = 0; try self.writeLinkeditSegmentData(&ncmds, lc_writer); - try writeDylinkerLC(&ncmds, lc_writer); + try load_commands.writeDylinkerLC(&ncmds, lc_writer); - self.writeMainLC(&ncmds, lc_writer) catch |err| switch (err) { - error.MissingMainEntrypoint => { - self.error_flags.no_entry_point_found = true; - }, - else => |e| return e, - }; + if (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 load_commands.writeMainLC(@intCast(u32, sym.n_value - seg.vmaddr), &self.base.options, &ncmds, lc_writer); + } - try self.writeDylibIdLC(&ncmds, lc_writer); - try self.writeRpathLCs(&ncmds, lc_writer); + try load_commands.writeDylibIdLC(self.base.allocator, &self.base.options, &ncmds, lc_writer); + try load_commands.writeRpathLCs(self.base.allocator, &self.base.options, &ncmds, lc_writer); { try lc_writer.writeStruct(macho.source_version_command{ @@ -581,7 +586,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No ncmds += 1; } - try self.writeBuildVersionLC(&ncmds, lc_writer); + try load_commands.writeBuildVersionLC(&self.base.options, &ncmds, lc_writer); { std.crypto.random.bytes(&self.uuid.uuid); @@ -589,7 +594,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No ncmds += 1; } - try self.writeLoadDylibLCs(&ncmds, lc_writer); + try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), &ncmds, lc_writer); const target = self.base.options.target; const requires_codesig = blk: { @@ -1702,195 +1707,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 +2792,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 +2804,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, diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig new file mode 100644 index 0000000000..9b0e61e919 --- /dev/null +++ b/src/link/MachO/load_commands.zig @@ -0,0 +1,325 @@ +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 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; +} + +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; +} + +pub fn writeDylibIdLC(gpa: Allocator, options: *const link.Options, ncmds: *u32, 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, + }, ncmds, lc_writer); +} + +pub fn writeMainLC(entryoff: u32, options: *const link.Options, ncmds: *u32, lc_writer: anytype) !void { + assert(options.output_mode == .Exe); + try lc_writer.writeStruct(macho.entry_point_command{ + .cmd = .MAIN, + .cmdsize = @sizeOf(macho.entry_point_command), + .entryoff = entryoff, + .stacksize = options.stack_size_override orelse 0, + }); + ncmds.* += 1; +} + +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, ncmds: *u32, 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); + } + ncmds.* += 1; + } +} + +pub fn writeBuildVersionLC(options: *const link.Options, ncmds: *u32, 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, + })); + ncmds.* += 1; +} + +pub fn writeLoadDylibLCs(dylibs: []const Dylib, referenced: []u16, ncmds: *u32, 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, + }, ncmds, lc_writer); + } +} + +pub fn writeSourceVersionLC(ncmds: *u32, lc_writer: anytype) !void { + try lc_writer.writeStruct(macho.source_version_command{ + .cmdsize = @sizeOf(macho.source_version_command), + .version = 0x0, + }); + ncmds.* += 1; +} diff --git a/src/link/MachO/zld.zig b/src/link/MachO/zld.zig index 9baecd326a..7bffc10e85 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; @@ -34,7 +35,7 @@ pub const Zld = struct { gpa: Allocator, file: fs.File, page_size: u16, - options: link.Options, + options: *const link.Options, objects: std.ArrayListUnmanaged(Object) = .{}, archives: std.ArrayListUnmanaged(Archive) = .{}, @@ -1227,195 +1228,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 +1328,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 +1550,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); } } @@ -3734,7 +3446,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 +3596,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{ @@ -4301,20 +4013,22 @@ 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); + try load_commands.writeDylinkerLC(&ncmds, lc_writer); - { - try lc_writer.writeStruct(macho.source_version_command{ - .cmdsize = @sizeOf(macho.source_version_command), - .version = 0x0, - }); - ncmds += 1; + 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 load_commands.writeMainLC(@intCast(u32, sym.n_value - seg.vmaddr), options, &ncmds, lc_writer); + } else { + assert(zld.options.output_mode == .Lib); + try load_commands.writeDylibIdLC(zld.gpa, zld.options, &ncmds, lc_writer); } - try zld.writeBuildVersionLC(&ncmds, lc_writer); + try load_commands.writeRpathLCs(zld.gpa, zld.options, &ncmds, lc_writer); + try load_commands.writeSourceVersionLC(&ncmds, lc_writer); + try load_commands.writeBuildVersionLC(zld.options, &ncmds, lc_writer); { var uuid_lc = macho.uuid_command{ @@ -4326,7 +4040,7 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr ncmds += 1; } - try zld.writeLoadDylibLCs(&ncmds, lc_writer); + try load_commands.writeLoadDylibLCs(zld.dylibs.items, zld.referenced_dylibs.keys(), &ncmds, lc_writer); const requires_codesig = blk: { if (options.entitlements) |_| break :blk true;