diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2021-06-23 15:11:31 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2021-06-24 18:57:11 +0200 |
| commit | 5ac5cd9de7c5387e37baa4f287d609c5d2f34564 (patch) | |
| tree | 65d97ee06e85f411493dbef7a5b1688e627f70c6 /src/link | |
| parent | 3cb6b6bd90c3b304bf771b37e974dd943c060e2b (diff) | |
| download | zig-5ac5cd9de7c5387e37baa4f287d609c5d2f34564.tar.gz zig-5ac5cd9de7c5387e37baa4f287d609c5d2f34564.zip | |
zld: naively parse all dylib deps in stubs
Diffstat (limited to 'src/link')
| -rw-r--r-- | src/link/MachO.zig | 1 | ||||
| -rw-r--r-- | src/link/MachO/Archive.zig | 63 | ||||
| -rw-r--r-- | src/link/MachO/Dylib.zig | 148 | ||||
| -rw-r--r-- | src/link/MachO/Object.zig | 52 | ||||
| -rw-r--r-- | src/link/MachO/Zld.zig | 230 | ||||
| -rw-r--r-- | src/link/tapi.zig | 3 |
6 files changed, 263 insertions, 234 deletions
diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 3521874129..4ede9c516e 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -789,6 +789,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { zld.deinit(); } zld.arch = target.cpu.arch; + zld.syslibroot = self.base.options.syslibroot; zld.stack_size = stack_size; // Positional arguments to the linker such as object files and static archives. diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index 48bc33f0fa..22439d300a 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -8,12 +8,13 @@ const macho = std.macho; const mem = std.mem; const Allocator = mem.Allocator; +const Arch = std.Target.Cpu.Arch; const Object = @import("Object.zig"); usingnamespace @import("commands.zig"); allocator: *Allocator, -arch: ?std.Target.Cpu.Arch = null, +arch: ?Arch = null, file: ?fs.File = null, header: ?ar_hdr = null, name: ?[]const u8 = null, @@ -85,10 +86,36 @@ const ar_hdr = extern struct { } }; -pub fn init(allocator: *Allocator) Archive { - return .{ +pub fn createAndParseFromPath(allocator: *Allocator, arch: Arch, 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 archive = try allocator.create(Archive); + errdefer allocator.destroy(archive); + + const name = try allocator.dupe(u8, path); + errdefer allocator.free(name); + + archive.* = .{ .allocator = allocator, + .arch = arch, + .name = name, + .file = file, + }; + + archive.parse() catch |err| switch (err) { + error.EndOfStream, error.NotArchive => { + archive.deinit(); + allocator.destroy(archive); + return null; + }, + else => |e| return e, }; + + return archive; } pub fn deinit(self: *Archive) void { @@ -116,15 +143,15 @@ pub fn parse(self: *Archive) !void { const magic = try reader.readBytesNoEof(SARMAG); if (!mem.eql(u8, &magic, ARMAG)) { - log.err("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic }); - return error.MalformedArchive; + log.debug("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic }); + return error.NotArchive; } self.header = try reader.readStruct(ar_hdr); if (!mem.eql(u8, &self.header.?.ar_fmag, ARFMAG)) { - log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, self.header.?.ar_fmag }); - return error.MalformedArchive; + log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, self.header.?.ar_fmag }); + return error.NotArchive; } var embedded_name = try parseName(self.allocator, self.header.?, reader); @@ -222,23 +249,15 @@ pub fn parseObject(self: Archive, offset: u32) !*Object { var object = try self.allocator.create(Object); errdefer self.allocator.destroy(object); - object.* = Object.init(self.allocator); - object.arch = self.arch.?; - object.file = try fs.cwd().openFile(self.name.?, .{}); - object.name = name; - object.file_offset = @intCast(u32, try reader.context.getPos()); + object.* = .{ + .allocator = self.allocator, + .arch = self.arch.?, + .file = try fs.cwd().openFile(self.name.?, .{}), + .name = name, + .file_offset = @intCast(u32, try reader.context.getPos()), + }; try object.parse(); - try reader.context.seekTo(0); return object; } - -pub fn isArchive(file: fs.File) !bool { - const magic = file.reader().readBytesNoEof(Archive.SARMAG) catch |err| switch (err) { - error.EndOfStream => return false, - else => |e| return e, - }; - try file.seekTo(0); - return mem.eql(u8, &magic, Archive.ARMAG); -} diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 44b012905e..723277c541 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -8,6 +8,7 @@ const macho = std.macho; const mem = std.mem; const Allocator = mem.Allocator; +const Arch = std.Target.Cpu.Arch; const Symbol = @import("Symbol.zig"); const LibStub = @import("../tapi.zig").LibStub; @@ -15,10 +16,11 @@ usingnamespace @import("commands.zig"); allocator: *Allocator, -arch: ?std.Target.Cpu.Arch = null, +arch: ?Arch = null, header: ?macho.mach_header_64 = null, file: ?fs.File = null, name: ?[]const u8 = null, +syslibroot: ?[]const u8 = null, ordinal: ?u16 = null, @@ -35,6 +37,11 @@ id: ?Id = null, /// a symbol is referenced by an object file. symbols: std.StringArrayHashMapUnmanaged(void) = .{}, +// TODO we should keep track of already parsed dylibs so that +// we don't unnecessarily reparse them again. +// TODO add dylib dep analysis and extraction for .dylib files. +dylibs: std.ArrayListUnmanaged(*Dylib) = .{}, + pub const Id = struct { name: []const u8, timestamp: u32, @@ -46,8 +53,57 @@ pub const Id = struct { } }; -pub fn init(allocator: *Allocator) Dylib { - return .{ .allocator = allocator }; +pub const Error = error{ + OutOfMemory, + EmptyStubFile, + MismatchedCpuArchitecture, + UnsupportedCpuArchitecture, +} || fs.File.OpenError || std.os.PReadError; + +pub fn createAndParseFromPath( + allocator: *Allocator, + arch: Arch, + path: []const u8, + syslibroot: ?[]const u8, + recurse_libs: bool, +) Error!?*Dylib { + const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => return null, + else => |e| return e, + }; + errdefer file.close(); + + const dylib = try allocator.create(Dylib); + errdefer allocator.destroy(dylib); + + const name = try allocator.dupe(u8, path); + errdefer allocator.free(name); + + dylib.* = .{ + .allocator = allocator, + .arch = arch, + .name = name, + .file = file, + .syslibroot = syslibroot, + }; + + dylib.parse(recurse_libs) catch |err| switch (err) { + error.EndOfStream, error.NotDylib => { + try file.seekTo(0); + + var lib_stub = LibStub.loadFromFile(allocator, file) catch { + dylib.deinit(); + allocator.destroy(dylib); + return null; + }; + defer lib_stub.deinit(); + + try dylib.parseFromStub(lib_stub, recurse_libs); + }, + else => |e| return e, + }; + + return dylib; } pub fn deinit(self: *Dylib) void { @@ -60,6 +116,7 @@ pub fn deinit(self: *Dylib) void { self.allocator.free(key); } self.symbols.deinit(self.allocator); + self.dylibs.deinit(self.allocator); if (self.name) |name| { self.allocator.free(name); @@ -76,15 +133,15 @@ pub fn closeFile(self: Dylib) void { } } -pub fn parse(self: *Dylib) !void { +pub fn parse(self: *Dylib, recurse_libs: bool) !void { log.debug("parsing shared library '{s}'", .{self.name.?}); var reader = self.file.?.reader(); self.header = try reader.readStruct(macho.mach_header_64); if (self.header.?.filetype != macho.MH_DYLIB) { - log.err("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, self.header.?.filetype }); - return error.MalformedDylib; + log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, self.header.?.filetype }); + return error.NotDylib; } const this_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) { @@ -190,7 +247,7 @@ fn addObjCClassSymbols(self: *Dylib, sym_name: []const u8) !void { } } -pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void { +pub fn parseFromStub(self: *Dylib, lib_stub: LibStub, recurse_libs: bool) !void { if (lib_stub.inner.len == 0) return error.EmptyStubFile; log.debug("parsing shared library from stub '{s}'", .{self.name.?}); @@ -236,9 +293,17 @@ pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void { for (reexports) |reexp| { if (!hasTarget(reexp.targets, target_string)) continue; - for (reexp.symbols) |sym_name| { - if (self.symbols.contains(sym_name)) continue; - try self.symbols.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), {}); + if (reexp.symbols) |symbols| { + for (symbols) |sym_name| { + if (self.symbols.contains(sym_name)) continue; + try self.symbols.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), {}); + } + } + + if (reexp.objc_classes) |classes| { + for (classes) |sym_name| { + try self.addObjCClassSymbols(sym_name); + } } } } @@ -249,6 +314,60 @@ pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void { } } } + + for (lib_stub.inner) |stub| { + if (!hasTarget(stub.targets, target_string)) continue; + + if (stub.reexported_libraries) |reexports| reexports: { + if (!recurse_libs) break :reexports; + + for (reexports) |reexp| { + if (!hasTarget(reexp.targets, target_string)) continue; + + outer: for (reexp.libraries) |lib| { + const dirname = fs.path.dirname(lib) orelse { + log.warn("unable to resolve dependency {s}", .{lib}); + continue; + }; + const filename = fs.path.basename(lib); + const without_ext = if (mem.lastIndexOfScalar(u8, filename, '.')) |index| + filename[0..index] + else + filename; + + for (&[_][]const u8{ "dylib", "tbd" }) |ext| { + const with_ext = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ + without_ext, + ext, + }); + defer self.allocator.free(with_ext); + + const lib_path = if (self.syslibroot) |syslibroot| + try fs.path.join(self.allocator, &.{ syslibroot, dirname, with_ext }) + else + try fs.path.join(self.allocator, &.{ dirname, with_ext }); + + log.debug("trying dependency at fully resolved path {s}", .{lib_path}); + + const dylib = (try createAndParseFromPath( + self.allocator, + self.arch.?, + lib_path, + self.syslibroot, + true, + )) orelse { + continue; + }; + + try self.dylibs.append(self.allocator, dylib); + continue :outer; + } else { + log.warn("unable to resolve dependency {s}", .{lib}); + } + } + } + } + } } fn hasTarget(targets: []const []const u8, target: []const u8) bool { @@ -258,15 +377,6 @@ fn hasTarget(targets: []const []const u8, target: []const u8) bool { return false; } -pub fn isDylib(file: fs.File) !bool { - const header = file.reader().readStruct(macho.mach_header_64) catch |err| switch (err) { - error.EndOfStream => return false, - else => |e| return e, - }; - try file.seekTo(0); - return header.filetype == macho.MH_DYLIB; -} - pub fn createProxy(self: *Dylib, sym_name: []const u8) !?*Symbol { if (!self.symbols.contains(sym_name)) return null; diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 64db2fe091..747adaab87 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -11,6 +11,7 @@ const mem = std.mem; const reloc = @import("reloc.zig"); const Allocator = mem.Allocator; +const Arch = std.Target.Cpu.Arch; const Relocation = reloc.Relocation; const Symbol = @import("Symbol.zig"); const parseName = @import("Zld.zig").parseName; @@ -18,7 +19,7 @@ const parseName = @import("Zld.zig").parseName; usingnamespace @import("commands.zig"); allocator: *Allocator, -arch: ?std.Target.Cpu.Arch = null, +arch: ?Arch = null, header: ?macho.mach_header_64 = null, file: ?fs.File = null, file_offset: ?u32 = null, @@ -173,10 +174,36 @@ const DebugInfo = struct { } }; -pub fn init(allocator: *Allocator) Object { - return .{ +pub fn createAndParseFromPath(allocator: *Allocator, arch: Arch, 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 object = try allocator.create(Object); + errdefer allocator.destroy(object); + + const name = try allocator.dupe(u8, path); + errdefer allocator.free(name); + + object.* = .{ .allocator = allocator, + .arch = arch, + .name = name, + .file = file, }; + + object.parse() catch |err| switch (err) { + error.EndOfStream, error.NotObject => { + object.deinit(); + allocator.destroy(object); + return null; + }, + else => |e| return e, + }; + + return object; } pub fn deinit(self: *Object) void { @@ -223,11 +250,15 @@ pub fn parse(self: *Object) !void { self.header = try reader.readStruct(macho.mach_header_64); if (self.header.?.filetype != macho.MH_OBJECT) { - log.err("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_OBJECT, self.header.?.filetype }); - return error.MalformedObject; + log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ + macho.MH_OBJECT, + self.header.?.filetype, + }); + + return error.NotObject; } - const this_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) { + const this_arch: Arch = switch (self.header.?.cputype) { macho.CPU_TYPE_ARM64 => .aarch64, macho.CPU_TYPE_X86_64 => .x86_64, else => |value| { @@ -533,12 +564,3 @@ pub fn parseDataInCode(self: *Object) !void { try self.data_in_code_entries.append(self.allocator, dice); } } - -pub fn isObject(file: fs.File) !bool { - const header = file.reader().readStruct(macho.mach_header_64) catch |err| switch (err) { - error.EndOfStream => return false, - else => |e| return e, - }; - try file.seekTo(0); - return header.filetype == macho.MH_OBJECT; -} diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index b46feced49..a4ee93cf58 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -16,7 +16,6 @@ const Allocator = mem.Allocator; const Archive = @import("Archive.zig"); const CodeSignature = @import("CodeSignature.zig"); const Dylib = @import("Dylib.zig"); -const LibStub = @import("../tapi.zig").LibStub; const Object = @import("Object.zig"); const Symbol = @import("Symbol.zig"); const Trie = @import("Trie.zig"); @@ -33,6 +32,7 @@ out_path: ?[]const u8 = null, // TODO these args will become obselete once Zld is coalesced with incremental // linker. +syslibroot: ?[]const u8 = null, stack_size: u64 = 0, objects: std.ArrayListUnmanaged(*Object) = .{}, @@ -257,214 +257,90 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L } fn parseInputFiles(self: *Zld, files: []const []const u8) !void { - const Input = struct { - kind: union(enum) { - object: fs.File, - archive: fs.File, - dylib: fs.File, - stub: LibStub, - }, - name: []const u8, - - fn deinit(input: *@This()) void { - switch (input.kind) { - .stub => |*stub| { - stub.deinit(); - }, - else => {}, - } - } - }; - var classified = std.ArrayList(Input).init(self.allocator); - defer { - for (classified.items) |*input| { - input.deinit(); - } - classified.deinit(); - } - - // First, classify input files: object, archive, dylib or stub (tbd). for (files) |file_name| { - const file = try fs.cwd().openFile(file_name, .{}); const full_path = full_path: { var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; const path = try std.fs.realpath(file_name, &buffer); break :full_path try self.allocator.dupe(u8, path); }; - try_object: { - if (!(try Object.isObject(file))) break :try_object; - try classified.append(.{ - .kind = .{ .object = file }, - .name = full_path, - }); - continue; - } - - try_archive: { - if (!(try Archive.isArchive(file))) break :try_archive; - try classified.append(.{ - .kind = .{ .archive = file }, - .name = full_path, - }); + if (try Object.createAndParseFromPath(self.allocator, self.arch.?, full_path)) |object| { + try self.objects.append(self.allocator, object); continue; } - try_dylib: { - if (!(try Dylib.isDylib(file))) break :try_dylib; - try classified.append(.{ - .kind = .{ .dylib = file }, - .name = full_path, - }); + if (try Archive.createAndParseFromPath(self.allocator, self.arch.?, full_path)) |archive| { + try self.archives.append(self.allocator, archive); continue; } - try_stub: { - var lib_stub = LibStub.loadFromFile(self.allocator, file) catch { - break :try_stub; - }; - try classified.append(.{ - .kind = .{ .stub = lib_stub }, - .name = full_path, - }); - file.close(); + if (try Dylib.createAndParseFromPath( + self.allocator, + self.arch.?, + full_path, + self.syslibroot, + true, + )) |dylib| { + try self.dylibs.append(self.allocator, dylib); continue; } - file.close(); log.warn("unknown filetype for positional input file: '{s}'", .{file_name}); } - - // Based on our classification, proceed with parsing. - for (classified.items) |input| { - switch (input.kind) { - .object => |file| { - const object = try self.allocator.create(Object); - errdefer self.allocator.destroy(object); - - object.* = Object.init(self.allocator); - object.arch = self.arch.?; - object.name = input.name; - object.file = file; - - try object.parse(); - try self.objects.append(self.allocator, object); - }, - .archive => |file| { - const archive = try self.allocator.create(Archive); - errdefer self.allocator.destroy(archive); - - archive.* = Archive.init(self.allocator); - archive.arch = self.arch.?; - archive.name = input.name; - archive.file = file; - - try archive.parse(); - try self.archives.append(self.allocator, archive); - }, - .dylib, .stub => { - const dylib = try self.allocator.create(Dylib); - errdefer self.allocator.destroy(dylib); - - dylib.* = Dylib.init(self.allocator); - dylib.arch = self.arch.?; - dylib.name = input.name; - - if (input.kind == .dylib) { - dylib.file = input.kind.dylib; - try dylib.parse(); - } else { - try dylib.parseFromStub(input.kind.stub); - } - - try self.dylibs.append(self.allocator, dylib); - }, - } - } } fn parseLibs(self: *Zld, libs: []const []const u8) !void { - for (libs) |lib| { - const file = try fs.cwd().openFile(lib, .{}); - - var kind: ?union(enum) { - archive, - dylib, - stub: LibStub, - } = kind: { - if (try Archive.isArchive(file)) break :kind .archive; - if (try Dylib.isDylib(file)) break :kind .dylib; - var lib_stub = LibStub.loadFromFile(self.allocator, file) catch { - break :kind null; - }; - break :kind .{ .stub = lib_stub }; - }; - defer { - if (kind) |*kk| { - switch (kk.*) { - .stub => |*stub| { - stub.deinit(); - }, - else => {}, - } + const DylibDeps = struct { + fn bubbleUp(out: *std.ArrayList(*Dylib), next: *Dylib) error{OutOfMemory}!void { + try out.ensureUnusedCapacity(next.dylibs.items.len); + for (next.dylibs.items) |dylib| { + out.appendAssumeCapacity(dylib); + } + for (next.dylibs.items) |dylib| { + try bubbleUp(out, dylib); } } + }; - const unwrapped = kind orelse { - file.close(); - log.warn("unknown filetype for a library: '{s}'", .{lib}); + for (libs) |lib| { + if (try Dylib.createAndParseFromPath( + self.allocator, + self.arch.?, + lib, + self.syslibroot, + true, + )) |dylib| { + try self.dylibs.append(self.allocator, dylib); continue; - }; - switch (unwrapped) { - .archive => { - const archive = try self.allocator.create(Archive); - errdefer self.allocator.destroy(archive); - - archive.* = Archive.init(self.allocator); - archive.arch = self.arch.?; - archive.name = try self.allocator.dupe(u8, lib); - archive.file = file; - - try archive.parse(); - try self.archives.append(self.allocator, archive); - }, - .dylib, .stub => { - const dylib = try self.allocator.create(Dylib); - errdefer self.allocator.destroy(dylib); - - dylib.* = Dylib.init(self.allocator); - dylib.arch = self.arch.?; - dylib.name = try self.allocator.dupe(u8, lib); - - if (unwrapped == .dylib) { - dylib.file = file; - try dylib.parse(); - } else { - try dylib.parseFromStub(unwrapped.stub); - } - - try self.dylibs.append(self.allocator, dylib); - }, } - } -} -fn parseLibSystem(self: *Zld, libc_stub_path: []const u8) !void { - const file = try fs.cwd().openFile(libc_stub_path, .{}); - defer file.close(); + if (try Archive.createAndParseFromPath(self.allocator, self.arch.?, lib)) |archive| { + try self.archives.append(self.allocator, archive); + continue; + } - var lib_stub = try LibStub.loadFromFile(self.allocator, file); - defer lib_stub.deinit(); + log.warn("unknown filetype for a library: '{s}'", .{lib}); + } - const dylib = try self.allocator.create(Dylib); - errdefer self.allocator.destroy(dylib); + // Flatten out any parsed dependencies. + var deps = std.ArrayList(*Dylib).init(self.allocator); + defer deps.deinit(); - dylib.* = Dylib.init(self.allocator); - dylib.arch = self.arch.?; - dylib.name = try self.allocator.dupe(u8, libc_stub_path); + for (self.dylibs.items) |dylib| { + try DylibDeps.bubbleUp(&deps, dylib); + } - try dylib.parseFromStub(lib_stub); + try self.dylibs.appendSlice(self.allocator, deps.toOwnedSlice()); +} +fn parseLibSystem(self: *Zld, libc_stub_path: []const u8) !void { + const dylib = (try Dylib.createAndParseFromPath( + self.allocator, + self.arch.?, + libc_stub_path, + self.syslibroot, + false, + )) orelse return error.FailedToParseLibSystem; self.libsystem_dylib_index = @intCast(u16, self.dylibs.items.len); try self.dylibs.append(self.allocator, dylib); diff --git a/src/link/tapi.zig b/src/link/tapi.zig index 51d51d6ed3..dc696577e8 100644 --- a/src/link/tapi.zig +++ b/src/link/tapi.zig @@ -41,7 +41,8 @@ pub const LibStub = struct { }, reexports: ?[]const struct { targets: []const []const u8, - symbols: []const []const u8, + symbols: ?[]const []const u8, + objc_classes: ?[]const []const u8, }, allowable_clients: ?[]const struct { targets: []const []const u8, |
