zld: abstract Symbol creation logic

This commit is contained in:
Jakub Konka
2021-07-01 17:25:51 +02:00
parent 3622fe08db
commit 2b3bda43e3
6 changed files with 231 additions and 184 deletions

View File

@@ -724,7 +724,7 @@ fn linkWithZld(self: *MachO, comp: *Compilation) !void {
try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
}
} else {
var zld = Zld.init(self.base.allocator);
var zld = try Zld.init(self.base.allocator);
defer {
zld.closeFiles();
zld.deinit();

View File

@@ -146,7 +146,12 @@ pub const CreateOpts = struct {
id: ?Id = null,
};
pub fn createAndParseFromPath(allocator: *Allocator, arch: Arch, path: []const u8, opts: CreateOpts) Error!?[]*Dylib {
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,
@@ -505,18 +510,7 @@ pub fn parseDependentLibs(self: *Dylib, out: *std.ArrayList(*Dylib)) !void {
pub fn createProxy(self: *Dylib, sym_name: []const u8) !?*Symbol {
if (!self.symbols.contains(sym_name)) return null;
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,
},
return Symbol.Proxy.new(self.allocator, sym_name, .{
.file = self,
};
return &proxy.base;
});
}

View File

@@ -9,12 +9,12 @@ const log = std.log.scoped(.object);
const macho = std.macho;
const mem = std.mem;
const reloc = @import("reloc.zig");
const parseName = @import("Zld.zig").parseName;
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;
usingnamespace @import("commands.zig");
@@ -437,47 +437,26 @@ pub fn parseSymbols(self: *Object) !void {
if (Symbol.isWeakDef(sym) or Symbol.isPext(sym)) break :linkage .linkage_unit;
break :linkage .global;
};
const regular = try self.allocator.create(Symbol.Regular);
errdefer self.allocator.destroy(regular);
regular.* = .{
.base = .{
.@"type" = .regular,
.name = name,
},
break :symbol try Symbol.Regular.new(self.allocator, name, .{
.linkage = linkage,
.address = sym.n_value,
.section = sym.n_sect - 1,
.weak_ref = Symbol.isWeakRef(sym),
.file = self,
};
break :symbol &regular.base;
});
}
if (sym.n_value != 0) {
const tentative = try self.allocator.create(Symbol.Tentative);
errdefer self.allocator.destroy(tentative);
tentative.* = .{
.base = .{
.@"type" = .tentative,
.name = name,
},
break :symbol try Symbol.Tentative.new(self.allocator, name, .{
.size = sym.n_value,
.alignment = (sym.n_desc >> 8) & 0x0f,
.file = self,
};
break :symbol &tentative.base;
});
}
const undef = try self.allocator.create(Symbol.Unresolved);
errdefer self.allocator.destroy(undef);
undef.* = .{
.base = .{
.@"type" = .unresolved,
.name = name,
},
break :symbol try Symbol.Unresolved.new(self.allocator, name, .{
.file = self,
};
break :symbol &undef.base;
});
};
try self.symbols.append(self.allocator, symbol);

View File

