zld: recurse dylibs reexports when defined and desired

This commit is contained in:
Jakub Konka 2021-06-28 12:17:55 +02:00
parent 5aff1d5d6f
commit eca12b74b8
4 changed files with 115 additions and 138 deletions

View File

@ -871,17 +871,19 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
// If we're compiling native and we can find libSystem.B.{dylib, tbd},
// we link against that instead of embedded libSystem.B.tbd file.
const libc_stub_path = blk: {
if (self.base.options.is_native_os) {
if (try resolveLib(arena, lib_dirs.items, "System", .lib)) |full_path| {
break :blk full_path;
}
var link_native_libsystem = false;
if (self.base.options.is_native_os) {
if (try resolveLib(arena, lib_dirs.items, "System", .lib)) |full_path| {
try libs.append(full_path);
link_native_libsystem = true;
}
break :blk try comp.zig_lib_directory.join(arena, &[_][]const u8{
}
if (!link_native_libsystem) {
const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
"libc", "darwin", "libSystem.B.tbd",
});
};
try positionals.append(full_path);
}
// frameworks
var framework_dirs = std.ArrayList([]const u8).init(arena);
@ -935,6 +937,10 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
try argv.append("-o");
try argv.append(full_out_path);
if (link_native_libsystem) {
try argv.append("-lSystem");
}
for (search_lib_names.items) |l_name| {
try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name}));
}
@ -950,7 +956,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
.syslibroot = self.base.options.sysroot,
.libs = libs.items,
.rpaths = rpaths.items,
.libc_stub_path = libc_stub_path,
});
break :outer;

View File

