From 8c1feef4cd3af223ed8278bd55e8191936b526f0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 25 Jun 2022 00:34:16 +0200 Subject: [PATCH] macho: implement -headerpad_size option Includes both traditiona and incremental codepaths with one caveat that in incremental case, the requested size cannot be smaller than the default padding size due to prealloc required due to incremental nature of linking. Also parse `-headerpad_max_install_names`, however, not actionable just yet - missing implementation. --- lib/std/build.zig | 15 +++++++++++++++ src/Compilation.zig | 6 ++++++ src/link.zig | 6 ++++++ src/link/MachO.zig | 35 ++++++++++++++++++++++++++++------- src/main.zig | 26 ++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 7 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index 0b4908774763ca7734ae4a2e6052e6c45cbf5da8..20f59040c929112674a7f10633174419695bf2b4 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 ccaad7c8c49b92a751f98eca66e162876fb18a99..30603b91c52dca6fe5b32fddf9d7dd91faa7f4a9 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 cfcb112e9bd72144d57d51850940d2ff64040755..96ec3ef8cd3f32f059d1428165b70ab83ff79e1f 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 2bbd2e7a72360f6a88e5666e950881732a5eb0f8..dc6fe6e194332cc318373a70ddcb54719b9d375f 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 3d65875975e8d921259ae56f539733f65d7f157d..87b0e6fc8ae886707ac8cabba65314c146b4cb12 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; -- 2.54.0