@@ -8,7 +8,6 @@ const Allocator = mem.Allocator;
allocator: *Allocator,
buffer: std.ArrayListUnmanaged(u8) = .{},
used_offsets: std.ArrayListUnmanaged(u32) = .{},
cache: std.StringHashMapUnmanaged(u32) = .{},
pub const Error = error{OutOfMemory};
@@ -22,8 +21,13 @@ pub fn init(allocator: *Allocator) Error!StringTable {
}
pub fn deinit(self: *StringTable) void {
{
var it = self.cache.keyIterator();
while (it.next()) |key| {
self.allocator.free(key.*);
}
}
self.cache.deinit(self.allocator);
self.used_offsets.deinit(self.allocator);
self.buffer.deinit(self.allocator);
}
@@ -33,8 +37,6 @@ pub fn getOrPut(self: *StringTable, string: []const u8) Error!u32 {
return off;
}
const invalidate_cache = self.needsToGrow(string.len + 1);
try self.buffer.ensureUnusedCapacity(self.allocator, string.len + 1);
const new_off = @intCast(u32, self.buffer.items.len);
@@ -43,25 +45,7 @@ pub fn getOrPut(self: *StringTable, string: []const u8) Error!u32 {
self.buffer.appendSliceAssumeCapacity(string);
self.buffer.appendAssumeCapacity(0);
if (invalidate_cache) {
log.debug("invalidating cache", .{});
// Re-create the cache.
self.cache.clearRetainingCapacity();
for (self.used_offsets.items) |off| {
try self.cache.putNoClobber(self.allocator, self.get(off).?, off);
}
}
{
log.debug("cache:", .{});
var it = self.cache.iterator();
while (it.next()) |entry| {
log.debug(" | {s} => {}", .{ entry.key_ptr.*, entry.value_ptr.* });
}
}
try self.cache.putNoClobber(self.allocator, self.get(new_off).?, new_off);
try self.used_offsets.append(self.allocator, new_off);
try self.cache.putNoClobber(self.allocator, try self.allocator.dupe(u8, string), new_off);
return new_off;
}
@@ -78,7 +62,3 @@ pub fn asSlice(self: StringTable) []const u8 {
pub fn size(self: StringTable) u64 {
return self.buffer.items.len;
}
fn needsToGrow(self: StringTable, needed_space: u64) bool {
return self.buffer.capacity < needed_space + self.size();
}

View File

@@ -7,6 +7,7 @@ const mem = std.mem;
const Allocator = mem.Allocator;
const Dylib = @import("Dylib.zig");
const Object = @import("Object.zig");
const StringTable = @import("StringTable.zig");
pub const Type = enum {
regular,
@@ -19,7 +20,7 @@ pub const Type = enum {
@"type": Type,
/// Symbol name. Owned slice.
name: []u8,
name: []const u8,
/// Alias of.
alias: ?*Symbol = null,
@@ -43,23 +44,14 @@ pub const Regular = struct {
section: u8,
/// Whether the symbol is a weak ref.
weak_ref: bool,
weak_ref: bool = false,
/// Object file where to locate this symbol.
file: *Object,
/// null means self-reference.
file: ?*Object = null,
/// Debug stab if defined.
stab: ?struct {
/// Stab kind
kind: enum {
function,
global,
static,
},
/// Size of the stab.
size: u64,
} = null,
stab: ?Stab = null,
/// True if symbol was already committed into the final
/// symbol table.
@@ -73,6 +65,68 @@ pub const Regular = struct {
global,
};
pub const Stab = struct {
/// Stab kind
kind: enum {
function,
global,
static,
},
/// Size of the stab.
size: u64,
};
const Opts = struct {
linkage: Linkage = .translation_unit,
address: u64 = 0,
section: u8 = 0,
weak_ref: bool = false,
file: ?*Object = null,
stab: ?Stab = null,
};
pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
const reg = try allocator.create(Regular);
errdefer allocator.destroy(reg);
reg.* = .{
.base = .{
.@"type" = .regular,
.name = try allocator.dupe(u8, name),
},
.linkage = opts.linkage,
.address = opts.address,
.section = opts.section,
.weak_ref = opts.weak_ref,
.file = opts.file,
.stab = opts.stab,
};
return &reg.base;
}
pub fn asNlist(regular: *Regular, strtab: *StringTable) !macho.nlist_64 {
const n_strx = try strtab.getOrPut(regular.base.name);
var nlist = macho.nlist_64{
.n_strx = n_strx,
.n_type = macho.N_SECT,
.n_sect = regular.section,
.n_desc = 0,
.n_value = regular.address,
};
if (regular.linkage != .translation_unit) {
nlist.n_type |= macho.N_EXT;
}
if (regular.linkage == .linkage_unit) {
nlist.n_type |= macho.N_PEXT;
nlist.n_desc |= macho.N_WEAK_DEF;
}
return nlist;
}
pub fn isTemp(regular: *Regular) bool {
if (regular.linkage == .translation_unit) {
return mem.startsWith(u8, regular.base.name, "l") or mem.startsWith(u8, regular.base.name, "L");
@@ -97,6 +151,36 @@ pub const Proxy = struct {
pub const base_type: Symbol.Type = .proxy;
const Opts = struct {
file: ?*Dylib = null,
};
pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
const proxy = try allocator.create(Proxy);
errdefer allocator.destroy(proxy);
proxy.* = .{
.base = .{
.@"type" = .proxy,
.name = try allocator.dupe(u8, name),
},
.file = opts.file,
};
return &proxy.base;
}
pub fn asNlist(proxy: *Proxy, strtab: *StringTable) !macho.nlist_64 {
const n_strx = try strtab.getOrPut(proxy.base.name);
return macho.nlist_64{
.n_strx = n_strx,
.n_type = macho.N_UNDF | macho.N_EXT,
.n_sect = 0,
.n_desc = (proxy.dylibOrdinal() * macho.N_SYMBOL_RESOLVER) | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY,
.n_value = 0,
};
}
pub fn deinit(proxy: *Proxy, allocator: *Allocator) void {
proxy.bind_info.deinit(allocator);
}
@@ -115,6 +199,36 @@ pub const Unresolved = struct {
file: ?*Object = null,
pub const base_type: Symbol.Type = .unresolved;
const Opts = struct {
file: ?*Object = null,
};
pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
const undef = try allocator.create(Unresolved);
errdefer allocator.destroy(undef);
undef.* = .{
.base = .{
.@"type" = .unresolved,
.name = try allocator.dupe(u8, name),
},
.file = opts.file,
};
return &undef.base;
}
pub fn asNlist(undef: *Unresolved, strtab: *StringTable) !macho.nlist_64 {
const n_strx = try strtab.getOrPut(undef.base.name);
return macho.nlist_64{
.n_strx = n_strx,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
};
}
};
pub const Tentative = struct {
@@ -127,13 +241,49 @@ pub const Tentative = struct {
alignment: u16,
/// File where this symbol was referenced.
file: *Object,
file: ?*Object = null,
pub const base_type: Symbol.Type = .tentative;
const Opts = struct {
size: u64 = 0,
alignment: u16 = 0,
file: ?*Object = null,
};
pub fn new(allocator: *Allocator, name: []const u8, opts: Opts) !*Symbol {
const tent = try allocator.create(Tentative);
errdefer allocator.destroy(tent);
tent.* = .{
.base = .{
.@"type" = .tentative,
.name = try allocator.dupe(u8, name),
},
.size = opts.size,
.alignment = opts.alignment,
.file = opts.file,
};
return &tent.base;
}
pub fn asNlist(tent: *Tentative, strtab: *StringTable) !macho.nlist_64 {
// TODO
const n_strx = try strtab.getOrPut(tent.base.name);
return macho.nlist_64{
.n_strx = n_strx,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
};
}
};
pub fn deinit(base: *Symbol, allocator: *Allocator) void {
allocator.free(base.name);
switch (base.@"type") {
.proxy => @fieldParentPtr(Proxy, "base", base).deinit(allocator),
else => {},
@@ -154,6 +304,15 @@ pub fn getTopmostAlias(base: *Symbol) *Symbol {
return base;
}
pub fn asNlist(base: *Symbol, strtab: *StringTable) !macho.nlist_64 {
return switch (base.tag) {
.regular => @fieldParentPtr(Regular, "base", base).asNlist(strtab),
.proxy => @fieldParentPtr(Proxy, "base", base).asNlist(strtab),
.unresolved => @fieldParentPtr(Unresolved, "base", base).asNlist(strtab),
.tentative => @fieldParentPtr(Tentative, "base", base).asNlist(strtab),
};
}
pub fn isStab(sym: macho.nlist_64) bool {
return (macho.N_STAB & sym.n_type) != 0;
}

View File

@@ -17,6 +17,7 @@ const Archive = @import("Archive.zig");
const CodeSignature = @import("CodeSignature.zig");
const Dylib = @import("Dylib.zig");
const Object = @import("Object.zig");
const StringTable = @import("StringTable.zig");
const Symbol = @import("Symbol.zig");
const Trie = @import("Trie.zig");
@@ -24,6 +25,7 @@ usingnamespace @import("commands.zig");
usingnamespace @import("bind.zig");
allocator: *Allocator,
strtab: StringTable,
target: ?std.Target = null,
page_size: ?u16 = null,
@@ -109,9 +111,6 @@ tentatives: std.StringArrayHashMapUnmanaged(*Symbol) = .{},
/// Set if the linker found tentative definitions in any of the objects.
tentative_defs_offset: u64 = 0,
strtab: std.ArrayListUnmanaged(u8) = .{},
strtab_dir: std.StringHashMapUnmanaged(u32) = .{},
threadlocal_offsets: std.ArrayListUnmanaged(TlvOffset) = .{}, // TODO merge with Symbol abstraction
local_rebases: std.ArrayListUnmanaged(Pointer) = .{},
stubs: std.ArrayListUnmanaged(*Symbol) = .{},
@@ -138,8 +137,11 @@ const TlvOffset = struct {
/// Default path to dyld
const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld";
pub fn init(allocator: *Allocator) Zld {
return .{ .allocator = allocator };
pub fn init(allocator: *Allocator) !Zld {
return Zld{
.allocator = allocator,
.strtab = try StringTable.init(allocator),
};
}
pub fn deinit(self: *Zld) void {
@@ -180,15 +182,7 @@ pub fn deinit(self: *Zld) void {
self.tentatives.deinit(self.allocator);
self.globals.deinit(self.allocator);
self.unresolved.deinit(self.allocator);
self.strtab.deinit(self.allocator);
{
var it = self.strtab_dir.keyIterator();
while (it.next()) |key| {
self.allocator.free(key.*);
}
}
self.strtab_dir.deinit(self.allocator);
self.strtab.deinit();
}
pub fn closeFiles(self: Zld) void {
@@ -1137,16 +1131,7 @@ fn allocateTentativeSymbols(self: *Zld) !void {
// Convert tentative definitions into regular symbols.
for (self.tentatives.values()) |sym| {
const tent = sym.cast(Symbol.Tentative) orelse unreachable;
const reg = try self.allocator.create(Symbol.Regular);
errdefer self.allocator.destroy(reg);
reg.* = .{
.base = .{
.@"type" = .regular,
.name = try self.allocator.dupe(u8, tent.base.name),
.got_index = tent.base.got_index,
.stubs_index = tent.base.stubs_index,
},
const reg = try Symbol.Regular.new(self.allocator, tent.base.name, .{
.linkage = .global,
.address = base_address,
.section = section,
@@ -1156,16 +1141,18 @@ fn allocateTentativeSymbols(self: *Zld) !void {
.kind = .global,
.size = 0,
},
};
});
reg.got_index = tent.base.got_index;
reg.stubs_index = tent.base.stubs_index;
try self.globals.putNoClobber(self.allocator, reg.base.name, &reg.base);
tent.base.alias = &reg.base;
try self.globals.putNoClobber(self.allocator, reg.name, reg);
tent.base.alias = reg;
if (tent.base.got_index) |idx| {
self.got_entries.items[idx] = &reg.base;
self.got_entries.items[idx] = reg;
}
if (tent.base.stubs_index) |idx| {
self.stubs.items[idx] = &reg.base;
self.stubs.items[idx] = reg;
}
const address = mem.alignForwardGeneric(u64, base_address + tent.size, alignment);
@@ -1615,15 +1602,8 @@ fn resolveSymbols(self: *Zld) !void {
{
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);
const undef = try Symbol.Unresolved.new(self.allocator, name, .{});
try unresolved.append(undef);
}
var referenced = std.AutoHashMap(*Dylib, void).init(self.allocator);
@@ -1641,16 +1621,7 @@ fn resolveSymbols(self: *Zld) !void {
// TODO this is just a temp patch until I work out what to actually
// do with ___dso_handle and __mh_execute_header symbols which are
// synthetically created by the linker on macOS.
const name = try self.allocator.dupe(u8, undef.name);
const proxy = try self.allocator.create(Symbol.Proxy);
errdefer self.allocator.destroy(proxy);
proxy.* = .{
.base = .{
.@"type" = .proxy,
.name = name,
},
};
break :inner &proxy.base;
break :inner try Symbol.Proxy.new(self.allocator, undef.name, .{});
}
self.unresolved.putAssumeCapacityNoClobber(undef.name, undef);
@@ -2126,7 +2097,6 @@ fn populateMetadata(self: *Zld) !void {
.strsize = 0,
},
});
try self.strtab.append(self.allocator, 0);
}
if (self.dysymtab_cmd_index == null) {
@@ -2752,7 +2722,7 @@ fn writeDebugInfo(self: *Zld) !void {
const dirname = std.fs.path.dirname(tu_path) orelse "./";
// Current dir
try stabs.append(.{
.n_strx = try self.makeString(tu_path[0 .. dirname.len + 1]),
.n_strx = try self.strtab.getOrPut(tu_path[0 .. dirname.len + 1]),
.n_type = macho.N_SO,
.n_sect = 0,
.n_desc = 0,
@@ -2760,7 +2730,7 @@ fn writeDebugInfo(self: *Zld) !void {
});
// Artifact name
try stabs.append(.{
.n_strx = try self.makeString(tu_path[dirname.len + 1 ..]),
.n_strx = try self.strtab.getOrPut(tu_path[dirname.len + 1 ..]),
.n_type = macho.N_SO,
.n_sect = 0,
.n_desc = 0,
@@ -2768,7 +2738,7 @@ fn writeDebugInfo(self: *Zld) !void {
});
// Path to object file with debug info
try stabs.append(.{
.n_strx = try self.makeString(object.name.?),
.n_strx = try self.strtab.getOrPut(object.name.?),
.n_type = macho.N_OSO,
.n_sect = 0,
.n_desc = 1,
@@ -2801,7 +2771,7 @@ fn writeDebugInfo(self: *Zld) !void {
.n_value = reg.address,
});
try stabs.append(.{
.n_strx = try self.makeString(sym.name),
.n_strx = try self.strtab.getOrPut(sym.name),
.n_type = macho.N_FUN,
.n_sect = reg.section,
.n_desc = 0,
@@ -2824,7 +2794,7 @@ fn writeDebugInfo(self: *Zld) !void {
},
.global => {
try stabs.append(.{
.n_strx = try self.makeString(sym.name),
.n_strx = try self.strtab.getOrPut(sym.name),
.n_type = macho.N_GSYM,
.n_sect = 0,
.n_desc = 0,
@@ -2833,7 +2803,7 @@ fn writeDebugInfo(self: *Zld) !void {
},
.static => {
try stabs.append(.{
.n_strx = try self.makeString(sym.name),
.n_strx = try self.strtab.getOrPut(sym.name),
.n_type = macho.N_STSYM,
.n_sect = reg.section,
.n_desc = 0,
@@ -2892,24 +2862,14 @@ fn writeSymbolTable(self: *Zld) !void {
if (reg.isTemp()) continue;
if (reg.visited) continue;
const nlist = try reg.asNlist(&self.strtab);
switch (reg.linkage) {
.translation_unit => {
try locals.append(.{
.n_strx = try self.makeString(sym.name),
.n_type = macho.N_SECT,
.n_sect = reg.section,
.n_desc = 0,
.n_value = reg.address,
});
try locals.append(nlist);
},
else => {
try exports.append(.{
.n_strx = try self.makeString(sym.name),
.n_type = macho.N_SECT | macho.N_EXT,
.n_sect = reg.section,
.n_desc = 0,
.n_value = reg.address,
});
try exports.append(nlist);
},
}
@@ -2922,13 +2882,8 @@ fn writeSymbolTable(self: *Zld) !void {
for (self.imports.values()) |sym| {
const proxy = sym.cast(Symbol.Proxy) orelse unreachable;
try undefs.append(.{
.n_strx = try self.makeString(sym.name),
.n_type = macho.N_UNDF | macho.N_EXT,
.n_sect = 0,
.n_desc = (proxy.dylibOrdinal() * macho.N_SYMBOL_RESOLVER) | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY,
.n_value = 0,
});
const nlist = try proxy.asNlist(&self.strtab);
try undefs.append(nlist);
}
const nlocals = locals.items.len;
@@ -3017,14 +2972,14 @@ fn writeStringTable(self: *Zld) !void {
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64)));
symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.size(), @alignOf(u64)));
seg.inner.filesize += symtab.strsize;
log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
try self.file.?.pwriteAll(self.strtab.items, symtab.stroff);
try self.file.?.pwriteAll(self.strtab.asSlice(), symtab.stroff);
if (symtab.strsize > self.strtab.items.len and self.target.?.cpu.arch == .x86_64) {
if (symtab.strsize > self.strtab.size() and self.target.?.cpu.arch == .x86_64) {
// This is the last section, so we need to pad it out.
try self.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1);
}
@@ -3173,26 +3128,6 @@ fn writeHeader(self: *Zld) !void {
try self.file.?.pwriteAll(mem.asBytes(&header), 0);
}
fn makeString(self: *Zld, bytes: []const u8) !u32 {
if (self.strtab_dir.get(bytes)) |offset| {
log.debug("reusing '{s}' from string table at offset 0x{x}", .{ bytes, offset });
return offset;
}
try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1);
const offset = @intCast(u32, self.strtab.items.len);
log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset });
self.strtab.appendSliceAssumeCapacity(bytes);
self.strtab.appendAssumeCapacity(0);
try self.strtab_dir.putNoClobber(self.allocator, try self.allocator.dupe(u8, bytes), offset);
return offset;
}
fn getString(self: *const Zld, str_off: u32) []const u8 {
assert(str_off < self.strtab.items.len);
return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off));
}
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];