macho: add TAPI v3 parser
This turns out needed to correctly support version back to macOS 10.14 (Mojave)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user