authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-25 21:15:59+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-06-25 21:15:59+02:00
log76b28ed45238fff843e04e94657e2790ee95954b
treec8e26db7d4f8d83facb6b4a91514bbbff496ed78
parent0078d36ff39f2ef35e32f2d58e5d447b62f3a37e
parent589bf67635a9fe9c3e6df4b63c09e0cc4954d29a
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11929 from ziglang/headerpad_max_install_names

macho: handle `-headerpad` and `-headerpad_max_install_names`

13 files changed, 375 insertions(+), 99 deletions(-)

lib/std/build.zig+15
...@@ -1593,6 +1593,14 @@ pub const LibExeObjStep = struct {...@@ -1593,6 +1593,14 @@ pub const LibExeObjStep = struct {
1593 /// search strategy.1593 /// search strategy.
1594 search_strategy: ?enum { paths_first, dylibs_first } = null,1594 search_strategy: ?enum { paths_first, dylibs_first } = null,
15951595
1596 /// (Darwin) Set size of the padding between the end of load commands
1597 /// and start of `__TEXT,__text` section.
1598 headerpad_size: ?u32 = null,
1599
1600 /// (Darwin) Automatically Set size of the padding between the end of load commands
1601 /// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN.
1602 headerpad_max_install_names: bool = false,
1603
1596 /// Position Independent Code1604 /// Position Independent Code
1597 force_pic: ?bool = null,1605 force_pic: ?bool = null,
15981606
...@@ -2661,6 +2669,13 @@ pub const LibExeObjStep = struct {...@@ -2661,6 +2669,13 @@ pub const LibExeObjStep = struct {
2661 .paths_first => try zig_args.append("-search_paths_first"),2669 .paths_first => try zig_args.append("-search_paths_first"),
2662 .dylibs_first => try zig_args.append("-search_dylibs_first"),2670 .dylibs_first => try zig_args.append("-search_dylibs_first"),
2663 };2671 };
2672 if (self.headerpad_size) |headerpad_size| {
2673 const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{headerpad_size});
2674 try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size });
2675 }
2676 if (self.headerpad_max_install_names) {
2677 try zig_args.append("-headerpad_max_install_names");
2678 }
26642679
2665 if (self.bundle_compiler_rt) |x| {2680 if (self.bundle_compiler_rt) |x| {
2666 if (x) {2681 if (x) {
lib/std/build/CheckObjectStep.zig+137-82
...@@ -3,6 +3,7 @@ const assert = std.debug.assert;...@@ -3,6 +3,7 @@ const assert = std.debug.assert;
3const build = std.build;3const build = std.build;
4const fs = std.fs;4const fs = std.fs;
5const macho = std.macho;5const macho = std.macho;
6const math = std.math;
6const mem = std.mem;7const mem = std.mem;
7const testing = std.testing;8const testing = std.testing;
89
...@@ -36,20 +37,24 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe...@@ -36,20 +37,24 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe
36 return self;37 return self;
37}38}
3839
39const Action = union(enum) {40/// There two types of actions currently suported:
40 match: MatchAction,41/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}`
41 compute_eq: ComputeEqAction,
42};
43
44/// MatchAction is the main building block of standard matchers with optional eat-all token `{*}`
45/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature42/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature
46/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use43/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use
47/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc.44/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc.
48/// it should be plenty useful in its current form.45/// it should be plenty useful in its current form.
49const MatchAction = struct {46/// * `.compute_cmp` - can be used to perform an operation on the extracted global variables
50 needle: []const u8,47/// using the MatchAction. It currently only supports an addition. The operation is required
48/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well,
49/// to avoid any parsing really).
50/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively
51/// they could then be added with this simple program `vmaddr entryoff +`.
52const Action = struct {
53 tag: enum { match, compute_cmp },
54 phrase: []const u8,
55 expected: ?ComputeCompareExpected = null,
5156
52 /// Will return true if the `needle` was found in the `haystack`.57 /// Will return true if the `phrase` was found in the `haystack`.
53 /// Some examples include:58 /// Some examples include:
54 ///59 ///
55 /// LC 0 => will match in its entirety60 /// LC 0 => will match in its entirety
...@@ -57,9 +62,11 @@ const MatchAction = struct {...@@ -57,9 +62,11 @@ const MatchAction = struct {
57 /// and save under `vmaddr` global name (see `global_vars` param)62 /// and save under `vmaddr` global name (see `global_vars` param)
58 /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib`63 /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib`
59 /// in that order with other letters in between64 /// in that order with other letters in between
60 fn match(act: MatchAction, haystack: []const u8, global_vars: anytype) !bool {65 fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool {
66 assert(act.tag == .match);
67
61 var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " ");68 var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " ");
62 var needle_it = mem.tokenize(u8, mem.trim(u8, act.needle, " "), " ");69 var needle_it = mem.tokenize(u8, mem.trim(u8, act.phrase, " "), " ");
6370
64 while (needle_it.next()) |needle_tok| {71 while (needle_it.next()) |needle_tok| {
65 const hay_tok = hay_it.next() orelse return false;72 const hay_tok = hay_it.next() orelse return false;
...@@ -93,22 +100,80 @@ const MatchAction = struct {...@@ -93,22 +100,80 @@ const MatchAction = struct {
93100
94 return true;101 return true;
95 }102 }
96};
97103
98/// ComputeEqAction can be used to perform an operation on the extracted global variables104 /// Will return true if the `phrase` is correctly parsed into an RPN program and
99/// using the MatchAction. It currently only supports an addition. The operation is required105 /// its reduced, computed value compares using `op` with the expected value, either
100/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well,106 /// a literal or another extracted variable.
101/// to avoid any parsing really).107 fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool {
102/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively108 var op_stack = std.ArrayList(enum { add }).init(gpa);
103/// they could then be added with this simple program `vmaddr entryoff +`.109 var values = std.ArrayList(u64).init(gpa);
104const ComputeEqAction = struct {110
105 expected: []const u8,111 var it = mem.tokenize(u8, act.phrase, " ");
106 var_stack: std.ArrayList([]const u8),112 while (it.next()) |next| {
107 op_stack: std.ArrayList(Op),113 if (mem.eql(u8, next, "+")) {
114 try op_stack.append(.add);
115 } else {
116 const val = global_vars.get(next) orelse {
117 std.debug.print(
118 \\
119 \\========= Variable was not extracted: ===========
120 \\{s}
121 \\
122 , .{next});
123 return error.UnknownVariable;
124 };
125 try values.append(val);
126 }
127 }
108128
109 const Op = enum {129 var op_i: usize = 1;
110 add,130 var reduced: u64 = values.items[0];
111 };131 for (op_stack.items) |op| {
132 const other = values.items[op_i];
133 switch (op) {
134 .add => {
135 reduced += other;
136 },
137 }
138 }
139
140 const exp_value = switch (act.expected.?.value) {
141 .variable => |name| global_vars.get(name) orelse {
142 std.debug.print(
143 \\
144 \\========= Variable was not extracted: ===========
145 \\{s}
146 \\
147 , .{name});
148 return error.UnknownVariable;
149 },
150 .literal => |x| x,
151 };
152 return math.compare(reduced, act.expected.?.op, exp_value);
153 }
154};
155
156const ComputeCompareExpected = struct {
157 op: math.CompareOperator,
158 value: union(enum) {
159 variable: []const u8,
160 literal: u64,
161 },
162
163 pub fn format(
164 value: @This(),
165 comptime fmt: []const u8,
166 options: std.fmt.FormatOptions,
167 writer: anytype,
168 ) !void {
169 _ = fmt;
170 _ = options;
171 try writer.print("{s} ", .{@tagName(value.op)});
172 switch (value.value) {
173 .variable => |name| try writer.writeAll(name),
174 .literal => |x| try writer.print("{x}", .{x}),
175 }
176 }
112};177};
113178
114const Check = struct {179const Check = struct {
...@@ -122,15 +187,18 @@ const Check = struct {...@@ -122,15 +187,18 @@ const Check = struct {
122 };187 };
123 }188 }
124189
125 fn match(self: *Check, needle: []const u8) void {190 fn match(self: *Check, phrase: []const u8) void {
126 self.actions.append(.{191 self.actions.append(.{
127 .match = .{ .needle = self.builder.dupe(needle) },192 .tag = .match,
193 .phrase = self.builder.dupe(phrase),
128 }) catch unreachable;194 }) catch unreachable;
129 }195 }
130196
131 fn computeEq(self: *Check, act: ComputeEqAction) void {197 fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void {
132 self.actions.append(.{198 self.actions.append(.{
133 .compute_eq = act,199 .tag = .compute_cmp,
200 .phrase = self.builder.dupe(phrase),
201 .expected = expected,
134 }) catch unreachable;202 }) catch unreachable;
135 }203 }
136};204};
...@@ -165,25 +233,13 @@ pub fn checkInSymtab(self: *CheckObjectStep) void {...@@ -165,25 +233,13 @@ pub fn checkInSymtab(self: *CheckObjectStep) void {
165/// Creates a new standalone, singular check which allows running simple binary operations233/// Creates a new standalone, singular check which allows running simple binary operations
166/// on the extracted variables. It will then compare the reduced program with the value of234/// on the extracted variables. It will then compare the reduced program with the value of
167/// the expected variable.235/// the expected variable.
168pub fn checkComputeEq(self: *CheckObjectStep, program: []const u8, expected: []const u8) void {236pub fn checkComputeCompare(
169 const gpa = self.builder.allocator;237 self: *CheckObjectStep,
170 var ca = ComputeEqAction{238 program: []const u8,
171 .expected = expected,239 expected: ComputeCompareExpected,
172 .var_stack = std.ArrayList([]const u8).init(gpa),240) void {
173 .op_stack = std.ArrayList(ComputeEqAction.Op).init(gpa),
174 };
175
176 var it = mem.tokenize(u8, program, " ");
177 while (it.next()) |next| {
178 if (mem.eql(u8, next, "+")) {
179 ca.op_stack.append(.add) catch unreachable;
180 } else {
181 ca.var_stack.append(self.builder.dupe(next)) catch unreachable;
182 }
183 }
184
185 var new_check = Check.create(self.builder);241 var new_check = Check.create(self.builder);
186 new_check.computeEq(ca);242 new_check.computeCmp(program, expected);
187 self.checks.append(new_check) catch unreachable;243 self.checks.append(new_check) catch unreachable;
188}244}
189245
...@@ -210,10 +266,10 @@ fn make(step: *Step) !void {...@@ -210,10 +266,10 @@ fn make(step: *Step) !void {
210 for (self.checks.items) |chk| {266 for (self.checks.items) |chk| {
211 var it = mem.tokenize(u8, output, "\r\n");267 var it = mem.tokenize(u8, output, "\r\n");
212 for (chk.actions.items) |act| {268 for (chk.actions.items) |act| {
213 switch (act) {269 switch (act.tag) {
214 .match => |match_act| {270 .match => {
215 while (it.next()) |line| {271 while (it.next()) |line| {
216 if (try match_act.match(line, &vars)) break;272 if (try act.match(line, &vars)) break;
217 } else {273 } else {
218 std.debug.print(274 std.debug.print(
219 \\275 \\
...@@ -222,51 +278,33 @@ fn make(step: *Step) !void {...@@ -222,51 +278,33 @@ fn make(step: *Step) !void {
222 \\========= But parsed file does not contain it: =======278 \\========= But parsed file does not contain it: =======
223 \\{s}279 \\{s}
224 \\280 \\
225 , .{ match_act.needle, output });281 , .{ act.phrase, output });
226 return error.TestFailed;282 return error.TestFailed;
227 }283 }
228 },284 },
229 .compute_eq => |c_eq| {285 .compute_cmp => {
230 var values = std.ArrayList(u64).init(gpa);286 const res = act.computeCmp(gpa, vars) catch |err| switch (err) {
231 try values.ensureTotalCapacity(c_eq.var_stack.items.len);287 error.UnknownVariable => {
232 for (c_eq.var_stack.items) |vv| {
233 const val = vars.get(vv) orelse {
234 std.debug.print(288 std.debug.print(
235 \\
236 \\========= Variable was not extracted: ===========
237 \\{s}
238 \\========= From parsed file: =====================289 \\========= From parsed file: =====================
239 \\{s}290 \\{s}
240 \\291 \\
241 , .{ vv, output });292 , .{output});
242 return error.TestFailed;293 return error.TestFailed;
243 };294 },
244 values.appendAssumeCapacity(val);295 else => |e| return e,
245 }296 };
246297 if (!res) {
247 var op_i: usize = 1;
248 var reduced: u64 = values.items[0];
249 for (c_eq.op_stack.items) |op| {
250 const other = values.items[op_i];
251 switch (op) {
252 .add => {
253 reduced += other;
254 },
255 }
256 }
257
258 const expected = vars.get(c_eq.expected) orelse {
259 std.debug.print(298 std.debug.print(
260 \\299 \\
261 \\========= Variable was not extracted: ===========300 \\========= Comparison failed for action: ===========
262 \\{s}301 \\{s} {s}
263 \\========= From parsed file: =====================302 \\========= From parsed file: =======================
264 \\{s}303 \\{s}
265 \\304 \\
266 , .{ c_eq.expected, output });305 , .{ act.phrase, act.expected.?, output });
267 return error.TestFailed;306 return error.TestFailed;
268 };307 }
269 try testing.expectEqual(reduced, expected);
270 },308 },
271 }309 }
272 }310 }
...@@ -349,6 +387,23 @@ const MachODumper = struct {...@@ -349,6 +387,23 @@ const MachODumper = struct {
349 seg.fileoff,387 seg.fileoff,
350 seg.filesize,388 seg.filesize,
351 });389 });
390
391 for (lc.segment.sections.items) |sect| {
392 try writer.writeByte('\n');
393 try writer.print(
394 \\sectname {s}
395 \\addr {x}
396 \\size {x}
397 \\offset {x}
398 \\align {x}
399 , .{
400 sect.sectName(),
401 sect.addr,
402 sect.size,
403 sect.offset,
404 sect.@"align",
405 });
406 }
352 },407 },
353408
354 .ID_DYLIB,409 .ID_DYLIB,
src/Compilation.zig+10-2
...@@ -907,6 +907,10 @@ pub const InitOptions = struct {...@@ -907,6 +907,10 @@ pub const InitOptions = struct {
907 pagezero_size: ?u64 = null,907 pagezero_size: ?u64 = null,
908 /// (Darwin) search strategy for system libraries908 /// (Darwin) search strategy for system libraries
909 search_strategy: ?link.File.MachO.SearchStrategy = null,909 search_strategy: ?link.File.MachO.SearchStrategy = null,
910 /// (Darwin) set minimum space for future expansion of the load commands
911 headerpad_size: ?u32 = null,
912 /// (Darwin) set enough space as if all paths were MATPATHLEN
913 headerpad_max_install_names: bool = false,
910};914};
911915
912fn addPackageTableToCacheHash(916fn addPackageTableToCacheHash(
...@@ -1748,6 +1752,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1748,6 +1752,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1748 .entitlements = options.entitlements,1752 .entitlements = options.entitlements,
1749 .pagezero_size = options.pagezero_size,1753 .pagezero_size = options.pagezero_size,
1750 .search_strategy = options.search_strategy,1754 .search_strategy = options.search_strategy,
1755 .headerpad_size = options.headerpad_size,
1756 .headerpad_max_install_names = options.headerpad_max_install_names,
1751 });1757 });
1752 errdefer bin_file.destroy();1758 errdefer bin_file.destroy();
1753 comp.* = .{1759 comp.* = .{
...@@ -2363,7 +2369,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo...@@ -2363,7 +2369,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
2363/// to remind the programmer to update multiple related pieces of code that2369/// to remind the programmer to update multiple related pieces of code that
2364/// are in different locations. Bump this number when adding or deleting2370/// are in different locations. Bump this number when adding or deleting
2365/// anything from the link cache manifest.2371/// anything from the link cache manifest.
2366pub const link_hash_implementation_version = 5;2372pub const link_hash_implementation_version = 6;
23672373
2368fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {2374fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
2369 const gpa = comp.gpa;2375 const gpa = comp.gpa;
...@@ -2373,7 +2379,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -2373,7 +2379,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
2373 defer arena_allocator.deinit();2379 defer arena_allocator.deinit();
2374 const arena = arena_allocator.allocator();2380 const arena = arena_allocator.allocator();
23752381
2376 comptime assert(link_hash_implementation_version == 5);2382 comptime assert(link_hash_implementation_version == 6);
23772383
2378 if (comp.bin_file.options.module) |mod| {2384 if (comp.bin_file.options.module) |mod| {
2379 const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{2385 const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{
...@@ -2480,6 +2486,8 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -2480,6 +2486,8 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
2480 try man.addOptionalFile(comp.bin_file.options.entitlements);2486 try man.addOptionalFile(comp.bin_file.options.entitlements);
2481 man.hash.addOptional(comp.bin_file.options.pagezero_size);2487 man.hash.addOptional(comp.bin_file.options.pagezero_size);
2482 man.hash.addOptional(comp.bin_file.options.search_strategy);2488 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);
24832491
2484 // COFF specific stuff2492 // COFF specific stuff
2485 man.hash.addOptional(comp.bin_file.options.subsystem);2493 man.hash.addOptional(comp.bin_file.options.subsystem);
src/link.zig+6
...@@ -193,6 +193,12 @@ pub const Options = struct {...@@ -193,6 +193,12 @@ pub const Options = struct {
193 /// (Darwin) search strategy for system libraries193 /// (Darwin) search strategy for system libraries
194 search_strategy: ?File.MachO.SearchStrategy = null,194 search_strategy: ?File.MachO.SearchStrategy = null,
195195
196 /// (Darwin) set minimum space for future expansion of the load commands
197 headerpad_size: ?u32 = null,
198
199 /// (Darwin) set enough space as if all paths were MATPATHLEN
200 headerpad_max_install_names: bool = false,
201
196 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {202 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
197 return if (options.use_lld) .Obj else options.output_mode;203 return if (options.use_lld) .Obj else options.output_mode;
198 }204 }
src/link/Coff.zig+1-1
...@@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !...@@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !
969 man = comp.cache_parent.obtain();969 man = comp.cache_parent.obtain();
970 self.base.releaseLock();970 self.base.releaseLock();
971971
972 comptime assert(Compilation.link_hash_implementation_version == 5);972 comptime assert(Compilation.link_hash_implementation_version == 6);
973973
974 for (self.base.options.objects) |obj| {974 for (self.base.options.objects) |obj| {
975 _ = try man.addFile(obj.path, null);975 _ = 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...@@ -1298,7 +1298,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
1298 // We are about to obtain this lock, so here we give other processes a chance first.1298 // We are about to obtain this lock, so here we give other processes a chance first.
1299 self.base.releaseLock();1299 self.base.releaseLock();
13001300
1301 comptime assert(Compilation.link_hash_implementation_version == 5);1301 comptime assert(Compilation.link_hash_implementation_version == 6);
13021302
1303 try man.addOptionalFile(self.base.options.linker_script);1303 try man.addOptionalFile(self.base.options.linker_script);
1304 try man.addOptionalFile(self.base.options.version_script);1304 try man.addOptionalFile(self.base.options.version_script);
src/link/MachO.zig+49-11
...@@ -69,11 +69,6 @@ page_size: u16,...@@ -69,11 +69,6 @@ page_size: u16,
69/// and potentially stage2 release builds in the future.69/// and potentially stage2 release builds in the future.
70needs_prealloc: bool = true,70needs_prealloc: bool = true,
7171
72/// We commit 0x1000 = 4096 bytes of space to the header and
73/// the table of load commands. This should be plenty for any
74/// potential future extensions.
75header_pad: u16 = 0x1000,
76
77/// The absolute address of the entry point.72/// The absolute address of the entry point.
78entry_addr: ?u64 = null,73entry_addr: ?u64 = null,
7974
...@@ -295,6 +290,11 @@ pub const min_text_capacity = padToIdeal(minimum_text_block_size);...@@ -295,6 +290,11 @@ pub const min_text_capacity = padToIdeal(minimum_text_block_size);
295/// start of __TEXT segment.290/// start of __TEXT segment.
296const default_pagezero_vmsize: u64 = 0x100000000;291const default_pagezero_vmsize: u64 = 0x100000000;
297292
293/// We commit 0x1000 = 4096 bytes of space to the header and
294/// the table of load commands. This should be plenty for any
295/// potential future extensions.
296const default_headerpad_size: u32 = 0x1000;
297
298pub const Export = struct {298pub const Export = struct {
299 sym_index: ?u32 = null,299 sym_index: ?u32 = null,
300};300};
...@@ -541,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -541,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
541 // We are about to obtain this lock, so here we give other processes a chance first.541 // We are about to obtain this lock, so here we give other processes a chance first.
542 self.base.releaseLock();542 self.base.releaseLock();
543543
544 comptime assert(Compilation.link_hash_implementation_version == 5);544 comptime assert(Compilation.link_hash_implementation_version == 6);
545545
546 for (self.base.options.objects) |obj| {546 for (self.base.options.objects) |obj| {
547 _ = try man.addFile(obj.path, null);547 _ = try man.addFile(obj.path, null);
...@@ -556,6 +556,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -556,6 +556,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
556 man.hash.add(stack_size);556 man.hash.add(stack_size);
557 man.hash.addOptional(self.base.options.pagezero_size);557 man.hash.addOptional(self.base.options.pagezero_size);
558 man.hash.addOptional(self.base.options.search_strategy);558 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);
559 man.hash.addListOfBytes(self.base.options.lib_dirs);561 man.hash.addListOfBytes(self.base.options.lib_dirs);
560 man.hash.addListOfBytes(self.base.options.framework_dirs);562 man.hash.addListOfBytes(self.base.options.framework_dirs);
561 man.hash.addListOfBytes(self.base.options.frameworks);563 man.hash.addListOfBytes(self.base.options.frameworks);
...@@ -976,6 +978,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No...@@ -976,6 +978,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
976 .dylibs_first => try argv.append("-search_dylibs_first"),978 .dylibs_first => try argv.append("-search_dylibs_first"),
977 };979 };
978980
981 if (self.base.options.headerpad_size) |headerpad_size| {
982 try argv.append("-headerpad_size");
983 try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{headerpad_size}));
984 }
985
986 if (self.base.options.headerpad_max_install_names) {
987 try argv.append("-headerpad_max_install_names");
988 }
989
979 if (self.base.options.entry) |entry| {990 if (self.base.options.entry) |entry| {
980 try argv.append("-e");991 try argv.append("-e");
981 try argv.append(entry);992 try argv.append(entry);
...@@ -4451,9 +4462,10 @@ fn populateMissingMetadata(self: *MachO) !void {...@@ -4451,9 +4462,10 @@ fn populateMissingMetadata(self: *MachO) !void {
4451 if (self.text_segment_cmd_index == null) {4462 if (self.text_segment_cmd_index == null) {
4452 self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);4463 self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
4453 const needed_size = if (self.needs_prealloc) blk: {4464 const needed_size = if (self.needs_prealloc) blk: {
4465 const headerpad_size = @maximum(self.base.options.headerpad_size orelse 0, default_headerpad_size);
4454 const program_code_size_hint = self.base.options.program_code_size_hint;4466 const program_code_size_hint = self.base.options.program_code_size_hint;
4455 const got_size_hint = @sizeOf(u64) * self.base.options.symbol_count_hint;4467 const got_size_hint = @sizeOf(u64) * self.base.options.symbol_count_hint;
4456 const ideal_size = self.header_pad + program_code_size_hint + got_size_hint;4468 const ideal_size = headerpad_size + program_code_size_hint + got_size_hint;
4457 const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), self.page_size);4469 const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), self.page_size);
4458 log.debug("found __TEXT segment free space 0x{x} to 0x{x}", .{ 0, needed_size });4470 log.debug("found __TEXT segment free space 0x{x} to 0x{x}", .{ 0, needed_size });
4459 break :blk needed_size;4471 break :blk needed_size;
...@@ -4956,12 +4968,35 @@ fn allocateTextSegment(self: *MachO) !void {...@@ -4956,12 +4968,35 @@ fn allocateTextSegment(self: *MachO) !void {
4956 seg.inner.fileoff = 0;4968 seg.inner.fileoff = 0;
4957 seg.inner.vmaddr = base_vmaddr;4969 seg.inner.vmaddr = base_vmaddr;
49584970
4959 var sizeofcmds: u64 = 0;4971 var sizeofcmds: u32 = 0;
4960 for (self.load_commands.items) |lc| {4972 for (self.load_commands.items) |lc| {
4961 sizeofcmds += lc.cmdsize();4973 sizeofcmds += lc.cmdsize();
4962 }4974 }
49634975
4964 try self.allocateSegment(self.text_segment_cmd_index.?, @sizeOf(macho.mach_header_64) + sizeofcmds);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});
4999 try self.allocateSegment(self.text_segment_cmd_index.?, offset);
49655000
4966 // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments.5001 // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments.
4967 var min_alignment: u32 = 0;5002 var min_alignment: u32 = 0;
...@@ -5088,7 +5123,10 @@ fn initSection(...@@ -5088,7 +5123,10 @@ fn initSection(
50885123
5089 if (self.needs_prealloc) {5124 if (self.needs_prealloc) {
5090 const alignment_pow_2 = try math.powi(u32, 2, alignment);5125 const alignment_pow_2 = try math.powi(u32, 2, alignment);
5091 const padding: ?u64 = if (segment_id == self.text_segment_cmd_index.?) self.header_pad 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;
5092 const off = self.findFreeSpace(segment_id, alignment_pow_2, padding);5130 const off = self.findFreeSpace(segment_id, alignment_pow_2, padding);
5093 log.debug("allocating {s},{s} section from 0x{x} to 0x{x}", .{5131 log.debug("allocating {s},{s} section from 0x{x} to 0x{x}", .{
5094 sect.segName(),5132 sect.segName(),
...@@ -5127,7 +5165,7 @@ fn initSection(...@@ -5127,7 +5165,7 @@ fn initSection(
5127 return index;5165 return index;
5128}5166}
51295167
5130fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u64) u64 {5168fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u32) u64 {
5131 const seg = self.load_commands.items[segment_id].segment;5169 const seg = self.load_commands.items[segment_id].segment;
5132 if (seg.sections.items.len == 0) {5170 if (seg.sections.items.len == 0) {
5133 return if (start) |v| v else seg.inner.fileoff;5171 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) !...@@ -2481,7 +2481,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
2481 // We are about to obtain this lock, so here we give other processes a chance first.2481 // We are about to obtain this lock, so here we give other processes a chance first.
2482 self.base.releaseLock();2482 self.base.releaseLock();
24832483
2484 comptime assert(Compilation.link_hash_implementation_version == 5);2484 comptime assert(Compilation.link_hash_implementation_version == 6);
24852485
2486 for (self.base.options.objects) |obj| {2486 for (self.base.options.objects) |obj| {
2487 _ = try man.addFile(obj.path, null);2487 _ = try man.addFile(obj.path, null);
src/main.zig+26
...@@ -450,6 +450,8 @@ const usage_build_generic =...@@ -450,6 +450,8 @@ const usage_build_generic =
450 \\ -pagezero_size [value] (Darwin) size of the __PAGEZERO segment in hexadecimal notation450 \\ -pagezero_size [value] (Darwin) size of the __PAGEZERO segment in hexadecimal notation
451 \\ -search_paths_first (Darwin) search each dir in library search paths for `libx.dylib` then `libx.a`451 \\ -search_paths_first (Darwin) search each dir in library search paths for `libx.dylib` then `libx.a`
452 \\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a`452 \\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a`
453 \\ -headerpad [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation
454 \\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN
453 \\ --import-memory (WebAssembly) import memory from the environment455 \\ --import-memory (WebAssembly) import memory from the environment
454 \\ --import-table (WebAssembly) import function table from the host environment456 \\ --import-table (WebAssembly) import function table from the host environment
455 \\ --export-table (WebAssembly) export function table to the host environment457 \\ --export-table (WebAssembly) export function table to the host environment
...@@ -699,6 +701,8 @@ fn buildOutputType(...@@ -699,6 +701,8 @@ fn buildOutputType(
699 var entitlements: ?[]const u8 = null;701 var entitlements: ?[]const u8 = null;
700 var pagezero_size: ?u64 = null;702 var pagezero_size: ?u64 = null;
701 var search_strategy: ?link.File.MachO.SearchStrategy = null;703 var search_strategy: ?link.File.MachO.SearchStrategy = null;
704 var headerpad_size: ?u32 = null;
705 var headerpad_max_install_names: bool = false;
702706
703 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.707 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
704 // This array is populated by zig cc frontend and then has to be converted to zig-style708 // This array is populated by zig cc frontend and then has to be converted to zig-style
...@@ -924,6 +928,15 @@ fn buildOutputType(...@@ -924,6 +928,15 @@ fn buildOutputType(
924 search_strategy = .paths_first;928 search_strategy = .paths_first;
925 } else if (mem.eql(u8, arg, "-search_dylibs_first")) {929 } else if (mem.eql(u8, arg, "-search_dylibs_first")) {
926 search_strategy = .dylibs_first;930 search_strategy = .dylibs_first;
931 } else if (mem.eql(u8, arg, "-headerpad")) {
932 const next_arg = args_iter.next() orelse {
933 fatal("expected parameter after {s}", .{arg});
934 };
935 headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| {
936 fatal("unable to parser '{s}': {s}", .{ arg, @errorName(err) });
937 };
938 } else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
939 headerpad_max_install_names = true;
927 } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {940 } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
928 linker_script = args_iter.next() orelse {941 linker_script = args_iter.next() orelse {
929 fatal("expected parameter after {s}", .{arg});942 fatal("expected parameter after {s}", .{arg});
...@@ -1676,6 +1689,17 @@ fn buildOutputType(...@@ -1676,6 +1689,17 @@ fn buildOutputType(
1676 pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {1689 pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
1677 fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });1690 fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });
1678 };1691 };
1692 } else if (mem.eql(u8, arg, "-headerpad")) {
1693 i += 1;
1694 if (i >= linker_args.items.len) {
1695 fatal("expected linker arg after '{s}'", .{arg});
1696 }
1697 const next_arg = linker_args.items[i];
1698 headerpad_size = std.fmt.parseUnsigned(u32, eatIntPrefix(next_arg, 16), 16) catch |err| {
1699 fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });
1700 };
1701 } else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
1702 headerpad_max_install_names = true;
1679 } else if (mem.eql(u8, arg, "--gc-sections")) {1703 } else if (mem.eql(u8, arg, "--gc-sections")) {
1680 linker_gc_sections = true;1704 linker_gc_sections = true;
1681 } else if (mem.eql(u8, arg, "--no-gc-sections")) {1705 } else if (mem.eql(u8, arg, "--no-gc-sections")) {
...@@ -2795,6 +2819,8 @@ fn buildOutputType(...@@ -2795,6 +2819,8 @@ fn buildOutputType(
2795 .entitlements = entitlements,2819 .entitlements = entitlements,
2796 .pagezero_size = pagezero_size,2820 .pagezero_size = pagezero_size,
2797 .search_strategy = search_strategy,2821 .search_strategy = search_strategy,
2822 .headerpad_size = headerpad_size,
2823 .headerpad_max_install_names = headerpad_max_install_names,
2798 }) catch |err| switch (err) {2824 }) catch |err| switch (err) {
2799 error.LibCUnavailable => {2825 error.LibCUnavailable => {
2800 const target = target_info.target;2826 const target = target_info.target;
test/link.zig+5
...@@ -64,5 +64,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void {...@@ -64,5 +64,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
64 cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{64 cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{
65 .build_modes = true,65 .build_modes = true,
66 });66 });
67
68 cases.addBuildFile("test/link/macho/headerpad/build.zig", .{
69 .build_modes = true,
70 .requires_macos_sdk = true,
71 });
67 }72 }
68}73}
test/link/macho/entry/build.zig+1-1
...@@ -24,7 +24,7 @@ pub fn build(b: *Builder) void {...@@ -24,7 +24,7 @@ pub fn build(b: *Builder) void {
24 check_exe.checkInSymtab();24 check_exe.checkInSymtab();
25 check_exe.checkNext("_non_main {n_value}");25 check_exe.checkNext("_non_main {n_value}");
2626
27 check_exe.checkComputeEq("vmaddr entryoff +", "n_value");27 check_exe.checkComputeCompare("vmaddr entryoff +", .{ .op = .eq, .value = .{ .variable = "n_value" } });
2828
29 test_step.dependOn(&check_exe.step);29 test_step.dependOn(&check_exe.step);
3030
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}