authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-25 10:39:53+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-25 18:04:40+02:00
log589bf67635a9fe9c3e6df4b63c09e0cc4954d29a
treec8e26db7d4f8d83facb6b4a91514bbbff496ed78
parenta6fbdfabb9b60262594da012b8d877a43d8d8e99

macho: implement -headerpad_max_install_names


13 files changed, 180 insertions(+), 61 deletions(-)

lib/std/build.zig+2-2
......@@ -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+5-3
......@@ -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.
2372pub const link_hash_implementation_version = 5;
2372pub const link_hash_implementation_version = 6;
23732373
23742374fn 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+1-1
......@@ -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+1-1
......@@ -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+1-1
......@@ -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+35-18
......@@ -69,9 +69,6 @@ page_size: u16,
6969/// and potentially stage2 release builds in the future.
7070needs_prealloc: bool = true,
7171
72/// Size of the padding between the end of load commands and start of the '__TEXT,__text' section.
73headerpad_size: u64,
74
7572/// The absolute address of the entry point.
7673entry_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.
299const default_headerpad_size: u64 = 0x1000;
296const default_headerpad_size: u32 = 0x1000;
300297
301298pub 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
5151fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u64) u64 {
5168fn 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+1-1
......@@ -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+6-6
......@@ -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+5
......@@ -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 created+120
......@@ -0,0 +1,120 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const Builder = std.build.Builder;
4const LibExeObjectStep = std.build.LibExeObjStep;
5
6pub 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
97fn 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}
test/link/macho/headerpad/main.c created+3
......@@ -0,0 +1,3 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}
test/link/macho/headerpad_size/build.zig deleted-25
......@@ -1,25 +0,0 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9
10 const exe = b.addExecutable("main", null);
11 exe.setBuildMode(mode);
12 exe.addCSourceFile("main.c", &.{});
13 exe.linkLibC();
14 exe.headerpad_size = 0x10000;
15
16 const check = exe.checkObject(.macho);
17 check.checkStart("sectname __text");
18 check.checkNext("offset {offset}");
19 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
20
21 test_step.dependOn(&check.step);
22
23 const run = exe.run();
24 test_step.dependOn(&run.step);
25}
test/link/macho/headerpad_size/main.c deleted-3
......@@ -1,3 +0,0 @@
1int main(int argc, char* argv[]) {
2 return 0;
3}