Skip to content

Commit 589bf67

Browse files
committed
macho: implement -headerpad_max_install_names
1 parent a6fbdfa commit 589bf67

File tree

12 files changed

+177
-58
lines changed

12 files changed

+177
-58
lines changed

lib/std/build.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,7 +1595,7 @@ pub const LibExeObjStep = struct {
15951595

15961596
/// (Darwin) Set size of the padding between the end of load commands
15971597
/// and start of `__TEXT,__text` section.
1598-
headerpad_size: ?u64 = null,
1598+
headerpad_size: ?u32 = null,
15991599

16001600
/// (Darwin) Automatically Set size of the padding between the end of load commands
16011601
/// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN.
@@ -2671,7 +2671,7 @@ pub const LibExeObjStep = struct {
26712671
};
26722672
if (self.headerpad_size) |headerpad_size| {
26732673
const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{headerpad_size});
2674-
try zig_args.appendSlice(&[_][]const u8{ "-headerpad_size", size });
2674+
try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size });
26752675
}
26762676
if (self.headerpad_max_install_names) {
26772677
try zig_args.append("-headerpad_max_install_names");

src/Compilation.zig

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,7 @@ pub const InitOptions = struct {
908908
/// (Darwin) search strategy for system libraries
909909
search_strategy: ?link.File.MachO.SearchStrategy = null,
910910
/// (Darwin) set minimum space for future expansion of the load commands
911-
headerpad_size: ?u64 = null,
911+
headerpad_size: ?u32 = null,
912912
/// (Darwin) set enough space as if all paths were MATPATHLEN
913913
headerpad_max_install_names: bool = false,
914914
};
@@ -2369,7 +2369,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
23692369
/// to remind the programmer to update multiple related pieces of code that
23702370
/// are in different locations. Bump this number when adding or deleting
23712371
/// anything from the link cache manifest.
2372-
pub const link_hash_implementation_version = 5;
2372+
pub const link_hash_implementation_version = 6;
23732373

23742374
fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
23752375
const gpa = comp.gpa;
@@ -2379,7 +2379,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
23792379
defer arena_allocator.deinit();
23802380
const arena = arena_allocator.allocator();
23812381

2382-
comptime assert(link_hash_implementation_version == 5);
2382+
comptime assert(link_hash_implementation_version == 6);
23832383

23842384
if (comp.bin_file.options.module) |mod| {
23852385
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
24862486
try man.addOptionalFile(comp.bin_file.options.entitlements);
24872487
man.hash.addOptional(comp.bin_file.options.pagezero_size);
24882488
man.hash.addOptional(comp.bin_file.options.search_strategy);
2489+
man.hash.addOptional(comp.bin_file.options.headerpad_size);
2490+
man.hash.add(comp.bin_file.options.headerpad_max_install_names);
24892491

24902492
// COFF specific stuff
24912493
man.hash.addOptional(comp.bin_file.options.subsystem);

src/link.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ pub const Options = struct {
194194
search_strategy: ?File.MachO.SearchStrategy = null,
195195

196196
/// (Darwin) set minimum space for future expansion of the load commands
197-
headerpad_size: ?u64 = null,
197+
headerpad_size: ?u32 = null,
198198

199199
/// (Darwin) set enough space as if all paths were MATPATHLEN
200200
headerpad_max_install_names: bool = false,

src/link/Coff.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !
969969
man = comp.cache_parent.obtain();
970970
self.base.releaseLock();
971971

972-
comptime assert(Compilation.link_hash_implementation_version == 5);
972+
comptime assert(Compilation.link_hash_implementation_version == 6);
973973

974974
for (self.base.options.objects) |obj| {
975975
_ = try man.addFile(obj.path, null);

src/link/Elf.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
12981298
// We are about to obtain this lock, so here we give other processes a chance first.
12991299
self.base.releaseLock();
13001300

1301-
comptime assert(Compilation.link_hash_implementation_version == 5);
1301+
comptime assert(Compilation.link_hash_implementation_version == 6);
13021302

13031303
try man.addOptionalFile(self.base.options.linker_script);
13041304
try man.addOptionalFile(self.base.options.version_script);

src/link/MachO.zig

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,6 @@ page_size: u16,
6969
/// and potentially stage2 release builds in the future.
7070
needs_prealloc: bool = true,
7171

72-
/// Size of the padding between the end of load commands and start of the '__TEXT,__text' section.
73-
headerpad_size: u64,
74-
7572
/// The absolute address of the entry point.
7673
entry_addr: ?u64 = null,
7774

@@ -296,7 +293,7 @@ const default_pagezero_vmsize: u64 = 0x100000000;
296293
/// We commit 0x1000 = 4096 bytes of space to the header and
297294
/// the table of load commands. This should be plenty for any
298295
/// potential future extensions.
299-
const default_headerpad_size: u64 = 0x1000;
296+
const default_headerpad_size: u32 = 0x1000;
300297

301298
pub const Export = struct {
302299
sym_index: ?u32 = null,
@@ -403,12 +400,6 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {
403400
const use_llvm = build_options.have_llvm and options.use_llvm;
404401
const use_stage1 = build_options.is_stage1 and options.use_stage1;
405402
const needs_prealloc = !(use_stage1 or use_llvm or options.cache_mode == .whole);
406-
// TODO handle `headerpad_max_install_names` in incremental context
407-
const explicit_headerpad_size = options.headerpad_size orelse 0;
408-
const headerpad_size = if (needs_prealloc)
409-
@maximum(explicit_headerpad_size, default_headerpad_size)
410-
else
411-
explicit_headerpad_size;
412403

413404
const self = try gpa.create(MachO);
414405
errdefer gpa.destroy(self);
@@ -421,7 +412,6 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {
421412
.file = null,
422413
},
423414
.page_size = page_size,
424-
.headerpad_size = headerpad_size,
425415
.code_signature = if (requires_adhoc_codesig) CodeSignature.init(page_size) else null,
426416
.needs_prealloc = needs_prealloc,
427417
};
@@ -551,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
551541
// We are about to obtain this lock, so here we give other processes a chance first.
552542
self.base.releaseLock();
553543

554-
comptime assert(Compilation.link_hash_implementation_version == 5);
544+
comptime assert(Compilation.link_hash_implementation_version == 6);
555545

556546
for (self.base.options.objects) |obj| {
557547
_ = try man.addFile(obj.path, null);
@@ -566,6 +556,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
566556
man.hash.add(stack_size);
567557
man.hash.addOptional(self.base.options.pagezero_size);
568558
man.hash.addOptional(self.base.options.search_strategy);
559+
man.hash.addOptional(self.base.options.headerpad_size);
560+
man.hash.add(self.base.options.headerpad_max_install_names);
569561
man.hash.addListOfBytes(self.base.options.lib_dirs);
570562
man.hash.addListOfBytes(self.base.options.framework_dirs);
571563
man.hash.addListOfBytes(self.base.options.frameworks);
@@ -4470,9 +4462,10 @@ fn populateMissingMetadata(self: *MachO) !void {
44704462
if (self.text_segment_cmd_index == null) {
44714463
self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
44724464
const needed_size = if (self.needs_prealloc) blk: {
4465+
const headerpad_size = @maximum(self.base.options.headerpad_size orelse 0, default_headerpad_size);
44734466
const program_code_size_hint = self.base.options.program_code_size_hint;
44744467
const got_size_hint = @sizeOf(u64) * self.base.options.symbol_count_hint;
4475-
const ideal_size = self.headerpad_size + program_code_size_hint + got_size_hint;
4468+
const ideal_size = headerpad_size + program_code_size_hint + got_size_hint;
44764469
const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), self.page_size);
44774470
log.debug("found __TEXT segment free space 0x{x} to 0x{x}", .{ 0, needed_size });
44784471
break :blk needed_size;
@@ -4975,13 +4968,34 @@ fn allocateTextSegment(self: *MachO) !void {
49754968
seg.inner.fileoff = 0;
49764969
seg.inner.vmaddr = base_vmaddr;
49774970

4978-
var sizeofcmds: u64 = 0;
4971+
var sizeofcmds: u32 = 0;
49794972
for (self.load_commands.items) |lc| {
49804973
sizeofcmds += lc.cmdsize();
49814974
}
49824975

4983-
// TODO verify if `headerpad_max_install_names` leads to larger padding size
4984-
const offset = @sizeOf(macho.mach_header_64) + sizeofcmds + self.headerpad_size;
4976+
var padding: u32 = sizeofcmds + (self.base.options.headerpad_size orelse 0);
4977+
log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
4978+
4979+
if (self.base.options.headerpad_max_install_names) {
4980+
var min_headerpad_size: u32 = 0;
4981+
for (self.load_commands.items) |lc| switch (lc.cmd()) {
4982+
.ID_DYLIB,
4983+
.LOAD_WEAK_DYLIB,
4984+
.LOAD_DYLIB,
4985+
.REEXPORT_DYLIB,
4986+
=> {
4987+
min_headerpad_size += @sizeOf(macho.dylib_command) + std.os.PATH_MAX + 1;
4988+
},
4989+
4990+
else => {},
4991+
};
4992+
log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
4993+
min_headerpad_size + @sizeOf(macho.mach_header_64),
4994+
});
4995+
padding = @maximum(padding, min_headerpad_size);
4996+
}
4997+
const offset = @sizeOf(macho.mach_header_64) + padding;
4998+
log.debug("actual headerpad size 0x{x}", .{offset});
49854999
try self.allocateSegment(self.text_segment_cmd_index.?, offset);
49865000

49875001
// Shift all sections to the back to minimize jump size between __TEXT and __DATA segments.
@@ -5109,7 +5123,10 @@ fn initSection(
51095123

51105124
if (self.needs_prealloc) {
51115125
const alignment_pow_2 = try math.powi(u32, 2, alignment);
5112-
const padding: ?u64 = if (segment_id == self.text_segment_cmd_index.?) self.headerpad_size else null;
5126+
const padding: ?u32 = if (segment_id == self.text_segment_cmd_index.?)
5127+
@maximum(self.base.options.headerpad_size orelse 0, default_headerpad_size)
5128+
else
5129+
null;
51135130
const off = self.findFreeSpace(segment_id, alignment_pow_2, padding);
51145131
log.debug("allocating {s},{s} section from 0x{x} to 0x{x}", .{
51155132
sect.segName(),
@@ -5148,7 +5165,7 @@ fn initSection(
51485165
return index;
51495166
}
51505167

5151-
fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u64) u64 {
5168+
fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u32) u64 {
51525169
const seg = self.load_commands.items[segment_id].segment;
51535170
if (seg.sections.items.len == 0) {
51545171
return if (start) |v| v else seg.inner.fileoff;

src/link/Wasm.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2481,7 +2481,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
24812481
// We are about to obtain this lock, so here we give other processes a chance first.
24822482
self.base.releaseLock();
24832483

2484-
comptime assert(Compilation.link_hash_implementation_version == 5);
2484+
comptime assert(Compilation.link_hash_implementation_version == 6);
24852485

24862486
for (self.base.options.objects) |obj| {
24872487
_ = try man.addFile(obj.path, null);

src/main.zig

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ const usage_build_generic =
450450
\\ -pagezero_size [value] (Darwin) size of the __PAGEZERO segment in hexadecimal notation
451451
\\ -search_paths_first (Darwin) search each dir in library search paths for `libx.dylib` then `libx.a`
452452
\\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a`
453-
\\ -headerpad_size [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation
453+
\\ -headerpad [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation
454454
\\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN
455455
\\ --import-memory (WebAssembly) import memory from the environment
456456
\\ --import-table (WebAssembly) import function table from the host environment
@@ -701,7 +701,7 @@ fn buildOutputType(
701701
var entitlements: ?[]const u8 = null;
702702
var pagezero_size: ?u64 = null;
703703
var search_strategy: ?link.File.MachO.SearchStrategy = null;
704-
var headerpad_size: ?u64 = null;
704+
var headerpad_size: ?u32 = null;
705705
var headerpad_max_install_names: bool = false;
706706

707707
// e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
@@ -928,11 +928,11 @@ fn buildOutputType(
928928
search_strategy = .paths_first;
929929
} else if (mem.eql(u8, arg, "-search_dylibs_first")) {
930930
search_strategy = .dylibs_first;
931-
} else if (mem.eql(u8, arg, "-headerpad_size")) {
931+
} else if (mem.eql(u8, arg, "-headerpad")) {
932932
const next_arg = args_iter.next() orelse {
933933
fatal("expected parameter after {s}", .{arg});
934934
};
935-
headerpad_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
935+
headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| {
936936
fatal("unable to parser '{s}': {s}", .{ arg, @errorName(err) });
937937
};
938938
} else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
@@ -1689,13 +1689,13 @@ fn buildOutputType(
16891689
pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
16901690
fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });
16911691
};
1692-
} else if (mem.eql(u8, arg, "-headerpad_size")) {
1692+
} else if (mem.eql(u8, arg, "-headerpad")) {
16931693
i += 1;
16941694
if (i >= linker_args.items.len) {
16951695
fatal("expected linker arg after '{s}'", .{arg});
16961696
}
16971697
const next_arg = linker_args.items[i];
1698-
headerpad_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
1698+
headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| {
16991699
fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });
17001700
};
17011701
} else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {

test/link.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
6464
cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{
6565
.build_modes = true,
6666
});
67+
68+
cases.addBuildFile("test/link/macho/headerpad/build.zig", .{
69+
.build_modes = true,
70+
.requires_macos_sdk = true,
71+
});
6772
}
6873
}

test/link/macho/headerpad/build.zig

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
const std = @import("std");
2+
const builtin = @import("builtin");
3+
const Builder = std.build.Builder;
4+
const LibExeObjectStep = std.build.LibExeObjStep;
5+
6+
pub fn build(b: *Builder) void {
7+
const mode = b.standardReleaseOptions();
8+
9+
const test_step = b.step("test", "Test");
10+
test_step.dependOn(b.getInstallStep());
11+
12+
{
13+
// Test -headerpad_max_install_names
14+
const exe = simpleExe(b, mode);
15+
exe.headerpad_max_install_names = true;
16+
17+
const check = exe.checkObject(.macho);
18+
check.checkStart("sectname __text");
19+
check.checkNext("offset {offset}");
20+
21+
switch (builtin.cpu.arch) {
22+
.aarch64 => {
23+
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } });
24+
},
25+
.x86_64 => {
26+
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } });
27+
},
28+
else => unreachable,
29+
}
30+
31+
test_step.dependOn(&check.step);
32+
33+
const run = exe.run();
34+
test_step.dependOn(&run.step);
35+
}
36+
37+
{
38+
// Test -headerpad
39+
const exe = simpleExe(b, mode);
40+
exe.headerpad_size = 0x10000;
41+
42+
const check = exe.checkObject(.macho);
43+
check.checkStart("sectname __text");
44+
check.checkNext("offset {offset}");
45+
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
46+
47+
test_step.dependOn(&check.step);
48+
49+
const run = exe.run();
50+
test_step.dependOn(&run.step);
51+
}
52+
53+
{
54+
// Test both flags with -headerpad overriding -headerpad_max_install_names
55+
const exe = simpleExe(b, mode);
56+
exe.headerpad_max_install_names = true;
57+
exe.headerpad_size = 0x10000;
58+
59+
const check = exe.checkObject(.macho);
60+
check.checkStart("sectname __text");
61+
check.checkNext("offset {offset}");
62+
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
63+
64+
test_step.dependOn(&check.step);
65+
66+
const run = exe.run();
67+
test_step.dependOn(&run.step);
68+
}
69+
70+
{
71+
// Test both flags with -headerpad_max_install_names overriding -headerpad
72+
const exe = simpleExe(b, mode);
73+
exe.headerpad_size = 0x1000;
74+
exe.headerpad_max_install_names = true;
75+
76+
const check = exe.checkObject(.macho);
77+
check.checkStart("sectname __text");
78+
check.checkNext("offset {offset}");
79+
80+
switch (builtin.cpu.arch) {
81+
.aarch64 => {
82+
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } });
83+
},
84+
.x86_64 => {
85+
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } });
86+
},
87+
else => unreachable,
88+
}
89+
90+
test_step.dependOn(&check.step);
91+
92+
const run = exe.run();
93+
test_step.dependOn(&run.step);
94+
}
95+
}
96+
97+
fn simpleExe(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
98+
const exe = b.addExecutable("main", null);
99+
exe.setBuildMode(mode);
100+
exe.addCSourceFile("main.c", &.{});
101+
exe.linkLibC();
102+
exe.linkFramework("CoreFoundation");
103+
exe.linkFramework("Foundation");
104+
exe.linkFramework("Cocoa");
105+
exe.linkFramework("CoreGraphics");
106+
exe.linkFramework("CoreHaptics");
107+
exe.linkFramework("CoreAudio");
108+
exe.linkFramework("AVFoundation");
109+
exe.linkFramework("CoreImage");
110+
exe.linkFramework("CoreLocation");
111+
exe.linkFramework("CoreML");
112+
exe.linkFramework("CoreVideo");
113+
exe.linkFramework("CoreText");
114+
exe.linkFramework("CryptoKit");
115+
exe.linkFramework("GameKit");
116+
exe.linkFramework("SwiftUI");
117+
exe.linkFramework("StoreKit");
118+
exe.linkFramework("SpriteKit");
119+
return exe;
120+
}

0 commit comments

Comments
 (0)