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.
This commit is contained in:
Jakub Konka 2022-06-25 00:34:16 +02:00
parent f91503e577
commit 8c1feef4cd
5 changed files with 81 additions and 7 deletions

View File

@ -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) {

View File

@ -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.* = .{

View File

@ -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;
}

View File

@ -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(),

View File

@ -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;