macho: add TAPI v3 parser

This turns out needed to correctly support version back to macOS
10.14 (Mojave)
This commit is contained in:
Jakub Konka
2021-08-07 10:23:30 +02:00
parent 60a5552d41
commit 8afe6210e9
2 changed files with 229 additions and 76 deletions

View File

@@ -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,

View File

@@ -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;