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_ENCRYPTION_INFO_64
66 if (macho_file.needsEncryptionInfo()) {
67 sizeofcmds += @sizeOf(macho.encryption_info_command_64);
68 }
69 // LC_RPATH
70 {
71 for (macho_file.rpath_list) |rpath| {
72 sizeofcmds += calcInstallNameLen(
73 @sizeOf(macho.rpath_command),
74 rpath,
75 assume_max_path_len,
76 );
77 }
78
79 if (comp.config.any_sanitize_thread) {
80 const path = try comp.tsan_lib.?.full_object_path.toString(gpa);
81 defer gpa.free(path);
82 const rpath = std.fs.path.dirname(path) orelse ".";
83 sizeofcmds += calcInstallNameLen(
84 @sizeOf(macho.rpath_command),
85 rpath,
86 assume_max_path_len,
87 );
88 }
89 }
90 // LC_SOURCE_VERSION
91 sizeofcmds += @sizeOf(macho.source_version_command);
92 if (macho_file.platform.isBuildVersionCompatible()) {
93 // LC_BUILD_VERSION
94 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
95 } else {
96 // LC_VERSION_MIN_*
97 sizeofcmds += @sizeOf(macho.version_min_command);
98 }
99 // LC_UUID
100 sizeofcmds += @sizeOf(macho.uuid_command);
101 // LC_LOAD_DYLIB
102 for (macho_file.dylibs.items) |index| {
103 const dylib = macho_file.getFile(index).?.dylib;
104 assert(dylib.isAlive(macho_file));
105 const dylib_id = dylib.id.?;
106 sizeofcmds += calcInstallNameLen(
107 @sizeOf(macho.dylib_command),
108 dylib_id.name,
109 assume_max_path_len,
110 );
111 }
112 // LC_CODE_SIGNATURE
113 if (macho_file.requiresCodeSig()) {
114 sizeofcmds += @sizeOf(macho.linkedit_data_command);
115 }
116
117 return @as(u32, @intCast(sizeofcmds));
118}
119
120pub fn calcLoadCommandsSizeDsym(macho_file: *MachO, dsym: *const DebugSymbols) u32 {
121 var sizeofcmds: u64 = 0;
122
123 // LC_SEGMENT_64
124 sizeofcmds += @sizeOf(macho.segment_command_64) * (macho_file.segments.items.len - 1);
125 for (macho_file.segments.items) |seg| {
126 sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
127 }
128 sizeofcmds += @sizeOf(macho.segment_command_64) * dsym.segments.items.len;
129 for (dsym.segments.items) |seg| {
130 sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
131 }
132
133 // LC_SYMTAB
134 sizeofcmds += @sizeOf(macho.symtab_command);
135 // LC_UUID
136 sizeofcmds += @sizeOf(macho.uuid_command);
137
138 return @as(u32, @intCast(sizeofcmds));
139}
140
141pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {
142 var sizeofcmds: u64 = 0;
143
144 // LC_SEGMENT_64
145 {
146 assert(macho_file.segments.items.len == 1);
147 sizeofcmds += @sizeOf(macho.segment_command_64);
148 const seg = macho_file.segments.items[0];
149 sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
150 }
151
152 // LC_DATA_IN_CODE
153 sizeofcmds += @sizeOf(macho.linkedit_data_command);
154 // LC_SYMTAB
155 sizeofcmds += @sizeOf(macho.symtab_command);
156 // LC_DYSYMTAB
157 sizeofcmds += @sizeOf(macho.dysymtab_command);
158
159 if (macho_file.platform.isBuildVersionCompatible()) {
160 // LC_BUILD_VERSION
161 sizeofcmds += @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
162 } else {
163 // LC_VERSION_MIN_*
164 sizeofcmds += @sizeOf(macho.version_min_command);
165 }
166
167 return @as(u32, @intCast(sizeofcmds));
168}
169
170pub fn calcMinHeaderSize(macho_file: *MachO) !u32 {
171 var padding: u32 = (try calcLoadCommandsSize(macho_file, false)) +
172 (macho_file.headerpad_size orelse MachO.default_headerpad_size);
173 log.debug("minimum requested header + padding size 0x{x}", .{padding + @sizeOf(macho.mach_header_64)});
174
175 if (macho_file.headerpad_max_install_names) {
176 const min_headerpad_size: u32 = try calcLoadCommandsSize(macho_file, true);
177 log.debug("headerpad_max_install_names minimum header + padding size 0x{x}", .{
178 min_headerpad_size + @sizeOf(macho.mach_header_64),
179 });
180 padding = @max(padding, min_headerpad_size);
181 }
182
183 const offset = @sizeOf(macho.mach_header_64) + padding;
184 log.debug("actual header + padding size 0x{x}", .{offset});
185
186 // Encryption is done at page granularity, so if the output needs a load
187 // command for encryption info, ensure that the header + load commands have
188 // at least one full, unencrypted page.
189 return if (macho_file.needsEncryptionInfo())
190 mem.alignForward(u32, offset, macho_file.getPageSize())
191 else
192 offset;
193}
194
195pub fn writeDylinkerLC(writer: *Writer) !void {
196 const name_len = mem.sliceTo(default_dyld_path, 0).len;
197 const cmdsize = @as(u32, @intCast(mem.alignForward(
198 u64,
199 @sizeOf(macho.dylinker_command) + name_len,
200 @sizeOf(u64),
201 )));
202 try writer.writeStruct(@as(macho.dylinker_command, .{
203 .cmd = .LOAD_DYLINKER,
204 .cmdsize = cmdsize,
205 .name = @sizeOf(macho.dylinker_command),
206 }), .little);
207 try writer.writeAll(mem.sliceTo(default_dyld_path, 0));
208 const padding = cmdsize - @sizeOf(macho.dylinker_command) - name_len;
209 if (padding > 0) {
210 try writer.splatByteAll(0, padding);
211 }
212}
213
214const WriteDylibLCCtx = struct {
215 cmd: macho.LC,
216 name: []const u8,
217 timestamp: u32 = 2,
218 current_version: u32 = 0x10000,
219 compatibility_version: u32 = 0x10000,
220};
221
222pub fn writeDylibLC(ctx: WriteDylibLCCtx, writer: *Writer) !void {
223 const name_len = ctx.name.len + 1;
224 const cmdsize = @as(u32, @intCast(mem.alignForward(
225 u64,
226 @sizeOf(macho.dylib_command) + name_len,
227 @sizeOf(u64),
228 )));
229 try writer.writeStruct(@as(macho.dylib_command, .{
230 .cmd = ctx.cmd,
231 .cmdsize = cmdsize,
232 .dylib = .{
233 .name = @sizeOf(macho.dylib_command),
234 .timestamp = ctx.timestamp,
235 .current_version = ctx.current_version,
236 .compatibility_version = ctx.compatibility_version,
237 },
238 }), .little);
239 try writer.writeAll(ctx.name);
240 try writer.writeByte(0);
241 const padding = cmdsize - @sizeOf(macho.dylib_command) - name_len;
242 if (padding > 0) {
243 try writer.splatByteAll(0, padding);
244 }
245}
246
247pub fn writeDylibIdLC(macho_file: *MachO, writer: *Writer) !void {
248 const comp = macho_file.base.comp;
249 const gpa = comp.gpa;
250 assert(comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
251 const emit = macho_file.base.emit;
252 const install_name = macho_file.install_name orelse
253 try emit.root_dir.join(gpa, &.{emit.sub_path});
254 defer if (macho_file.install_name == null) gpa.free(install_name);
255 const curr = comp.version orelse std.SemanticVersion{
256 .major = 1,
257 .minor = 0,
258 .patch = 0,
259 };
260 const compat = macho_file.compatibility_version orelse std.SemanticVersion{
261 .major = 1,
262 .minor = 0,
263 .patch = 0,
264 };
265 try writeDylibLC(.{
266 .cmd = .ID_DYLIB,
267 .name = install_name,
268 .current_version = @as(u32, @intCast(curr.major << 16 | curr.minor << 8 | curr.patch)),
269 .compatibility_version = @as(u32, @intCast(compat.major << 16 | compat.minor << 8 | compat.patch)),
270 }, writer);
271}
272
273pub fn writeEncryptionInfoLC(macho_file: *MachO, writer: *Writer) !void {
274 try writer.writeAll(mem.asBytes(&macho.encryption_info_command_64{
275 .cryptoff = macho_file.header_size.?,
276 .cryptsize = @as(u32, @intCast(macho_file.getTextSegment().filesize)) - macho_file.header_size.?,
277 }));
278}
279
280pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void {
281 const rpath_len = rpath.len + 1;
282 const cmdsize = @as(u32, @intCast(mem.alignForward(
283 u64,
284 @sizeOf(macho.rpath_command) + rpath_len,
285 @sizeOf(u64),
286 )));
287 try writer.writeStruct(@as(macho.rpath_command, .{
288 .cmdsize = cmdsize,
289 .path = @sizeOf(macho.rpath_command),
290 }), .little);
291 try writer.writeAll(rpath);
292 try writer.writeByte(0);
293 const padding = cmdsize - @sizeOf(macho.rpath_command) - rpath_len;
294 if (padding > 0) {
295 try writer.splatByteAll(0, padding);
296 }
297}
298
299pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: *Writer) !void {
300 const cmd: macho.LC = switch (platform.os_tag) {
301 .macos => .VERSION_MIN_MACOSX,
302 .ios, .maccatalyst => .VERSION_MIN_IPHONEOS,
303 .tvos => .VERSION_MIN_TVOS,
304 .watchos => .VERSION_MIN_WATCHOS,
305 else => unreachable,
306 };
307 try writer.writeAll(mem.asBytes(&macho.version_min_command{
308 .cmd = cmd,
309 .version = platform.toAppleVersion(),
310 .sdk = if (sdk_version) |ver|
311 MachO.semanticVersionToAppleVersion(ver)
312 else
313 platform.toAppleVersion(),
314 }));
315}
316
317pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: *Writer) !void {
318 const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version);
319 try writer.writeStruct(@as(macho.build_version_command, .{
320 .cmdsize = cmdsize,
321 .platform = platform.toApplePlatform(),
322 .minos = platform.toAppleVersion(),
323 .sdk = if (sdk_version) |ver|
324 MachO.semanticVersionToAppleVersion(ver)
325 else
326 platform.toAppleVersion(),
327 .ntools = 1,
328 }), .little);
329 try writer.writeAll(mem.asBytes(&macho.build_tool_version{
330 .tool = .ZIG,
331 .version = 0x0,
332 }));
333}