zld: draft symbol resolver on macho.nlist_64 only

This commit is contained in:
Jakub Konka 2021-07-16 00:51:21 +02:00
parent f519e781c6
commit 5a2bea2931
2 changed files with 545 additions and 261 deletions

View File

@ -56,9 +56,6 @@ tu_name: ?[]const u8 = null,
tu_comp_dir: ?[]const u8 = null,
mtime: ?u64 = null,
symbols: std.ArrayListUnmanaged(*Symbol) = .{},
sections_as_symbols: std.AutoHashMapUnmanaged(u8, *Symbol) = .{},
text_blocks: std.ArrayListUnmanaged(*TextBlock) = .{},
const DebugInfo = struct {
@ -165,8 +162,6 @@ pub fn deinit(self: *Object) void {
self.data_in_code_entries.deinit(self.allocator);
self.symtab.deinit(self.allocator);
self.strtab.deinit(self.allocator);
self.symbols.deinit(self.allocator);
self.sections_as_symbols.deinit(self.allocator);
self.text_blocks.deinit(self.allocator);
if (self.debug_info) |*db| {

View File

@ -16,7 +16,6 @@ const Archive = @import("Archive.zig");
const CodeSignature = @import("CodeSignature.zig");
const Dylib = @import("Dylib.zig");
const Object = @import("Object.zig");
const Symbol = @import("Symbol.zig");
const TextBlock = @import("TextBlock.zig");
const Trie = @import("Trie.zig");
@ -100,22 +99,55 @@ objc_selrefs_section_index: ?u16 = null,
objc_classrefs_section_index: ?u16 = null,
objc_data_section_index: ?u16 = null,
locals: std.ArrayListUnmanaged(*Symbol) = .{},
imports: std.ArrayListUnmanaged(*Symbol) = .{},
globals: std.StringArrayHashMapUnmanaged(*Symbol) = .{},
locals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
globals: std.ArrayListUnmanaged(macho.nlist_64) = .{},
imports: std.ArrayListUnmanaged(macho.nlist_64) = .{},
undefs: std.ArrayListUnmanaged(macho.nlist_64) = .{},
tentatives: std.ArrayListUnmanaged(macho.nlist_64) = .{},
symbol_resolver: std.StringArrayHashMapUnmanaged(SymbolWithLoc) = .{},
object_mapping: std.AutoHashMapUnmanaged(u16, []u32) = .{},
stubs: std.ArrayListUnmanaged(*Symbol) = .{},
got_entries: std.ArrayListUnmanaged(*Symbol) = .{},
strtab: std.ArrayListUnmanaged(u8) = .{},
// stubs: std.ArrayListUnmanaged(*Symbol) = .{},
got_entries: std.ArrayListUnmanaged(GotEntry) = .{},
stub_helper_stubs_start_off: ?u64 = null,
blocks: std.AutoHashMapUnmanaged(MatchingSection, *TextBlock) = .{},
strtab: std.ArrayListUnmanaged(u8) = .{},
has_dices: bool = false,
has_stabs: bool = false,
const SymbolWithLoc = struct {
// Table where the symbol can be found.
where: enum {
global,
import,
undef,
tentative,
},
where_index: u32,
local_sym_index: u32 = 0,
file: u16 = 0,
};
pub const GotEntry = struct {
/// GOT entry can either be a local pointer or an extern (nonlazy) import.
kind: enum {
local,
import,
},
/// Id to the macho.nlist_64 from the respective table: either locals or nonlazy imports.
/// TODO I'm more and more inclined to just manage a single, max two symbol tables
/// rather than 4 as we currently do, but I'll follow up in the future PR.
local_sym_index: u32,
/// Index of this entry in the GOT.
got_index: u32,
};
pub const Output = struct {
tag: enum { exe, dylib },
path: []const u8,
@ -130,7 +162,7 @@ pub fn init(allocator: *Allocator) !Zld {
}
pub fn deinit(self: *Zld) void {
self.stubs.deinit(self.allocator);
// self.stubs.deinit(self.allocator);
self.got_entries.deinit(self.allocator);
for (self.load_commands.items) |*lc| {
@ -156,20 +188,24 @@ pub fn deinit(self: *Zld) void {
}
self.dylibs.deinit(self.allocator);
for (self.imports.items) |sym| {
self.allocator.destroy(sym);
}
self.imports.deinit(self.allocator);
for (self.locals.items) |sym| {
self.allocator.destroy(sym);
}
self.locals.deinit(self.allocator);
self.globals.deinit(self.allocator);
self.imports.deinit(self.allocator);
self.undefs.deinit(self.allocator);
self.tentatives.deinit(self.allocator);
for (self.globals.keys()) |key| {
for (self.symbol_resolver.keys()) |key| {
self.allocator.free(key);
}
self.globals.deinit(self.allocator);
self.symbol_resolver.deinit(self.allocator);
{
var it = self.object_mapping.valueIterator();
while (it.next()) |value_ptr| {
self.allocator.free(value_ptr.*);
}
}
self.object_mapping.deinit(self.allocator);
self.strtab.deinit(self.allocator);
@ -213,28 +249,73 @@ pub fn link(self: *Zld, files: []const []const u8, output: Output, args: LinkArg
try self.parseInputFiles(files, args.syslibroot);
try self.parseLibs(args.libs, args.syslibroot);
try self.resolveSymbols();
try self.parseTextBlocks();
try self.sortSections();
try self.addRpaths(args.rpaths);
try self.addDataInCodeLC();
try self.addCodeSignatureLC();
try self.allocateTextSegment();
try self.allocateDataConstSegment();
try self.allocateDataSegment();
self.allocateLinkeditSegment();
try self.allocateTextBlocks();
// var it = self.blocks.iterator();
// while (it.next()) |entry| {
// const seg = self.load_commands.items[entry.key_ptr.seg].Segment;
// const sect = seg.sections.items[entry.key_ptr.sect];
log.warn("locals", .{});
for (self.locals.items) |sym| {
log.warn(" | {s}: {}", .{ self.getString(sym.n_strx), sym });
}
// log.warn("\n\n{s},{s} contents:", .{ segmentName(sect), sectionName(sect) });
// log.warn(" {}", .{sect});
// entry.value_ptr.*.print(self);
// }
log.warn("globals", .{});
for (self.globals.items) |sym| {
log.warn(" | {s}: {}", .{ self.getString(sym.n_strx), sym });
}
try self.flush();
log.warn("tentatives", .{});
for (self.tentatives.items) |sym| {
log.warn(" | {s}: {}", .{ self.getString(sym.n_strx), sym });
}
log.warn("undefines", .{});
for (self.undefs.items) |sym| {
log.warn(" | {s}: {}", .{ self.getString(sym.n_strx), sym });
}
log.warn("imports", .{});
for (self.imports.items) |sym| {
log.warn(" | {s}: {}", .{ self.getString(sym.n_strx), sym });
}
log.warn("symbol resolver", .{});
for (self.symbol_resolver.keys()) |key| {
log.warn(" | {s} => {}", .{ key, self.symbol_resolver.get(key).? });
}
log.warn("mappings", .{});
for (self.objects.items) |object, id| {
const object_id = @intCast(u16, id);
log.warn(" in object {s}", .{object.name.?});
for (object.symtab.items) |sym, sym_id| {
if (self.localSymIndex(object_id, @intCast(u32, sym_id))) |local_id| {
log.warn(" | {d} => {d}", .{ sym_id, local_id });
} else {
log.warn(" | {d} no local mapping for {s}", .{ sym_id, object.getString(sym.n_strx) });
}
}
}
return error.TODO;
// try self.parseTextBlocks();
// try self.sortSections();
// try self.addRpaths(args.rpaths);
// try self.addDataInCodeLC();
// try self.addCodeSignatureLC();
// try self.allocateTextSegment();
// try self.allocateDataConstSegment();
// try self.allocateDataSegment();
// self.allocateLinkeditSegment();
// try self.allocateTextBlocks();
// // var it = self.blocks.iterator();
// // while (it.next()) |entry| {
// // const seg = self.load_commands.items[entry.key_ptr.seg].Segment;
// // const sect = seg.sections.items[entry.key_ptr.sect];
// // log.warn("\n\n{s},{s} contents:", .{ segmentName(sect), sectionName(sect) });
// // log.warn(" {}", .{sect});
// // entry.value_ptr.*.print(self);
// // }
// try self.flush();
}
fn parseInputFiles(self: *Zld, files: []const []const u8, syslibroot: ?[]const u8) !void {
@ -1328,130 +1409,242 @@ fn writeStubInStubHelper(self: *Zld, index: u32) !void {
try self.file.?.pwriteAll(code, stub_off);
}
fn resolveSymbolsInObject(self: *Zld, object: *Object) !void {
log.debug("resolving symbols in '{s}'", .{object.name});
fn resolveSymbolsInObject(self: *Zld, object_id: u16) !void {
const object = self.objects.items[object_id];
for (object.symtab.items) |sym| {
log.warn("resolving symbols in '{s}'", .{object.name});
const mapping = try self.allocator.alloc(u32, object.symtab.items.len);
mem.set(u32, mapping, 0);
try self.object_mapping.putNoClobber(self.allocator, object_id, mapping);
for (object.symtab.items) |sym, id| {
const sym_id = @intCast(u32, id);
const sym_name = object.getString(sym.n_strx);
if (Symbol.isStab(sym)) {
log.err("unhandled symbol type: stab {s}", .{sym_name});
log.err(" | first definition in {s}", .{object.name.?});
if (symbolIsStab(sym)) {
log.err("unhandled symbol type: stab", .{});
log.err(" symbol '{s}'", .{sym_name});
log.err(" first definition in '{s}'", .{object.name.?});
return error.UnhandledSymbolType;
}
if (Symbol.isIndr(sym)) {
log.err("unhandled symbol type: indirect {s}", .{sym_name});
log.err(" | first definition in {s}", .{object.name.?});
if (symbolIsIndr(sym)) {
log.err("unhandled symbol type: indirect", .{});
log.err(" symbol '{s}'", .{sym_name});
log.err(" first definition in '{s}'", .{object.name.?});
return error.UnhandledSymbolType;
}
if (Symbol.isAbs(sym)) {
log.err("unhandled symbol type: absolute {s}", .{sym_name});
log.err(" | first definition in {s}", .{object.name.?});
if (symbolIsAbs(sym)) {
log.err("unhandled symbol type: absolute", .{});
log.err(" symbol '{s}'", .{sym_name});
log.err(" first definition in '{s}'", .{object.name.?});
return error.UnhandledSymbolType;
}
if (Symbol.isSect(sym) and !Symbol.isExt(sym)) {
// Regular symbol local to translation unit
const symbol = try self.allocator.create(Symbol);
symbol.* = .{
.strx = try self.makeString(sym_name),
.payload = .{
.regular = .{
.linkage = .translation_unit,
.address = sym.n_value,
.weak_ref = Symbol.isWeakRef(sym),
.file = object,
.local_sym_index = @intCast(u32, self.locals.items.len),
},
},
};
try self.locals.append(self.allocator, symbol);
try object.symbols.append(self.allocator, symbol);
continue;
}
const symbol = self.globals.get(sym_name) orelse symbol: {
// Insert new global symbol.
const symbol = try self.allocator.create(Symbol);
symbol.* = .{
.strx = try self.makeString(sym_name),
.payload = .{ .undef = .{ .file = object } },
};
const alloc_name = try self.allocator.dupe(u8, sym_name);
try self.globals.putNoClobber(self.allocator, alloc_name, symbol);
break :symbol symbol;
};
if (Symbol.isSect(sym)) {
// Global symbol
const linkage: Symbol.Regular.Linkage = if (Symbol.isWeakDef(sym) or Symbol.isPext(sym))
.linkage_unit
else
.global;
const should_update = if (symbol.payload == .regular) blk: {
if (symbol.payload.regular.linkage == .global and linkage == .global) {
log.err("symbol '{s}' defined multiple times", .{sym_name});
log.err(" | first definition in {s}", .{symbol.payload.regular.file.?.name.?});
log.err(" | next definition in {s}", .{object.name.?});
return error.MultipleSymbolDefinitions;
if (symbolIsSect(sym)) {
// Defined symbol regardless of scope lands in the locals symbol table.
const n_strx = blk: {
if (self.symbol_resolver.get(sym_name)) |resolv| {
switch (resolv.where) {
.global => break :blk self.globals.items[resolv.where_index].n_strx,
.tentative => break :blk self.tentatives.items[resolv.where_index].n_strx,
.undef => break :blk self.undefs.items[resolv.where_index].n_strx,
.import => unreachable,
}
}
break :blk symbol.payload.regular.linkage != .global;
} else true;
break :blk try self.makeString(sym_name);
};
const local_sym_index = @intCast(u32, self.locals.items.len);
try self.locals.append(self.allocator, .{
.n_strx = n_strx,
.n_type = macho.N_SECT,
.n_sect = 0,
.n_desc = 0,
.n_value = sym.n_value,
});
mapping[sym_id] = local_sym_index;
if (should_update) {
symbol.payload = .{
.regular = .{
.linkage = linkage,
.address = sym.n_value,
.weak_ref = Symbol.isWeakRef(sym),
.file = object,
},
};
}
} else if (sym.n_value != 0) {
// Tentative definition
const should_update = switch (symbol.payload) {
.tentative => |tent| tent.size < sym.n_value,
.undef => true,
else => false,
// If the symbol's scope is not local aka translation unit, then we need work out
// if we should save the symbol as a global, or potentially flag the error.
if (!symbolIsExt(sym)) continue;
const local = self.locals.items[local_sym_index];
const resolv = self.symbol_resolver.getPtr(sym_name) orelse {
const global_sym_index = @intCast(u32, self.globals.items.len);
try self.globals.append(self.allocator, .{
.n_strx = n_strx,
.n_type = sym.n_type,
.n_sect = 0,
.n_desc = sym.n_desc,
.n_value = sym.n_value,
});
try self.symbol_resolver.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), .{
.where = .global,
.where_index = global_sym_index,
.local_sym_index = local_sym_index,
.file = object_id,
});
continue;
};
if (should_update) {
symbol.payload = .{
.tentative = .{
.size = sym.n_value,
.alignment = (sym.n_desc >> 8) & 0x0f,
.file = object,
},
};
}
}
switch (resolv.where) {
.import => unreachable,
.global => {
const global = &self.globals.items[resolv.where_index];
try object.symbols.append(self.allocator, symbol);
if (!(symbolIsWeakDef(sym) and symbolIsPext(sym)) and
!(symbolIsWeakDef(global.*) and symbolIsPext(global.*)))
{
log.err("symbol '{s}' defined multiple times", .{sym_name});
log.err(" first definition in '{s}'", .{self.objects.items[resolv.file].name.?});
log.err(" next definition in '{s}'", .{object.name.?});
return error.MultipleSymbolDefinitions;
}
if (symbolIsWeakDef(sym) or symbolIsPext(sym)) continue; // Current symbol is weak, so skip it.
// Otherwise, update the resolver and the global symbol.
global.n_type = sym.n_type;
resolv.local_sym_index = local_sym_index;
resolv.file = object_id;
continue;
},
.undef => {
const undef = &self.undefs.items[resolv.where_index];
undef.* = .{
.n_strx = 0,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
};
},
.tentative => {
const tentative = &self.tentatives.items[resolv.where_index];
tentative.* = .{
.n_strx = 0,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
};
},
}
const global_sym_index = @intCast(u32, self.globals.items.len);
try self.globals.append(self.allocator, .{
.n_strx = local.n_strx,
.n_type = sym.n_type,
.n_sect = 0,
.n_desc = sym.n_desc,
.n_value = sym.n_value,
});
resolv.* = .{
.where = .global,
.where_index = global_sym_index,
.local_sym_index = local_sym_index,
.file = object_id,
};
} else if (symbolIsTentative(sym)) {
// Symbol is a tentative definition.
const resolv = self.symbol_resolver.getPtr(sym_name) orelse {
const tent_sym_index = @intCast(u32, self.tentatives.items.len);
try self.tentatives.append(self.allocator, .{
.n_strx = try self.makeString(sym_name),
.n_type = sym.n_type,
.n_sect = 0,
.n_desc = sym.n_desc,
.n_value = sym.n_value,
});
try self.symbol_resolver.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), .{
.where = .tentative,
.where_index = tent_sym_index,
.file = object_id,
});
continue;
};
switch (resolv.where) {
.import => unreachable,
.global => {},
.undef => {
const undef = &self.undefs.items[resolv.where_index];
const tent_sym_index = @intCast(u32, self.tentatives.items.len);
try self.tentatives.append(self.allocator, .{
.n_strx = undef.n_strx,
.n_type = sym.n_type,
.n_sect = 0,
.n_desc = sym.n_desc,
.n_value = sym.n_value,
});
resolv.* = .{
.where = .tentative,
.where_index = tent_sym_index,
.file = object_id,
};
undef.* = .{
.n_strx = 0,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
};
},
.tentative => {
const tentative = &self.tentatives.items[resolv.where_index];
if (tentative.n_value >= sym.n_value) continue;
tentative.n_desc = sym.n_desc;
tentative.n_value = sym.n_value;
resolv.file = object_id;
},
}
} else {
// Symbol is undefined.
if (self.symbol_resolver.contains(sym_name)) continue;
const undef_sym_index = @intCast(u32, self.undefs.items.len);
try self.undefs.append(self.allocator, .{
.n_strx = try self.makeString(sym_name),
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
});
try self.symbol_resolver.putNoClobber(self.allocator, try self.allocator.dupe(u8, sym_name), .{
.where = .undef,
.where_index = undef_sym_index,
.file = object_id,
});
}
}
}
fn resolveSymbols(self: *Zld) !void {
// TODO mimicking insertion of null symbol from incremental linker.
// This will need to moved.
const null_sym = try self.allocator.create(Symbol);
null_sym.* = .{ .strx = 0, .payload = .{ .undef = .{} } };
try self.locals.append(self.allocator, null_sym);
try self.locals.append(self.allocator, .{
.n_strx = 0,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
});
try self.strtab.append(self.allocator, 0);
// First pass, resolve symbols in provided objects.
for (self.objects.items) |object| {
try self.resolveSymbolsInObject(object);
for (self.objects.items) |_, object_id| {
try self.resolveSymbolsInObject(@intCast(u16, object_id));
}
// Second pass, resolve symbols in static libraries.
var sym_it = self.globals.iterator();
while (sym_it.next()) |entry| {
const sym_name = entry.key_ptr.*;
const symbol = entry.value_ptr.*;
if (symbol.payload != .undef) continue;
loop: for (self.undefs.items) |sym| {
if (symbolIsNull(sym)) continue;
const sym_name = self.getString(sym.n_strx);
for (self.archives.items) |archive| {
// Check if the entry exists in a static archive.
@ -1462,167 +1655,184 @@ fn resolveSymbols(self: *Zld) !void {
assert(offsets.items.len > 0);
const object = try archive.parseObject(offsets.items[0]);
const object_id = @intCast(u16, self.objects.items.len);
try self.objects.append(self.allocator, object);
try self.resolveSymbolsInObject(object);
try self.resolveSymbolsInObject(object_id);
sym_it = self.globals.iterator();
break;
continue :loop;
}
}
// Put any globally defined regular symbol as local.
// Convert any tentative definition into a regular symbol and allocate
// text blocks for each tentative defintion.
for (self.globals.values()) |symbol| {
switch (symbol.payload) {
.regular => |*reg| {
reg.local_sym_index = @intCast(u32, self.locals.items.len);
try self.locals.append(self.allocator, symbol);
},
.tentative => |tent| {
const match: MatchingSection = blk: {
if (self.common_section_index == null) {
const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
self.common_section_index = @intCast(u16, data_seg.sections.items.len);
try data_seg.addSection(self.allocator, "__common", .{
.flags = macho.S_ZEROFILL,
});
}
break :blk .{
.seg = self.data_segment_cmd_index.?,
.sect = self.common_section_index.?,
};
};
for (self.tentatives.items) |sym| {
const sym_name = self.getString(sym.n_strx);
const match: MatchingSection = blk: {
if (self.common_section_index == null) {
const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment;
self.common_section_index = @intCast(u16, data_seg.sections.items.len);
try data_seg.addSection(self.allocator, "__common", .{
.flags = macho.S_ZEROFILL,
});
}
break :blk .{
.seg = self.data_segment_cmd_index.?,
.sect = self.common_section_index.?,
};
};
const size = tent.size;
const code = try self.allocator.alloc(u8, size);
mem.set(u8, code, 0);
const alignment = tent.alignment;
const local_sym_index = @intCast(u32, self.locals.items.len);
const size = sym.n_value;
const code = try self.allocator.alloc(u8, size);
mem.set(u8, code, 0);
const alignment = (sym.n_desc >> 8) & 0x0f;
symbol.payload = .{
.regular = .{
.linkage = .global,
.segment_id = self.data_segment_cmd_index.?,
.section_id = self.common_section_index.?,
.local_sym_index = local_sym_index,
},
};
try self.locals.append(self.allocator, symbol);
const resolv = self.symbol_resolver.getPtr(sym_name) orelse unreachable;
const local_sym_index = @intCast(u32, self.locals.items.len);
var nlist = macho.nlist_64{
.n_strx = sym.n_strx,
.n_type = macho.N_SECT,
.n_sect = self.sectionId(match),
.n_desc = 0,
.n_value = 0,
};
try self.locals.append(self.allocator, nlist);
const global_sym_index = @intCast(u32, self.globals.items.len);
nlist.n_type |= macho.N_EXT;
try self.globals.append(self.allocator, nlist);
resolv.* = .{
.where = .global,
.where_index = global_sym_index,
.local_sym_index = local_sym_index,
};
const block = try self.allocator.create(TextBlock);
errdefer self.allocator.destroy(block);
const block = try self.allocator.create(TextBlock);
errdefer self.allocator.destroy(block);
block.* = TextBlock.init(self.allocator);
block.local_sym_index = local_sym_index;
block.code = code;
block.size = size;
block.alignment = alignment;
block.* = TextBlock.init(self.allocator);
block.local_sym_index = local_sym_index;
block.code = code;
block.size = size;
block.alignment = alignment;
// Update target section's metadata
// TODO should we update segment's size here too?
// How does it tie with incremental space allocs?
const tseg = &self.load_commands.items[match.seg].Segment;
const tsect = &tseg.sections.items[match.sect];
const new_alignment = math.max(tsect.@"align", block.alignment);
const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
tsect.size = new_size;
tsect.@"align" = new_alignment;
// Update target section's metadata
// TODO should we update segment's size here too?
// How does it tie with incremental space allocs?
const tseg = &self.load_commands.items[match.seg].Segment;
const tsect = &tseg.sections.items[match.sect];
const new_alignment = math.max(tsect.@"align", block.alignment);
const new_alignment_pow_2 = try math.powi(u32, 2, new_alignment);
const new_size = mem.alignForwardGeneric(u64, tsect.size, new_alignment_pow_2) + block.size;
tsect.size = new_size;
tsect.@"align" = new_alignment;
if (self.blocks.getPtr(match)) |last| {
last.*.next = block;
block.prev = last.*;
last.* = block;
} else {
try self.blocks.putNoClobber(self.allocator, match, block);
}
},
else => {},
if (self.blocks.getPtr(match)) |last| {
last.*.next = block;
block.prev = last.*;
last.* = block;
} else {
try self.blocks.putNoClobber(self.allocator, match, block);
}
}
// Third pass, resolve symbols in dynamic libraries.
{
// Put dyld_stub_binder as an undefined special symbol.
const symbol = try self.allocator.create(Symbol);
symbol.* = .{
.strx = try self.makeString("dyld_stub_binder"),
.payload = .{ .undef = .{} },
};
const index = @intCast(u32, self.got_entries.items.len);
symbol.got_index = index;
try self.got_entries.append(self.allocator, symbol);
const alloc_name = try self.allocator.dupe(u8, "dyld_stub_binder");
try self.globals.putNoClobber(self.allocator, alloc_name, symbol);
const undef_sym_index = @intCast(u32, self.undefs.items.len);
try self.undefs.append(self.allocator, .{
.n_strx = try self.makeString("dyld_stub_binder"),
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
});
try self.symbol_resolver.putNoClobber(self.allocator, try self.allocator.dupe(u8, "dyld_stub_binder"), .{
.where = .undef,
.where_index = undef_sym_index,
});
}
var referenced = std.AutoHashMap(*Dylib, void).init(self.allocator);
defer referenced.deinit();
loop: for (self.globals.keys()) |sym_name| {
const symbol = self.globals.get(sym_name).?;
if (symbol.payload != .undef) continue;
loop: for (self.undefs.items) |sym| {
if (symbolIsNull(sym)) continue;
const sym_name = self.getString(sym.n_strx);
for (self.dylibs.items) |dylib| {
if (!dylib.symbols.contains(sym_name)) continue;
try referenced.put(dylib, {});
const index = @intCast(u32, self.imports.items.len);
symbol.payload = .{
.proxy = .{
.file = dylib,
.local_sym_index = index,
},
if (!referenced.contains(dylib)) {
// Add LC_LOAD_DYLIB load command for each referenced dylib/stub.
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;
try referenced.putNoClobber(dylib, {});
}
const resolv = self.symbol_resolver.getPtr(sym_name) orelse unreachable;
const undef = &self.undefs.items[resolv.where_index];
const import_sym_index = @intCast(u32, self.imports.items.len);
try self.imports.append(self.allocator, .{
.n_strx = undef.n_strx,
.n_type = macho.N_UNDF | macho.N_EXT,
.n_sect = 0,
.n_desc = (dylib.ordinal.? * macho.N_SYMBOL_RESOLVER) | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY,
.n_value = 0,
});
resolv.* = .{
.where = .import,
.where_index = import_sym_index,
};
try self.imports.append(self.allocator, symbol);
undef.* = .{
.n_strx = 0,
.n_type = macho.N_UNDF,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
};
continue :loop;
}
}
// Add LC_LOAD_DYLIB load command for each referenced dylib/stub.
var it = referenced.iterator();
while (it.next()) |entry| {
const dylib = entry.key_ptr.*;
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;
}
// Fourth pass, handle synthetic symbols and flag any undefined references.
if (self.globals.get("___dso_handle")) |symbol| {
if (symbol.payload == .undef) {
const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
symbol.payload = .{
.regular = .{
.linkage = .translation_unit,
.address = seg.inner.vmaddr,
.weak_ref = true,
.local_sym_index = @intCast(u32, self.locals.items.len),
},
};
try self.locals.append(self.allocator, symbol);
}
if (self.symbol_resolver.getPtr("___dso_handle")) |resolv| blk: {
if (resolv.where != .undef) break :blk;
const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
const undef = &self.undefs.items[resolv.where_index];
const global_sym_index = @intCast(u32, self.globals.items.len);
try self.globals.append(self.allocator, .{
.n_strx = undef.n_strx,
.n_type = macho.N_PEXT | macho.N_EXT | macho.N_SECT,
.n_sect = 0,
.n_desc = macho.N_WEAK_DEF,
.n_value = seg.inner.vmaddr,
});
resolv.* = .{
.where = .global,
.where_index = global_sym_index,
};
}
var has_undefined = false;
for (self.globals.keys()) |sym_name| {
const symbol = self.globals.get(sym_name).?;
if (symbol.payload != .undef) continue;
for (self.undefs.items) |sym| {
if (symbolIsNull(sym)) continue;
const sym_name = self.getString(sym.n_strx);
const resolv = self.symbol_resolver.get(sym_name) orelse unreachable;
log.err("undefined reference to symbol '{s}'", .{sym_name});
if (symbol.payload.undef.file) |file| {
log.err(" | referenced in {s}", .{file.name.?});
}
log.err(" first referenced in '{s}'", .{self.objects.items[resolv.file].name.?});
has_undefined = true;
}
@ -2776,3 +2986,82 @@ pub fn getString(self: *Zld, off: u32) []const u8 {
assert(off < self.strtab.items.len);
return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + off));
}
fn localSymIndex(self: Zld, object_id: u16, orig_id: u32) ?u32 {
const mapping = self.object_mapping.get(object_id) orelse return null;
const local_sym_index = mapping[orig_id];
if (local_sym_index == 0) {
return null;
}
return local_sym_index;
}
pub fn symbolIsStab(sym: macho.nlist_64) bool {
return (macho.N_STAB & sym.n_type) != 0;
}
pub fn symbolIsPext(sym: macho.nlist_64) bool {
return (macho.N_PEXT & sym.n_type) != 0;
}
pub fn symbolIsExt(sym: macho.nlist_64) bool {
return (macho.N_EXT & sym.n_type) != 0;
}
pub fn symbolIsSect(sym: macho.nlist_64) bool {
const type_ = macho.N_TYPE & sym.n_type;
return type_ == macho.N_SECT;
}
pub fn symbolIsUndf(sym: macho.nlist_64) bool {
const type_ = macho.N_TYPE & sym.n_type;
return type_ == macho.N_UNDF;
}
pub fn symbolIsIndr(sym: macho.nlist_64) bool {
const type_ = macho.N_TYPE & sym.n_type;
return type_ == macho.N_INDR;
}
pub fn symbolIsAbs(sym: macho.nlist_64) bool {
const type_ = macho.N_TYPE & sym.n_type;
return type_ == macho.N_ABS;
}
pub fn symbolIsWeakDef(sym: macho.nlist_64) bool {
return (sym.n_desc & macho.N_WEAK_DEF) != 0;
}
pub fn symbolIsWeakRef(sym: macho.nlist_64) bool {
return (sym.n_desc & macho.N_WEAK_REF) != 0;
}
pub fn symbolIsTentative(sym: macho.nlist_64) bool {
if (!symbolIsUndf(sym)) return false;
return sym.n_value != 0;
}
pub fn symbolIsNull(sym: macho.nlist_64) bool {
return sym.n_value == 0 and sym.n_desc == 0 and sym.n_type == 0 and sym.n_strx == 0 and sym.n_sect == 0;
}
pub fn symbolIsTemp(self: Zld, sym: macho.nlist_64) bool {
if (!symbolIsSect(sym)) return false;
if (symbolIsExt(sym)) return false;
const sym_name = self.getString(sym.n_strx);
return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L");
}
pub fn sectionId(self: Zld, match: MatchingSection) u8 {
// TODO there might be a more generic way of doing this.
var section: u8 = 0;
for (self.load_commands.items) |cmd, cmd_id| {
if (cmd != .Segment) break;
if (cmd_id == match.seg) {
section += @intCast(u8, match.sect) + 1;
break;
}
section += @intCast(u8, cmd.Segment.sections.items.len);
}
return section;
}