diff --git a/lib/std/build.zig b/lib/std/build.zig index 20f59040c929..968ee043bff4 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1595,7 +1595,7 @@ pub const LibExeObjStep = struct { /// (Darwin) Set size of the padding between the end of load commands /// and start of `__TEXT,__text` section. - headerpad_size: ?u64 = null, + headerpad_size: ?u32 = null, /// (Darwin) Automatically Set size of the padding between the end of load commands /// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN. @@ -2671,7 +2671,7 @@ pub const LibExeObjStep = struct { }; if (self.headerpad_size) |headerpad_size| { const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{headerpad_size}); - try zig_args.appendSlice(&[_][]const u8{ "-headerpad_size", size }); + try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); } if (self.headerpad_max_install_names) { try zig_args.append("-headerpad_max_install_names"); diff --git a/src/Compilation.zig b/src/Compilation.zig index 30603b91c52d..652938d74110 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -908,7 +908,7 @@ pub const InitOptions = struct { /// (Darwin) search strategy for system libraries search_strategy: ?link.File.MachO.SearchStrategy = null, /// (Darwin) set minimum space for future expansion of the load commands - headerpad_size: ?u64 = null, + headerpad_size: ?u32 = null, /// (Darwin) set enough space as if all paths were MATPATHLEN headerpad_max_install_names: bool = false, }; @@ -2369,7 +2369,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo /// to remind the programmer to update multiple related pieces of code that /// are in different locations. Bump this number when adding or deleting /// anything from the link cache manifest. -pub const link_hash_implementation_version = 5; +pub const link_hash_implementation_version = 6; fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void { const gpa = comp.gpa; @@ -2379,7 +2379,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - comptime assert(link_hash_implementation_version == 5); + comptime assert(link_hash_implementation_version == 6); if (comp.bin_file.options.module) |mod| { const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{ @@ -2486,6 +2486,8 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes try man.addOptionalFile(comp.bin_file.options.entitlements); man.hash.addOptional(comp.bin_file.options.pagezero_size); man.hash.addOptional(comp.bin_file.options.search_strategy); + man.hash.addOptional(comp.bin_file.options.headerpad_size); + man.hash.add(comp.bin_file.options.headerpad_max_install_names); // COFF specific stuff man.hash.addOptional(comp.bin_file.options.subsystem); diff --git a/src/link.zig b/src/link.zig index 96ec3ef8cd3f..21d54d531c10 100644 --- a/src/link.zig +++ b/src/link.zig @@ -194,7 +194,7 @@ pub const Options = struct { search_strategy: ?File.MachO.SearchStrategy = null, /// (Darwin) set minimum space for future expansion of the load commands - headerpad_size: ?u64 = null, + headerpad_size: ?u32 = null, /// (Darwin) set enough space as if all paths were MATPATHLEN headerpad_max_install_names: bool = false, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 3418a206dfcc..77059a7fd98e 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) ! man = comp.cache_parent.obtain(); self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 5); + comptime assert(Compilation.link_hash_implementation_version == 6); for (self.base.options.objects) |obj| { _ = try man.addFile(obj.path, null); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 1051c0eb78e8..79545d1e1a18 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1298,7 +1298,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v // We are about to obtain this lock, so here we give other processes a chance first. self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 5); + comptime assert(Compilation.link_hash_implementation_version == 6); try man.addOptionalFile(self.base.options.linker_script); try man.addOptionalFile(self.base.options.version_script); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index dc6fe6e19433..4ddee493b88f 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -69,9 +69,6 @@ page_size: u16, /// and potentially stage2 release builds in the future. needs_prealloc: bool = true, -/// Size of the padding between the end of load commands and start of the '__TEXT,__text' section. -headerpad_size: u64, - /// The absolute address of the entry point. entry_addr: ?u64 = null, @@ -296,7 +293,7 @@ const default_pagezero_vmsize: u64 = 0x100000000; /// We commit 0x1000 = 4096 bytes of space to the header and /// the table of load commands. This should be plenty for any /// potential future extensions. -const default_headerpad_size: u64 = 0x1000; +const default_headerpad_size: u32 = 0x1000; pub const Export = struct { sym_index: ?u32 = null, @@ -403,12 +400,6 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO { const use_llvm = build_options.have_llvm and options.use_llvm; const use_stage1 = build_options.is_stage1 and options.use_stage1; const needs_prealloc = !(use_stage1 or use_llvm or options.cache_mode == .whole); - // TODO handle `headerpad_max_install_names` in incremental context - const explicit_headerpad_size = options.headerpad_size orelse 0; - const headerpad_size = if (needs_prealloc) - @maximum(explicit_headerpad_size, default_headerpad_size) - else - explicit_headerpad_size; const self = try gpa.create(MachO); errdefer gpa.destroy(self); @@ -421,7 +412,6 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO { .file = null, }, .page_size = page_size, - .headerpad_size = headerpad_size, .code_signature = if (requires_adhoc_codesig) CodeSignature.init(page_size) else null, .needs_prealloc = needs_prealloc, }; @@ -551,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No // We are about to obtain this lock, so here we give other processes a chance first. self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 5); + comptime assert(Compilation.link_hash_implementation_version == 6); for (self.base.options.objects) |obj| { _ = try man.addFile(obj.path, null); @@ -566,6 +556,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No man.hash.add(stack_size); man.hash.addOptional(self.base.options.pagezero_size); man.hash.addOptional(self.base.options.search_strategy); + man.hash.addOptional(self.base.options.headerpad_size); + man.hash.add(self.base.options.headerpad_max_install_names); man.hash.addListOfBytes(self.base.options.lib_dirs); man.hash.addListOfBytes(self.base.options.framework_dirs); man.hash.addListOfBytes(self.base.options.frameworks); @@ -4470,9 +4462,10 @@ fn populateMissingMetadata(self: *MachO) !void { if (self.text_segment_cmd_index == null) { self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const needed_size = if (self.needs_prealloc) blk: { + const headerpad_size = @maximum(self.base.options.headerpad_size orelse 0, default_headerpad_size); const program_code_size_hint = self.base.options.program_code_size_hint; const got_size_hint = @sizeOf(u64) * self.base.options.symbol_count_hint; - const ideal_size = self.headerpad_size + program_code_size_hint + got_size_hint; + const ideal_size = headerpad_size + program_code_size_hint + got_size_hint; const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), self.page_size); log.debug("found __TEXT segment free space 0x{x} to 0x{x}", .{ 0, needed_size }); break :blk needed_size; @@ -4975,13 +4968,34 @@ fn allocateTextSegment(self: *MachO) !void { seg.inner.fileoff = 0; seg.inner.vmaddr = base_vmaddr; - var sizeofcmds: u64 = 0; + var sizeofcmds: u32 = 0; for (self.load_commands.items) |lc| { sizeofcmds += lc.cmdsize(); } - // TODO verify if `headerpad_max_install_names` leads to larger padding size - const offset = @sizeOf(macho.mach_header_64) + sizeofcmds + self.headerpad_size; + var padding: u32 = sizeofcmds + (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 = 0; + for (self.load_commands.items) |lc| switch (lc.cmd()) { + .ID_DYLIB, + .LOAD_WEAK_DYLIB, + .LOAD_DYLIB, + .REEXPORT_DYLIB, + => { + min_headerpad_size += @sizeOf(macho.dylib_command) + std.os.PATH_MAX + 1; + }, + + else => {}, + }; + log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{ + min_headerpad_size + @sizeOf(macho.mach_header_64), + }); + padding = @maximum(padding, min_headerpad_size); + } + const offset = @sizeOf(macho.mach_header_64) + padding; + log.debug("actual headerpad size 0x{x}", .{offset}); try self.allocateSegment(self.text_segment_cmd_index.?, offset); // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments. @@ -5109,7 +5123,10 @@ fn initSection( if (self.needs_prealloc) { const alignment_pow_2 = try math.powi(u32, 2, alignment); - const padding: ?u64 = if (segment_id == self.text_segment_cmd_index.?) self.headerpad_size else null; + const padding: ?u32 = if (segment_id == self.text_segment_cmd_index.?) + @maximum(self.base.options.headerpad_size orelse 0, default_headerpad_size) + else + null; const off = self.findFreeSpace(segment_id, alignment_pow_2, padding); log.debug("allocating {s},{s} section from 0x{x} to 0x{x}", .{ sect.segName(), @@ -5148,7 +5165,7 @@ fn initSection( return index; } -fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u64) u64 { +fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u32) u64 { const seg = self.load_commands.items[segment_id].segment; if (seg.sections.items.len == 0) { return if (start) |v| v else seg.inner.fileoff; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index c654fbe71965..bc7e70b7dab7 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2481,7 +2481,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) ! // We are about to obtain this lock, so here we give other processes a chance first. self.base.releaseLock(); - comptime assert(Compilation.link_hash_implementation_version == 5); + comptime assert(Compilation.link_hash_implementation_version == 6); for (self.base.options.objects) |obj| { _ = try man.addFile(obj.path, null); diff --git a/src/main.zig b/src/main.zig index 87b0e6fc8ae8..d63e2353600d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -450,7 +450,7 @@ const usage_build_generic = \\ -pagezero_size [value] (Darwin) size of the __PAGEZERO segment in hexadecimal notation \\ -search_paths_first (Darwin) search each dir in library search paths for `libx.dylib` then `libx.a` \\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a` - \\ -headerpad_size [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation + \\ -headerpad [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation \\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN \\ --import-memory (WebAssembly) import memory from the environment \\ --import-table (WebAssembly) import function table from the host environment @@ -701,7 +701,7 @@ fn buildOutputType( var entitlements: ?[]const u8 = null; var pagezero_size: ?u64 = null; var search_strategy: ?link.File.MachO.SearchStrategy = null; - var headerpad_size: ?u64 = null; + var headerpad_size: ?u32 = null; var headerpad_max_install_names: bool = false; // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names. @@ -928,11 +928,11 @@ fn buildOutputType( search_strategy = .paths_first; } else if (mem.eql(u8, arg, "-search_dylibs_first")) { search_strategy = .dylibs_first; - } else if (mem.eql(u8, arg, "-headerpad_size")) { + } else if (mem.eql(u8, arg, "-headerpad")) { const next_arg = args_iter.next() orelse { fatal("expected parameter after {s}", .{arg}); }; - headerpad_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| { + headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| { fatal("unable to parser '{s}': {s}", .{ arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-headerpad_max_install_names")) { @@ -1689,13 +1689,13 @@ fn buildOutputType( pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| { fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; - } else if (mem.eql(u8, arg, "-headerpad_size")) { + } else if (mem.eql(u8, arg, "-headerpad")) { i += 1; if (i >= linker_args.items.len) { fatal("expected linker arg after '{s}'", .{arg}); } const next_arg = linker_args.items[i]; - headerpad_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| { + headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| { fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "-headerpad_max_install_names")) { diff --git a/test/link.zig b/test/link.zig index 62bdcff4b0bd..0c301d6bcbe2 100644 --- a/test/link.zig +++ b/test/link.zig @@ -64,5 +64,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void { cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{ .build_modes = true, }); + + cases.addBuildFile("test/link/macho/headerpad/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); } } diff --git a/test/link/macho/headerpad/build.zig b/test/link/macho/headerpad/build.zig new file mode 100644 index 000000000000..0730a01d442e --- /dev/null +++ b/test/link/macho/headerpad/build.zig @@ -0,0 +1,120 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Builder = std.build.Builder; +const LibExeObjectStep = std.build.LibExeObjStep; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); + + { + // Test -headerpad_max_install_names + const exe = simpleExe(b, mode); + exe.headerpad_max_install_names = true; + + const check = exe.checkObject(.macho); + check.checkStart("sectname __text"); + check.checkNext("offset {offset}"); + + switch (builtin.cpu.arch) { + .aarch64 => { + check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } }); + }, + .x86_64 => { + check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } }); + }, + else => unreachable, + } + + test_step.dependOn(&check.step); + + const run = exe.run(); + test_step.dependOn(&run.step); + } + + { + // Test -headerpad + const exe = simpleExe(b, mode); + exe.headerpad_size = 0x10000; + + const check = exe.checkObject(.macho); + check.checkStart("sectname __text"); + check.checkNext("offset {offset}"); + check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } }); + + test_step.dependOn(&check.step); + + const run = exe.run(); + test_step.dependOn(&run.step); + } + + { + // Test both flags with -headerpad overriding -headerpad_max_install_names + const exe = simpleExe(b, mode); + exe.headerpad_max_install_names = true; + exe.headerpad_size = 0x10000; + + const check = exe.checkObject(.macho); + check.checkStart("sectname __text"); + check.checkNext("offset {offset}"); + check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } }); + + test_step.dependOn(&check.step); + + const run = exe.run(); + test_step.dependOn(&run.step); + } + + { + // Test both flags with -headerpad_max_install_names overriding -headerpad + const exe = simpleExe(b, mode); + exe.headerpad_size = 0x1000; + exe.headerpad_max_install_names = true; + + const check = exe.checkObject(.macho); + check.checkStart("sectname __text"); + check.checkNext("offset {offset}"); + + switch (builtin.cpu.arch) { + .aarch64 => { + check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } }); + }, + .x86_64 => { + check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } }); + }, + else => unreachable, + } + + test_step.dependOn(&check.step); + + const run = exe.run(); + test_step.dependOn(&run.step); + } +} + +fn simpleExe(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep { + const exe = b.addExecutable("main", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.linkFramework("CoreFoundation"); + exe.linkFramework("Foundation"); + exe.linkFramework("Cocoa"); + exe.linkFramework("CoreGraphics"); + exe.linkFramework("CoreHaptics"); + exe.linkFramework("CoreAudio"); + exe.linkFramework("AVFoundation"); + exe.linkFramework("CoreImage"); + exe.linkFramework("CoreLocation"); + exe.linkFramework("CoreML"); + exe.linkFramework("CoreVideo"); + exe.linkFramework("CoreText"); + exe.linkFramework("CryptoKit"); + exe.linkFramework("GameKit"); + exe.linkFramework("SwiftUI"); + exe.linkFramework("StoreKit"); + exe.linkFramework("SpriteKit"); + return exe; +} diff --git a/test/link/macho/headerpad_size/main.c b/test/link/macho/headerpad/main.c similarity index 100% rename from test/link/macho/headerpad_size/main.c rename to test/link/macho/headerpad/main.c diff --git a/test/link/macho/headerpad_size/build.zig b/test/link/macho/headerpad_size/build.zig deleted file mode 100644 index 0a02405e7945..000000000000 --- a/test/link/macho/headerpad_size/build.zig +++ /dev/null @@ -1,25 +0,0 @@ -const std = @import("std"); -const Builder = std.build.Builder; - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - - const test_step = b.step("test", "Test"); - test_step.dependOn(b.getInstallStep()); - - const exe = b.addExecutable("main", null); - exe.setBuildMode(mode); - exe.addCSourceFile("main.c", &.{}); - exe.linkLibC(); - exe.headerpad_size = 0x10000; - - const check = exe.checkObject(.macho); - check.checkStart("sectname __text"); - check.checkNext("offset {offset}"); - check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } }); - - test_step.dependOn(&check.step); - - const run = exe.run(); - test_step.dependOn(&run.step); -}