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