master
  1const std = @import("std");
  2const assert = std.debug.assert;
  3const log = std.log.scoped(.link);
  4const macho = std.macho;
  5const mem = std.mem;
  6const Writer = std.Io.Writer;
  7const Allocator = std.mem.Allocator;
  8
  9const DebugSymbols = @import("DebugSymbols.zig");
 10const Dylib = @import("Dylib.zig");
 11const MachO = @import("../MachO.zig");
 12
 13pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld";
 14
 15fn calcInstallNameLen(cmd_size: u64, name: []const u8, assume_max_path_len: bool) u64 {
 16    const darwin_path_max = 1024;
 17    const name_len = if (assume_max_path_len) darwin_path_max else name.len + 1;
 18    return mem.alignForward(u64, cmd_size + name_len, @alignOf(u64));
 19}
 20
 21pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) !u32 {
 22    const comp = macho_file.base.comp;
 23    const gpa = comp.gpa;
 24
 25    var sizeofcmds: u64 = 0;
 26
 27    // LC_SEGMENT_64
 28    sizeofcmds += @sizeOf(macho.segment_command_64) * macho_file.segments.items.len;
 29    for (macho_file.segments.items) |seg| {
 30        sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
 31    }
 32
 33    // LC_DYLD_INFO_ONLY
 34    sizeofcmds += @sizeOf(macho.dyld_info_command);
 35    // LC_FUNCTION_STARTS
 36    sizeofcmds += @sizeOf(macho.linkedit_data_command);
 37    // LC_DATA_IN_CODE
 38    sizeofcmds += @sizeOf(macho.linkedit_data_command);
 39    // LC_SYMTAB
 40    sizeofcmds += @sizeOf(macho.symtab_command);
 41    // LC_DYSYMTAB
 42    sizeofcmds += @sizeOf(macho.dysymtab_command);
 43    // LC_LOAD_DYLINKER
 44    sizeofcmds += calcInstallNameLen(
 45        @sizeOf(macho.dylinker_command),
 46        mem.sliceTo(default_dyld_path, 0),
 47        false,
 48    );
 49    // LC_MAIN
 50    if (!macho_file.base.isDynLib()) {
 51        sizeofcmds += @sizeOf(macho.entry_point_command);
 52    }
 53    // LC_ID_DYLIB
 54    if (macho_file.base.isDynLib()) {
 55        const emit = macho_file.base.emit;
 56        const install_name = macho_file.install_name orelse
 57            try emit.root_dir.join(gpa, &.{emit.sub_path});
 58        defer if (macho_file.install_name == null) gpa.free(install_name);
 59        sizeofcmds += calcInstallNameLen(
 60            @sizeOf(macho.dylib_command),
 61            install_name,
 62            assume_max_path_len,
 63        );
 64    }
 65    // LC_RPATH
 66    {
 67        for (macho_file.rpath_list) |rpath| {
 68            sizeofcmds += calcInstallNameLen(
 69                @sizeOf(macho.rpath_command),
 70                rpath,
 71                assume_max_path_len,
 72            );
 73        }
 74
 75        if (comp.config.any_sanitize_thread) {
 76            const path = try comp.tsan_lib.?.full_object_path.toString(gpa);
 77            defer gpa.free(path);
 78            const rpath = std.fs.path.dirname(path) orelse ".";
 79            sizeofcmds += calcInstallNameLen(
 80                @sizeOf(macho.rpath_command),
 81                rpath,
 82                assume_max_path_len,
 83            );
 84        }
 85    }
 86    // LC_SOURCE_VERSION
 87    sizeofcmds += @sizeOf(macho.source_version_command);
 88    if (macho_file.platform.isBuildVersionCompatible()) {
 89        // LC_BUILD_VERSION
 90        sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
 91    } else {
 92        // LC_VERSION_MIN_*
 93        sizeofcmds += @sizeOf(macho.version_min_command);
 94    }
 95    // LC_UUID
 96    sizeofcmds += @sizeOf(macho.uuid_command);
 97    // LC_LOAD_DYLIB
 98    for (macho_file.dylibs.items) |index| {
 99        const dylib = macho_file.getFile(index).?.dylib;
100        assert(dylib.isAlive(macho_file));
101        const dylib_id = dylib.id.?;
102        sizeofcmds += calcInstallNameLen(
103            @sizeOf(macho.dylib_command),
104            dylib_id.name,
105            assume_max_path_len,
106        );
107    }
108    // LC_CODE_SIGNATURE
109    if (macho_file.requiresCodeSig()) {
110        sizeofcmds += @sizeOf(macho.linkedit_data_command);
111    }
112
113    return @as(u32, @intCast(sizeofcmds));
114}
115
116pub fn calcLoadCommandsSizeDsym(macho_file: *MachO, dsym: *const DebugSymbols) u32 {
117    var sizeofcmds: u64 = 0;
118
119    // LC_SEGMENT_64
120    sizeofcmds += @sizeOf(macho.segment_command_64) * (macho_file.segments.items.len - 1);
121    for (macho_file.segments.items) |seg| {
122        sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
123    }
124    sizeofcmds += @sizeOf(macho.segment_command_64) * dsym.segments.items.len;
125    for (dsym.segments.items) |seg| {
126        sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
127    }
128
129    // LC_SYMTAB
130    sizeofcmds += @sizeOf(macho.symtab_command);
131    // LC_UUID
132    sizeofcmds += @sizeOf(macho.uuid_command);
133
134    return @as(u32, @intCast(sizeofcmds));
135}
136
137pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {
138    var sizeofcmds: u64 = 0;
139
140    // LC_SEGMENT_64
141    {
142        assert(macho_file.segments.items.len == 1);
143        sizeofcmds += @sizeOf(macho.segment_command_64);
144        const seg = macho_file.segments.items[0];
145        sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
146    }
147
148    // LC_DATA_IN_CODE
149    sizeofcmds += @sizeOf(macho.linkedit_data_command);
150    // LC_SYMTAB
151    sizeofcmds += @sizeOf(macho.symtab_command);
152    // LC_DYSYMTAB
153    sizeofcmds += @sizeOf(macho.dysymtab_command);
154
155    if (macho_file.platform.isBuildVersionCompatible()) {
156        // LC_BUILD_VERSION
157        sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
158    } else {
159        // LC_VERSION_MIN_*
160        sizeofcmds += @sizeOf(macho.version_min_command);
161    }
162
163    return @as(u32, @intCast(sizeofcmds));
164}
165
166pub fn calcMinHeaderPadSize(macho_file: *MachO) !u32 {
167    var padding: u32 = (try calcLoadCommandsSize(macho_file, false)) + (macho_file.headerpad_size orelse 0);
168    log.debug("minimum requested headerpad size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
169
170    if (macho_file.headerpad_max_install_names) {
171        const min_headerpad_size: u32 = try calcLoadCommandsSize(macho_file, true);
172        log.debug("headerpad_max_install_names minimum headerpad size 0x{x}", .{
173            min_headerpad_size + @sizeOf(macho.mach_header_64),
174        });
175        padding = @max(padding, min_headerpad_size);
176    }
177
178    const offset = @sizeOf(macho.mach_header_64) + padding;
179    log.debug("actual headerpad size 0x{x}", .{offset});
180
181    return offset;
182}
183
184pub fn writeDylinkerLC(writer: *Writer) !void {
185    const name_len = mem.sliceTo(default_dyld_path, 0).len;
186    const cmdsize = @as(u32, @intCast(mem.alignForward(
187        u64,
188        @sizeOf(macho.dylinker_command) + name_len,
189        @sizeOf(u64),
190    )));
191    try writer.writeStruct(@as(macho.dylinker_command, .{
192        .cmd = .LOAD_DYLINKER,
193        .cmdsize = cmdsize,
194        .name = @sizeOf(macho.dylinker_command),
195    }), .little);
196    try writer.writeAll(mem.sliceTo(default_dyld_path, 0));
197    const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
198    if (padding > 0) {
199        try writer.splatByteAll(0, padding);
200    }
201}
202
203const WriteDylibLCCtx = struct {
204    cmd: macho.LC,
205    name: []const u8,
206    timestamp: u32 = 2,
207    current_version: u32 = 0x10000,
208    compatibility_version: u32 = 0x10000,
209};
210
211pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: *Writer) !void {
212    const name_len = ctx.name.len + 1;
213    const cmdsize = @as(u32, @intCast(mem.alignForward(
214        u64,
215        @sizeOf(macho.dylib_command) + name_len,
216        @sizeOf(u64),
217    )));
218    try writer.writeStruct(@as(macho.dylib_command, .{
219        .cmd = ctx.cmd,
220        .cmdsize = cmdsize,
221        .dylib = .{
222            .name = @sizeOf(macho.dylib_command),
223            .timestamp = ctx.timestamp,
224            .current_version = ctx.current_version,
225            .compatibility_version = ctx.compatibility_version,
226        },
227    }), .little);
228    try writer.writeAll(ctx.name);
229    try writer.writeByte(0);
230    const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
231    if (padding > 0) {
232        try writer.splatByteAll(0, padding);
233    }
234}
235
236pub fn writeDylibIdLC(macho_file: *MachO, writer: *Writer) !void {
237    const comp = macho_file.base.comp;
238    const gpa = comp.gpa;
239    assert(comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
240    const emit = macho_file.base.emit;
241    const install_name = macho_file.install_name orelse
242        try emit.root_dir.join(gpa, &.{emit.sub_path});
243    defer if (macho_file.install_name == null) gpa.free(install_name);
244    const curr = comp.version orelse std.SemanticVersion{
245        .major = 1,
246        .minor = 0,
247        .patch = 0,
248    };
249    const compat = macho_file.compatibility_version orelse std.SemanticVersion{
250        .major = 1,
251        .minor = 0,
252        .patch = 0,
253    };
254    try writeDylibLC(.{
255        .cmd = .ID_DYLIB,
256        .name = install_name,
257        .current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)),
258        .compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)),
259    }, writer);
260}
261
262pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void {
263    const rpath_len = rpath.len + 1;
264    const cmdsize = @as(u32, @intCast(mem.alignForward(
265        u64,
266        @sizeOf(macho.rpath_command) + rpath_len,
267        @sizeOf(u64),
268    )));
269    try writer.writeStruct(@as(macho.rpath_command, .{
270        .cmdsize = cmdsize,
271        .path = @sizeOf(macho.rpath_command),
272    }), .little);
273    try writer.writeAll(rpath);
274    try writer.writeByte(0);
275    const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
276    if (padding > 0) {
277        try writer.splatByteAll(0, padding);
278    }
279}
280
281pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: *Writer) !void {
282    const cmd: macho.LC = switch (platform.os_tag) {
283        .macos => .VERSION_MIN_MACOSX,
284        .ios, .maccatalyst => .VERSION_MIN_IPHONEOS,
285        .tvos => .VERSION_MIN_TVOS,
286        .watchos => .VERSION_MIN_WATCHOS,
287        else => unreachable,
288    };
289    try writer.writeAll(mem.asBytes(&macho.version_min_command{
290        .cmd = cmd,
291        .version = platform.toAppleVersion(),
292        .sdk = if (sdk_version) |ver|
293            MachO.semanticVersionToAppleVersion(ver)
294        else
295            platform.toAppleVersion(),
296    }));
297}
298
299pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: *Writer) !void {
300    const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
301    try writer.writeStruct(@as(macho.build_version_command, .{
302        .cmdsize = cmdsize,
303        .platform = platform.toApplePlatform(),
304        .minos = platform.toAppleVersion(),
305        .sdk = if (sdk_version) |ver|
306            MachO.semanticVersionToAppleVersion(ver)
307        else
308            platform.toAppleVersion(),
309        .ntools = 1,
310    }), .little);
311    try writer.writeAll(mem.asBytes(&macho.build_tool_version{
312        .tool = .ZIG,
313        .version = 0x0,
314    }));
315}