@ -45,8 +45,8 @@ id: ?Id = null,
/// a symbol is referenced by an object file.
symbols: std.StringArrayHashMapUnmanaged(void) = .{},
// TODO add parsing re-exported libs from binary dylibs
dependent_libs: std.StringArrayHashMapUnmanaged(void) = .{},
/// Array list of all dependent libs of this dylib.
dependent_libs: std.ArrayListUnmanaged(Id) = .{},
pub const Id = struct {
name: []const u8,
@ -54,15 +54,28 @@ pub const Id = struct {
current_version: u32,
compatibility_version: u32,
pub fn default(name: []const u8) Id {
return .{
.name = name,
pub fn default(allocator: *Allocator, name: []const u8) !Id {
return Id{
.name = try allocator.dupe(u8, name),
.timestamp = 2,
.current_version = 0x10000,
.compatibility_version = 0x10000,
};
}
pub fn fromLoadCommand(allocator: *Allocator, lc: GenericCommandWithData(macho.dylib_command)) !Id {
const dylib = lc.inner.dylib;
const dylib_name = @ptrCast([*:0]const u8, lc.data[dylib.name - @sizeOf(macho.dylib_command) ..]);
const name = try allocator.dupe(u8, mem.spanZ(dylib_name));
return Id{
.name = name,
.timestamp = dylib.timestamp,
.current_version = dylib.current_version,
.compatibility_version = dylib.compatibility_version,
};
}
pub fn deinit(id: *Id, allocator: *Allocator) void {
allocator.free(id.name);
}
@ -129,12 +142,12 @@ pub const Error = error{
UnsupportedCpuArchitecture,
} || fs.File.OpenError || std.os.PReadError || Id.ParseError;
pub fn createAndParseFromPath(
allocator: *Allocator,
arch: Arch,
path: []const u8,
syslibroot: ?[]const u8,
) Error!?[]*Dylib {
pub const CreateOpts = struct {
syslibroot: ?[]const u8 = null,
id: ?Id = null,
};
pub fn createAndParseFromPath(allocator: *Allocator, arch: Arch, 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,
@ -152,7 +165,7 @@ pub fn createAndParseFromPath(
.arch = arch,
.name = name,
.file = file,
.syslibroot = syslibroot,
.syslibroot = opts.syslibroot,
};
dylib.parse() catch |err| switch (err) {
@ -171,6 +184,20 @@ pub fn createAndParseFromPath(
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.destroy(dylib);
return null;
}
}
var dylibs = std.ArrayList(*Dylib).init(allocator);
defer dylibs.deinit();
@ -191,8 +218,8 @@ pub fn deinit(self: *Dylib) void {
}
self.symbols.deinit(self.allocator);
for (self.dependent_libs.keys()) |key| {
self.allocator.free(key);
for (self.dependent_libs.items) |*id| {
id.deinit(self.allocator);
}
self.dependent_libs.deinit(self.allocator);
@ -287,6 +314,8 @@ fn readFatStruct(reader: anytype, comptime T: type) !T {
}
fn readLoadCommands(self: *Dylib, reader: anytype) !void {
const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds);
var i: u16 = 0;
@ -302,6 +331,13 @@ fn readLoadCommands(self: *Dylib, reader: anytype) !void {
macho.LC_ID_DYLIB => {
self.id_cmd_index = i;
},
macho.LC_REEXPORT_DYLIB => {
if (should_lookup_reexports) {
// Parse install_name to dependent dylib.
const id = try Id.fromLoadCommand(self.allocator, cmd.Dylib);
try self.dependent_libs.append(self.allocator, id);
}
},
else => {
log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()});
},
@ -313,22 +349,10 @@ fn readLoadCommands(self: *Dylib, reader: anytype) !void {
fn parseId(self: *Dylib) !void {
const index = self.id_cmd_index orelse {
log.debug("no LC_ID_DYLIB load command found; using hard-coded defaults...", .{});
self.id = Id.default(try self.allocator.dupe(u8, self.name.?));
self.id = try Id.default(self.allocator, self.name.?);
return;
};
const id_cmd = self.load_commands.items[index].Dylib;
const dylib = id_cmd.inner.dylib;
// TODO should we compare the name from the dylib's id with the user-specified one?
const dylib_name = @ptrCast([*:0]const u8, id_cmd.data[dylib.name - @sizeOf(macho.dylib_command) ..]);
const name = try self.allocator.dupe(u8, mem.spanZ(dylib_name));
self.id = .{
.name = name,
.timestamp = dylib.timestamp,
.current_version = dylib.current_version,
.compatibility_version = dylib.compatibility_version,
};
self.id = try Id.fromLoadCommand(self.allocator, self.load_commands.items[index].Dylib);
}
fn parseSymbols(self: *Dylib) !void {
@ -380,7 +404,7 @@ pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void {
const umbrella_lib = lib_stub.inner[0];
var id = Id.default(try self.allocator.dupe(u8, umbrella_lib.install_name));
var id = try Id.default(self.allocator, umbrella_lib.install_name);
if (umbrella_lib.current_version) |version| {
try id.parseCurrentVersion(version);
}
@ -470,7 +494,9 @@ pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void {
}
log.debug(" | {s}", .{lib});
try self.dependent_libs.put(self.allocator, try self.allocator.dupe(u8, lib), {});
const dep_id = try Id.default(self.allocator, lib);
try self.dependent_libs.append(self.allocator, dep_id);
}
}
}
@ -478,36 +504,40 @@ pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void {
}
pub fn parseDependentLibs(self: *Dylib, out: *std.ArrayList(*Dylib)) !void {
outer: for (self.dependent_libs.keys()) |lib| {
const dirname = fs.path.dirname(lib) orelse {
log.warn("unable to resolve dependency {s}", .{lib});
continue;
outer: for (self.dependent_libs.items) |id| {
const has_ext = blk: {
const basename = fs.path.basename(id.name);
break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
};
const filename = fs.path.basename(lib);
const without_ext = if (mem.lastIndexOfScalar(u8, filename, '.')) |index|
filename[0..index]
else
filename;
const extension = if (has_ext) fs.path.extension(id.name) else "";
const without_ext = if (has_ext) blk: {
const index = mem.lastIndexOfScalar(u8, id.name, '.') orelse unreachable;
break :blk id.name[0..index];
} else id.name;
for (&[_][]const u8{ "dylib", "tbd" }) |ext| {
const with_ext = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{
for (&[_][]const u8{ extension, ".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 })
const full_path = if (self.syslibroot) |syslibroot|
try fs.path.join(self.allocator, &.{ syslibroot, with_ext })
else
try fs.path.join(self.allocator, &.{ dirname, with_ext });
with_ext;
defer if (self.syslibroot) |_| self.allocator.free(full_path);
log.debug("trying dependency at fully resolved path {s}", .{lib_path});
log.debug("trying dependency at fully resolved path {s}", .{full_path});
const dylibs = (try createAndParseFromPath(
self.allocator,
self.arch.?,
lib_path,
self.syslibroot,
full_path,
.{
.id = id,
.syslibroot = self.syslibroot,
},
)) orelse {
continue;
};
@ -516,7 +546,7 @@ pub fn parseDependentLibs(self: *Dylib, out: *std.ArrayList(*Dylib)) !void {
continue :outer;
} else {
log.warn("unable to resolve dependency {s}", .{lib});
log.warn("unable to resolve dependency {s}", .{id.name});
}
}
}

View File

@ -111,7 +111,8 @@ pub const Unresolved = struct {
base: Symbol,
/// File where this symbol was referenced.
file: *Object,
/// null means synthetic, e.g., dyld_stub_binder.
file: ?*Object = null,
pub const base_type: Symbol.Type = .unresolved;
};

View File

@ -38,7 +38,6 @@ objects: std.ArrayListUnmanaged(*Object) = .{},
archives: std.ArrayListUnmanaged(*Archive) = .{},
dylibs: std.ArrayListUnmanaged(*Dylib) = .{},
libsystem_dylib_index: ?u16 = null,
next_dylib_ordinal: u16 = 1,
load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
@ -199,7 +198,6 @@ const LinkArgs = struct {
syslibroot: ?[]const u8,
libs: []const []const u8,
rpaths: []const []const u8,
libc_stub_path: []const u8,
};
pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: LinkArgs) !void {
@ -240,7 +238,6 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L
try self.populateMetadata();
try self.parseInputFiles(files, args.syslibroot);
try self.parseLibs(args.libs, args.syslibroot);
try self.parseLibSystem(args.libc_stub_path, args.syslibroot);
try self.resolveSymbols();
try self.resolveStubsAndGotEntries();
try self.updateMetadata();
@ -280,7 +277,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8, syslibroot: ?[]const u
self.allocator,
self.arch.?,
full_path,
syslibroot,
.{ .syslibroot = syslibroot },
)) |dylibs| {
defer self.allocator.free(dylibs);
try self.dylibs.appendSlice(self.allocator, dylibs);
@ -297,7 +294,7 @@ fn parseLibs(self: *Zld, libs: []const []const u8, syslibroot: ?[]const u8) !voi
self.allocator,
self.arch.?,
lib,
syslibroot,
.{ .syslibroot = syslibroot },
)) |dylibs| {
defer self.allocator.free(dylibs);
try self.dylibs.appendSlice(self.allocator, dylibs);
@ -313,36 +310,6 @@ fn parseLibs(self: *Zld, libs: []const []const u8, syslibroot: ?[]const u8) !voi
}
}
fn parseLibSystem(self: *Zld, libc_stub_path: []const u8, syslibroot: ?[]const u8) !void {
const dylibs = (try Dylib.createAndParseFromPath(
self.allocator,
self.arch.?,
libc_stub_path,
syslibroot,
)) orelse return error.FailedToParseLibSystem;
defer self.allocator.free(dylibs);
assert(dylibs.len == 1); // More than one dylib output from parsing libSystem!
const dylib = dylibs[0];
self.libsystem_dylib_index = @intCast(u16, self.dylibs.items.len);
try self.dylibs.append(self.allocator, dylib);
// Add LC_LOAD_DYLIB load command.
dylib.ordinal = self.next_dylib_ordinal;
const dylib_id = dylib.id orelse unreachable;
var dylib_cmd = try createLoadDylibCommand(
self.allocator,
dylib_id.name,
dylib_id.timestamp,
dylib_id.current_version,
dylib_id.compatibility_version,
);
errdefer dylib_cmd.deinit(self.allocator);
try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
self.next_dylib_ordinal += 1;
}
fn mapAndUpdateSections(
self: *Zld,
object: *Object,
@ -1656,6 +1623,21 @@ fn resolveSymbols(self: *Zld) !void {
}
self.unresolved.clearRetainingCapacity();
// Put dyld_stub_binder as an unresolved special symbol.
{
const name = try self.allocator.dupe(u8, "dyld_stub_binder");
errdefer self.allocator.free(name);
const undef = try self.allocator.create(Symbol.Unresolved);
errdefer self.allocator.destroy(undef);
undef.* = .{
.base = .{
.@"type" = .unresolved,
.name = name,
},
};
try unresolved.append(&undef.base);
}
var referenced = std.AutoHashMap(*Dylib, void).init(self.allocator);
defer referenced.deinit();
@ -1664,9 +1646,7 @@ fn resolveSymbols(self: *Zld) !void {
const proxy = inner: {
for (self.dylibs.items) |dylib, i| {
const proxy = (try dylib.createProxy(undef.name)) orelse continue;
if (self.libsystem_dylib_index.? != @intCast(u16, i)) { // LibSystem gets load command seperately.
try referenced.put(dylib, {});
}
try referenced.put(dylib, {});
break :inner proxy;
}
if (mem.eql(u8, undef.name, "___dso_handle")) {
@ -1681,7 +1661,6 @@ fn resolveSymbols(self: *Zld) !void {
.@"type" = .proxy,
.name = name,
},
.file = null,
};
break :inner &proxy.base;
}
@ -1717,21 +1696,13 @@ fn resolveSymbols(self: *Zld) !void {
if (self.unresolved.count() > 0) {
for (self.unresolved.values()) |undef| {
log.err("undefined reference to symbol '{s}'", .{undef.name});
log.err(" | referenced in {s}", .{
undef.cast(Symbol.Unresolved).?.file.name.?,
});
if (undef.cast(Symbol.Unresolved).?.file) |file| {
log.err(" | referenced in {s}", .{file.name.?});
}
}
return error.UndefinedSymbolReference;
}
// Finally put dyld_stub_binder as an Import
const libsystem_dylib = self.dylibs.items[self.libsystem_dylib_index.?];
const proxy = (try libsystem_dylib.createProxy("dyld_stub_binder")) orelse {
log.err("undefined reference to symbol 'dyld_stub_binder'", .{});
return error.UndefinedSymbolReference;
};
try self.imports.putNoClobber(self.allocator, proxy.name, proxy);
}
fn resolveStubsAndGotEntries(self: *Zld) !void {
@ -3173,33 +3144,3 @@ pub fn parseName(name: *const [16]u8) []const u8 {
const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len;
return name[0..len];
}
fn printSymbols(self: *Zld) void {
log.debug("globals", .{});
for (self.globals.values()) |value| {
const sym = value.cast(Symbol.Regular) orelse unreachable;
log.debug(" | {s} @ {*}", .{ sym.base.name, value });
log.debug(" => alias of {*}", .{sym.base.alias});
log.debug(" => linkage {s}", .{sym.linkage});
log.debug(" => defined in {s}", .{sym.file.name.?});
}
for (self.objects.items) |object| {
log.debug("locals in {s}", .{object.name.?});
for (object.symbols.items) |sym| {
log.debug(" | {s} @ {*}", .{ sym.name, sym });
log.debug(" => alias of {*}", .{sym.alias});
if (sym.cast(Symbol.Regular)) |reg| {
log.debug(" => linkage {s}", .{reg.linkage});
} else {
log.debug(" => unresolved", .{});
}
}
}
log.debug("proxies", .{});
for (self.imports.values()) |value| {
const sym = value.cast(Symbol.Proxy) orelse unreachable;
log.debug(" | {s} @ {*}", .{ sym.base.name, value });
log.debug(" => alias of {*}", .{sym.base.alias});
log.debug(" => defined in libSystem.B.dylib", .{});
}
}