authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-08-11 22:48:03+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-08-11 22:48:03+02:00
log0686954802d17e87114542878615fd0a9a245e49
tree0b00c0add254a980d2b7ed6676769ac0f20ef71a
parent60a5552d414ffedf84117df57963fd5bf099c2ea
parent5d548cc65125cc33ebf4840fb73a97030b1d0505
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #9549 from ziglang/tapi-v3

macho: handle TAPI v3 and simplify handling of dependent dynamic libraries

6 files changed, 393 insertions(+), 318 deletions(-)

src/link/MachO.zig+137-37
...@@ -31,6 +31,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig");...@@ -31,6 +31,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig");
31const Dylib = @import("MachO/Dylib.zig");31const Dylib = @import("MachO/Dylib.zig");
32const File = link.File;32const File = link.File;
33const Object = @import("MachO/Object.zig");33const Object = @import("MachO/Object.zig");
34const LibStub = @import("tapi.zig").LibStub;
34const Liveness = @import("../Liveness.zig");35const Liveness = @import("../Liveness.zig");
35const LlvmObject = @import("../codegen/llvm.zig").Object;36const LlvmObject = @import("../codegen/llvm.zig").Object;
36const LoadCommand = commands.LoadCommand;37const LoadCommand = commands.LoadCommand;
...@@ -65,6 +66,7 @@ objects: std.ArrayListUnmanaged(Object) = .{},...@@ -65,6 +66,7 @@ objects: std.ArrayListUnmanaged(Object) = .{},
65archives: std.ArrayListUnmanaged(Archive) = .{},66archives: std.ArrayListUnmanaged(Archive) = .{},
6667
67dylibs: std.ArrayListUnmanaged(Dylib) = .{},68dylibs: std.ArrayListUnmanaged(Dylib) = .{},
69dylibs_map: std.StringHashMapUnmanaged(u16) = .{},
68referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{},70referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{},
6971
70load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},72load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
...@@ -994,6 +996,133 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {...@@ -994,6 +996,133 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {
994 }996 }
995}997}
996998
999fn parseObject(self: *MachO, path: []const u8) !bool {
1000 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
1001 error.FileNotFound => return false,
1002 else => |e| return e,
1003 };
1004 errdefer file.close();
1005
1006 const name = try self.base.allocator.dupe(u8, path);
1007 errdefer self.base.allocator.free(name);
1008
1009 var object = Object{
1010 .name = name,
1011 .file = file,
1012 };
1013
1014 object.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
1015 error.EndOfStream, error.NotObject => {
1016 object.deinit(self.base.allocator);
1017 return false;
1018 },
1019 else => |e| return e,
1020 };
1021
1022 try self.objects.append(self.base.allocator, object);
1023
1024 return true;
1025}
1026
1027fn parseArchive(self: *MachO, path: []const u8) !bool {
1028 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
1029 error.FileNotFound => return false,
1030 else => |e| return e,
1031 };
1032 errdefer file.close();
1033
1034 const name = try self.base.allocator.dupe(u8, path);
1035 errdefer self.base.allocator.free(name);
1036
1037 var archive = Archive{
1038 .name = name,
1039 .file = file,
1040 };
1041
1042 archive.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
1043 error.EndOfStream, error.NotArchive => {
1044 archive.deinit(self.base.allocator);
1045 return false;
1046 },
1047 else => |e| return e,
1048 };
1049
1050 try self.archives.append(self.base.allocator, archive);
1051
1052 return true;
1053}
1054
1055const ParseDylibError = error{
1056 OutOfMemory,
1057 EmptyStubFile,
1058 MismatchedCpuArchitecture,
1059 UnsupportedCpuArchitecture,
1060} || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError;
1061
1062const DylibCreateOpts = struct {
1063 syslibroot: ?[]const u8 = null,
1064 id: ?Dylib.Id = null,
1065 is_dependent: bool = false,
1066};
1067
1068pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool {
1069 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
1070 error.FileNotFound => return false,
1071 else => |e| return e,
1072 };
1073 errdefer file.close();
1074
1075 const name = try self.base.allocator.dupe(u8, path);
1076 errdefer self.base.allocator.free(name);
1077
1078 var dylib = Dylib{
1079 .name = name,
1080 .file = file,
1081 };
1082
1083 dylib.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
1084 error.EndOfStream, error.NotDylib => {
1085 try file.seekTo(0);
1086
1087 var lib_stub = LibStub.loadFromFile(self.base.allocator, file) catch {
1088 dylib.deinit(self.base.allocator);
1089 return false;
1090 };
1091 defer lib_stub.deinit();
1092
1093 try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub);
1094 },
1095 else => |e| return e,
1096 };
1097
1098 if (opts.id) |id| {
1099 if (dylib.id.?.current_version < id.compatibility_version) {
1100 log.warn("found dylib is incompatible with the required minimum version", .{});
1101 log.warn(" dylib: {s}", .{id.name});
1102 log.warn(" required minimum version: {}", .{id.compatibility_version});
1103 log.warn(" dylib version: {}", .{dylib.id.?.current_version});
1104
1105 // TODO maybe this should be an error and facilitate auto-cleanup?
1106 dylib.deinit(self.base.allocator);
1107 return false;
1108 }
1109 }
1110
1111 const dylib_id = @intCast(u16, self.dylibs.items.len);
1112 try self.dylibs.append(self.base.allocator, dylib);
1113 try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id);
1114
1115 if (!(opts.is_dependent or self.referenced_dylibs.contains(dylib_id))) {
1116 try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
1117 }
1118
1119 // TODO this should not be performed if the user specifies `-flat_namespace` flag.
1120 // See ld64 manpages.
1121 try dylib.parseDependentLibs(self, opts.syslibroot);
1122
1123 return true;
1124}
1125
997fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void {1126fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void {
998 for (files) |file_name| {1127 for (files) |file_name| {
999 const full_path = full_path: {1128 const full_path = full_path: {
...@@ -1003,28 +1132,11 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const...@@ -1003,28 +1132,11 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const
1003 };1132 };
1004 defer self.base.allocator.free(full_path);1133 defer self.base.allocator.free(full_path);
10051134
1006 if (try Object.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |object| {1135 if (try self.parseObject(full_path)) continue;
1007 try self.objects.append(self.base.allocator, object);1136 if (try self.parseArchive(full_path)) continue;
1008 continue;1137 if (try self.parseDylib(full_path, .{
1009 }
1010
1011 if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |archive| {
1012 try self.archives.append(self.base.allocator, archive);
1013 continue;
1014 }
1015
1016 if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path, .{
1017 .syslibroot = syslibroot,1138 .syslibroot = syslibroot,
1018 })) |dylibs| {1139 })) continue;
1019 defer self.base.allocator.free(dylibs);
1020 const dylib_id = @intCast(u16, self.dylibs.items.len);
1021 try self.dylibs.appendSlice(self.base.allocator, dylibs);
1022 // We always have to add the dylib that was on the linker line.
1023 if (!self.referenced_dylibs.contains(dylib_id)) {
1024 try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
1025 }
1026 continue;
1027 }
10281140
1029 log.warn("unknown filetype for positional input file: '{s}'", .{file_name});1141 log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
1030 }1142 }
...@@ -1032,23 +1144,10 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const...@@ -1032,23 +1144,10 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const
10321144
1033fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void {1145fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void {
1034 for (libs) |lib| {1146 for (libs) |lib| {
1035 if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, lib, .{1147 if (try self.parseDylib(lib, .{
1036 .syslibroot = syslibroot,1148 .syslibroot = syslibroot,
1037 })) |dylibs| {1149 })) continue;
1038 defer self.base.allocator.free(dylibs);1150 if (try self.parseArchive(lib)) continue;
1039 const dylib_id = @intCast(u16, self.dylibs.items.len);
1040 try self.dylibs.appendSlice(self.base.allocator, dylibs);
1041 // We always have to add the dylib that was on the linker line.
1042 if (!self.referenced_dylibs.contains(dylib_id)) {
1043 try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
1044 }
1045 continue;
1046 }
1047
1048 if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, lib)) |archive| {
1049 try self.archives.append(self.base.allocator, archive);
1050 continue;
1051 }
10521151
1053 log.warn("unknown filetype for a library: '{s}'", .{lib});1152 log.warn("unknown filetype for a library: '{s}'", .{lib});
1054 }1153 }
...@@ -3360,6 +3459,7 @@ pub fn deinit(self: *MachO) void {...@@ -3360,6 +3459,7 @@ pub fn deinit(self: *MachO) void {
3360 dylib.deinit(self.base.allocator);3459 dylib.deinit(self.base.allocator);
3361 }3460 }
3362 self.dylibs.deinit(self.base.allocator);3461 self.dylibs.deinit(self.base.allocator);
3462 self.dylibs_map.deinit(self.base.allocator);
3363 self.referenced_dylibs.deinit(self.base.allocator);3463 self.referenced_dylibs.deinit(self.base.allocator);
33643464
3365 for (self.load_commands.items) |*lc| {3465 for (self.load_commands.items) |*lc| {
src/link/MachO/Archive.zig-26
...@@ -103,32 +103,6 @@ pub fn deinit(self: *Archive, allocator: *Allocator) void {...@@ -103,32 +103,6 @@ pub fn deinit(self: *Archive, allocator: *Allocator) void {
103 allocator.free(self.name);103 allocator.free(self.name);
104}104}
105105
106pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Archive {
107 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
108 error.FileNotFound => return null,
109 else => |e| return e,
110 };
111 errdefer file.close();
112
113 const name = try allocator.dupe(u8, path);
114 errdefer allocator.free(name);
115
116 var archive = Archive{
117 .name = name,
118 .file = file,
119 };
120
121 archive.parse(allocator, target) catch |err| switch (err) {
122 error.EndOfStream, error.NotArchive => {
123 archive.deinit(allocator);
124 return null;
125 },
126 else => |e| return e,
127 };
128
129 return archive;
130}
131
132pub fn parse(self: *Archive, allocator: *Allocator, target: std.Target) !void {106pub fn parse(self: *Archive, allocator: *Allocator, target: std.Target) !void {
133 const reader = self.file.reader();107 const reader = self.file.reader();
134 self.library_offset = try fat.getLibraryOffset(reader, target);108 self.library_offset = try fat.getLibraryOffset(reader, target);
src/link/MachO/Dylib.zig+144-176
...@@ -73,7 +73,7 @@ pub const Id = struct {...@@ -73,7 +73,7 @@ pub const Id = struct {
73 allocator.free(id.name);73 allocator.free(id.name);
74 }74 }
7575
76 const ParseError = fmt.ParseIntError || fmt.BufPrintError;76 pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
7777
78 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {78 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
79 id.current_version = try parseVersion(version);79 id.current_version = try parseVersion(version);
...@@ -109,7 +109,7 @@ pub const Id = struct {...@@ -109,7 +109,7 @@ pub const Id = struct {
109 var count: u4 = 0;109 var count: u4 = 0;
110 while (split.next()) |value| {110 while (split.next()) |value| {
111 if (count > 2) {111 if (count > 2) {
112 log.warn("malformed version field: {s}", .{string});112 log.debug("malformed version field: {s}", .{string});
113 return 0x10000;113 return 0x10000;
114 }114 }
115 values[count] = value;115 values[count] = value;
...@@ -128,78 +128,6 @@ pub const Id = struct {...@@ -128,78 +128,6 @@ pub const Id = struct {
128 }128 }
129};129};
130130
131pub const Error = error{
132 OutOfMemory,
133 EmptyStubFile,
134 MismatchedCpuArchitecture,
135 UnsupportedCpuArchitecture,
136} || fs.File.OpenError || std.os.PReadError || Id.ParseError;
137
138pub const CreateOpts = struct {
139 syslibroot: ?[]const u8 = null,
140 id: ?Id = null,
141 target: ?std.Target = null,
142};
143
144pub fn createAndParseFromPath(
145 allocator: *Allocator,
146 target: std.Target,
147 path: []const u8,
148 opts: CreateOpts,
149) Error!?[]Dylib {
150 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
151 error.FileNotFound => return null,
152 else => |e| return e,
153 };
154 errdefer file.close();
155
156 const name = try allocator.dupe(u8, path);
157 errdefer allocator.free(name);
158
159 var dylib = Dylib{
160 .name = name,
161 .file = file,
162 };
163
164 dylib.parse(allocator, target) catch |err| switch (err) {
165 error.EndOfStream, error.NotDylib => {
166 try file.seekTo(0);
167
168 var lib_stub = LibStub.loadFromFile(allocator, file) catch {
169 dylib.deinit(allocator);
170 return null;
171 };
172 defer lib_stub.deinit();
173
174 try dylib.parseFromStub(allocator, target, lib_stub);
175 },
176 else => |e| return e,
177 };
178
179 if (opts.id) |id| {
180 if (dylib.id.?.current_version < id.compatibility_version) {
181 log.warn("found dylib is incompatible with the required minimum version", .{});
182 log.warn(" | dylib: {s}", .{id.name});
183 log.warn(" | required minimum version: {}", .{id.compatibility_version});
184 log.warn(" | dylib version: {}", .{dylib.id.?.current_version});
185
186 // TODO maybe this should be an error and facilitate auto-cleanup?
187 dylib.deinit(allocator);
188 return null;
189 }
190 }
191
192 var dylibs = std.ArrayList(Dylib).init(allocator);
193 defer dylibs.deinit();
194
195 try dylibs.append(dylib);
196 // TODO this should not be performed if the user specifies `-flat_namespace` flag.
197 // See ld64 manpages.
198 try dylib.parseDependentLibs(allocator, target, &dylibs, opts.syslibroot);
199
200 return dylibs.toOwnedSlice();
201}
202
203pub fn deinit(self: *Dylib, allocator: *Allocator) void {131pub fn deinit(self: *Dylib, allocator: *Allocator) void {
204 for (self.load_commands.items) |*lc| {132 for (self.load_commands.items) |*lc| {
205 lc.deinit(allocator);133 lc.deinit(allocator);
...@@ -315,14 +243,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {...@@ -315,14 +243,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {
315 }243 }
316}244}
317245
318fn hasTarget(targets: []const []const u8, target: []const u8) bool {246fn addObjCClassSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
319 for (targets) |t| {
320 if (mem.eql(u8, t, target)) return true;
321 }
322 return false;
323}
324
325fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
326 const expanded = &[_][]const u8{247 const expanded = &[_][]const u8{
327 try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),248 try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),
328 try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),249 try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),
...@@ -334,30 +255,21 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8...@@ -334,30 +255,21 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8
334 }255 }
335}256}
336257
337fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {258fn addSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
338 const arch = switch (target.cpu.arch) {259 if (self.symbols.contains(sym_name)) return;
339 .aarch64 => "arm64",260 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
340 .x86_64 => "x86_64",
341 else => unreachable,
342 };
343 const os = @tagName(target.os.tag);
344 const abi: ?[]const u8 = switch (target.abi) {
345 .gnu => null,
346 .simulator => "simulator",
347 else => unreachable,
348 };
349 if (abi) |x| {
350 return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x });
351 }
352 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os });
353}261}
354262
355const TargetMatcher = struct {263const TargetMatcher = struct {
356 allocator: *Allocator,264 allocator: *Allocator,
265 target: std.Target,
357 target_strings: std.ArrayListUnmanaged([]const u8) = .{},266 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
358267
359 fn init(allocator: *Allocator, target: std.Target) !TargetMatcher {268 fn init(allocator: *Allocator, target: std.Target) !TargetMatcher {
360 var self = TargetMatcher{ .allocator = allocator };269 var self = TargetMatcher{
270 .allocator = allocator,
271 .target = target,
272 };
361 try self.target_strings.append(allocator, try targetToAppleString(allocator, target));273 try self.target_strings.append(allocator, try targetToAppleString(allocator, target));
362274
363 if (target.abi == .simulator) {275 if (target.abi == .simulator) {
...@@ -380,12 +292,41 @@ const TargetMatcher = struct {...@@ -380,12 +292,41 @@ const TargetMatcher = struct {
380 self.target_strings.deinit(self.allocator);292 self.target_strings.deinit(self.allocator);
381 }293 }
382294
383 fn matches(self: TargetMatcher, targets: []const []const u8) bool {295 fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {
296 const arch = switch (target.cpu.arch) {
297 .aarch64 => "arm64",
298 .x86_64 => "x86_64",
299 else => unreachable,
300 };
301 const os = @tagName(target.os.tag);
302 const abi: ?[]const u8 = switch (target.abi) {
303 .gnu => null,
304 .simulator => "simulator",
305 else => unreachable,
306 };
307 if (abi) |x| {
308 return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x });
309 }
310 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os });
311 }
312
313 fn hasValue(stack: []const []const u8, needle: []const u8) bool {
314 for (stack) |v| {
315 if (mem.eql(u8, v, needle)) return true;
316 }
317 return false;
318 }
319
320 fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
384 for (self.target_strings.items) |t| {321 for (self.target_strings.items) |t| {
385 if (hasTarget(targets, t)) return true;322 if (hasValue(targets, t)) return true;
386 }323 }
387 return false;324 return false;
388 }325 }
326
327 fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
328 return hasValue(archs, @tagName(self.target.cpu.arch));
329 }
389};330};
390331
391pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {332pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
...@@ -395,93 +336,130 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li...@@ -395,93 +336,130 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
395336
396 const umbrella_lib = lib_stub.inner[0];337 const umbrella_lib = lib_stub.inner[0];
397338
398 var id = try Id.default(allocator, umbrella_lib.install_name);339 var id = try Id.default(allocator, umbrella_lib.installName());
399 if (umbrella_lib.current_version) |version| {340 if (umbrella_lib.currentVersion()) |version| {
400 try id.parseCurrentVersion(version);341 try id.parseCurrentVersion(version);
401 }342 }
402 if (umbrella_lib.compatibility_version) |version| {343 if (umbrella_lib.compatibilityVersion()) |version| {
403 try id.parseCompatibilityVersion(version);344 try id.parseCompatibilityVersion(version);
404 }345 }
405 self.id = id;346 self.id = id;
406347
407 var matcher = try TargetMatcher.init(allocator, target);
408 defer matcher.deinit();
409
410 var umbrella_libs = std.StringHashMap(void).init(allocator);348 var umbrella_libs = std.StringHashMap(void).init(allocator);
411 defer umbrella_libs.deinit();349 defer umbrella_libs.deinit();
412350
413 for (lib_stub.inner) |stub, stub_index| {351 log.debug(" (install_name '{s}')", .{umbrella_lib.installName()});
414 if (!matcher.matches(stub.targets)) continue;352
353 var matcher = try TargetMatcher.init(allocator, target);
354 defer matcher.deinit();
355
356 for (lib_stub.inner) |elem, stub_index| {
357 const is_match = switch (elem) {
358 .v3 => |stub| matcher.matchesArch(stub.archs),
359 .v4 => |stub| matcher.matchesTarget(stub.targets),
360 };
361 if (!is_match) continue;
415362
416 if (stub_index > 0) {363 if (stub_index > 0) {
417 // TODO I thought that we could switch on presence of `parent-umbrella` map;364 // TODO I thought that we could switch on presence of `parent-umbrella` map;
418 // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`365 // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`
419 // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps?366 // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps?
420 try umbrella_libs.put(stub.install_name, .{});367 try umbrella_libs.put(elem.installName(), .{});
421 }368 }
422369
423 if (stub.exports) |exports| {370 switch (elem) {
424 for (exports) |exp| {371 .v3 => |stub| {
425 if (!matcher.matches(exp.targets)) continue;372 if (stub.exports) |exports| {
426373 for (exports) |exp| {
427 if (exp.symbols) |symbols| {374 if (!matcher.matchesArch(exp.archs)) continue;
428 for (symbols) |sym_name| {375
429 if (self.symbols.contains(sym_name)) continue;376 if (exp.symbols) |symbols| {
430 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});377 for (symbols) |sym_name| {
378 try self.addSymbol(allocator, sym_name);
379 }
380 }
381
382 if (exp.objc_classes) |objc_classes| {
383 for (objc_classes) |class_name| {
384 try self.addObjCClassSymbol(allocator, class_name);
385 }
386 }
387
388 // TODO track which libs were already parsed in different steps
389 if (exp.re_exports) |re_exports| {
390 for (re_exports) |lib| {
391 if (umbrella_libs.contains(lib)) continue;
392
393 log.debug(" (found re-export '{s}')", .{lib});
394
395 const dep_id = try Id.default(allocator, lib);
396 try self.dependent_libs.append(allocator, dep_id);
397 }
398 }
431 }399 }
432 }400 }
433401 },
434 if (exp.objc_classes) |classes| {402 .v4 => |stub| {
435 for (classes) |sym_name| {403 if (stub.exports) |exports| {
436 try self.addObjCClassSymbols(allocator, sym_name);404 for (exports) |exp| {
405 if (!matcher.matchesTarget(exp.targets)) continue;
406
407 if (exp.symbols) |symbols| {
408 for (symbols) |sym_name| {
409 try self.addSymbol(allocator, sym_name);
410 }
411 }
412
413 if (exp.objc_classes) |classes| {
414 for (classes) |sym_name| {
415 try self.addObjCClassSymbol(allocator, sym_name);
416 }
417 }
437 }418 }
438 }419 }
439 }
440 }
441
442 if (stub.reexports) |reexports| {
443 for (reexports) |reexp| {
444 if (!matcher.matches(reexp.targets)) continue;
445420
446 if (reexp.symbols) |symbols| {421 if (stub.reexports) |reexports| {
447 for (symbols) |sym_name| {422 for (reexports) |reexp| {
448 if (self.symbols.contains(sym_name)) continue;423 if (!matcher.matchesTarget(reexp.targets)) continue;
449 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});424
425 if (reexp.symbols) |symbols| {
426 for (symbols) |sym_name| {
427 try self.addSymbol(allocator, sym_name);
428 }
429 }
430
431 if (reexp.objc_classes) |classes| {
432 for (classes) |sym_name| {
433 try self.addObjCClassSymbol(allocator, sym_name);
434 }
435 }
450 }436 }
451 }437 }
452438
453 if (reexp.objc_classes) |classes| {439 if (stub.objc_classes) |classes| {
454 for (classes) |sym_name| {440 for (classes) |sym_name| {
455 try self.addObjCClassSymbols(allocator, sym_name);441 try self.addObjCClassSymbol(allocator, sym_name);
456 }442 }
457 }443 }
458 }444 },
459 }
460
461 if (stub.objc_classes) |classes| {
462 for (classes) |sym_name| {
463 try self.addObjCClassSymbols(allocator, sym_name);
464 }
465 }445 }
466 }446 }
467447
468 log.debug("{s}", .{umbrella_lib.install_name});448 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include
469449 // re-exports directly in the stub file.
470 // TODO track which libs were already parsed in different steps450 for (lib_stub.inner) |elem| {
471 for (lib_stub.inner) |stub| {451 if (elem == .v3) break;
472 if (!matcher.matches(stub.targets)) continue;452 const stub = elem.v4;
473453
454 // TODO track which libs were already parsed in different steps
474 if (stub.reexported_libraries) |reexports| {455 if (stub.reexported_libraries) |reexports| {
475 for (reexports) |reexp| {456 for (reexports) |reexp| {
476 if (!matcher.matches(reexp.targets)) continue;457 if (!matcher.matchesTarget(reexp.targets)) continue;
477458
478 for (reexp.libraries) |lib| {459 for (reexp.libraries) |lib| {
479 if (umbrella_libs.contains(lib)) {460 if (umbrella_libs.contains(lib)) continue;
480 log.debug(" | {s} <= {s}", .{ lib, umbrella_lib.install_name });
481 continue;
482 }
483461
484 log.debug(" | {s}", .{lib});462 log.debug(" (found re-export '{s}')", .{lib});
485463
486 const dep_id = try Id.default(allocator, lib);464 const dep_id = try Id.default(allocator, lib);
487 try self.dependent_libs.append(allocator, dep_id);465 try self.dependent_libs.append(allocator, dep_id);
...@@ -493,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li...@@ -493,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
493471
494pub fn parseDependentLibs(472pub fn parseDependentLibs(
495 self: *Dylib,473 self: *Dylib,
496 allocator: *Allocator,474 macho_file: *MachO,
497 target: std.Target,
498 out: *std.ArrayList(Dylib),
499 syslibroot: ?[]const u8,475 syslibroot: ?[]const u8,
500) !void {476) !void {
501 outer: for (self.dependent_libs.items) |id| {477 outer: for (self.dependent_libs.items) |id| {
478 if (macho_file.dylibs_map.contains(id.name)) continue :outer;
479
502 const has_ext = blk: {480 const has_ext = blk: {
503 const basename = fs.path.basename(id.name);481 const basename = fs.path.basename(id.name);
504 break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;482 break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
...@@ -510,38 +488,28 @@ pub fn parseDependentLibs(...@@ -510,38 +488,28 @@ pub fn parseDependentLibs(
510 } else id.name;488 } else id.name;
511489
512 for (&[_][]const u8{ extension, ".tbd" }) |ext| {490 for (&[_][]const u8{ extension, ".tbd" }) |ext| {
513 const with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{491 const with_ext = try std.fmt.allocPrint(macho_file.base.allocator, "{s}{s}", .{
514 without_ext,492 without_ext,
515 ext,493 ext,
516 });494 });
517 defer allocator.free(with_ext);495 defer macho_file.base.allocator.free(with_ext);
518496
519 const full_path = if (syslibroot) |root|497 const full_path = if (syslibroot) |root|
520 try fs.path.join(allocator, &.{ root, with_ext })498 try fs.path.join(macho_file.base.allocator, &.{ root, with_ext })
521 else499 else
522 with_ext;500 with_ext;
523 defer if (syslibroot) |_| allocator.free(full_path);501 defer if (syslibroot) |_| macho_file.base.allocator.free(full_path);
524502
525 log.debug("trying dependency at fully resolved path {s}", .{full_path});503 log.debug("trying dependency at fully resolved path {s}", .{full_path});
526504
527 const dylibs = (try createAndParseFromPath(505 const did_parse_successfully = try macho_file.parseDylib(full_path, .{
528 allocator,506 .id = id,
529 target,507 .syslibroot = syslibroot,
530 full_path,508 .is_dependent = true,
531 .{509 });
532 .id = id,510 if (!did_parse_successfully) continue;
533 .syslibroot = syslibroot,
534 },
535 )) orelse {
536 continue;
537 };
538 defer allocator.free(dylibs);
539
540 try out.appendSlice(dylibs);
541
542 continue :outer;
543 } else {511 } else {
544 log.warn("unable to resolve dependency {s}", .{id.name});512 log.debug("unable to resolve dependency {s}", .{id.name});
545 }513 }
546 }514 }
547}515}
src/link/MachO/Object.zig-26
...@@ -153,32 +153,6 @@ pub fn deinit(self: *Object, allocator: *Allocator) void {...@@ -153,32 +153,6 @@ pub fn deinit(self: *Object, allocator: *Allocator) void {
153 }153 }
154}154}
155155
156pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Object {
157 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
158 error.FileNotFound => return null,
159 else => |e| return e,
160 };
161 errdefer file.close();
162
163 const name = try allocator.dupe(u8, path);
164 errdefer allocator.free(name);
165
166 var object = Object{
167 .name = name,
168 .file = file,
169 };
170
171 object.parse(allocator, target) catch |err| switch (err) {
172 error.EndOfStream, error.NotObject => {
173 object.deinit(allocator);
174 return null;
175 },
176 else => |e| return e,
177 };
178
179 return object;
180}
181
182pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void {156pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void {
183 const reader = self.file.reader();157 const reader = self.file.reader();
184 if (self.file_offset) |offset| {158 if (self.file_offset) |offset| {
src/link/tapi.zig+111-52
...@@ -6,6 +6,89 @@ const log = std.log.scoped(.tapi);...@@ -6,6 +6,89 @@ const log = std.log.scoped(.tapi);
6const Allocator = mem.Allocator;6const Allocator = mem.Allocator;
7const Yaml = @import("tapi/yaml.zig").Yaml;7const Yaml = @import("tapi/yaml.zig").Yaml;
88
9const VersionField = union(enum) {
10 string: []const u8,
11 float: f64,
12 int: u64,
13};
14
15pub const TbdV3 = struct {
16 archs: []const []const u8,
17 uuids: []const []const u8,
18 platform: []const u8,
19 install_name: []const u8,
20 current_version: ?VersionField,
21 compatibility_version: ?VersionField,
22 objc_constraint: ?[]const u8,
23 exports: ?[]const struct {
24 archs: []const []const u8,
25 re_exports: ?[]const []const u8,
26 symbols: ?[]const []const u8,
27 objc_classes: ?[]const []const u8,
28 },
29};
30
31pub const TbdV4 = struct {
32 tbd_version: u3,
33 targets: []const []const u8,
34 uuids: []const struct {
35 target: []const u8,
36 value: []const u8,
37 },
38 install_name: []const u8,
39 current_version: ?VersionField,
40 compatibility_version: ?VersionField,
41 reexported_libraries: ?[]const struct {
42 targets: []const []const u8,
43 libraries: []const []const u8,
44 },
45 parent_umbrella: ?[]const struct {
46 targets: []const []const u8,
47 umbrella: []const u8,
48 },
49 exports: ?[]const struct {
50 targets: []const []const u8,
51 symbols: ?[]const []const u8,
52 objc_classes: ?[]const []const u8,
53 },
54 reexports: ?[]const struct {
55 targets: []const []const u8,
56 symbols: ?[]const []const u8,
57 objc_classes: ?[]const []const u8,
58 },
59 allowable_clients: ?[]const struct {
60 targets: []const []const u8,
61 clients: []const []const u8,
62 },
63 objc_classes: ?[]const []const u8,
64};
65
66pub const Tbd = union(enum) {
67 v3: TbdV3,
68 v4: TbdV4,
69
70 pub fn currentVersion(self: Tbd) ?VersionField {
71 return switch (self) {
72 .v3 => |v3| v3.current_version,
73 .v4 => |v4| v4.current_version,
74 };
75 }
76
77 pub fn compatibilityVersion(self: Tbd) ?VersionField {
78 return switch (self) {
79 .v3 => |v3| v3.compatibility_version,
80 .v4 => |v4| v4.compatibility_version,
81 };
82 }
83
84 pub fn installName(self: Tbd) []const u8 {
85 return switch (self) {
86 .v3 => |v3| v3.install_name,
87 .v4 => |v4| v4.install_name,
88 };
89 }
90};
91
9pub const LibStub = struct {92pub const LibStub = struct {
10 /// Underlying memory for stub's contents.93 /// Underlying memory for stub's contents.
11 yaml: Yaml,94 yaml: Yaml,
...@@ -13,49 +96,6 @@ pub const LibStub = struct {...@@ -13,49 +96,6 @@ pub const LibStub = struct {
13 /// Typed contents of the tbd file.96 /// Typed contents of the tbd file.
14 inner: []Tbd,97 inner: []Tbd,
1598
16 const Tbd = struct {
17 tbd_version: u3,
18 targets: []const []const u8,
19 uuids: []const struct {
20 target: []const u8,
21 value: []const u8,
22 },
23 install_name: []const u8,
24 current_version: ?union(enum) {
25 string: []const u8,
26 float: f64,
27 int: u64,
28 },
29 compatibility_version: ?union(enum) {
30 string: []const u8,
31 float: f64,
32 int: u64,
33 },
34 reexported_libraries: ?[]const struct {
35 targets: []const []const u8,
36 libraries: []const []const u8,
37 },
38 parent_umbrella: ?[]const struct {
39 targets: []const []const u8,
40 umbrella: []const u8,
41 },
42 exports: ?[]const struct {
43 targets: []const []const u8,
44 symbols: ?[]const []const u8,
45 objc_classes: ?[]const []const u8,
46 },
47 reexports: ?[]const struct {
48 targets: []const []const u8,
49 symbols: ?[]const []const u8,
50 objc_classes: ?[]const []const u8,
51 },
52 allowable_clients: ?[]const struct {
53 targets: []const []const u8,
54 clients: []const []const u8,
55 },
56 objc_classes: ?[]const []const u8,
57 };
58
59 pub fn loadFromFile(allocator: *Allocator, file: fs.File) !LibStub {99 pub fn loadFromFile(allocator: *Allocator, file: fs.File) !LibStub {
60 const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32));100 const source = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
61 defer allocator.free(source);101 defer allocator.free(source);
...@@ -65,16 +105,35 @@ pub const LibStub = struct {...@@ -65,16 +105,35 @@ pub const LibStub = struct {
65 .inner = undefined,105 .inner = undefined,
66 };106 };
67107
68 lib_stub.inner = lib_stub.yaml.parse([]Tbd) catch |err| blk: {108 // TODO revisit this logic in the hope of simplifying it.
69 switch (err) {109 lib_stub.inner = blk: {
70 error.TypeMismatch => {110 err: {
71 // TODO clean this up.111 log.debug("trying to parse as []TbdV4", .{});
72 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);112 const inner = lib_stub.yaml.parse([]TbdV4) catch break :err;
73 out[0] = try lib_stub.yaml.parse(Tbd);113 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, inner.len);
74 break :blk out;114 for (inner) |doc, i| {
75 },115 out[i] = .{ .v4 = doc };
76 else => |e| return e,116 }
117 break :blk out;
118 }
119
120 err: {
121 log.debug("trying to parse as TbdV4", .{});
122 const inner = lib_stub.yaml.parse(TbdV4) catch break :err;
123 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
124 out[0] = .{ .v4 = inner };
125 break :blk out;
126 }
127
128 err: {
129 log.debug("trying to parse as TbdV3", .{});
130 const inner = lib_stub.yaml.parse(TbdV3) catch break :err;
131 var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
132 out[0] = .{ .v3 = inner };
133 break :blk out;
77 }134 }
135
136 return error.NotLibStub;
78 };137 };
79138
80 return lib_stub;139 return lib_stub;
src/link/tapi/yaml.zig+1-1
...@@ -371,7 +371,7 @@ pub const Yaml = struct {...@@ -371,7 +371,7 @@ pub const Yaml = struct {
371 }371 }
372372
373 const unwrapped = value orelse {373 const unwrapped = value orelse {
374 log.err("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) });374 log.debug("missing struct field: {s}: {s}", .{ field.name, @typeName(field.field_type) });
375 return error.StructFieldMissing;375 return error.StructFieldMissing;
376 };376 };
377 @field(parsed, field.name) = try self.parseValue(field.field_type, unwrapped);377 @field(parsed, field.name) = try self.parseValue(field.field_type, unwrapped);