Commit 589bf67635

Jakub Konka <kubkon@jakubkonka.com>
2022-06-25 10:39:53
macho: implement -headerpad_max_install_names
1 parent a6fbdfa
Changed files (12)
lib/std/build.zig
@@ -1595,7 +1595,7 @@ pub const LibExeObjStep = struct {
 
     /// (Darwin) Set size of the padding between the end of load commands
     /// and start of `__TEXT,__text` section.
-    headerpad_size: ?u64 = null,
+    headerpad_size: ?u32 = 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.
@@ -2671,7 +2671,7 @@ pub const LibExeObjStep = struct {
         };
         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 });
+            try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size });
         }
         if (self.headerpad_max_install_names) {
             try zig_args.append("-headerpad_max_install_names");
src/link/Coff.zig
@@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !
         man = comp.cache_parent.obtain();
         self.base.releaseLock();
 
-        comptime assert(Compilation.link_hash_implementation_version == 5);
+        comptime assert(Compilation.link_hash_implementation_version == 6);
 
         for (self.base.options.objects) |obj| {
             _ = try man.addFile(obj.path, null);
src/link/Elf.zig
@@ -1298,7 +1298,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
         // We are about to obtain this lock, so here we give other processes a chance first.
         self.base.releaseLock();
 
-        comptime assert(Compilation.link_hash_implementation_version == 5);
+        comptime assert(Compilation.link_hash_implementation_version == 6);
 
         try man.addOptionalFile(self.base.options.linker_script);
         try man.addOptionalFile(self.base.options.version_script);
src/link/MachO.zig
@@ -69,9 +69,6 @@ page_size: u16,
 /// and potentially stage2 release builds in the future.
 needs_prealloc: bool = true,
 
-/// 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,
 
@@ -296,7 +293,7 @@ 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;
+const default_headerpad_size: u32 = 0x1000;
 
 pub const Export = struct {
     sym_index: ?u32 = null,
@@ -403,12 +400,6 @@ 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);
@@ -421,7 +412,6 @@ 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,
     };
@@ -551,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
         // We are about to obtain this lock, so here we give other processes a chance first.
         self.base.releaseLock();
 
-        comptime assert(Compilation.link_hash_implementation_version == 5);
+        comptime assert(Compilation.link_hash_implementation_version == 6);
 
         for (self.base.options.objects) |obj| {
             _ = try man.addFile(obj.path, null);
@@ -566,6 +556,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
         man.hash.add(stack_size);
         man.hash.addOptional(self.base.options.pagezero_size);
         man.hash.addOptional(self.base.options.search_strategy);
+        man.hash.addOptional(self.base.options.headerpad_size);
+        man.hash.add(self.base.options.headerpad_max_install_names);
         man.hash.addListOfBytes(self.base.options.lib_dirs);
         man.hash.addListOfBytes(self.base.options.framework_dirs);
         man.hash.addListOfBytes(self.base.options.frameworks);
@@ -4470,9 +4462,10 @@ fn populateMissingMetadata(self: *MachO) !void {
     if (self.text_segment_cmd_index == null) {
         self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
         const needed_size = if (self.needs_prealloc) blk: {
+            const headerpad_size = @maximum(self.base.options.headerpad_size orelse 0, default_headerpad_size);
             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.headerpad_size + program_code_size_hint + got_size_hint;
+            const ideal_size = 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;
@@ -4975,13 +4968,34 @@ fn allocateTextSegment(self: *MachO) !void {
     seg.inner.fileoff = 0;
     seg.inner.vmaddr = base_vmaddr;
 
-    var sizeofcmds: u64 = 0;
+    var sizeofcmds: u32 = 0;
     for (self.load_commands.items) |lc| {
         sizeofcmds += lc.cmdsize();
     }
 
-    // TODO verify if `headerpad_max_install_names` leads to larger padding size
-    const offset = @sizeOf(macho.mach_header_64) + sizeofcmds + self.headerpad_size;
+    var padding: u32 = sizeofcmds + (self.base.options.headerpad_size orelse 0);
+    log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
+
+    if (self.base.options.headerpad_max_install_names) {
+        var min_headerpad_size: u32 = 0;
+        for (self.load_commands.items) |lc| switch (lc.cmd()) {
+            .ID_DYLIB,
+            .LOAD_WEAK_DYLIB,
+            .LOAD_DYLIB,
+            .REEXPORT_DYLIB,
+            => {
+                min_headerpad_size += @sizeOf(macho.dylib_command) + std.os.PATH_MAX + 1;
+            },
+
+            else => {},
+        };
+        log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
+            min_headerpad_size + @sizeOf(macho.mach_header_64),
+        });
+        padding = @maximum(padding, min_headerpad_size);
+    }
+    const offset = @sizeOf(macho.mach_header_64) + padding;
+    log.debug("actual headerpad size 0x{x}", .{offset});
     try self.allocateSegment(self.text_segment_cmd_index.?, offset);
 
     // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments.
@@ -5109,7 +5123,10 @@ 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.headerpad_size else null;
+        const padding: ?u32 = if (segment_id == self.text_segment_cmd_index.?)
+            @maximum(self.base.options.headerpad_size orelse 0, default_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(),
@@ -5148,7 +5165,7 @@ fn initSection(
     return index;
 }
 
-fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u64) u64 {
+fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u32) u64 {
     const seg = self.load_commands.items[segment_id].segment;
     if (seg.sections.items.len == 0) {
         return if (start) |v| v else seg.inner.fileoff;
src/link/Wasm.zig
@@ -2481,7 +2481,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
         // We are about to obtain this lock, so here we give other processes a chance first.
         self.base.releaseLock();
 
-        comptime assert(Compilation.link_hash_implementation_version == 5);
+        comptime assert(Compilation.link_hash_implementation_version == 6);
 
         for (self.base.options.objects) |obj| {
             _ = try man.addFile(obj.path, null);
src/Compilation.zig
@@ -908,7 +908,7 @@ pub const InitOptions = struct {
     /// (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,
+    headerpad_size: ?u32 = null,
     /// (Darwin) set enough space as if all paths were MATPATHLEN
     headerpad_max_install_names: bool = false,
 };
@@ -2369,7 +2369,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
 /// to remind the programmer to update multiple related pieces of code that
 /// are in different locations. Bump this number when adding or deleting
 /// anything from the link cache manifest.
-pub const link_hash_implementation_version = 5;
+pub const link_hash_implementation_version = 6;
 
 fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
     const gpa = comp.gpa;
@@ -2379,7 +2379,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
     defer arena_allocator.deinit();
     const arena = arena_allocator.allocator();
 
-    comptime assert(link_hash_implementation_version == 5);
+    comptime assert(link_hash_implementation_version == 6);
 
     if (comp.bin_file.options.module) |mod| {
         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
     try man.addOptionalFile(comp.bin_file.options.entitlements);
     man.hash.addOptional(comp.bin_file.options.pagezero_size);
     man.hash.addOptional(comp.bin_file.options.search_strategy);
+    man.hash.addOptional(comp.bin_file.options.headerpad_size);
+    man.hash.add(comp.bin_file.options.headerpad_max_install_names);
 
     // COFF specific stuff
     man.hash.addOptional(comp.bin_file.options.subsystem);
src/link.zig
@@ -194,7 +194,7 @@ pub const Options = struct {
     search_strategy: ?File.MachO.SearchStrategy = null,
 
     /// (Darwin) set minimum space for future expansion of the load commands
-    headerpad_size: ?u64 = null,
+    headerpad_size: ?u32 = null,
 
     /// (Darwin) set enough space as if all paths were MATPATHLEN
     headerpad_max_install_names: bool = false,
src/main.zig
@@ -450,7 +450,7 @@ 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 [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
@@ -701,7 +701,7 @@ 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_size: ?u32 = null;
     var headerpad_max_install_names: bool = false;
 
     // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
@@ -928,11 +928,11 @@ 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")) {
+                    } else if (mem.eql(u8, arg, "-headerpad")) {
                         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| {
+                        headerpad_size = std.fmt.parseUnsigned(u32, 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")) {
@@ -1689,13 +1689,13 @@ 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")) {
+                } else if (mem.eql(u8, arg, "-headerpad")) {
                     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| {
+                    headerpad_size = std.fmt.parseUnsigned(u32, 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")) {
test/link/macho/headerpad/build.zig
@@ -0,0 +1,120 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const Builder = std.build.Builder;
+const LibExeObjectStep = std.build.LibExeObjStep;
+
+pub fn build(b: *Builder) void {
+    const mode = b.standardReleaseOptions();
+
+    const test_step = b.step("test", "Test");
+    test_step.dependOn(b.getInstallStep());
+
+    {
+        // Test -headerpad_max_install_names
+        const exe = simpleExe(b, mode);
+        exe.headerpad_max_install_names = true;
+
+        const check = exe.checkObject(.macho);
+        check.checkStart("sectname __text");
+        check.checkNext("offset {offset}");
+
+        switch (builtin.cpu.arch) {
+            .aarch64 => {
+                check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } });
+            },
+            .x86_64 => {
+                check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } });
+            },
+            else => unreachable,
+        }
+
+        test_step.dependOn(&check.step);
+
+        const run = exe.run();
+        test_step.dependOn(&run.step);
+    }
+
+    {
+        // Test -headerpad
+        const exe = simpleExe(b, mode);
+        exe.headerpad_size = 0x10000;
+
+        const check = exe.checkObject(.macho);
+        check.checkStart("sectname __text");
+        check.checkNext("offset {offset}");
+        check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
+
+        test_step.dependOn(&check.step);
+
+        const run = exe.run();
+        test_step.dependOn(&run.step);
+    }
+
+    {
+        // Test both flags with -headerpad overriding -headerpad_max_install_names
+        const exe = simpleExe(b, mode);
+        exe.headerpad_max_install_names = true;
+        exe.headerpad_size = 0x10000;
+
+        const check = exe.checkObject(.macho);
+        check.checkStart("sectname __text");
+        check.checkNext("offset {offset}");
+        check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
+
+        test_step.dependOn(&check.step);
+
+        const run = exe.run();
+        test_step.dependOn(&run.step);
+    }
+
+    {
+        // Test both flags with -headerpad_max_install_names overriding -headerpad
+        const exe = simpleExe(b, mode);
+        exe.headerpad_size = 0x1000;
+        exe.headerpad_max_install_names = true;
+
+        const check = exe.checkObject(.macho);
+        check.checkStart("sectname __text");
+        check.checkNext("offset {offset}");
+
+        switch (builtin.cpu.arch) {
+            .aarch64 => {
+                check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } });
+            },
+            .x86_64 => {
+                check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } });
+            },
+            else => unreachable,
+        }
+
+        test_step.dependOn(&check.step);
+
+        const run = exe.run();
+        test_step.dependOn(&run.step);
+    }
+}
+
+fn simpleExe(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
+    const exe = b.addExecutable("main", null);
+    exe.setBuildMode(mode);
+    exe.addCSourceFile("main.c", &.{});
+    exe.linkLibC();
+    exe.linkFramework("CoreFoundation");
+    exe.linkFramework("Foundation");
+    exe.linkFramework("Cocoa");
+    exe.linkFramework("CoreGraphics");
+    exe.linkFramework("CoreHaptics");
+    exe.linkFramework("CoreAudio");
+    exe.linkFramework("AVFoundation");
+    exe.linkFramework("CoreImage");
+    exe.linkFramework("CoreLocation");
+    exe.linkFramework("CoreML");
+    exe.linkFramework("CoreVideo");
+    exe.linkFramework("CoreText");
+    exe.linkFramework("CryptoKit");
+    exe.linkFramework("GameKit");
+    exe.linkFramework("SwiftUI");
+    exe.linkFramework("StoreKit");
+    exe.linkFramework("SpriteKit");
+    return exe;
+}
test/link/macho/headerpad_size/main.c → test/link/macho/headerpad/main.c
File renamed without changes
test/link/macho/headerpad_size/build.zig
@@ -1,25 +0,0 @@
-const std = @import("std");
-const Builder = std.build.Builder;
-
-pub fn build(b: *Builder) void {
-    const mode = b.standardReleaseOptions();
-
-    const test_step = b.step("test", "Test");
-    test_step.dependOn(b.getInstallStep());
-
-    const exe = b.addExecutable("main", null);
-    exe.setBuildMode(mode);
-    exe.addCSourceFile("main.c", &.{});
-    exe.linkLibC();
-    exe.headerpad_size = 0x10000;
-
-    const check = exe.checkObject(.macho);
-    check.checkStart("sectname __text");
-    check.checkNext("offset {offset}");
-    check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
-
-    test_step.dependOn(&check.step);
-
-    const run = exe.run();
-    test_step.dependOn(&run.step);
-}
test/link.zig
@@ -64,5 +64,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
         cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{
             .build_modes = true,
         });
+
+        cases.addBuildFile("test/link/macho/headerpad/build.zig", .{
+            .build_modes = true,
+            .requires_macos_sdk = true,
+        });
     }
 }