aboutsummaryrefslogtreecommitdiff
path: root/src/link
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2021-08-07 10:23:30 +0200
committerJakub Konka <kubkon@jakubkonka.com>2021-08-11 19:38:00 +0200
commit8afe6210e95dd4608584535e8aed882285dc2078 (patch)
treee30ba3d2dd2b804a812b55db3ea746fb27d434a1 /src/link
parent60a5552d414ffedf84117df57963fd5bf099c2ea (diff)
downloadzig-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/link')
-rw-r--r--src/link/MachO/Dylib.zig137
-rw-r--r--src/link/tapi.zig168
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;