| 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; |
| 3 | const log = std.log.scoped(.link); |
| 4 | const macho = std.macho; |
| 5 | const mem = std.mem; |
| 6 | const Writer = std.Io.Writer; |
| 7 | const Allocator = std.mem.Allocator; |
| 8 | |
| 9 | const DebugSymbols = @import("DebugSymbols.zig"); |
| 10 | const Dylib = @import("Dylib.zig"); |
| 11 | const MachO = @import("../MachO.zig"); |
| 12 | |
| 13 | pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; |
| 14 | |
| 15 | fn 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 | |
| 21 | pub 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 | |
| 120 | pub 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 | |
| 141 | pub 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 | |
| 170 | pub 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 | |
| 195 | pub 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 | |
| 214 | const 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 | |
| 222 | pub 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 | |
| 247 | pub 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 | |
| 273 | pub 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 | |
| 280 | pub 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 | |
| 299 | pub 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 | |
| 317 | pub 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 | } |