zld: naively parse all dylib deps in stubs

This commit is contained in:
Jakub Konka 2021-06-23 15:11:31 +02:00
parent 3cb6b6bd90
commit 5ac5cd9de7
6 changed files with 267 additions and 238 deletions

View File

@ -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.

View File

@ -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 .{
.allocator = allocator,
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);
}

View File

@ -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;

View File

@ -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 .{
.allocator = allocator,
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;
}

View File

@ -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,
});
if (try Object.createAndParseFromPath(self.allocator, self.arch.?, full_path)) |object| {
try self.objects.append(self.allocator, object);
continue;
}
try_archive: {
if (!(try Archive.isArchive(file))) break :try_archive;
try classified.append(.{
.kind = .{ .archive = file },
.name = full_path,
});
if (try Archive.createAndParseFromPath(self.allocator, self.arch.?, full_path)) |archive| {
try self.archives.append(self.allocator, archive);
continue;
}
try_dylib: {
if (!(try Dylib.isDylib(file))) break :try_dylib;
try classified.append(.{
.kind = .{ .dylib = file },
.name = full_path,
});
if (try Dylib.createAndParseFromPath(
self.allocator,
self.arch.?,
full_path,
self.syslibroot,
true,
)) |dylib| {
try self.dylibs.append(self.allocator, dylib);
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();
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);
},
}
if (try Archive.createAndParseFromPath(self.allocator, self.arch.?, lib)) |archive| {
try self.archives.append(self.allocator, archive);
continue;
}
log.warn("unknown filetype for a library: '{s}'", .{lib});
}
// Flatten out any parsed dependencies.
var deps = std.ArrayList(*Dylib).init(self.allocator);
defer deps.deinit();
for (self.dylibs.items) |dylib| {
try DylibDeps.bubbleUp(&deps, dylib);
}
try self.dylibs.appendSlice(self.allocator, deps.toOwnedSlice());
}
fn parseLibSystem(self: *Zld, libc_stub_path: []const u8) !void {
const file = try fs.cwd().openFile(libc_stub_path, .{});
defer file.close();
var lib_stub = try LibStub.loadFromFile(self.allocator, file);
defer lib_stub.deinit();
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, libc_stub_path);
try dylib.parseFromStub(lib_stub);
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);

View File

@ -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,