zld: parse lib stubs as tbds on the linker line

This commit is contained in:
Jakub Konka 2021-06-19 16:12:29 +02:00
parent 089577a71d
commit 96a0479db2
5 changed files with 175 additions and 103 deletions

View File

@ -234,8 +234,8 @@ pub fn parseObject(self: Archive, offset: u32) !*Object {
return object;
}
pub fn isArchive(file: fs.File) !bool {
const magic = try file.reader().readBytesNoEof(Archive.SARMAG);
try file.seekTo(0);
pub fn isArchive(file: fs.File) bool {
const magic = file.reader().readBytesNoEof(Archive.SARMAG) catch return false;
file.seekTo(0) catch return false;
return mem.eql(u8, &magic, Archive.ARMAG);
}

View File

@ -1,6 +1,7 @@
const Dylib = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const log = std.log.scoped(.dylib);
const macho = std.macho;
@ -8,6 +9,7 @@ const mem = std.mem;
const Allocator = mem.Allocator;
const Symbol = @import("Symbol.zig");
const LibStub = @import("../tapi.zig").LibStub;
usingnamespace @import("commands.zig");
@ -184,8 +186,88 @@ pub fn parseSymbols(self: *Dylib) !void {
}
}
pub fn isDylib(file: fs.File) !bool {
const header = try file.reader().readStruct(macho.mach_header_64);
try file.seekTo(0);
pub fn isDylib(file: fs.File) bool {
const header = file.reader().readStruct(macho.mach_header_64) catch return false;
file.seekTo(0) catch return false;
return header.filetype == macho.MH_DYLIB;
}
pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void {
assert(lib_stub.inner.len > 0);
log.debug("parsing shared library from stub '{s}'", .{self.name.?});
const umbrella_lib = lib_stub.inner[0];
self.id = .{
.name = try self.allocator.dupe(u8, umbrella_lib.install_name),
// TODO parse from the stub
.timestamp = 2,
.current_version = 0,
.compatibility_version = 0,
};
const target_string: []const u8 = switch (self.arch.?) {
.aarch64 => "arm64-macos",
.x86_64 => "x86_64-macos",
else => unreachable,
};
for (lib_stub.inner) |stub| {
if (!hasTarget(stub.targets, target_string)) continue;
if (stub.exports) |exports| {
for (exports) |exp| {
if (!hasTarget(exp.targets, target_string)) continue;
for (exp.symbols) |sym_name| {
if (self.symbols.contains(sym_name)) continue;
const name = try self.allocator.dupe(u8, sym_name);
const proxy = try self.allocator.create(Symbol.Proxy);
errdefer self.allocator.destroy(proxy);
proxy.* = .{
.base = .{
.@"type" = .proxy,
.name = name,
},
.dylib = self,
};
try self.symbols.putNoClobber(self.allocator, name, &proxy.base);
}
}
}
if (stub.reexports) |reexports| {
for (reexports) |reexp| {
if (!hasTarget(reexp.targets, target_string)) continue;
for (reexp.symbols) |sym_name| {
if (self.symbols.contains(sym_name)) continue;
const name = try self.allocator.dupe(u8, sym_name);
const proxy = try self.allocator.create(Symbol.Proxy);
errdefer self.allocator.destroy(proxy);
proxy.* = .{
.base = .{
.@"type" = .proxy,
.name = name,
},
.dylib = self,
};
try self.symbols.putNoClobber(self.allocator, name, &proxy.base);
}
}
}
}
}
fn hasTarget(targets: []const []const u8, target: []const u8) bool {
for (targets) |t| {
if (mem.eql(u8, t, target)) return true;
}
return false;
}

View File

@ -534,8 +534,8 @@ pub fn parseDataInCode(self: *Object) !void {
}
}
pub fn isObject(file: fs.File) !bool {
const header = try file.reader().readStruct(macho.mach_header_64);
try file.seekTo(0);
pub fn isObject(file: fs.File) bool {
const header = file.reader().readStruct(macho.mach_header_64) catch return false;
file.seekTo(0) catch return false;
return header.filetype == macho.MH_OBJECT;
}

View File

@ -243,14 +243,16 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
object,
archive,
dylib,
stub,
},
file: fs.File,
name: []const u8,
stub: ?LibStub = null,
};
var classified = std.ArrayList(Input).init(self.allocator);
defer classified.deinit();
// First, classify input files: object, archive or dylib.
// 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: {
@ -260,7 +262,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
};
try_object: {
if (!(try Object.isObject(file))) break :try_object;
if (!Object.isObject(file)) break :try_object;
try classified.append(.{
.kind = .object,
.file = file,
@ -270,7 +272,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
}
try_archive: {
if (!(try Archive.isArchive(file))) break :try_archive;
if (!Archive.isArchive(file)) break :try_archive;
try classified.append(.{
.kind = .archive,
.file = file,
@ -280,7 +282,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
}
try_dylib: {
if (!(try Dylib.isDylib(file))) break :try_dylib;
if (!Dylib.isDylib(file)) break :try_dylib;
try classified.append(.{
.kind = .dylib,
.file = file,
@ -289,6 +291,19 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
continue;
}
try_stub: {
var lib_stub = LibStub.loadFromFile(self.allocator, file) catch |_| {
break :try_stub;
};
try classified.append(.{
.kind = .stub,
.file = file,
.name = full_path,
.stub = lib_stub,
});
continue;
}
file.close();
log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
}
@ -318,7 +333,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
try archive.parse();
try self.archives.append(self.allocator, archive);
},
.dylib => {
.dylib, .stub => {
const dylib = try self.allocator.create(Dylib);
errdefer self.allocator.destroy(dylib);
@ -329,7 +344,11 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
dylib.ordinal = @intCast(u16, self.dylibs.items.len) + 1;
// TODO Defer parsing of the dylibs until they are actually needed
try dylib.parse();
if (input.stub) |stub| {
try dylib.parseFromStub(stub);
} else {
try dylib.parse();
}
try self.dylibs.append(self.allocator, dylib);
// Add LC_LOAD_DYLIB command
@ -353,7 +372,7 @@ fn parseLibs(self: *Zld, libs: []const []const u8) !void {
for (libs) |lib| {
const file = try fs.cwd().openFile(lib, .{});
if (try Dylib.isDylib(file)) {
if (Dylib.isDylib(file)) {
const dylib = try self.allocator.create(Dylib);
errdefer self.allocator.destroy(dylib);
@ -379,30 +398,57 @@ fn parseLibs(self: *Zld, libs: []const []const u8) !void {
errdefer dylib_cmd.deinit(self.allocator);
try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
} else if (try Archive.isArchive(file)) {
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);
} else {
file.close();
log.warn("unknown filetype for a library: '{s}'", .{lib});
// Try tbd stub file next.
if (LibStub.loadFromFile(self.allocator, file)) |*lib_stub| {
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, lib);
dylib.file = file;
dylib.ordinal = @intCast(u16, self.dylibs.items.len) + 1;
try dylib.parseFromStub(lib_stub.*);
try self.dylibs.append(self.allocator, dylib);
// Add LC_LOAD_DYLIB command
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 });
} else |_| {
// TODO this entire logic has to be cleaned up.
try file.seekTo(0);
if (Archive.isArchive(file)) {
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);
} else {
file.close();
log.warn("unknown filetype for a library: '{s}'", .{lib});
}
}
}
}
}
fn hasTarget(targets: []const []const u8, target: []const u8) bool {
for (targets) |t| {
if (mem.eql(u8, t, target)) return true;
}
return false;
}
fn parseLibSystem(self: *Zld, lib_system_path: []const u8) !void {
const file = try fs.cwd().openFile(lib_system_path, .{});
@ -418,73 +464,7 @@ fn parseLibSystem(self: *Zld, lib_system_path: []const u8) !void {
dylib.file = file;
dylib.ordinal = @intCast(u16, self.dylibs.items.len) + 1;
const umbrella_lib = lib_stub.inner[0];
dylib.id = .{
.name = try self.allocator.dupe(u8, umbrella_lib.install_name),
// TODO parse from the stub
.timestamp = 2,
.current_version = 0,
.compatibility_version = 0,
};
const target_string: []const u8 = switch (self.arch.?) {
.aarch64 => "arm64-macos",
.x86_64 => "x86_64-macos",
else => unreachable,
};
for (lib_stub.inner) |stub| {
if (!hasTarget(stub.targets, target_string)) continue;
if (stub.exports) |exports| {
for (exports) |exp| {
if (!hasTarget(exp.targets, target_string)) continue;
for (exp.symbols) |sym_name| {
if (dylib.symbols.contains(sym_name)) continue;
const name = try self.allocator.dupe(u8, sym_name);
const proxy = try self.allocator.create(Symbol.Proxy);
errdefer self.allocator.destroy(proxy);
proxy.* = .{
.base = .{
.@"type" = .proxy,
.name = name,
},
.dylib = dylib,
};
try dylib.symbols.putNoClobber(self.allocator, name, &proxy.base);
}
}
}
if (stub.reexports) |reexports| {
for (reexports) |reexp| {
if (!hasTarget(reexp.targets, target_string)) continue;
for (reexp.symbols) |sym_name| {
if (dylib.symbols.contains(sym_name)) continue;
const name = try self.allocator.dupe(u8, sym_name);
const proxy = try self.allocator.create(Symbol.Proxy);
errdefer self.allocator.destroy(proxy);
proxy.* = .{
.base = .{
.@"type" = .proxy,
.name = name,
},
.dylib = dylib,
};
try dylib.symbols.putNoClobber(self.allocator, name, &proxy.base);
}
}
}
}
try dylib.parseFromStub(lib_stub);
try self.dylibs.append(self.allocator, dylib);
// Add LC_LOAD_DYLIB command

View File

@ -58,7 +58,17 @@ pub const LibStub = struct {
.inner = undefined,
};
lib_stub.inner = try lib_stub.yaml.parse([]Tbd);
lib_stub.inner = lib_stub.yaml.parse([]Tbd) catch |err| blk: {
switch (err) {
error.TypeMismatch => {
// TODO clean this up.
var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
out[0] = try lib_stub.yaml.parse(Tbd);
break :blk out;
},
else => |e| return e,
}
};
return lib_stub;
}