| 1 | /// Non-zero for fat dylibs |
| 2 | offset: u64, |
| 3 | path: Path, |
| 4 | index: File.Index, |
| 5 | file_handle: File.HandleIndex, |
| 6 | tag: enum { dylib, tbd }, |
| 7 | |
| 8 | exports: std.MultiArrayList(Export) = .{}, |
| 9 | strtab: std.ArrayList(u8) = .empty, |
| 10 | id: ?Id = null, |
| 11 | ordinal: u16 = 0, |
| 12 | |
| 13 | symbols: std.ArrayList(Symbol) = .empty, |
| 14 | symbols_extra: std.ArrayList(u32) = .empty, |
| 15 | globals: std.ArrayList(MachO.SymbolResolver.Index) = .empty, |
| 16 | dependents: std.ArrayList(Id) = .empty, |
| 17 | rpaths: std.array_hash_map.String(void) = .empty, |
| 18 | umbrella: File.Index, |
| 19 | platform: ?MachO.Platform = null, |
| 20 | |
| 21 | needed: bool, |
| 22 | weak: bool, |
| 23 | reexport: bool, |
| 24 | explicit: bool, |
| 25 | hoisted: bool = true, |
| 26 | referenced: bool = false, |
| 27 | |
| 28 | output_symtab_ctx: MachO.SymtabCtx = .{}, |
| 29 | |
| 30 | pub fn deinit(self: *Dylib, allocator: Allocator) void { |
| 31 | allocator.free(self.path.sub_path); |
| 32 | self.exports.deinit(allocator); |
| 33 | self.strtab.deinit(allocator); |
| 34 | if (self.id) |*id| id.deinit(allocator); |
| 35 | self.symbols.deinit(allocator); |
| 36 | self.symbols_extra.deinit(allocator); |
| 37 | self.globals.deinit(allocator); |
| 38 | for (self.dependents.items) |*id| { |
| 39 | id.deinit(allocator); |
| 40 | } |
| 41 | self.dependents.deinit(allocator); |
| 42 | for (self.rpaths.keys()) |rpath| { |
| 43 | allocator.free(rpath); |
| 44 | } |
| 45 | self.rpaths.deinit(allocator); |
| 46 | } |
| 47 | |
| 48 | pub fn parse(self: *Dylib, macho_file: *MachO) !void { |
| 49 | switch (self.tag) { |
| 50 | .tbd => try self.parseTbd(macho_file), |
| 51 | .dylib => try self.parseBinary(macho_file), |
| 52 | } |
| 53 | try self.initSymbols(macho_file); |
| 54 | } |
| 55 | |
| 56 | fn parseBinary(self: *Dylib, macho_file: *MachO) !void { |
| 57 | const tracy = trace(@src()); |
| 58 | defer tracy.end(); |
| 59 | |
| 60 | const comp = macho_file.base.comp; |
| 61 | const io = comp.io; |
| 62 | const gpa = comp.gpa; |
| 63 | const file = macho_file.getFileHandle(self.file_handle); |
| 64 | const offset = self.offset; |
| 65 | |
| 66 | log.debug("parsing dylib from binary: {f}", .{@as(Path, self.path)}); |
| 67 | |
| 68 | var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; |
| 69 | { |
| 70 | const amt = try file.readPositionalAll(io, &header_buffer, offset); |
| 71 | if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput; |
| 72 | } |
| 73 | const header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*; |
| 74 | |
| 75 | const this_cpu_arch: std.Target.Cpu.Arch = switch (header.cputype) { |
| 76 | macho.CPU_TYPE_ARM64 => .aarch64, |
| 77 | macho.CPU_TYPE_X86_64 => .x86_64, |
| 78 | else => |x| { |
| 79 | try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x}); |
| 80 | return error.InvalidMachineType; |
| 81 | }, |
| 82 | }; |
| 83 | if (macho_file.getTarget().cpu.arch != this_cpu_arch) { |
| 84 | try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)}); |
| 85 | return error.InvalidMachineType; |
| 86 | } |
| 87 | |
| 88 | const lc_buffer = try gpa.alloc(u8, header.sizeofcmds); |
| 89 | defer gpa.free(lc_buffer); |
| 90 | { |
| 91 | const amt = try file.readPositionalAll(io, lc_buffer, offset + @sizeOf(macho.mach_header_64)); |
| 92 | if (amt != lc_buffer.len) return error.InputOutput; |
| 93 | } |
| 94 | |
| 95 | var it = LoadCommandIterator.init(&header, lc_buffer) catch |err| std.debug.panic("bad dylib: {t}", .{err}); |
| 96 | while (it.next() catch |err| std.debug.panic("bad dylib: {t}", .{err})) |cmd| switch (cmd.hdr.cmd) { |
| 97 | .ID_DYLIB => { |
| 98 | self.id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName()); |
| 99 | }, |
| 100 | .REEXPORT_DYLIB => if (header.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0) { |
| 101 | const id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName()); |
| 102 | try self.dependents.append(gpa, id); |
| 103 | }, |
| 104 | .DYLD_INFO_ONLY => { |
| 105 | const dyld_cmd = cmd.cast(macho.dyld_info_command).?; |
| 106 | const data = try gpa.alloc(u8, dyld_cmd.export_size); |
| 107 | defer gpa.free(data); |
| 108 | const amt = try file.readPositionalAll(io, data, dyld_cmd.export_off + offset); |
| 109 | if (amt != data.len) return error.InputOutput; |
| 110 | try self.parseTrie(data, macho_file); |
| 111 | }, |
| 112 | .DYLD_EXPORTS_TRIE => { |
| 113 | const ld_cmd = cmd.cast(macho.linkedit_data_command).?; |
| 114 | const data = try gpa.alloc(u8, ld_cmd.datasize); |
| 115 | defer gpa.free(data); |
| 116 | const amt = try file.readPositionalAll(io, data, ld_cmd.dataoff + offset); |
| 117 | if (amt != data.len) return error.InputOutput; |
| 118 | try self.parseTrie(data, macho_file); |
| 119 | }, |
| 120 | .RPATH => { |
| 121 | const path = cmd.getRpathPathName(); |
| 122 | try self.rpaths.put(gpa, try gpa.dupe(u8, path), {}); |
| 123 | }, |
| 124 | .BUILD_VERSION, |
| 125 | .VERSION_MIN_MACOSX, |
| 126 | .VERSION_MIN_IPHONEOS, |
| 127 | .VERSION_MIN_TVOS, |
| 128 | .VERSION_MIN_WATCHOS, |
| 129 | => { |
| 130 | self.platform = MachO.Platform.fromLoadCommand(cmd); |
| 131 | }, |
| 132 | else => {}, |
| 133 | }; |
| 134 | |
| 135 | if (self.id == null) { |
| 136 | try macho_file.reportParseError2(self.index, "missing LC_ID_DYLIB load command", .{}); |
| 137 | return error.MalformedDylib; |
| 138 | } |
| 139 | |
| 140 | if (self.platform) |platform| { |
| 141 | if (!macho_file.platform.eqlTarget(platform)) { |
| 142 | try macho_file.reportParseError2(self.index, "invalid platform: {f}", .{ |
| 143 | platform.fmtTarget(macho_file.getTarget().cpu.arch), |
| 144 | }); |
| 145 | return error.InvalidTarget; |
| 146 | } |
| 147 | // TODO: this can cause the CI to fail so I'm commenting this check out so that |
| 148 | // I can work out the rest of the changes first |
| 149 | // if (macho_file.platform.version.order(platform.version) == .lt) { |
| 150 | // try macho_file.reportParseError2(self.index, "object file built for newer platform: {f}: {f} < {f}", .{ |
| 151 | // macho_file.platform.fmtTarget(macho_file.getTarget().cpu.arch), |
| 152 | // macho_file.platform.version, |
| 153 | // platform.version, |
| 154 | // }); |
| 155 | // return error.InvalidTarget; |
| 156 | // } |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | const TrieIterator = struct { |
| 161 | stream: std.Io.Reader, |
| 162 | |
| 163 | fn readUleb128(it: *TrieIterator) !u64 { |
| 164 | return it.stream.takeLeb128(u64); |
| 165 | } |
| 166 | |
| 167 | fn readString(it: *TrieIterator) ![:0]const u8 { |
| 168 | return it.stream.takeSentinel(0); |
| 169 | } |
| 170 | |
| 171 | fn readByte(it: *TrieIterator) !u8 { |
| 172 | return it.stream.takeByte(); |
| 173 | } |
| 174 | }; |
| 175 | |
| 176 | pub fn addExport(self: *Dylib, allocator: Allocator, name: []const u8, flags: Export.Flags) !void { |
| 177 | try self.exports.append(allocator, .{ |
| 178 | .name = try self.addString(allocator, name), |
| 179 | .flags = flags, |
| 180 | }); |
| 181 | } |
| 182 | |
| 183 | fn parseTrieNode( |
| 184 | self: *Dylib, |
| 185 | it: *TrieIterator, |
| 186 | allocator: Allocator, |
| 187 | arena: Allocator, |
| 188 | prefix: []const u8, |
| 189 | ) !void { |
| 190 | const tracy = trace(@src()); |
| 191 | defer tracy.end(); |
| 192 | const size = try it.readUleb128(); |
| 193 | if (size > 0) { |
| 194 | const flags = try it.readUleb128(); |
| 195 | const kind = flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK; |
| 196 | const out_flags = Export.Flags{ |
| 197 | .abs = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE, |
| 198 | .tlv = kind == macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL, |
| 199 | .weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0, |
| 200 | }; |
| 201 | if (flags & macho.EXPORT_SYMBOL_FLAGS_REEXPORT != 0) { |
| 202 | _ = try it.readUleb128(); // dylib ordinal |
| 203 | const name = try it.readString(); |
| 204 | try self.addExport(allocator, if (name.len > 0) name else prefix, out_flags); |
| 205 | } else if (flags & macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER != 0) { |
| 206 | _ = try it.readUleb128(); // stub offset |
| 207 | _ = try it.readUleb128(); // resolver offset |
| 208 | try self.addExport(allocator, prefix, out_flags); |
| 209 | } else { |
| 210 | _ = try it.readUleb128(); // VM offset |
| 211 | try self.addExport(allocator, prefix, out_flags); |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | const nedges = try it.readByte(); |
| 216 | |
| 217 | for (0..nedges) |_| { |
| 218 | const label = try it.readString(); |
| 219 | const off = try it.readUleb128(); |
| 220 | const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label }); |
| 221 | const curr = it.stream.seek; |
| 222 | it.stream.seek = math.cast(usize, off) orelse return error.Overflow; |
| 223 | try self.parseTrieNode(it, allocator, arena, prefix_label); |
| 224 | it.stream.seek = curr; |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | fn parseTrie(self: *Dylib, data: []const u8, macho_file: *MachO) !void { |
| 229 | const tracy = trace(@src()); |
| 230 | defer tracy.end(); |
| 231 | const gpa = macho_file.base.comp.gpa; |
| 232 | var arena = std.heap.ArenaAllocator.init(gpa); |
| 233 | defer arena.deinit(); |
| 234 | |
| 235 | var it: TrieIterator = .{ .stream = .fixed(data) }; |
| 236 | try self.parseTrieNode(&it, gpa, arena.allocator(), ""); |
| 237 | } |
| 238 | |
| 239 | fn parseTbd(self: *Dylib, macho_file: *MachO) !void { |
| 240 | const tracy = trace(@src()); |
| 241 | defer tracy.end(); |
| 242 | |
| 243 | const comp = macho_file.base.comp; |
| 244 | const gpa = comp.gpa; |
| 245 | const io = comp.io; |
| 246 | |
| 247 | log.debug("parsing dylib from stub: {f}", .{self.path}); |
| 248 | |
| 249 | const file = macho_file.getFileHandle(self.file_handle); |
| 250 | var lib_stub = LibStub.loadFromFile(gpa, io, file) catch |err| { |
| 251 | try macho_file.reportParseError2(self.index, "failed to parse TBD file: {t}", .{err}); |
| 252 | return error.MalformedTbd; |
| 253 | }; |
| 254 | defer lib_stub.deinit(); |
| 255 | const umbrella_lib = lib_stub.inner[0]; |
| 256 | |
| 257 | { |
| 258 | var id = try Id.default(gpa, umbrella_lib.installName()); |
| 259 | if (umbrella_lib.currentVersion()) |version| { |
| 260 | try id.parseCurrentVersion(version); |
| 261 | } |
| 262 | if (umbrella_lib.compatibilityVersion()) |version| { |
| 263 | try id.parseCompatibilityVersion(version); |
| 264 | } |
| 265 | self.id = id; |
| 266 | } |
| 267 | |
| 268 | var umbrella_libs = std.StringHashMap(void).init(gpa); |
| 269 | defer umbrella_libs.deinit(); |
| 270 | |
| 271 | log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); |
| 272 | |
| 273 | const cpu_arch = macho_file.getTarget().cpu.arch; |
| 274 | self.platform = macho_file.platform; |
| 275 | |
| 276 | var matcher = try TargetMatcher.init(gpa, cpu_arch, self.platform.?.toApplePlatform()); |
| 277 | defer matcher.deinit(); |
| 278 | |
| 279 | for (lib_stub.inner, 0..) |elem, stub_index| { |
| 280 | if (!(try matcher.matchesTargetTbd(elem))) continue; |
| 281 | |
| 282 | if (stub_index > 0) { |
| 283 | // TODO I thought that we could switch on presence of `parent-umbrella` map; |
| 284 | // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib` |
| 285 | // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps? |
| 286 | try umbrella_libs.put(elem.installName(), {}); |
| 287 | } |
| 288 | |
| 289 | switch (elem) { |
| 290 | .v3 => |stub| { |
| 291 | if (stub.exports) |exports| { |
| 292 | for (exports) |exp| { |
| 293 | if (!matcher.matchesArch(exp.archs)) continue; |
| 294 | |
| 295 | if (exp.symbols) |symbols| { |
| 296 | for (symbols) |sym_name| { |
| 297 | try self.addExport(gpa, sym_name, .{}); |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | if (exp.weak_symbols) |symbols| { |
| 302 | for (symbols) |sym_name| { |
| 303 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | if (exp.objc_classes) |objc_classes| { |
| 308 | for (objc_classes) |class_name| { |
| 309 | try self.addObjCClass(gpa, class_name); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | if (exp.objc_ivars) |objc_ivars| { |
| 314 | for (objc_ivars) |ivar| { |
| 315 | try self.addObjCIVar(gpa, ivar); |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | if (exp.objc_eh_types) |objc_eh_types| { |
| 320 | for (objc_eh_types) |eht| { |
| 321 | try self.addObjCEhType(gpa, eht); |
| 322 | } |
| 323 | } |
| 324 | |
| 325 | if (exp.re_exports) |re_exports| { |
| 326 | for (re_exports) |lib| { |
| 327 | if (umbrella_libs.contains(lib)) continue; |
| 328 | |
| 329 | log.debug(" (found re-export '{s}')", .{lib}); |
| 330 | |
| 331 | const dep_id = try Id.default(gpa, lib); |
| 332 | try self.dependents.append(gpa, dep_id); |
| 333 | } |
| 334 | } |
| 335 | } |
| 336 | } |
| 337 | }, |
| 338 | .v4 => |stub| { |
| 339 | if (stub.exports) |exports| { |
| 340 | for (exports) |exp| { |
| 341 | if (!matcher.matchesTarget(exp.targets)) continue; |
| 342 | |
| 343 | if (exp.symbols) |symbols| { |
| 344 | for (symbols) |sym_name| { |
| 345 | try self.addExport(gpa, sym_name, .{}); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | if (exp.weak_symbols) |symbols| { |
| 350 | for (symbols) |sym_name| { |
| 351 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | if (exp.objc_classes) |classes| { |
| 356 | for (classes) |sym_name| { |
| 357 | try self.addObjCClass(gpa, sym_name); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | if (exp.objc_ivars) |objc_ivars| { |
| 362 | for (objc_ivars) |ivar| { |
| 363 | try self.addObjCIVar(gpa, ivar); |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | if (exp.objc_eh_types) |objc_eh_types| { |
| 368 | for (objc_eh_types) |eht| { |
| 369 | try self.addObjCEhType(gpa, eht); |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | } |
| 374 | |
| 375 | if (stub.reexports) |reexports| { |
| 376 | for (reexports) |reexp| { |
| 377 | if (!matcher.matchesTarget(reexp.targets)) continue; |
| 378 | |
| 379 | if (reexp.symbols) |symbols| { |
| 380 | for (symbols) |sym_name| { |
| 381 | try self.addExport(gpa, sym_name, .{}); |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | if (reexp.weak_symbols) |symbols| { |
| 386 | for (symbols) |sym_name| { |
| 387 | try self.addExport(gpa, sym_name, .{ .weak = true }); |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | if (reexp.objc_classes) |classes| { |
| 392 | for (classes) |sym_name| { |
| 393 | try self.addObjCClass(gpa, sym_name); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | if (reexp.objc_ivars) |objc_ivars| { |
| 398 | for (objc_ivars) |ivar| { |
| 399 | try self.addObjCIVar(gpa, ivar); |
| 400 | } |
| 401 | } |
| 402 | |
| 403 | if (reexp.objc_eh_types) |objc_eh_types| { |
| 404 | for (objc_eh_types) |eht| { |
| 405 | try self.addObjCEhType(gpa, eht); |
| 406 | } |
| 407 | } |
| 408 | } |
| 409 | } |
| 410 | |
| 411 | if (stub.objc_classes) |classes| { |
| 412 | for (classes) |sym_name| { |
| 413 | try self.addObjCClass(gpa, sym_name); |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | if (stub.objc_ivars) |objc_ivars| { |
| 418 | for (objc_ivars) |ivar| { |
| 419 | try self.addObjCIVar(gpa, ivar); |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | if (stub.objc_eh_types) |objc_eh_types| { |
| 424 | for (objc_eh_types) |eht| { |
| 425 | try self.addObjCEhType(gpa, eht); |
| 426 | } |
| 427 | } |
| 428 | }, |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include |
| 433 | // re-exports directly in the stub file. |
| 434 | for (lib_stub.inner) |elem| { |
| 435 | if (elem == .v3) continue; |
| 436 | const stub = elem.v4; |
| 437 | |
| 438 | if (stub.reexported_libraries) |reexports| { |
| 439 | for (reexports) |reexp| { |
| 440 | if (!matcher.matchesTarget(reexp.targets)) continue; |
| 441 | |
| 442 | for (reexp.libraries) |lib| { |
| 443 | if (umbrella_libs.contains(lib)) continue; |
| 444 | |
| 445 | log.debug(" (found re-export '{s}')", .{lib}); |
| 446 | |
| 447 | const dep_id = try Id.default(gpa, lib); |
| 448 | try self.dependents.append(gpa, dep_id); |
| 449 | } |
| 450 | } |
| 451 | } |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | fn addObjCClass(self: *Dylib, allocator: Allocator, name: []const u8) !void { |
| 456 | try self.addObjCExport(allocator, "_OBJC_CLASS_", name); |
| 457 | try self.addObjCExport(allocator, "_OBJC_METACLASS_", name); |
| 458 | } |
| 459 | |
| 460 | fn addObjCIVar(self: *Dylib, allocator: Allocator, name: []const u8) !void { |
| 461 | try self.addObjCExport(allocator, "_OBJC_IVAR_", name); |
| 462 | } |
| 463 | |
| 464 | fn addObjCEhType(self: *Dylib, allocator: Allocator, name: []const u8) !void { |
| 465 | try self.addObjCExport(allocator, "_OBJC_EHTYPE_", name); |
| 466 | } |
| 467 | |
| 468 | fn addObjCExport( |
| 469 | self: *Dylib, |
| 470 | allocator: Allocator, |
| 471 | comptime prefix: []const u8, |
| 472 | name: []const u8, |
| 473 | ) !void { |
| 474 | const full_name = try std.fmt.allocPrint(allocator, prefix ++ "$_{s}", .{name}); |
| 475 | defer allocator.free(full_name); |
| 476 | try self.addExport(allocator, full_name, .{}); |
| 477 | } |
| 478 | |
| 479 | fn initSymbols(self: *Dylib, macho_file: *MachO) !void { |
| 480 | const gpa = macho_file.base.comp.gpa; |
| 481 | |
| 482 | const nsyms = self.exports.items(.name).len; |
| 483 | try self.symbols.ensureTotalCapacityPrecise(gpa, nsyms); |
| 484 | try self.symbols_extra.ensureTotalCapacityPrecise(gpa, nsyms * @sizeOf(Symbol.Extra)); |
| 485 | try self.globals.ensureTotalCapacityPrecise(gpa, nsyms); |
| 486 | self.globals.resize(gpa, nsyms) catch unreachable; |
| 487 | @memset(self.globals.items, 0); |
| 488 | |
| 489 | for (self.exports.items(.name), self.exports.items(.flags)) |noff, flags| { |
| 490 | const index = self.addSymbolAssumeCapacity(); |
| 491 | const symbol = &self.symbols.items[index]; |
| 492 | symbol.name = noff; |
| 493 | symbol.extra = self.addSymbolExtraAssumeCapacity(.{}); |
| 494 | symbol.flags.weak = flags.weak; |
| 495 | symbol.flags.tlv = flags.tlv; |
| 496 | symbol.visibility = .global; |
| 497 | } |
| 498 | } |
| 499 | |
| 500 | pub fn resolveSymbols(self: *Dylib, macho_file: *MachO) !void { |
| 501 | const tracy = trace(@src()); |
| 502 | defer tracy.end(); |
| 503 | |
| 504 | if (!self.explicit and !self.hoisted) return; |
| 505 | |
| 506 | const gpa = macho_file.base.comp.gpa; |
| 507 | |
| 508 | for (self.exports.items(.flags), self.globals.items, 0..) |flags, *global, i| { |
| 509 | const gop = try macho_file.resolver.getOrPut(gpa, .{ |
| 510 | .index = @intCast(i), |
| 511 | .file = self.index, |
| 512 | }, macho_file); |
| 513 | if (!gop.found_existing) { |
| 514 | gop.ref.* = .{ .index = 0, .file = 0 }; |
| 515 | } |
| 516 | global.* = gop.index; |
| 517 | |
| 518 | if (gop.ref.getFile(macho_file) == null) { |
| 519 | gop.ref.* = .{ .index = @intCast(i), .file = self.index }; |
| 520 | continue; |
| 521 | } |
| 522 | |
| 523 | if (self.asFile().getSymbolRank(.{ |
| 524 | .weak = flags.weak, |
| 525 | }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) { |
| 526 | gop.ref.* = .{ .index = @intCast(i), .file = self.index }; |
| 527 | } |
| 528 | } |
| 529 | } |
| 530 | |
| 531 | pub fn isAlive(self: Dylib, macho_file: *MachO) bool { |
| 532 | if (!macho_file.dead_strip_dylibs) return self.explicit or self.referenced or self.needed; |
| 533 | return self.referenced or self.needed; |
| 534 | } |
| 535 | |
| 536 | pub fn markReferenced(self: *Dylib, macho_file: *MachO) void { |
| 537 | const tracy = trace(@src()); |
| 538 | defer tracy.end(); |
| 539 | |
| 540 | for (0..self.symbols.items.len) |i| { |
| 541 | const ref = self.getSymbolRef(@intCast(i), macho_file); |
| 542 | const file = ref.getFile(macho_file) orelse continue; |
| 543 | if (file.getIndex() != self.index) continue; |
| 544 | const global = ref.getSymbol(macho_file).?; |
| 545 | if (global.isLocal()) continue; |
| 546 | self.referenced = true; |
| 547 | break; |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | pub fn calcSymtabSize(self: *Dylib, macho_file: *MachO) void { |
| 552 | const tracy = trace(@src()); |
| 553 | defer tracy.end(); |
| 554 | |
| 555 | for (self.symbols.items, 0..) |*sym, i| { |
| 556 | const ref = self.getSymbolRef(@intCast(i), macho_file); |
| 557 | const file = ref.getFile(macho_file) orelse continue; |
| 558 | if (file.getIndex() != self.index) continue; |
| 559 | if (sym.isLocal()) continue; |
| 560 | assert(sym.flags.import); |
| 561 | sym.flags.output_symtab = true; |
| 562 | sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file); |
| 563 | self.output_symtab_ctx.nimports += 1; |
| 564 | self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1)); |
| 565 | } |
| 566 | } |
| 567 | |
| 568 | pub fn writeSymtab(self: Dylib, macho_file: *MachO, ctx: anytype) void { |
| 569 | const tracy = trace(@src()); |
| 570 | defer tracy.end(); |
| 571 | |
| 572 | var n_strx = self.output_symtab_ctx.stroff; |
| 573 | for (self.symbols.items, 0..) |sym, i| { |
| 574 | const ref = self.getSymbolRef(@intCast(i), macho_file); |
| 575 | const file = ref.getFile(macho_file) orelse continue; |
| 576 | if (file.getIndex() != self.index) continue; |
| 577 | const idx = sym.getOutputSymtabIndex(macho_file) orelse continue; |
| 578 | const out_sym = &ctx.symtab.items[idx]; |
| 579 | out_sym.n_strx = n_strx; |
| 580 | sym.setOutputSym(macho_file, out_sym); |
| 581 | const name = sym.getName(macho_file); |
| 582 | @memcpy(ctx.strtab.items[n_strx..][0..name.len], name); |
| 583 | n_strx += @intCast(name.len); |
| 584 | ctx.strtab.items[n_strx] = 0; |
| 585 | n_strx += 1; |
| 586 | } |
| 587 | } |
| 588 | |
| 589 | pub inline fn getUmbrella(self: Dylib, macho_file: *MachO) *Dylib { |
| 590 | return macho_file.getFile(self.umbrella).?.dylib; |
| 591 | } |
| 592 | |
| 593 | fn addString(self: *Dylib, allocator: Allocator, name: []const u8) !MachO.String { |
| 594 | const off = @as(u32, @intCast(self.strtab.items.len)); |
| 595 | try self.strtab.ensureUnusedCapacity(allocator, name.len + 1); |
| 596 | self.strtab.appendSliceAssumeCapacity(name); |
| 597 | self.strtab.appendAssumeCapacity(0); |
| 598 | return .{ .pos = off, .len = @intCast(name.len + 1) }; |
| 599 | } |
| 600 | |
| 601 | pub fn getString(self: Dylib, string: MachO.String) [:0]const u8 { |
| 602 | assert(string.pos < self.strtab.items.len and string.pos + string.len <= self.strtab.items.len); |
| 603 | if (string.len == 0) return ""; |
| 604 | return self.strtab.items[string.pos..][0 .. string.len - 1 :0]; |
| 605 | } |
| 606 | |
| 607 | pub fn asFile(self: *Dylib) File { |
| 608 | return .{ .dylib = self }; |
| 609 | } |
| 610 | |
| 611 | fn addSymbol(self: *Dylib, allocator: Allocator) !Symbol.Index { |
| 612 | try self.symbols.ensureUnusedCapacity(allocator, 1); |
| 613 | return self.addSymbolAssumeCapacity(); |
| 614 | } |
| 615 | |
| 616 | fn addSymbolAssumeCapacity(self: *Dylib) Symbol.Index { |
| 617 | const index: Symbol.Index = @intCast(self.symbols.items.len); |
| 618 | const symbol = self.symbols.addOneAssumeCapacity(); |
| 619 | symbol.* = .{ .file = self.index }; |
| 620 | return index; |
| 621 | } |
| 622 | |
| 623 | pub fn getSymbolRef(self: Dylib, index: Symbol.Index, macho_file: *MachO) MachO.Ref { |
| 624 | const global_index = self.globals.items[index]; |
| 625 | if (macho_file.resolver.get(global_index)) |ref| return ref; |
| 626 | return .{ .index = index, .file = self.index }; |
| 627 | } |
| 628 | |
| 629 | pub fn addSymbolExtra(self: *Dylib, allocator: Allocator, extra: Symbol.Extra) !u32 { |
| 630 | const field_count = @typeInfo(Symbol.Extra).@"struct".field_names.len; |
| 631 | try self.symbols_extra.ensureUnusedCapacity(allocator, field_count); |
| 632 | return self.addSymbolExtraAssumeCapacity(extra); |
| 633 | } |
| 634 | |
| 635 | fn addSymbolExtraAssumeCapacity(self: *Dylib, extra: Symbol.Extra) u32 { |
| 636 | const index = @as(u32, @intCast(self.symbols_extra.items.len)); |
| 637 | const info = @typeInfo(Symbol.Extra).@"struct"; |
| 638 | inline for (info.field_names, info.field_types) |field_name, field_type| { |
| 639 | self.symbols_extra.appendAssumeCapacity(switch (field_type) { |
| 640 | u32 => @field(extra, field_name), |
| 641 | else => @compileError("bad field type"), |
| 642 | }); |
| 643 | } |
| 644 | return index; |
| 645 | } |
| 646 | |
| 647 | pub fn getSymbolExtra(self: Dylib, index: u32) Symbol.Extra { |
| 648 | const info = @typeInfo(Symbol.Extra).@"struct"; |
| 649 | var i: usize = index; |
| 650 | var result: Symbol.Extra = undefined; |
| 651 | inline for (info.field_names, info.field_types) |field_name, field_type| { |
| 652 | @field(result, field_name) = switch (field_type) { |
| 653 | u32 => self.symbols_extra.items[i], |
| 654 | else => @compileError("bad field type"), |
| 655 | }; |
| 656 | i += 1; |
| 657 | } |
| 658 | return result; |
| 659 | } |
| 660 | |
| 661 | pub fn setSymbolExtra(self: *Dylib, index: u32, extra: Symbol.Extra) void { |
| 662 | const info = @typeInfo(Symbol.Extra).@"struct"; |
| 663 | inline for (info.field_names, info.field_types, 0..) |field_name, field_type, i| { |
| 664 | self.symbols_extra.items[index + i] = switch (field_type) { |
| 665 | u32 => @field(extra, field_name), |
| 666 | else => @compileError("bad field type"), |
| 667 | }; |
| 668 | } |
| 669 | } |
| 670 | |
| 671 | pub fn fmtSymtab(self: *Dylib, macho_file: *MachO) std.fmt.Alt(Format, Format.symtab) { |
| 672 | return .{ .data = .{ |
| 673 | .dylib = self, |
| 674 | .macho_file = macho_file, |
| 675 | } }; |
| 676 | } |
| 677 | |
| 678 | const Format = struct { |
| 679 | dylib: *Dylib, |
| 680 | macho_file: *MachO, |
| 681 | |
| 682 | fn symtab(f: Format, w: *Writer) Writer.Error!void { |
| 683 | const dylib = f.dylib; |
| 684 | const macho_file = f.macho_file; |
| 685 | try w.writeAll(" globals\n"); |
| 686 | for (dylib.symbols.items, 0..) |sym, i| { |
| 687 | const ref = dylib.getSymbolRef(@intCast(i), macho_file); |
| 688 | if (ref.getFile(macho_file) == null) { |
| 689 | // TODO any better way of handling this? |
| 690 | try w.print(" {s} : unclaimed\n", .{sym.getName(macho_file)}); |
| 691 | } else { |
| 692 | try w.print(" {f}\n", .{ref.getSymbol(macho_file).?.fmt(macho_file)}); |
| 693 | } |
| 694 | } |
| 695 | } |
| 696 | }; |
| 697 | |
| 698 | pub const TargetMatcher = struct { |
| 699 | allocator: Allocator, |
| 700 | cpu_arch: std.Target.Cpu.Arch, |
| 701 | platform: macho.PLATFORM, |
| 702 | target_strings: std.ArrayList([]const u8) = .empty, |
| 703 | |
| 704 | pub fn init(allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, platform: macho.PLATFORM) !TargetMatcher { |
| 705 | var self = TargetMatcher{ |
| 706 | .allocator = allocator, |
| 707 | .cpu_arch = cpu_arch, |
| 708 | .platform = platform, |
| 709 | }; |
| 710 | |
| 711 | try self.addTargetStrings(cpuArchToAppleString(cpu_arch)); |
| 712 | // In Xcode 26.4, Apple unified their TBD files from having separate `arm64-macos` and `arm64e-macos` |
| 713 | // entries to having just the latter, presumably because the symbol lists are identical anyway. It |
| 714 | // sure would have been nice if they settled on the former as the unified name so as not to break the |
| 715 | // world, but evidently we can't have nice things. |
| 716 | if (cpu_arch == .aarch64) try self.addTargetStrings("arm64e"); |
| 717 | |
| 718 | return self; |
| 719 | } |
| 720 | |
| 721 | fn addTargetStrings(self: *TargetMatcher, arch: []const u8) !void { |
| 722 | try self.target_strings.append(self.allocator, try std.fmt.allocPrint( |
| 723 | self.allocator, |
| 724 | "{s}-{s}", |
| 725 | .{ arch, platformToAppleString(self.platform) }, |
| 726 | )); |
| 727 | |
| 728 | switch (self.platform) { |
| 729 | .MACCATALYST => { |
| 730 | // Mac Catalyst is allowed to link macOS libraries in a TBD because Apple were apparently too lazy |
| 731 | // to add the proper target strings despite doing so in other places in the format??? |
| 732 | try self.target_strings.append(self.allocator, try std.fmt.allocPrint(self.allocator, "{s}-macos", .{arch})); |
| 733 | }, |
| 734 | .IOSSIMULATOR, .TVOSSIMULATOR, .WATCHOSSIMULATOR, .VISIONOSSIMULATOR => { |
| 735 | // For Apple simulator targets, we need to link against the simulator host's libraries too. |
| 736 | try self.target_strings.append(self.allocator, try std.fmt.allocPrint(self.allocator, "{s}-macos", .{arch})); |
| 737 | }, |
| 738 | else => {}, |
| 739 | } |
| 740 | } |
| 741 | |
| 742 | pub fn deinit(self: *TargetMatcher) void { |
| 743 | for (self.target_strings.items) |t| { |
| 744 | self.allocator.free(t); |
| 745 | } |
| 746 | self.target_strings.deinit(self.allocator); |
| 747 | } |
| 748 | |
| 749 | fn cpuArchToAppleString(cpu_arch: std.Target.Cpu.Arch) []const u8 { |
| 750 | return switch (cpu_arch) { |
| 751 | .aarch64 => "arm64", |
| 752 | .x86_64 => "x86_64", |
| 753 | else => unreachable, |
| 754 | }; |
| 755 | } |
| 756 | |
| 757 | fn platformToAppleString(platform: macho.PLATFORM) []const u8 { |
| 758 | return switch (platform) { |
| 759 | .MACOS => "macos", |
| 760 | .IOS => "ios", |
| 761 | .TVOS => "tvos", |
| 762 | .WATCHOS => "watchos", |
| 763 | .VISIONOS => "xros", |
| 764 | .IOSSIMULATOR => "ios-simulator", |
| 765 | .TVOSSIMULATOR => "tvos-simulator", |
| 766 | .WATCHOSSIMULATOR => "watchos-simulator", |
| 767 | .VISIONOSSIMULATOR => "xros-simulator", |
| 768 | .BRIDGEOS => "bridgeos", |
| 769 | .MACCATALYST => "maccatalyst", |
| 770 | .DRIVERKIT => "driverkit", |
| 771 | else => unreachable, |
| 772 | }; |
| 773 | } |
| 774 | |
| 775 | fn hasValue(stack: []const []const u8, needle: []const u8) bool { |
| 776 | for (stack) |v| { |
| 777 | if (mem.eql(u8, v, needle)) return true; |
| 778 | } |
| 779 | return false; |
| 780 | } |
| 781 | |
| 782 | fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool { |
| 783 | return hasValue(archs, cpuArchToAppleString(self.cpu_arch)); |
| 784 | } |
| 785 | |
| 786 | fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool { |
| 787 | for (self.target_strings.items) |t| { |
| 788 | if (hasValue(targets, t)) return true; |
| 789 | } |
| 790 | return false; |
| 791 | } |
| 792 | |
| 793 | pub fn matchesTargetTbd(self: TargetMatcher, tbd: Tbd) !bool { |
| 794 | var arena = std.heap.ArenaAllocator.init(self.allocator); |
| 795 | defer arena.deinit(); |
| 796 | |
| 797 | const targets = switch (tbd) { |
| 798 | .v3 => |v3| blk: { |
| 799 | var targets = std.array_list.Managed([]const u8).init(arena.allocator()); |
| 800 | for (v3.archs) |arch| { |
| 801 | if (mem.eql(u8, v3.platform, "zippered")) { |
| 802 | // From Xcode 10.3 → 11.3.1, macos SDK .tbd files specify platform as 'zippered' |
| 803 | // which should map to [ '<arch>-macos', '<arch>-maccatalyst' ] |
| 804 | try targets.append(try std.fmt.allocPrint(arena.allocator(), "{s}-macos", .{arch})); |
| 805 | try targets.append(try std.fmt.allocPrint(arena.allocator(), "{s}-maccatalyst", .{arch})); |
| 806 | } else { |
| 807 | try targets.append(try std.fmt.allocPrint(arena.allocator(), "{s}-{s}", .{ arch, v3.platform })); |
| 808 | } |
| 809 | } |
| 810 | break :blk targets.items; |
| 811 | }, |
| 812 | .v4 => |v4| v4.targets, |
| 813 | }; |
| 814 | |
| 815 | return self.matchesTarget(targets); |
| 816 | } |
| 817 | }; |
| 818 | |
| 819 | pub const Id = struct { |
| 820 | name: []const u8, |
| 821 | timestamp: u32, |
| 822 | current_version: u32, |
| 823 | compatibility_version: u32, |
| 824 | |
| 825 | pub fn default(allocator: Allocator, name: []const u8) !Id { |
| 826 | return Id{ |
| 827 | .name = try allocator.dupe(u8, name), |
| 828 | .timestamp = 2, |
| 829 | .current_version = 0x10000, |
| 830 | .compatibility_version = 0x10000, |
| 831 | }; |
| 832 | } |
| 833 | |
| 834 | pub fn fromLoadCommand(allocator: Allocator, lc: macho.dylib_command, name: []const u8) !Id { |
| 835 | return Id{ |
| 836 | .name = try allocator.dupe(u8, name), |
| 837 | .timestamp = lc.dylib.timestamp, |
| 838 | .current_version = lc.dylib.current_version, |
| 839 | .compatibility_version = lc.dylib.compatibility_version, |
| 840 | }; |
| 841 | } |
| 842 | |
| 843 | pub fn deinit(id: Id, allocator: Allocator) void { |
| 844 | allocator.free(id.name); |
| 845 | } |
| 846 | |
| 847 | pub const ParseError = fmt.ParseIntError || mem.PrintError; |
| 848 | |
| 849 | pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void { |
| 850 | id.current_version = try parseVersion(version); |
| 851 | } |
| 852 | |
| 853 | pub fn parseCompatibilityVersion(id: *Id, version: anytype) ParseError!void { |
| 854 | id.compatibility_version = try parseVersion(version); |
| 855 | } |
| 856 | |
| 857 | fn parseVersion(version: anytype) ParseError!u32 { |
| 858 | const string = blk: { |
| 859 | switch (version) { |
| 860 | .int => |int| { |
| 861 | var out: u32 = 0; |
| 862 | const major = math.cast(u16, int) orelse return error.Overflow; |
| 863 | out += @as(u32, @intCast(major)) << 16; |
| 864 | return out; |
| 865 | }, |
| 866 | .float => |float| { |
| 867 | var buf: [256]u8 = undefined; |
| 868 | break :blk try mem.print(&buf, "{d}", .{float}); |
| 869 | }, |
| 870 | .string => |string| { |
| 871 | break :blk string; |
| 872 | }, |
| 873 | } |
| 874 | }; |
| 875 | |
| 876 | var out: u32 = 0; |
| 877 | var values: [3][]const u8 = undefined; |
| 878 | |
| 879 | var split = mem.splitScalar(u8, string, '.'); |
| 880 | var count: u4 = 0; |
| 881 | while (split.next()) |value| { |
| 882 | if (count > 2) { |
| 883 | log.debug("malformed version field: {s}", .{string}); |
| 884 | return 0x10000; |
| 885 | } |
| 886 | values[count] = value; |
| 887 | count += 1; |
| 888 | } |
| 889 | |
| 890 | if (count > 2) { |
| 891 | out += try fmt.parseInt(u8, values[2], 10); |
| 892 | } |
| 893 | if (count > 1) { |
| 894 | out += @as(u32, @intCast(try fmt.parseInt(u8, values[1], 10))) << 8; |
| 895 | } |
| 896 | out += @as(u32, @intCast(try fmt.parseInt(u16, values[0], 10))) << 16; |
| 897 | |
| 898 | return out; |
| 899 | } |
| 900 | }; |
| 901 | |
| 902 | const Export = struct { |
| 903 | name: MachO.String, |
| 904 | flags: Flags, |
| 905 | |
| 906 | const Flags = packed struct { |
| 907 | abs: bool = false, |
| 908 | weak: bool = false, |
| 909 | tlv: bool = false, |
| 910 | }; |
| 911 | }; |
| 912 | |
| 913 | const std = @import("std"); |
| 914 | const assert = std.debug.assert; |
| 915 | const fs = std.fs; |
| 916 | const fmt = std.fmt; |
| 917 | const log = std.log.scoped(.link); |
| 918 | const macho = std.macho; |
| 919 | const math = std.math; |
| 920 | const mem = std.mem; |
| 921 | const Allocator = mem.Allocator; |
| 922 | const Path = std.Build.Cache.Path; |
| 923 | const Writer = std.Io.Writer; |
| 924 | |
| 925 | const Dylib = @This(); |
| 926 | const File = @import("file.zig").File; |
| 927 | const LibStub = tapi.LibStub; |
| 928 | const LoadCommandIterator = macho.LoadCommandIterator; |
| 929 | const MachO = @import("../MachO.zig"); |
| 930 | const Symbol = @import("Symbol.zig"); |
| 931 | const Tbd = tapi.Tbd; |
| 932 | const fat = @import("fat.zig"); |
| 933 | const tapi = @import("../tapi.zig"); |
| 934 | const trace = @import("../../tracy.zig").trace; |