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");...@@ -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+18-101
...@@ -10,10 +10,9 @@ const math = std.math;...@@ -10,10 +10,9 @@ const math = std.math;
10const mem = std.mem;10const mem = std.mem;
11const fat = @import("fat.zig");11const fat = @import("fat.zig");
12const commands = @import("commands.zig");12const commands = @import("commands.zig");
13const tapi = @import("../tapi.zig");
1413
15const Allocator = mem.Allocator;14const Allocator = mem.Allocator;
16const LibStub = tapi.LibStub;15const LibStub = @import("../tapi.zig").LibStub;
17const LoadCommand = commands.LoadCommand;16const LoadCommand = commands.LoadCommand;
18const MachO = @import("../MachO.zig");17const MachO = @import("../MachO.zig");
1918
...@@ -74,7 +73,7 @@ pub const Id = struct {...@@ -74,7 +73,7 @@ pub const Id = struct {
74 allocator.free(id.name);73 allocator.free(id.name);
75 }74 }
7675
77 const ParseError = fmt.ParseIntError || fmt.BufPrintError;76 pub const ParseError = fmt.ParseIntError || fmt.BufPrintError;
7877
79 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {78 pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void {
80 id.current_version = try parseVersion(version);79 id.current_version = try parseVersion(version);
...@@ -110,7 +109,7 @@ pub const Id = struct {...@@ -110,7 +109,7 @@ pub const Id = struct {
110 var count: u4 = 0;109 var count: u4 = 0;
111 while (split.next()) |value| {110 while (split.next()) |value| {
112 if (count > 2) {111 if (count > 2) {
113 log.warn("malformed version field: {s}", .{string});112 log.debug("malformed version field: {s}", .{string});
114 return 0x10000;113 return 0x10000;
115 }114 }
116 values[count] = value;115 values[count] = value;
...@@ -129,78 +128,6 @@ pub const Id = struct {...@@ -129,78 +128,6 @@ pub const Id = struct {
129 }128 }
130};129};
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
204pub fn deinit(self: *Dylib, allocator: *Allocator) void {131pub fn deinit(self: *Dylib, allocator: *Allocator) void {
205 for (self.load_commands.items) |*lc| {132 for (self.load_commands.items) |*lc| {
206 lc.deinit(allocator);133 lc.deinit(allocator);
...@@ -421,7 +348,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li...@@ -421,7 +348,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
421 var umbrella_libs = std.StringHashMap(void).init(allocator);348 var umbrella_libs = std.StringHashMap(void).init(allocator);
422 defer umbrella_libs.deinit();349 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
426 var matcher = try TargetMatcher.init(allocator, target);353 var matcher = try TargetMatcher.init(allocator, target);
427 defer matcher.deinit();354 defer matcher.deinit();
...@@ -520,7 +447,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li...@@ -520,7 +447,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
520447
521 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include448 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include
522 // re-exports directly in the stub file.449 // re-exports directly in the stub file.
523 for (lib_stub.inner) |elem, stub_index| {450 for (lib_stub.inner) |elem| {
524 if (elem == .v3) break;451 if (elem == .v3) break;
525 const stub = elem.v4;452 const stub = elem.v4;
526453
...@@ -544,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li...@@ -544,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
544471
545pub fn parseDependentLibs(472pub fn parseDependentLibs(
546 self: *Dylib,473 self: *Dylib,
547 allocator: *Allocator,474 macho_file: *MachO,
548 target: std.Target,
549 out: *std.ArrayList(Dylib),
550 syslibroot: ?[]const u8,475 syslibroot: ?[]const u8,
551) !void {476) !void {
552 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
553 const has_ext = blk: {480 const has_ext = blk: {
554 const basename = fs.path.basename(id.name);481 const basename = fs.path.basename(id.name);
555 break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;482 break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
...@@ -561,36 +488,26 @@ pub fn parseDependentLibs(...@@ -561,36 +488,26 @@ pub fn parseDependentLibs(
561 } else id.name;488 } else id.name;
562489
563 for (&[_][]const u8{ extension, ".tbd" }) |ext| {490 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}", .{
565 without_ext,492 without_ext,
566 ext,493 ext,
567 });494 });
568 defer allocator.free(with_ext);495 defer macho_file.base.allocator.free(with_ext);
569496
570 const full_path = if (syslibroot) |root|497 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 })
572 else499 else
573 with_ext;500 with_ext;
574 defer if (syslibroot) |_| allocator.free(full_path);501 defer if (syslibroot) |_| macho_file.base.allocator.free(full_path);
575502
576 log.debug("trying dependency at fully resolved path {s}", .{full_path});503 log.debug("trying dependency at fully resolved path {s}", .{full_path});
577504
578 const dylibs = (try createAndParseFromPath(505 const did_parse_successfully = try macho_file.parseDylib(full_path, .{
579 allocator,506 .id = id,
580 target,507 .syslibroot = syslibroot,
581 full_path,508 .is_dependent = true,
582 .{509 });
583 .id = id,510 if (!did_parse_successfully) continue;
584 .syslibroot = syslibroot,
585 },
586 )) orelse {
587 continue;
588 };
589 defer allocator.free(dylibs);
590
591 try out.appendSlice(dylibs);
592
593 continue :outer;
594 } else {511 } else {
595 log.debug("unable to resolve dependency {s}", .{id.name});512 log.debug("unable to resolve dependency {s}", .{id.name});
596 }513 }
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| {