diff --git a/lib/std/build.zig b/lib/std/build.zig index 0b49087747..20f59040c9 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1593,6 +1593,14 @@ pub const LibExeObjStep = struct { /// search strategy. search_strategy: ?enum { paths_first, dylibs_first } = null, + /// (Darwin) Set size of the padding between the end of load commands + /// and start of `__TEXT,__text` section. + headerpad_size: ?u64 = 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. + headerpad_max_install_names: bool = false, + /// Position Independent Code force_pic: ?bool = null, @@ -2661,6 +2669,13 @@ pub const LibExeObjStep = struct { .paths_first => try zig_args.append("-search_paths_first"), .dylibs_first => try zig_args.append("-search_dylibs_first"), }; + 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 }); + } + if (self.headerpad_max_install_names) { + try zig_args.append("-headerpad_max_install_names"); + } if (self.bundle_compiler_rt) |x| { if (x) { diff --git a/src/Compilation.zig b/src/Compilation.zig index ccaad7c8c4..30603b91c5 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -907,6 +907,10 @@ pub const InitOptions = struct { pagezero_size: ?u64 = null, /// (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, + /// (Darwin) set enough space as if all paths were MATPATHLEN + headerpad_max_install_names: bool = false, }; fn addPackageTableToCacheHash( @@ -1748,6 +1752,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .entitlements = options.entitlements, .pagezero_size = options.pagezero_size, .search_strategy = options.search_strategy, + .headerpad_size = options.headerpad_size, + .headerpad_max_install_names = options.headerpad_max_install_names, }); errdefer bin_file.destroy(); comp.* = .{ diff --git a/src/link.zig b/src/link.zig index cfcb112e9b..96ec3ef8cd 100644 --- a/src/link.zig +++ b/src/link.zig @@ -193,6 +193,12 @@ pub const Options = struct { /// (Darwin) search strategy for system libraries search_strategy: ?File.MachO.SearchStrategy = null, + /// (Darwin) set minimum space for future expansion of the load commands + headerpad_size: ?u64 = null, + + /// (Darwin) set enough space as if all paths were MATPATHLEN + headerpad_max_install_names: bool = false, + pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode { return if (options.use_lld) .Obj else options.output_mode; } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 2bbd2e7a72..dc6fe6e194 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -69,10 +69,8 @@ page_size: u16, /// and potentially stage2 release builds in the future. needs_prealloc: bool = true, -/// 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. -header_pad: u16 = 0x1000, +/// 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, @@ -295,6 +293,11 @@ pub const min_text_capacity = padToIdeal(minimum_text_block_size); /// start of __TEXT segment. 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; + pub const Export = struct { sym_index: ?u32 = null, }; @@ -400,6 +403,12 @@ 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); @@ -412,6 +421,7 @@ 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, }; @@ -976,6 +986,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No .dylibs_first => try argv.append("-search_dylibs_first"), }; + if (self.base.options.headerpad_size) |headerpad_size| { + try argv.append("-headerpad_size"); + try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{headerpad_size})); + } + + if (self.base.options.headerpad_max_install_names) { + try argv.append("-headerpad_max_install_names"); + } + if (self.base.options.entry) |entry| { try argv.append("-e"); try argv.append(entry); @@ -4453,7 +4472,7 @@ fn populateMissingMetadata(self: *MachO) !void { const needed_size = if (self.needs_prealloc) blk: { 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.header_pad + program_code_size_hint + got_size_hint; + const ideal_size = self.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; @@ -4961,7 +4980,9 @@ fn allocateTextSegment(self: *MachO) !void { sizeofcmds += lc.cmdsize(); } - try self.allocateSegment(self.text_segment_cmd_index.?, @sizeOf(macho.mach_header_64) + sizeofcmds); + // TODO verify if `headerpad_max_install_names` leads to larger padding size + const offset = @sizeOf(macho.mach_header_64) + sizeofcmds + self.headerpad_size; + try self.allocateSegment(self.text_segment_cmd_index.?, offset); // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments. var min_alignment: u32 = 0; @@ -5088,7 +5109,7 @@ 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.header_pad else null; + const padding: ?u64 = if (segment_id == self.text_segment_cmd_index.?) self.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(), diff --git a/src/main.zig b/src/main.zig index 3d65875975..87b0e6fc8a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -450,6 +450,8 @@ 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_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 \\ --export-table (WebAssembly) export function table to the host environment @@ -699,6 +701,8 @@ 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_max_install_names: bool = false; // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names. // This array is populated by zig cc frontend and then has to be converted to zig-style @@ -924,6 +928,15 @@ 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")) { + 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| { + fatal("unable to parser '{s}': {s}", .{ arg, @errorName(err) }); + }; + } else if (mem.eql(u8, arg, "-headerpad_max_install_names")) { + headerpad_max_install_names = true; } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) { linker_script = args_iter.next() orelse { fatal("expected parameter after {s}", .{arg}); @@ -1676,6 +1689,17 @@ 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")) { + 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| { + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); + }; + } else if (mem.eql(u8, arg, "-headerpad_max_install_names")) { + headerpad_max_install_names = true; } else if (mem.eql(u8, arg, "--gc-sections")) { linker_gc_sections = true; } else if (mem.eql(u8, arg, "--no-gc-sections")) { @@ -2795,6 +2819,8 @@ fn buildOutputType( .entitlements = entitlements, .pagezero_size = pagezero_size, .search_strategy = search_strategy, + .headerpad_size = headerpad_size, + .headerpad_max_install_names = headerpad_max_install_names, }) catch |err| switch (err) { error.LibCUnavailable => { const target = target_info.target;