From 5d548cc65125cc33ebf4840fb73a97030b1d0505 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 11 Aug 2021 17:09:13 +0200 Subject: [PATCH] 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. --- src/link/MachO.zig | 174 +++++++++++++++++++++++++++++-------- src/link/MachO/Archive.zig | 26 ------ src/link/MachO/Dylib.zig | 119 ++++--------------------- src/link/MachO/Object.zig | 26 ------ 4 files changed, 155 insertions(+), 190 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index d23b696cb8c0ba86553ae1308e201bb94dc26e96..6fbe3bc150fd8b4f30824bf7b4fcdf6c22d9ec42 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -31,6 +31,7 @@ const DebugSymbols = @import("MachO/DebugSymbols.zig"); const Dylib = @import("MachO/Dylib.zig"); const File = link.File; const Object = @import("MachO/Object.zig"); +const LibStub = @import("tapi.zig").LibStub; const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; const LoadCommand = commands.LoadCommand; @@ -65,6 +66,7 @@ objects: std.ArrayListUnmanaged(Object) = .{}, archives: std.ArrayListUnmanaged(Archive) = .{}, dylibs: std.ArrayListUnmanaged(Dylib) = .{}, +dylibs_map: std.StringHashMapUnmanaged(u16) = .{}, referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{}, load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, @@ -994,6 +996,133 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void { } } +fn parseObject(self: *MachO, path: []const u8) !bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var object = Object{ + .name = name, + .file = file, + }; + + object.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotObject => { + object.deinit(self.base.allocator); + return false; + }, + else => |e| return e, + }; + + try self.objects.append(self.base.allocator, object); + + return true; +} + +fn parseArchive(self: *MachO, path: []const u8) !bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var archive = Archive{ + .name = name, + .file = file, + }; + + archive.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotArchive => { + archive.deinit(self.base.allocator); + return false; + }, + else => |e| return e, + }; + + try self.archives.append(self.base.allocator, archive); + + return true; +} + +const ParseDylibError = error{ + OutOfMemory, + EmptyStubFile, + MismatchedCpuArchitecture, + UnsupportedCpuArchitecture, +} || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError; + +const DylibCreateOpts = struct { + syslibroot: ?[]const u8 = null, + id: ?Dylib.Id = null, + is_dependent: bool = false, +}; + +pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return false, + else => |e| return e, + }; + errdefer file.close(); + + const name = try self.base.allocator.dupe(u8, path); + errdefer self.base.allocator.free(name); + + var dylib = Dylib{ + .name = name, + .file = file, + }; + + dylib.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) { + error.EndOfStream, error.NotDylib => { + try file.seekTo(0); + + var lib_stub = LibStub.loadFromFile(self.base.allocator, file) catch { + dylib.deinit(self.base.allocator); + return false; + }; + defer lib_stub.deinit(); + + try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub); + }, + else => |e| return e, + }; + + 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}); + + // TODO maybe this should be an error and facilitate auto-cleanup? + dylib.deinit(self.base.allocator); + return false; + } + } + + const dylib_id = @intCast(u16, self.dylibs.items.len); + try self.dylibs.append(self.base.allocator, dylib); + try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id); + + if (!(opts.is_dependent or self.referenced_dylibs.contains(dylib_id))) { + try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); + } + + // TODO this should not be performed if the user specifies `-flat_namespace` flag. + // See ld64 manpages. + try dylib.parseDependentLibs(self, opts.syslibroot); + + return true; +} + fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void { for (files) |file_name| { const full_path = full_path: { @@ -1003,28 +1132,11 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const }; defer self.base.allocator.free(full_path); - if (try Object.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |object| { - try self.objects.append(self.base.allocator, object); - continue; - } - - if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path)) |archive| { - try self.archives.append(self.base.allocator, archive); - continue; - } - - if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, full_path, .{ + if (try self.parseObject(full_path)) continue; + if (try self.parseArchive(full_path)) continue; + if (try self.parseDylib(full_path, .{ .syslibroot = syslibroot, - })) |dylibs| { - defer self.base.allocator.free(dylibs); - const dylib_id = @intCast(u16, self.dylibs.items.len); - try self.dylibs.appendSlice(self.base.allocator, dylibs); - // We always have to add the dylib that was on the linker line. - if (!self.referenced_dylibs.contains(dylib_id)) { - try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); - } - continue; - } + })) continue; log.warn("unknown filetype for positional input file: '{s}'", .{file_name}); } @@ -1032,23 +1144,10 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void { for (libs) |lib| { - if (try Dylib.createAndParseFromPath(self.base.allocator, self.base.options.target, lib, .{ + if (try self.parseDylib(lib, .{ .syslibroot = syslibroot, - })) |dylibs| { - defer self.base.allocator.free(dylibs); - const dylib_id = @intCast(u16, self.dylibs.items.len); - try self.dylibs.appendSlice(self.base.allocator, dylibs); - // We always have to add the dylib that was on the linker line. - if (!self.referenced_dylibs.contains(dylib_id)) { - try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {}); - } - continue; - } - - if (try Archive.createAndParseFromPath(self.base.allocator, self.base.options.target, lib)) |archive| { - try self.archives.append(self.base.allocator, archive); - continue; - } + })) continue; + if (try self.parseArchive(lib)) continue; log.warn("unknown filetype for a library: '{s}'", .{lib}); } @@ -3360,6 +3459,7 @@ pub fn deinit(self: *MachO) void { dylib.deinit(self.base.allocator); } self.dylibs.deinit(self.base.allocator); + self.dylibs_map.deinit(self.base.allocator); self.referenced_dylibs.deinit(self.base.allocator); for (self.load_commands.items) |*lc| { diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index 3b01233e1f670e3d44450ffc862b08ee365d7ab7..1f370088f0ec42ced70e77cf94e30de999dbfdb4 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -103,32 +103,6 @@ pub fn deinit(self: *Archive, allocator: *Allocator) void { allocator.free(self.name); } -pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Archive { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var archive = Archive{ - .name = name, - .file = file, - }; - - archive.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotArchive => { - archive.deinit(allocator); - return null; - }, - else => |e| return e, - }; - - return archive; -} - pub fn parse(self: *Archive, allocator: *Allocator, target: std.Target) !void { const reader = self.file.reader(); self.library_offset = try fat.getLibraryOffset(reader, target); diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index bdf3cfe6bb30d21c6a373fd8cd48d319b47072ad..05d44559ce3d5c627211ef537acfe6ddcda020a4 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -10,10 +10,9 @@ 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 = tapi.LibStub; +const LibStub = @import("../tapi.zig").LibStub; const LoadCommand = commands.LoadCommand; const MachO = @import("../MachO.zig"); @@ -74,7 +73,7 @@ pub const Id = struct { allocator.free(id.name); } - const ParseError = fmt.ParseIntError || fmt.BufPrintError; + pub const ParseError = fmt.ParseIntError || fmt.BufPrintError; pub fn parseCurrentVersion(id: *Id, version: anytype) ParseError!void { id.current_version = try parseVersion(version); @@ -110,7 +109,7 @@ pub const Id = struct { var count: u4 = 0; while (split.next()) |value| { if (count > 2) { - log.warn("malformed version field: {s}", .{string}); + log.debug("malformed version field: {s}", .{string}); return 0x10000; } values[count] = value; @@ -129,78 +128,6 @@ pub const Id = struct { } }; -pub const Error = error{ - OutOfMemory, - EmptyStubFile, - MismatchedCpuArchitecture, - UnsupportedCpuArchitecture, -} || fs.File.OpenError || std.os.PReadError || Id.ParseError; - -pub const CreateOpts = struct { - syslibroot: ?[]const u8 = null, - id: ?Id = null, - target: ?std.Target = null, -}; - -pub fn createAndParseFromPath( - allocator: *Allocator, - target: std.Target, - path: []const u8, - opts: CreateOpts, -) Error!?[]Dylib { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var dylib = Dylib{ - .name = name, - .file = file, - }; - - dylib.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotDylib => { - try file.seekTo(0); - - var lib_stub = LibStub.loadFromFile(allocator, file) catch { - dylib.deinit(allocator); - return null; - }; - defer lib_stub.deinit(); - - try dylib.parseFromStub(allocator, target, lib_stub); - }, - else => |e| return e, - }; - - 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}); - - // TODO maybe this should be an error and facilitate auto-cleanup? - dylib.deinit(allocator); - return null; - } - } - - var dylibs = std.ArrayList(Dylib).init(allocator); - defer dylibs.deinit(); - - try dylibs.append(dylib); - // TODO this should not be performed if the user specifies `-flat_namespace` flag. - // See ld64 manpages. - try dylib.parseDependentLibs(allocator, target, &dylibs, opts.syslibroot); - - return dylibs.toOwnedSlice(); -} - pub fn deinit(self: *Dylib, allocator: *Allocator) void { for (self.load_commands.items) |*lc| { lc.deinit(allocator); @@ -421,7 +348,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li var umbrella_libs = std.StringHashMap(void).init(allocator); defer umbrella_libs.deinit(); - log.debug("found umbrella lib '{s}'", .{umbrella_lib.installName()}); + log.debug(" (install_name '{s}')", .{umbrella_lib.installName()}); var matcher = try TargetMatcher.init(allocator, target); defer matcher.deinit(); @@ -520,7 +447,7 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li // 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| { + for (lib_stub.inner) |elem| { if (elem == .v3) break; const stub = elem.v4; @@ -544,12 +471,12 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li pub fn parseDependentLibs( self: *Dylib, - allocator: *Allocator, - target: std.Target, - out: *std.ArrayList(Dylib), + macho_file: *MachO, syslibroot: ?[]const u8, ) !void { outer: for (self.dependent_libs.items) |id| { + if (macho_file.dylibs_map.contains(id.name)) continue :outer; + const has_ext = blk: { const basename = fs.path.basename(id.name); break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; @@ -561,36 +488,26 @@ pub fn parseDependentLibs( } else id.name; for (&[_][]const u8{ extension, ".tbd" }) |ext| { - const with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ + const with_ext = try std.fmt.allocPrint(macho_file.base.allocator, "{s}{s}", .{ without_ext, ext, }); - defer allocator.free(with_ext); + defer macho_file.base.allocator.free(with_ext); const full_path = if (syslibroot) |root| - try fs.path.join(allocator, &.{ root, with_ext }) + try fs.path.join(macho_file.base.allocator, &.{ root, with_ext }) else with_ext; - defer if (syslibroot) |_| allocator.free(full_path); + defer if (syslibroot) |_| macho_file.base.allocator.free(full_path); log.debug("trying dependency at fully resolved path {s}", .{full_path}); - const dylibs = (try createAndParseFromPath( - allocator, - target, - full_path, - .{ - .id = id, - .syslibroot = syslibroot, - }, - )) orelse { - continue; - }; - defer allocator.free(dylibs); - - try out.appendSlice(dylibs); - - continue :outer; + const did_parse_successfully = try macho_file.parseDylib(full_path, .{ + .id = id, + .syslibroot = syslibroot, + .is_dependent = true, + }); + if (!did_parse_successfully) continue; } else { log.debug("unable to resolve dependency {s}", .{id.name}); } diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index c6aa2fb631faad97d29975fbc974f332bcf7dc9c..06c76b259db8c4932797b6d9b0e5569955962600 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -153,32 +153,6 @@ pub fn deinit(self: *Object, allocator: *Allocator) void { } } -pub fn createAndParseFromPath(allocator: *Allocator, target: std.Target, path: []const u8) !?Object { - const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => return null, - else => |e| return e, - }; - errdefer file.close(); - - const name = try allocator.dupe(u8, path); - errdefer allocator.free(name); - - var object = Object{ - .name = name, - .file = file, - }; - - object.parse(allocator, target) catch |err| switch (err) { - error.EndOfStream, error.NotObject => { - object.deinit(allocator); - return null; - }, - else => |e| return e, - }; - - return object; -} - pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void { const reader = self.file.reader(); if (self.file_offset) |offset| { -- 2.54.0