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}