diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2021-08-11 10:52:21 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2021-08-11 19:38:00 +0200 |
| commit | 16bb5c05f15e1ec4cc1616c5c33e56f67ea0763e (patch) | |
| tree | 6592e0ea2bcc004ccd8667be271c4cdfa6f30910 /src | |
| parent | d95e8bc5f8c48752aed73d077e2f9c87293a6617 (diff) | |
| download | zig-16bb5c05f15e1ec4cc1616c5c33e56f67ea0763e.tar.gz zig-16bb5c05f15e1ec4cc1616c5c33e56f67ea0763e.zip | |
macho: refactor stub parsing in Dylib
Diffstat (limited to 'src')
| -rw-r--r-- | src/link/MachO/Dylib.zig | 297 | ||||
| -rw-r--r-- | src/link/tapi.zig | 5 |
2 files changed, 137 insertions, 165 deletions
diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 141e39f651..bdf3cfe6bb 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -180,9 +180,9 @@ pub fn createAndParseFromPath( if (opts.id) |id| { if (dylib.id.?.current_version < id.compatibility_version) { log.warn("found dylib is incompatible with the required minimum version", .{}); - log.warn(" | dylib: {s}", .{id.name}); - log.warn(" | required minimum version: {}", .{id.compatibility_version}); - log.warn(" | dylib version: {}", .{dylib.id.?.current_version}); + log.warn(" dylib: {s}", .{id.name}); + log.warn(" required minimum version: {}", .{id.compatibility_version}); + log.warn(" dylib version: {}", .{dylib.id.?.current_version}); // TODO maybe this should be an error and facilitate auto-cleanup? dylib.deinit(allocator); @@ -316,14 +316,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void { } } -fn hasValue(stack: []const []const u8, needle: []const u8) bool { - for (stack) |v| { - if (mem.eql(u8, v, needle)) return true; - } - return false; -} - -fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { +fn addObjCClassSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { const expanded = &[_][]const u8{ try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}), try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}), @@ -335,91 +328,21 @@ 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); - - log.debug("{s}", .{lib_stub.inner[0].installName()}); - - 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.objc_classes) |objc_classes| { - for (objc_classes) |class_name| { - try self.addObjCClassSymbols(allocator, class_name); - } - } - - if (exp.re_exports) |re_exports| { - for (re_exports) |lib| { - if (umbrella_libs.contains(lib)) { - log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() }); - 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", - .x86_64 => "x86_64", - else => unreachable, - }; - const os = @tagName(target.os.tag); - const abi: ?[]const u8 = switch (target.abi) { - .gnu => null, - .simulator => "simulator", - else => unreachable, - }; - if (abi) |x| { - return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x }); - } - return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os }); +fn addSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void { + if (self.symbols.contains(sym_name)) return; + try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {}); } const TargetMatcher = struct { allocator: *Allocator, + target: std.Target, target_strings: std.ArrayListUnmanaged([]const u8) = .{}, fn init(allocator: *Allocator, target: std.Target) !TargetMatcher { - var self = TargetMatcher{ .allocator = allocator }; + var self = TargetMatcher{ + .allocator = allocator, + .target = target, + }; try self.target_strings.append(allocator, try targetToAppleString(allocator, target)); if (target.abi == .simulator) { @@ -442,102 +365,174 @@ 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; + fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 { + const arch = switch (target.cpu.arch) { + .aarch64 => "arm64", + .x86_64 => "x86_64", + else => unreachable, + }; + const os = @tagName(target.os.tag); + const abi: ?[]const u8 = switch (target.abi) { + .gnu => null, + .simulator => "simulator", + else => unreachable, + }; + if (abi) |x| { + return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x }); + } + return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os }); + } + + fn hasValue(stack: []const []const u8, needle: []const u8) bool { + for (stack) |v| { + if (mem.eql(u8, v, needle)) return true; } return false; } - fn matches(self: TargetMatcher, targets: []const []const u8) bool { + fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool { for (self.target_strings.items) |t| { - if (hasTarget(targets, t)) return true; + if (hasValue(targets, t)) return true; } return false; } + + fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool { + return hasValue(archs, @tagName(self.target.cpu.arch)); + } }; -fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void { - var matcher = try TargetMatcher.init(allocator, target); - defer matcher.deinit(); +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; var umbrella_libs = std.StringHashMap(void).init(allocator); defer umbrella_libs.deinit(); + log.debug("found umbrella lib '{s}'", .{umbrella_lib.installName()}); + + var matcher = try TargetMatcher.init(allocator, target); + defer matcher.deinit(); + for (lib_stub.inner) |elem, stub_index| { - const stub = elem.v4; - if (!matcher.matches(stub.targets)) continue; + const is_match = switch (elem) { + .v3 => |stub| matcher.matchesArch(stub.archs), + .v4 => |stub| matcher.matchesTarget(stub.targets), + }; + if (!is_match) 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, .{}); + try umbrella_libs.put(elem.installName(), .{}); } - if (stub.exports) |exports| { - for (exports) |exp| { - if (!matcher.matches(exp.targets)) continue; + switch (elem) { + .v3 => |stub| { + if (stub.exports) |exports| { + for (exports) |exp| { + if (!matcher.matchesArch(exp.archs)) continue; + + if (exp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } - 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.objc_classes) |objc_classes| { + for (objc_classes) |class_name| { + try self.addObjCClassSymbol(allocator, class_name); + } + } + + // TODO track which libs were already parsed in different steps + if (exp.re_exports) |re_exports| { + for (re_exports) |lib| { + if (umbrella_libs.contains(lib)) continue; + + log.debug(" (found re-export '{s}')", .{lib}); + + const dep_id = try Id.default(allocator, lib); + try self.dependent_libs.append(allocator, dep_id); + } + } } } + }, + .v4 => |stub| { + if (stub.exports) |exports| { + for (exports) |exp| { + if (!matcher.matchesTarget(exp.targets)) continue; + + if (exp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } - if (exp.objc_classes) |classes| { - for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); + if (exp.objc_classes) |classes| { + for (classes) |sym_name| { + try self.addObjCClassSymbol(allocator, sym_name); + } + } } } - } - } - if (stub.reexports) |reexports| { - for (reexports) |reexp| { - if (!matcher.matches(reexp.targets)) continue; + if (stub.reexports) |reexports| { + for (reexports) |reexp| { + if (!matcher.matchesTarget(reexp.targets)) continue; + + if (reexp.symbols) |symbols| { + for (symbols) |sym_name| { + try self.addSymbol(allocator, sym_name); + } + } - if (reexp.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 (reexp.objc_classes) |classes| { + for (classes) |sym_name| { + try self.addObjCClassSymbol(allocator, sym_name); + } + } } } - if (reexp.objc_classes) |classes| { + if (stub.objc_classes) |classes| { for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); + try self.addObjCClassSymbol(allocator, sym_name); } } - } - } - - if (stub.objc_classes) |classes| { - for (classes) |sym_name| { - try self.addObjCClassSymbols(allocator, sym_name); - } + }, } } - log.debug("{s}", .{lib_stub.inner[0].installName()}); - - // TODO track which libs were already parsed in different steps - for (lib_stub.inner) |elem| { + // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include + // re-exports directly in the stub file. + for (lib_stub.inner) |elem, stub_index| { + if (elem == .v3) break; const stub = elem.v4; - if (!matcher.matches(stub.targets)) continue; + // TODO track which libs were already parsed in different steps if (stub.reexported_libraries) |reexports| { for (reexports) |reexp| { - if (!matcher.matches(reexp.targets)) continue; + if (!matcher.matchesTarget(reexp.targets)) continue; for (reexp.libraries) |lib| { - if (umbrella_libs.contains(lib)) { - log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() }); - continue; - } + if (umbrella_libs.contains(lib)) continue; - log.debug(" | {s}", .{lib}); + log.debug(" (found re-export '{s}')", .{lib}); const dep_id = try Id.default(allocator, lib); try self.dependent_libs.append(allocator, dep_id); @@ -547,28 +542,6 @@ fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_ } } -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, @@ -619,7 +592,7 @@ pub fn parseDependentLibs( continue :outer; } else { - log.warn("unable to resolve dependency {s}", .{id.name}); + log.debug("unable to resolve dependency {s}", .{id.name}); } } } diff --git a/src/link/tapi.zig b/src/link/tapi.zig index e412651c08..d10942ea02 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -105,7 +105,7 @@ pub const LibStub = struct { .inner = undefined, }; - // TODO clean this up. + // TODO revisit this logic in the hope of simplifying it. lib_stub.inner = blk: { err: { log.debug("trying to parse as []TbdV4", .{}); @@ -133,8 +133,7 @@ pub const LibStub = struct { break :blk out; } - // TODO this is clunky. Perhaps an optional would be better here? - return error.TypeMismatch; + return error.NotLibStub; }; return lib_stub; |
