authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-08-11 17:09:13+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-08-11 19:38:00+02:00
log5d548cc65125cc33ebf4840fb73a97030b1d0505
tree0b00c0add254a980d2b7ed6676769ac0f20ef71a
parent16bb5c05f15e1ec4cc1616c5c33e56f67ea0763e

macho: move parsing logic for Object, Archive and Dylib into MachO

This way, the functionality is better segregated, and we finally do not unnecessarily reparse dynamic libraries that were already visited and parsed.

4 files changed, 155 insertions(+), 190 deletions(-)

src/link/MachO.zig+137-37
......@@ -31,6 +31,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig");
3131const Dylib = @import("MachO/Dylib.zig");
3232const File = link.File;
3333const Object = @import("MachO/Object.zig");
34const LibStub = @import("tapi.zig").LibStub;
3435const Liveness = @import("../Liveness.zig");
3536const LlvmObject = @import("../codegen/llvm.zig").Object;
3637const LoadCommand = commands.LoadCommand;
......@@ -65,6 +66,7 @@ objects: std.ArrayListUnmanaged(Object) = .{},
6566archives: std.ArrayListUnmanaged(Archive) = .{},
6667
6768dylibs: std.ArrayListUnmanaged(Dylib) = .{},
69dylibs_map: std.StringHashMapUnmanaged(u16) = .{},
6870referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{},
6971
7072load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
......@@ -994,6 +996,133 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {
994996 }
995997}
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
9971126fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void {
9981127 for (files) |file_name| {
9991128 const full_path = full_path: {
......@@ -1003,28 +1132,11 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const
10031132 };
10041133 defer self.base.allocator.free(full_path);
10051134
1006 if (try Object.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |object| {
1007 try self.objects.append(self.base.allocator, object);
1008 continue;
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, .{
1135 if (try self.parseObject(full_path)) continue;
1136 if (try self.parseArchive(full_path)) continue;
1137 if (try self.parseDylib(full_path, .{
10171138 .syslibroot = syslibroot,
1018 })) |dylibs| {
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 }
1139 })) continue;
10281140
10291141 log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
10301142 }
......@@ -1032,23 +1144,10 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const
10321144
10331145fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void {
10341146 for (libs) |lib| {
1035 if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, lib, .{
1147 if (try self.parseDylib(lib, .{
10361148 .syslibroot = syslibroot,
1037 })) |dylibs| {
1038 defer self.base.allocator.free(dylibs);
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 }
1149 })) continue;
1150 if (try self.parseArchive(lib)) continue;
10521151
10531152 log.warn("unknown filetype for a library: '{s}'", .{lib});
10541153 }
......@@ -3360,6 +3459,7 @@ pub fn deinit(self: *MachO) void {
33603459 dylib.deinit(self.base.allocator);
33613460 }
33623461 self.dylibs.deinit(self.base.allocator);
3462 self.dylibs_map.deinit(self.base.allocator);
33633463 self.referenced_dylibs.deinit(self.base.allocator);
33643464
33653465 for (self.load_commands.items) |*lc| {
src/link/MachO/Archive.zig-26
......@@ -103,32 +103,6 @@ pub fn deinit(self: *Archive, allocator: *Allocator) void {
103103 allocator.free(self.name);
104104}
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
132106pub fn parse(self: *Archive, allocator: *Allocator, target: std.Target) !void {
133107 const reader = self.file.reader();
134108 self.library_offset = try fat.getLibraryOffset(reader, target);
src/link/MachO/Dylib.zig+18-101
......@@ -10,10 +10,9 @@ const math = std.math;
1010const mem = std.mem;
1111const fat = @import("fat.zig");
1212const commands = @import("commands.zig");
13const tapi = @import("../tapi.zig");
1413
1514const Allocator = mem.Allocator;
16const LibStub = tapi.LibStub;
15const LibStub = @import("../tapi.zig").LibStub;
1716const LoadCommand = commands.LoadCommand;
1817const MachO = @import("../MachO.zig");
1918
......@@ -74,7 +73,7 @@ pub const Id = struct {
7473 allocator.free(id.name);
7574 }
7675
77 const ParseError = fmt.ParseIntError || fmt.BufPrintError;
76 pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
7877
7978 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
8079 id.current_version = try parseVersion(version);
......@@ -110,7 +109,7 @@ pub const Id = struct {
110109 var count: u4 = 0;
111110 while (split.next()) |value| {
112111 if (count > 2) {
113 log.warn("malformed version field: {s}", .{string});
112 log.debug("malformed version field: {s}", .{string});
114113 return 0x10000;
115114 }
116115 values[count] = value;
......@@ -129,78 +128,6 @@ pub const Id = struct {
129128 }
130129};
131130
132pub const Error = error{
133 OutOfMemory,
134 EmptyStubFile,
135 MismatchedCpuArchitecture,
136 UnsupportedCpuArchitecture,
137} || fs.File.OpenError || std.os.PReadError || Id.ParseError;
138
139pub const CreateOpts = struct {
140 syslibroot: ?[]const u8 = null,
141 id: ?Id = null,
142 target: ?std.Target = null,
143};
144
145pub fn createAndParseFromPath(
146 allocator: *Allocator,
147 target: std.Target,
148 path: []const u8,
149 opts: CreateOpts,
150) Error!?[]Dylib {
151 const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) {
152 error.FileNotFound => return null,
153 else => |e| return e,
154 };
155 errdefer file.close();
156
157 const name = try allocator.dupe(u8, path);
158 errdefer allocator.free(name);
159
160 var dylib = Dylib{
161 .name = name,
162 .file = file,
163 };
164
165 dylib.parse(allocator, target) catch |err| switch (err) {
166 error.EndOfStream, error.NotDylib => {
167 try file.seekTo(0);
168
169 var lib_stub = LibStub.loadFromFile(allocator, file) catch {
170 dylib.deinit(allocator);
171 return null;
172 };
173 defer lib_stub.deinit();
174
175 try dylib.parseFromStub(allocator, target, lib_stub);
176 },
177 else => |e| return e,
178 };
179
180 if (opts.id) |id| {
181 if (dylib.id.?.current_version < id.compatibility_version) {
182 log.warn("found dylib is incompatible with the required minimum version", .{});
183 log.warn(" dylib: {s}", .{id.name});
184 log.warn(" required minimum version: {}", .{id.compatibility_version});
185 log.warn(" dylib version: {}", .{dylib.id.?.current_version});
186
187 // TODO maybe this should be an error and facilitate auto-cleanup?
188 dylib.deinit(allocator);
189 return null;
190 }
191 }
192
193 var dylibs = std.ArrayList(Dylib).init(allocator);
194 defer dylibs.deinit();
195
196 try dylibs.append(dylib);
197 // TODO this should not be performed if the user specifies `-flat_namespace` flag.
198 // See ld64 manpages.
199 try dylib.parseDependentLibs(allocator, target, &dylibs, opts.syslibroot);
200
201 return dylibs.toOwnedSlice();
202}
203
204131pub fn deinit(self: *Dylib, allocator: *Allocator) void {
205132 for (self.load_commands.items) |*lc| {
206133 lc.deinit(allocator);
......@@ -421,7 +348,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
421348 var umbrella_libs = std.StringHashMap(void).init(allocator);
422349 defer umbrella_libs.deinit();
423350
424 log.debug("found umbrella lib '{s}'", .{umbrella_lib.installName()});
351 log.debug(" (install_name '{s}')", .{umbrella_lib.installName()});
425352
426353 var matcher = try TargetMatcher.init(allocator, target);
427354 defer matcher.deinit();
......@@ -520,7 +447,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
520447
521448 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include
522449 // re-exports directly in the stub file.
523 for (lib_stub.inner) |elem, stub_index| {
450 for (lib_stub.inner) |elem| {
524451 if (elem == .v3) break;
525452 const stub = elem.v4;
526453
......@@ -544,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
544471
545472pub fn parseDependentLibs(
546473 self: *Dylib,
547 allocator: *Allocator,
548 target: std.Target,
549 out: *std.ArrayList(Dylib),
474 macho_file: *MachO,
550475 syslibroot: ?[]const u8,
551476) !void {
552477 outer: for (self.dependent_libs.items) |id| {
478 if (macho_file.dylibs_map.contains(id.name)) continue :outer;
479
553480 const has_ext = blk: {
554481 const basename = fs.path.basename(id.name);
555482 break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
......@@ -561,36 +488,26 @@ pub fn parseDependentLibs(
561488 } else id.name;
562489
563490 for (&[_][]const u8{ extension, ".tbd" }) |ext| {
564 const with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{
491 const with_ext = try std.fmt.allocPrint(macho_file.base.allocator, "{s}{s}", .{
565492 without_ext,
566493 ext,
567494 });
568 defer allocator.free(with_ext);
495 defer macho_file.base.allocator.free(with_ext);
569496
570497 const full_path = if (syslibroot) |root|
571 try fs.path.join(allocator, &.{ root, with_ext })
498 try fs.path.join(macho_file.base.allocator, &.{ root, with_ext })
572499 else
573500 with_ext;
574 defer if (syslibroot) |_| allocator.free(full_path);
501 defer if (syslibroot) |_| macho_file.base.allocator.free(full_path);
575502
576503 log.debug("trying dependency at fully resolved path {s}", .{full_path});
577504
578 const dylibs = (try createAndParseFromPath(
579 allocator,
580 target,
581 full_path,
582 .{
583 .id = id,
584 .syslibroot = syslibroot,
585 },
586 )) orelse {
587 continue;
588 };
589 defer allocator.free(dylibs);
590
591 try out.appendSlice(dylibs);
592
593 continue :outer;
505 const did_parse_successfully = try macho_file.parseDylib(full_path, .{
506 .id = id,
507 .syslibroot = syslibroot,
508 .is_dependent = true,
509 });
510 if (!did_parse_successfully) continue;
594511 } else {
595512 log.debug("unable to resolve dependency {s}", .{id.name});
596513 }
src/link/MachO/Object.zig-26
......@@ -153,32 +153,6 @@ pub fn deinit(self: *Object, allocator: *Allocator) void {
153153 }
154154}
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
182156pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void {
183157 const reader = self.file.reader();
184158 if (self.file_offset) |offset| {