authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-08-07 10:23:30+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-08-11 19:38:00+02:00
log8afe6210e95dd4608584535e8aed882285dc2078
treee30ba3d2dd2b804a812b55db3ea746fb27d434a1
parent60a5552d414ffedf84117df57963fd5bf099c2ea

macho: add TAPI v3 parser

This turns out needed to correctly support version back to macOS 10.14 (Mojave)

2 files changed, 229 insertions(+), 76 deletions(-)

src/link/MachO/Dylib.zig+113-24
...@@ -10,9 +10,10 @@ const math = std.math;...@@ -10,9 +10,10 @@ const math = std.math;
10const mem = std.mem;10const mem = std.mem;
11const fat = @import("fat.zig");11const fat = @import("fat.zig");
12const commands = @import("commands.zig");12const commands = @import("commands.zig");
13const tapi = @import("../tapi.zig");
1314
14const Allocator = mem.Allocator;15const Allocator = mem.Allocator;
15const LibStub = @import("../tapi.zig").LibStub;16const LibStub = tapi.LibStub;
16const LoadCommand = commands.LoadCommand;17const LoadCommand = commands.LoadCommand;
17const MachO = @import("../MachO.zig");18const MachO = @import("../MachO.zig");
1819
...@@ -315,9 +316,9 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {...@@ -315,9 +316,9 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {
315 }316 }
316}317}
317318
318fn hasTarget(targets: []const []const u8, target: []const u8) bool {319fn hasValue(stack: []const []const u8, needle: []const u8) bool {
319 for (targets) |t| {320 for (stack) |v| {
320 if (mem.eql(u8, t, target)) return true;321 if (mem.eql(u8, v, needle)) return true;
321 }322 }
322 return false;323 return false;
323}324}
...@@ -334,6 +335,78 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8...@@ -334,6 +335,78 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8
334 }335 }
335}336}
336337
338fn hasArch(archs: []const []const u8, arch: []const u8) bool {
339 for (archs) |x| {
340 if (mem.eql(u8, x, arch)) return true;
341 }
342 return false;
343}
344
345fn parseFromStubV3(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
346 var umbrella_libs = std.StringHashMap(void).init(allocator);
347 defer umbrella_libs.deinit();
348
349 const arch_string = @tagName(target.cpu.arch);
350
351 for (lib_stub.inner) |elem, stub_index| {
352 const stub = elem.v3;
353 if (!hasArch(stub.archs, arch_string)) continue;
354
355 if (stub_index > 0) {
356 // TODO I thought that we could switch on presence of `parent-umbrella` map;
357 // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`
358 // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps?
359 try umbrella_libs.put(stub.install_name, .{});
360 }
361
362 if (stub.exports) |exports| {
363 for (exports) |exp| {
364 if (!hasArch(exp.archs, arch_string)) continue;
365
366 if (exp.symbols) |symbols| {
367 for (symbols) |sym_name| {
368 if (self.symbols.contains(sym_name)) continue;
369 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
370 }
371 }
372
373 if (exp.re_exports) |re_exports| {
374 for (re_exports) |reexp| {
375 if (self.symbols.contains(reexp)) continue;
376 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, reexp), {});
377 }
378 }
379 }
380 }
381 }
382
383 log.debug("{s}", .{lib_stub.inner[0].installName()});
384
385 // // TODO track which libs were already parsed in different steps
386 // for (lib_stub.inner) |elem| {
387 // const stub = elem.v3;
388 // if (!archMatches(stub.archs, arch_string)) continue;
389
390 // if (stub.reexported_libraries) |reexports| {
391 // for (reexports) |reexp| {
392 // if (!matcher.matches(reexp.targets)) continue;
393
394 // for (reexp.libraries) |lib| {
395 // if (umbrella_libs.contains(lib)) {
396 // log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name });
397 // continue;
398 // }
399
400 // log.debug(" | {s}", .{lib});
401
402 // const dep_id = try Id.default(allocator, lib);
403 // try self.dependent_libs.append(allocator, dep_id);
404 // }
405 // }
406 // }
407 // }
408}
409
337fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {410fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {
338 const arch = switch (target.cpu.arch) {411 const arch = switch (target.cpu.arch) {
339 .aarch64 => "arm64",412 .aarch64 => "arm64",
...@@ -380,6 +453,13 @@ const TargetMatcher = struct {...@@ -380,6 +453,13 @@ const TargetMatcher = struct {
380 self.target_strings.deinit(self.allocator);453 self.target_strings.deinit(self.allocator);
381 }454 }
382455
456 fn hasTarget(targets: []const []const u8, target: []const u8) bool {
457 for (targets) |x| {
458 if (mem.eql(u8, x, target)) return true;
459 }
460 return false;
461 }
462
383 fn matches(self: TargetMatcher, targets: []const []const u8) bool {463 fn matches(self: TargetMatcher, targets: []const []const u8) bool {
384 for (self.target_strings.items) |t| {464 for (self.target_strings.items) |t| {
385 if (hasTarget(targets, t)) return true;465 if (hasTarget(targets, t)) return true;
...@@ -388,29 +468,15 @@ const TargetMatcher = struct {...@@ -388,29 +468,15 @@ const TargetMatcher = struct {
388 }468 }
389};469};
390470
391pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {471fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
392 if (lib_stub.inner.len == 0) return error.EmptyStubFile;
393
394 log.debug("parsing shared library from stub '{s}'", .{self.name});
395
396 const umbrella_lib = lib_stub.inner[0];
397
398 var id = try Id.default(allocator, umbrella_lib.install_name);
399 if (umbrella_lib.current_version) |version| {
400 try id.parseCurrentVersion(version);
401 }
402 if (umbrella_lib.compatibility_version) |version| {
403 try id.parseCompatibilityVersion(version);
404 }
405 self.id = id;
406
407 var matcher = try TargetMatcher.init(allocator, target);472 var matcher = try TargetMatcher.init(allocator, target);
408 defer matcher.deinit();473 defer matcher.deinit();
409474
410 var umbrella_libs = std.StringHashMap(void).init(allocator);475 var umbrella_libs = std.StringHashMap(void).init(allocator);
411 defer umbrella_libs.deinit();476 defer umbrella_libs.deinit();
412477
413 for (lib_stub.inner) |stub, stub_index| {478 for (lib_stub.inner) |elem, stub_index| {
479 const stub = elem.v4;
414 if (!matcher.matches(stub.targets)) continue;480 if (!matcher.matches(stub.targets)) continue;
415481
416 if (stub_index > 0) {482 if (stub_index > 0) {
...@@ -465,10 +531,11 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li...@@ -465,10 +531,11 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
465 }531 }
466 }532 }
467533
468 log.debug("{s}", .{umbrella_lib.install_name});534 log.debug("{s}", .{lib_stub.inner[0].installName()});
469535
470 // TODO track which libs were already parsed in different steps536 // TODO track which libs were already parsed in different steps
471 for (lib_stub.inner) |stub| {537 for (lib_stub.inner) |elem| {
538 const stub = elem.v4;
472 if (!matcher.matches(stub.targets)) continue;539 if (!matcher.matches(stub.targets)) continue;
473540
474 if (stub.reexported_libraries) |reexports| {541 if (stub.reexported_libraries) |reexports| {
...@@ -477,7 +544,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li...@@ -477,7 +544,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
477544
478 for (reexp.libraries) |lib| {545 for (reexp.libraries) |lib| {
479 if (umbrella_libs.contains(lib)) {546 if (umbrella_libs.contains(lib)) {
480 log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name });547 log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() });
481 continue;548 continue;
482 }549 }
483550
...@@ -491,6 +558,28 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li...@@ -491,6 +558,28 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
491 }558 }
492}559}
493560
561pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
562 if (lib_stub.inner.len == 0) return error.EmptyStubFile;
563
564 log.debug("parsing shared library from stub '{s}'", .{self.name});
565
566 const umbrella_lib = lib_stub.inner[0];
567
568 var id = try Id.default(allocator, umbrella_lib.installName());
569 if (umbrella_lib.currentVersion()) |version| {
570 try id.parseCurrentVersion(version);
571 }
572 if (umbrella_lib.compatibilityVersion()) |version| {
573 try id.parseCompatibilityVersion(version);
574 }
575 self.id = id;
576
577 switch (umbrella_lib) {
578 .v3 => try self.parseFromStubV3(allocator, target, lib_stub),
579 .v4 => try self.parseFromStubV4(allocator, target, lib_stub),
580 }
581}
582
494pub fn parseDependentLibs(583pub fn parseDependentLibs(
495 self: *Dylib,584 self: *Dylib,
496 allocator: *Allocator,585 allocator: *Allocator,
src/link/tapi.zig+116-52
...@@ -6,6 +6,88 @@ const log = std.log.scoped(.tapi);...@@ -6,6 +6,88 @@ const log = std.log.scoped(.tapi);
6const Allocator = mem.Allocator;6const Allocator = mem.Allocator;
7const Yaml = @import("tapi/yaml.zig").Yaml;7const Yaml = @import("tapi/yaml.zig").Yaml;
88
9const VersionField = union(enum) {
10 string: []const u8,
11 float: f64,
12 int: u64,
13};
14
15pub const TbdV3 = struct {
16 archs: []const []const u8,
17 uuids: []const []const u8,
18 platform: []const u8,
19 install_name: []const u8,
20 current_version: ?VersionField,
21 compatibility_version: ?VersionField,
22 objc_constraint: []const u8,
23 exports: ?[]const struct {
24 archs: []const []const u8,
25 re_exports: ?[]const []const u8,
26 symbols: ?[]const []const u8,
27 },
28};
29
30pub const TbdV4 = struct {
31 tbd_version: u3,
32 targets: []const []const u8,
33 uuids: []const struct {
34 target: []const u8,
35 value: []const u8,
36 },
37 install_name: []const u8,
38 current_version: ?VersionField,
39 compatibility_version: ?VersionField,
40 reexported_libraries: ?[]const struct {
41 targets: []const []const u8,
42 libraries: []const []const u8,
43 },
44 parent_umbrella: ?[]const struct {
45 targets: []const []const u8,
46 umbrella: []const u8,
47 },
48 exports: ?[]const struct {
49 targets: []const []const u8,
50 symbols: ?[]const []const u8,
51 objc_classes: ?[]const []const u8,
52 },
53 reexports: ?[]const struct {
54 targets: []const []const u8,
55 symbols: ?[]const []const u8,
56 objc_classes: ?[]const []const u8,
57 },
58 allowable_clients: ?[]const struct {
59 targets: []const []const u8,
60 clients: []const []const u8,
61 },
62 objc_classes: ?[]const []const u8,
63};
64
65pub const Tbd = union(enum) {
66 v3: TbdV3,
67 v4: TbdV4,
68
69 pub fn currentVersion(self: Tbd) ?VersionField {
70 return switch (self) {
71 .v3 => |v3| v3.current_version,
72 .v4 => |v4| v4.current_version,
73 };
74 }
75
76 pub fn compatibilityVersion(self: Tbd) ?VersionField {
77 return switch (self) {
78 .v3 => |v3| v3.compatibility_version,
79 .v4 => |v4| v4.compatibility_version,
80 };
81 }
82
83 pub fn installName(self: Tbd) []const u8 {
84 return switch (self) {
85 .v3 => |v3| v3.install_name,
86 .v4 => |v4| v4.install_name,
87 };
88 }
89};
90
9pub const LibStub = struct {91pub const LibStub = struct {
10 /// Underlying memory for stub's contents.92 /// Underlying memory for stub's contents.
11 yaml: Yaml,93 yaml: Yaml,
...@@ -13,49 +95,6 @@ pub const LibStub = struct {...@@ -13,49 +95,6 @@ pub const LibStub = struct {
13 /// Typed contents of the tbd file.95 /// Typed contents of the tbd file.
14 inner: []Tbd,96 inner: []Tbd,
1597
16 const Tbd = struct {
17 tbd_version: u3,
18 targets: []const []const u8,
19 uuids: []const struct {
20 target: []const u8,
21 value: []const u8,
22 },
23 install_name: []const u8,
24 current_version: ?union(enum) {
25 string: []const u8,
26 float: f64,
27 int: u64,
28 },
29 compatibility_version: ?union(enum) {
30 string: []const u8,
31 float: f64,
32 int: u64,
33 },
34 reexported_libraries: ?[]const struct {
35 targets: []const []const u8,
36 libraries: []const []const u8,
37 },
38 parent_umbrella: ?[]const struct {
39 targets: []const []const u8,
40 umbrella: []const u8,
41 },
42 exports: ?[]const struct {
43 targets: []const []const u8,
44 symbols: ?[]const []const u8,
45 objc_classes: ?[]const []const u8,
46 },
47 reexports: ?[]const struct {
48 targets: []const []const u8,
49 symbols: ?[]const []const u8,
50 objc_classes: ?[]const []const u8,
51 },
52 allowable_clients: ?[]const struct {
53 targets: []const []const u8,
54 clients: []const []const u8,
55 },
56 objc_classes: ?[]const []const u8,
57 };
58
59 pub fn loadFromFile(allocator: *Allocator, file: fs.File) !LibStub {98 pub fn loadFromFile(allocator: *Allocator, file: fs.File) !LibStub {
60 const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32));99 const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
61 defer allocator.free(source);100 defer allocator.free(source);
...@@ -65,16 +104,41 @@ pub const LibStub = struct {...@@ -65,16 +104,41 @@ pub const LibStub = struct {
65 .inner = undefined,104 .inner = undefined,
66 };105 };
67106
68 lib_stub.inner = lib_stub.yaml.parse([]Tbd) catch |err| blk: {107 // TODO clean this up.
69 switch (err) {108 lib_stub.inner = blk: {
70 error.TypeMismatch => {109 err: {
71 // TODO clean this up.110 const inner = lib_stub.yaml.parse([]TbdV4) catch |err| switch (err) {
72 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);111 error.TypeMismatch => break :err,
73 out[0] = try lib_stub.yaml.parse(Tbd);112 else => |e| return e,
74 break :blk out;113 };
75 },114 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, inner.len);
76 else => |e| return e,115 for (inner) |doc, i| {
116 out[i] = .{ .v4 = doc };
117 }
118 break :blk out;
119 }
120
121 err: {
122 const inner = lib_stub.yaml.parse(TbdV4) catch |err| switch (err) {
123 error.TypeMismatch => break :err,
124 else => |e| return e,
125 };
126 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
127 out[0] = .{ .v4 = inner };
128 break :blk out;
129 }
130
131 err: {
132 const inner = lib_stub.yaml.parse(TbdV3) catch |err| switch (err) {
133 error.TypeMismatch => break :err,
134 else => |e| return e,
135 };
136 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
137 out[0] = .{ .v3 = inner };
138 break :blk out;
77 }139 }
140
141 return error.TypeMismatch;
78 };142 };
79143
80 return lib_stub;144 return lib_stub;