diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2021-08-07 10:23:30 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2021-08-11 19:38:00 +0200 |
| commit | 8afe6210e95dd4608584535e8aed882285dc2078 (patch) | |
| tree | e30ba3d2dd2b804a812b55db3ea746fb27d434a1 /src | |
| parent | 60a5552d414ffedf84117df57963fd5bf099c2ea (diff) | |
| download | zig-8afe6210e95dd4608584535e8aed882285dc2078.tar.gz zig-8afe6210e95dd4608584535e8aed882285dc2078.zip | |
macho: add TAPI v3 parser
This turns out needed to correctly support version back to macOS
10.14 (Mojave)
Diffstat (limited to 'src')
| -rw-r--r-- | src/link/MachO/Dylib.zig | 137 | ||||
| -rw-r--r-- | src/link/tapi.zig | 168 |
2 files changed, 229 insertions, 76 deletions
diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 64be1fd2fa..d3de8827d7 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -10,9 +10,10 @@ const math = std.math; const mem = std.mem; const fat = @import("fat.zig"); const commands = @import("commands.zig"); +const tapi = @import("../tapi.zig"); const Allocator = mem.Allocator; -const LibStub = @import("../tapi.zig").LibStub; +const LibStub = tapi.LibStub; const LoadCommand = commands.LoadCommand; const MachO = @import("../MachO.zig"); @@ -315,9 +316,9 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void { } } -fn hasTarget(targets: []const []const u8, target: []const u8) bool { - for (targets) |t| { - if (mem.eql(u8, t, target)) return true; +fn hasValue(stack: []const []const u8, needle: []const u8) bool { + for (stack) |v| { + if (mem.eql(u8, v, needle)) return true; } return false; } @@ -334,6 +335,78 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8 } } +fn hasArch(archs: []const []const u8, arch: []const u8) bool { + for (archs) |x| { + if (mem.eql(u8, x, arch)) return true; + } + return false; +} + +fn parseFromStubV3(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { + var umbrella_libs = std.StringHashMap(void).init(allocator); + defer umbrella_libs.deinit(); + + const arch_string = @tagName(target.cpu.arch); + + for (lib_stub.inner) |elem, stub_index| { + const stub = elem.v3; + if (!hasArch(stub.archs, arch_string)) continue; + + if (stub_index > 0) { + // TODO I thought that we could switch on presence of `parent-umbrella` map; + // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib` + // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps? + try umbrella_libs.put(stub.install_name, .{}); + } + + if (stub.exports) |exports| { + for (exports) |exp| { + if (!hasArch(exp.archs, arch_string)) continue; + + if (exp.symbols) |symbols| { + for (symbols) |sym_name| { + if (self.symbols.contains(sym_name)) continue; + try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); + } + } + + if (exp.re_exports) |re_exports| { + for (re_exports) |reexp| { + if (self.symbols.contains(reexp)) continue; + try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, reexp), {}); + } + } + } + } + } + + log.debug("{s}", .{lib_stub.inner[0].installName()}); + + // // TODO track which libs were already parsed in different steps + // for (lib_stub.inner) |elem| { + // const stub = elem.v3; + // if (!archMatches(stub.archs, arch_string)) continue; + + // if (stub.reexported_libraries) |reexports| { + // for (reexports) |reexp| { + // if (!matcher.matches(reexp.targets)) continue; + + // for (reexp.libraries) |lib| { + // if (umbrella_libs.contains(lib)) { + // log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name }); + // continue; + // } + + // log.debug(" | {s}", .{lib}); + + // const dep_id = try Id.default(allocator, lib); + // try self.dependent_libs.append(allocator, dep_id); + // } + // } + // } + // } +} + fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 { const arch = switch (target.cpu.arch) { .aarch64 => "arm64", @@ -380,6 +453,13 @@ const TargetMatcher = struct { self.target_strings.deinit(self.allocator); } + fn hasTarget(targets: []const []const u8, target: []const u8) bool { + for (targets) |x| { + if (mem.eql(u8, x, target)) return true; + } + return false; + } + fn matches(self: TargetMatcher, targets: []const []const u8) bool { for (self.target_strings.items) |t| { if (hasTarget(targets, t)) return true; @@ -388,29 +468,15 @@ const TargetMatcher = struct { } }; -pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { - if (lib_stub.inner.len == 0) return error.EmptyStubFile; - - log.debug("parsing shared library from stub '{s}'", .{self.name}); - - const umbrella_lib = lib_stub.inner[0]; - - var id = try Id.default(allocator, umbrella_lib.install_name); - if (umbrella_lib.current_version) |version| { - try id.parseCurrentVersion(version); - } - if (umbrella_lib.compatibility_version) |version| { - try id.parseCompatibilityVersion(version); - } - self.id = id; - +fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { var matcher = try TargetMatcher.init(allocator, target); defer matcher.deinit(); var umbrella_libs = std.StringHashMap(void).init(allocator); defer umbrella_libs.deinit(); - for (lib_stub.inner) |stub, stub_index| { + for (lib_stub.inner) |elem, stub_index| { + const stub = elem.v4; if (!matcher.matches(stub.targets)) continue; if (stub_index > 0) { @@ -465,10 +531,11 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li } } - log.debug("{s}", .{umbrella_lib.install_name}); + log.debug("{s}", .{lib_stub.inner[0].installName()}); // TODO track which libs were already parsed in different steps - for (lib_stub.inner) |stub| { + for (lib_stub.inner) |elem| { + const stub = elem.v4; if (!matcher.matches(stub.targets)) continue; if (stub.reexported_libraries) |reexports| { @@ -477,7 +544,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li for (reexp.libraries) |lib| { if (umbrella_libs.contains(lib)) { - log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name }); + log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() }); continue; } @@ -491,6 +558,28 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li } } +pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { + if (lib_stub.inner.len == 0) return error.EmptyStubFile; + + log.debug("parsing shared library from stub '{s}'", .{self.name}); + + const umbrella_lib = lib_stub.inner[0]; + + var id = try Id.default(allocator, umbrella_lib.installName()); + if (umbrella_lib.currentVersion()) |version| { + try id.parseCurrentVersion(version); + } + if (umbrella_lib.compatibilityVersion()) |version| { + try id.parseCompatibilityVersion(version); + } + self.id = id; + + switch (umbrella_lib) { + .v3 => try self.parseFromStubV3(allocator, target, lib_stub), + .v4 => try self.parseFromStubV4(allocator, target, lib_stub), + } +} + pub fn parseDependentLibs( self: *Dylib, allocator: *Allocator, diff --git a/src/link/tapi.zig b/src/link/tapi.zig index 35193b0eec..aa953cc76a 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -6,6 +6,88 @@ const log = std.log.scoped(.tapi); const Allocator = mem.Allocator; const Yaml = @import("tapi/yaml.zig").Yaml; +const VersionField = union(enum) { + string: []const u8, + float: f64, + int: u64, +}; + +pub const TbdV3 = struct { + archs: []const []const u8, + uuids: []const []const u8, + platform: []const u8, + install_name: []const u8, + current_version: ?VersionField, + compatibility_version: ?VersionField, + objc_constraint: []const u8, + exports: ?[]const struct { + archs: []const []const u8, + re_exports: ?[]const []const u8, + symbols: ?[]const []const u8, + }, +}; + +pub const TbdV4 = struct { + tbd_version: u3, + targets: []const []const u8, + uuids: []const struct { + target: []const u8, + value: []const u8, + }, + install_name: []const u8, + current_version: ?VersionField, + compatibility_version: ?VersionField, + reexported_libraries: ?[]const struct { + targets: []const []const u8, + libraries: []const []const u8, + }, + parent_umbrella: ?[]const struct { + targets: []const []const u8, + umbrella: []const u8, + }, + exports: ?[]const struct { + targets: []const []const u8, + symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, + }, + reexports: ?[]const struct { + targets: []const []const u8, + symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, + }, + allowable_clients: ?[]const struct { + targets: []const []const u8, + clients: []const []const u8, + }, + objc_classes: ?[]const []const u8, +}; + +pub const Tbd = union(enum) { + v3: TbdV3, + v4: TbdV4, + + pub fn currentVersion(self: Tbd) ?VersionField { + return switch (self) { + .v3 => |v3| v3.current_version, + .v4 => |v4| v4.current_version, + }; + } + + pub fn compatibilityVersion(self: Tbd) ?VersionField { + return switch (self) { + .v3 => |v3| v3.compatibility_version, + .v4 => |v4| v4.compatibility_version, + }; + } + + pub fn installName(self: Tbd) []const u8 { + return switch (self) { + .v3 => |v3| v3.install_name, + .v4 => |v4| v4.install_name, + }; + } +}; + pub const LibStub = struct { /// Underlying memory for stub's contents. yaml: Yaml, @@ -13,49 +95,6 @@ pub const LibStub = struct { /// Typed contents of the tbd file. inner: []Tbd, - const Tbd = struct { - tbd_version: u3, - targets: []const []const u8, - uuids: []const struct { - target: []const u8, - value: []const u8, - }, - install_name: []const u8, - current_version: ?union(enum) { - string: []const u8, - float: f64, - int: u64, - }, - compatibility_version: ?union(enum) { - string: []const u8, - float: f64, - int: u64, - }, - reexported_libraries: ?[]const struct { - targets: []const []const u8, - libraries: []const []const u8, - }, - parent_umbrella: ?[]const struct { - targets: []const []const u8, - umbrella: []const u8, - }, - exports: ?[]const struct { - targets: []const []const u8, - symbols: ?[]const []const u8, - objc_classes: ?[]const []const u8, - }, - reexports: ?[]const struct { - targets: []const []const u8, - symbols: ?[]const []const u8, - objc_classes: ?[]const []const u8, - }, - allowable_clients: ?[]const struct { - targets: []const []const u8, - clients: []const []const u8, - }, - objc_classes: ?[]const []const u8, - }; - pub fn loadFromFile(allocator: *Allocator, file: fs.File) !LibStub { const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32)); defer allocator.free(source); @@ -65,16 +104,41 @@ pub const LibStub = struct { .inner = undefined, }; - lib_stub.inner = lib_stub.yaml.parse([]Tbd) catch |err| blk: { - switch (err) { - error.TypeMismatch => { - // TODO clean this up. - var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); - out[0] = try lib_stub.yaml.parse(Tbd); - break :blk out; - }, - else => |e| return e, + // TODO clean this up. + lib_stub.inner = blk: { + err: { + const inner = lib_stub.yaml.parse([]TbdV4) catch |err| switch (err) { + error.TypeMismatch => break :err, + else => |e| return e, + }; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, inner.len); + for (inner) |doc, i| { + out[i] = .{ .v4 = doc }; + } + break :blk out; + } + + err: { + const inner = lib_stub.yaml.parse(TbdV4) catch |err| switch (err) { + error.TypeMismatch => break :err, + else => |e| return e, + }; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); + out[0] = .{ .v4 = inner }; + break :blk out; + } + + err: { + const inner = lib_stub.yaml.parse(TbdV3) catch |err| switch (err) { + error.TypeMismatch => break :err, + else => |e| return e, + }; + var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1); + out[0] = .{ .v3 = inner }; + break :blk out; } + + return error.TypeMismatch; }; return lib_stub; |
