Merge pull request #16656 from ziglang/issue-16628

macho: track unwind/dwarf cfi records by symbol rather than atom
This commit is contained in:
Andrew Kelley 2023-08-02 22:28:02 -07:00 committed by GitHub
commit e85a46cb84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 515 additions and 324 deletions

View file

@ -75,11 +75,11 @@ exec_atoms: std.ArrayListUnmanaged(AtomIndex) = .{},
eh_frame_sect_id: ?u8 = null,
eh_frame_relocs_lookup: std.AutoArrayHashMapUnmanaged(u32, Record) = .{},
eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(AtomIndex, u32) = .{},
eh_frame_records_lookup: std.AutoArrayHashMapUnmanaged(SymbolWithLoc, u32) = .{},
unwind_info_sect_id: ?u8 = null,
unwind_relocs_lookup: []Record = undefined,
unwind_records_lookup: std.AutoHashMapUnmanaged(AtomIndex, u32) = .{},
unwind_records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .{},
const Entry = struct {
start: u32 = 0,
@ -274,11 +274,11 @@ const SymbolAtIndex = struct {
const sym = self.getSymbol(ctx);
if (!sym.ext()) {
const sym_name = self.getSymbolName(ctx);
if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 0;
return 1;
if (mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L")) return 3;
return 2;
}
if (sym.weakDef() or sym.pext()) return 2;
return 3;
if (sym.weakDef() or sym.pext()) return 1;
return 0;
}
/// Performs lexicographic-like check.
@ -423,8 +423,8 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
zld,
object_id,
sym_index,
0,
0,
sym_index,
1,
sect.size,
sect.@"align",
out_sect_id,
@ -502,8 +502,8 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
zld,
object_id,
sym_index,
0,
0,
sym_index,
1,
atom_size,
sect.@"align",
out_sect_id,
@ -521,7 +521,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
const atom_loc = filterSymbolsByAddress(symtab[next_sym_index..], addr, addr + 1);
assert(atom_loc.len > 0);
const atom_sym_index = atom_loc.index + next_sym_index;
const nsyms_trailing = atom_loc.len - 1;
const nsyms_trailing = atom_loc.len;
next_sym_index += atom_loc.len;
const atom_size = if (next_sym_index < sect_start_index + sect_loc.len)
@ -538,7 +538,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
zld,
object_id,
atom_sym_index,
atom_sym_index + 1,
atom_sym_index,
nsyms_trailing,
atom_size,
atom_align,
@ -772,8 +772,7 @@ fn parseEhFrameSection(self: *Object, zld: *Zld, object_id: u32) !void {
if (target.getFile() != object_id) {
self.eh_frame_relocs_lookup.getPtr(offset).?.dead = true;
} else {
const atom_index = self.getAtomIndexForSymbol(target.sym_index).?;
self.eh_frame_records_lookup.putAssumeCapacityNoClobber(atom_index, offset);
self.eh_frame_records_lookup.putAssumeCapacityNoClobber(target, offset);
}
}
}
@ -802,10 +801,10 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
_ = try zld.initSection("__TEXT", "__unwind_info", .{});
}
try self.unwind_records_lookup.ensureTotalCapacity(gpa, @as(u32, @intCast(self.exec_atoms.items.len)));
const unwind_records = self.getUnwindRecords();
try self.unwind_records_lookup.ensureTotalCapacity(gpa, @as(u32, @intCast(unwind_records.len)));
const needs_eh_frame = for (unwind_records) |record| {
if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) break true;
} else false;
@ -844,8 +843,7 @@ fn parseUnwindInfo(self: *Object, zld: *Zld, object_id: u32) !void {
if (target.getFile() != object_id) {
self.unwind_relocs_lookup[record_id].dead = true;
} else {
const atom_index = self.getAtomIndexForSymbol(target.sym_index).?;
self.unwind_records_lookup.putAssumeCapacityNoClobber(atom_index, @as(u32, @intCast(record_id)));
self.unwind_records_lookup.putAssumeCapacityNoClobber(target, @as(u32, @intCast(record_id)));
}
}
}
@ -1012,7 +1010,13 @@ pub fn getSymbolByAddress(self: Object, addr: u64, sect_hint: ?u8) u32 {
Predicate{ .addr = @as(i64, @intCast(addr)) },
);
if (target_sym_index > 0) {
return @as(u32, @intCast(lookup.start + target_sym_index - 1));
// Hone in on the most senior alias of the target symbol.
// See SymbolAtIndex.lessThan for more context.
var start = target_sym_index - 1;
while (start > 0 and
self.source_address_lookup[lookup.start..][start - 1] == addr) : (start -= 1)
{}
return @as(u32, @intCast(lookup.start + start));
}
}
return self.getSectionAliasSymbolIndex(sect_id);

View file

@ -26,7 +26,7 @@ gpa: Allocator,
/// List of all unwind records gathered from all objects and sorted
/// by source function address.
records: std.ArrayListUnmanaged(macho.compact_unwind_entry) = .{},
records_lookup: std.AutoHashMapUnmanaged(AtomIndex, RecordIndex) = .{},
records_lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, RecordIndex) = .{},
/// List of all personalities referenced by either unwind info entries
/// or __eh_frame entries.
@ -211,23 +211,22 @@ pub fn scanRelocs(zld: *Zld) !void {
for (zld.objects.items, 0..) |*object, object_id| {
const unwind_records = object.getUnwindRecords();
for (object.exec_atoms.items) |atom_index| {
const record_id = object.unwind_records_lookup.get(atom_index) orelse continue;
if (object.unwind_relocs_lookup[record_id].dead) continue;
const record = unwind_records[record_id];
if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
if (getPersonalityFunctionReloc(
zld,
@as(u32, @intCast(object_id)),
record_id,
)) |rel| {
// Personality function; add GOT pointer.
const target = Atom.parseRelocTarget(zld, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
try Atom.addGotEntry(zld, target);
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
while (inner_syms_it.next()) |sym| {
const record_id = object.unwind_records_lookup.get(sym) orelse continue;
if (object.unwind_relocs_lookup[record_id].dead) continue;
const record = unwind_records[record_id];
if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
if (getPersonalityFunctionReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
// Personality function; add GOT pointer.
const target = Atom.parseRelocTarget(zld, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
try Atom.addGotEntry(zld, target);
}
}
}
}
@ -242,8 +241,8 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa);
defer records.deinit();
var atom_indexes = std.ArrayList(AtomIndex).init(info.gpa);
defer atom_indexes.deinit();
var sym_indexes = std.ArrayList(SymbolWithLoc).init(info.gpa);
defer sym_indexes.deinit();
// TODO handle dead stripping
for (zld.objects.items, 0..) |*object, object_id| {
@ -253,80 +252,101 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
// Contents of unwind records does not have to cover all symbol in executable section
// so we need insert them ourselves.
try records.ensureUnusedCapacity(object.exec_atoms.items.len);
try atom_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
try sym_indexes.ensureUnusedCapacity(object.exec_atoms.items.len);
for (object.exec_atoms.items) |atom_index| {
var record = if (object.unwind_records_lookup.get(atom_index)) |record_id| blk: {
if (object.unwind_relocs_lookup[record_id].dead) continue;
var record = unwind_records[record_id];
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
var prev_symbol: ?SymbolWithLoc = null;
while (inner_syms_it.next()) |symbol| {
var record = if (object.unwind_records_lookup.get(symbol)) |record_id| blk: {
if (object.unwind_relocs_lookup[record_id].dead) continue;
var record = unwind_records[record_id];
if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), atom_index, &record);
} else {
if (getPersonalityFunctionReloc(
zld,
@as(u32, @intCast(object_id)),
record_id,
)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
const personality_index = info.getPersonalityFunction(target) orelse inner: {
const personality_index = info.personalities_count;
info.personalities[personality_index] = target;
info.personalities_count += 1;
break :inner personality_index;
};
if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), symbol, &record);
} else {
if (getPersonalityFunctionReloc(
zld,
@as(u32, @intCast(object_id)),
record_id,
)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
const personality_index = info.getPersonalityFunction(target) orelse inner: {
const personality_index = info.personalities_count;
info.personalities[personality_index] = target;
info.personalities_count += 1;
break :inner personality_index;
};
record.personalityFunction = personality_index + 1;
UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
}
if (getLsdaReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
record.lsda = @as(u64, @bitCast(target));
}
}
break :blk record;
} else blk: {
const atom = zld.getAtom(atom_index);
const sym = zld.getSymbol(atom.getSymbolWithLoc());
if (sym.n_desc == N_DEAD) continue;
if (!object.hasUnwindRecords()) {
if (object.eh_frame_records_lookup.get(atom_index)) |fde_offset| {
if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
var record = nullRecord();
try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), atom_index, &record);
switch (cpu_arch) {
.aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
.x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
else => unreachable,
record.personalityFunction = personality_index + 1;
UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1);
}
if (getLsdaReloc(zld, @as(u32, @intCast(object_id)), record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = @as(u32, @intCast(object_id)),
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
record.lsda = @as(u64, @bitCast(target));
}
break :blk record;
}
}
break :blk record;
} else blk: {
const sym = zld.getSymbol(symbol);
if (sym.n_desc == N_DEAD) continue;
if (prev_symbol) |prev_sym| {
const prev_addr = object.getSourceSymbol(prev_sym.sym_index).?.n_value;
const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
if (prev_addr == curr_addr) continue;
}
break :blk nullRecord();
};
if (!object.hasUnwindRecords()) {
if (object.eh_frame_records_lookup.get(symbol)) |fde_offset| {
if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
var record = nullRecord();
try info.collectPersonalityFromDwarf(zld, @as(u32, @intCast(object_id)), symbol, &record);
switch (cpu_arch) {
.aarch64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_ARM64_MODE.DWARF),
.x86_64 => UnwindEncoding.setMode(&record.compactUnwindEncoding, macho.UNWIND_X86_64_MODE.DWARF),
else => unreachable,
}
break :blk record;
}
}
const atom = zld.getAtom(atom_index);
const sym_loc = atom.getSymbolWithLoc();
const sym = zld.getSymbol(sym_loc);
assert(sym.n_desc != N_DEAD);
record.rangeStart = sym.n_value;
record.rangeLength = @as(u32, @intCast(atom.size));
break :blk nullRecord();
};
records.appendAssumeCapacity(record);
atom_indexes.appendAssumeCapacity(atom_index);
const atom = zld.getAtom(atom_index);
const sym = zld.getSymbol(symbol);
assert(sym.n_desc != N_DEAD);
const size = if (inner_syms_it.next()) |next_sym| blk: {
// All this trouble to account for symbol aliases.
// TODO I think that remodelling the linker so that a Symbol references an Atom
// is the way to go, kinda like we do for ELF. We might also want to perhaps tag
// symbol aliases somehow so that they are excluded from everything except relocation
// resolution.
defer inner_syms_it.pos -= 1;
const curr_addr = object.getSourceSymbol(symbol.sym_index).?.n_value;
const next_addr = object.getSourceSymbol(next_sym.sym_index).?.n_value;
if (next_addr > curr_addr) break :blk next_addr - curr_addr;
break :blk zld.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
} else zld.getSymbol(atom.getSymbolWithLoc()).n_value + atom.size - sym.n_value;
record.rangeStart = sym.n_value;
record.rangeLength = @as(u32, @intCast(size));
try records.append(record);
try sym_indexes.append(symbol);
prev_symbol = symbol;
}
}
}
@ -339,7 +359,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
// Fold records
try info.records.ensureTotalCapacity(info.gpa, records.items.len);
try info.records_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(atom_indexes.items.len)));
try info.records_lookup.ensureTotalCapacity(info.gpa, @as(u32, @intCast(sym_indexes.items.len)));
var maybe_prev: ?macho.compact_unwind_entry = null;
for (records.items, 0..) |record, i| {
@ -365,7 +385,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
break :blk record_id;
}
};
info.records_lookup.putAssumeCapacityNoClobber(atom_indexes.items[i], record_id);
info.records_lookup.putAssumeCapacityNoClobber(sym_indexes.items[i], record_id);
}
// Calculate common encodings
@ -501,12 +521,12 @@ fn collectPersonalityFromDwarf(
info: *UnwindInfo,
zld: *Zld,
object_id: u32,
atom_index: u32,
sym_loc: SymbolWithLoc,
record: *macho.compact_unwind_entry,
) !void {
const object = &zld.objects.items[object_id];
var it = object.getEhFrameRecordsIterator();
const fde_offset = object.eh_frame_records_lookup.get(atom_index).?;
const fde_offset = object.eh_frame_records_lookup.get(sym_loc).?;
it.seekTo(fde_offset);
const fde = (try it.next()).?;
const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);

View file

@ -84,14 +84,14 @@ pub inline fn getSymbolWithLoc(self: Atom) SymbolWithLoc {
const InnerSymIterator = struct {
sym_index: u32,
count: u32,
nsyms: u32,
file: u32,
pos: u32 = 0,
pub fn next(it: *@This()) ?SymbolWithLoc {
if (it.count == 0) return null;
const res = SymbolWithLoc{ .sym_index = it.sym_index, .file = it.file };
it.sym_index += 1;
it.count -= 1;
if (it.pos == it.nsyms) return null;
const res = SymbolWithLoc{ .sym_index = it.sym_index + it.pos, .file = it.file };
it.pos += 1;
return res;
}
};
@ -103,7 +103,7 @@ pub fn getInnerSymbolsIterator(zld: *Zld, atom_index: AtomIndex) InnerSymIterato
assert(atom.getFile() != null);
return .{
.sym_index = atom.inner_sym_index,
.count = atom.inner_nsyms_trailing,
.nsyms = atom.inner_nsyms_trailing,
.file = atom.file,
};
}
@ -228,11 +228,7 @@ pub fn parseRelocTarget(zld: *Zld, ctx: struct {
// Find containing atom
log.debug(" | locating symbol by address @{x} in section {d}", .{ address_in_section, sect_id });
const candidate = object.getSymbolByAddress(address_in_section, sect_id);
// Make sure we are not dealing with a local alias.
const atom_index = object.getAtomIndexForSymbol(candidate) orelse break :sym_index candidate;
const atom = zld.getAtom(atom_index);
break :sym_index atom.sym_index;
break :sym_index object.getSymbolByAddress(address_in_section, sect_id);
} else object.reverse_symtab_lookup[ctx.rel.r_symbolnum];
const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = ctx.object_id + 1 };

View file

@ -294,124 +294,131 @@ fn markUnwindRecords(zld: *Zld, object_id: u32, alive: *AtomTable) !void {
const unwind_records = object.getUnwindRecords();
for (object.exec_atoms.items) |atom_index| {
if (!object.hasUnwindRecords()) {
if (object.eh_frame_records_lookup.get(atom_index)) |fde_offset| {
const ptr = object.eh_frame_relocs_lookup.getPtr(fde_offset).?;
if (ptr.dead) continue; // already marked
if (!alive.contains(atom_index)) {
// Mark dead and continue.
ptr.dead = true;
} else {
// Mark references live and continue.
try markEhFrameRecord(zld, object_id, atom_index, alive);
}
continue;
}
}
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
const record_id = object.unwind_records_lookup.get(atom_index) orelse continue;
if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do
if (!alive.contains(atom_index)) {
// Mark the record dead and continue.
object.unwind_relocs_lookup[record_id].dead = true;
if (object.eh_frame_records_lookup.get(atom_index)) |fde_offset| {
object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
if (!object.hasUnwindRecords()) {
if (alive.contains(atom_index)) {
// Mark references live and continue.
try markEhFrameRecords(zld, object_id, atom_index, alive);
} else {
while (inner_syms_it.next()) |sym| {
if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
// Mark dead and continue.
object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
}
}
}
continue;
}
const record = unwind_records[record_id];
if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
try markEhFrameRecord(zld, object_id, atom_index, alive);
} else {
if (UnwindInfo.getPersonalityFunctionReloc(zld, object_id, record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = object_id,
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
const target_sym = zld.getSymbol(target);
if (!target_sym.undf()) {
while (inner_syms_it.next()) |sym| {
const record_id = object.unwind_records_lookup.get(sym) orelse continue;
if (object.unwind_relocs_lookup[record_id].dead) continue; // already marked, nothing to do
if (!alive.contains(atom_index)) {
// Mark the record dead and continue.
object.unwind_relocs_lookup[record_id].dead = true;
if (object.eh_frame_records_lookup.get(sym)) |fde_offset| {
object.eh_frame_relocs_lookup.getPtr(fde_offset).?.dead = true;
}
continue;
}
const record = unwind_records[record_id];
if (UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) {
try markEhFrameRecords(zld, object_id, atom_index, alive);
} else {
if (UnwindInfo.getPersonalityFunctionReloc(zld, object_id, record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = object_id,
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
const target_sym = zld.getSymbol(target);
if (!target_sym.undf()) {
const target_object = zld.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
markLive(zld, target_atom_index, alive);
}
}
if (UnwindInfo.getLsdaReloc(zld, object_id, record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = object_id,
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
const target_object = zld.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
markLive(zld, target_atom_index, alive);
}
}
if (UnwindInfo.getLsdaReloc(zld, object_id, record_id)) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = object_id,
.rel = rel,
.code = mem.asBytes(&record),
.base_offset = @as(i32, @intCast(record_id * @sizeOf(macho.compact_unwind_entry))),
});
const target_object = zld.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
markLive(zld, target_atom_index, alive);
}
}
}
}
fn markEhFrameRecord(zld: *Zld, object_id: u32, atom_index: AtomIndex, alive: *AtomTable) !void {
fn markEhFrameRecords(zld: *Zld, object_id: u32, atom_index: AtomIndex, alive: *AtomTable) !void {
const cpu_arch = zld.options.target.cpu.arch;
const object = &zld.objects.items[object_id];
var it = object.getEhFrameRecordsIterator();
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
const fde_offset = object.eh_frame_records_lookup.get(atom_index).?;
it.seekTo(fde_offset);
const fde = (try it.next()).?;
while (inner_syms_it.next()) |sym| {
const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue; // Continue in case we hit a temp symbol alias
it.seekTo(fde_offset);
const fde = (try it.next()).?;
const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);
const cie_offset = fde_offset + 4 - cie_ptr;
it.seekTo(cie_offset);
const cie = (try it.next()).?;
const cie_ptr = fde.getCiePointerSource(object_id, zld, fde_offset);
const cie_offset = fde_offset + 4 - cie_ptr;
it.seekTo(cie_offset);
const cie = (try it.next()).?;
switch (cpu_arch) {
.aarch64 => {
// Mark FDE references which should include any referenced LSDA record
const relocs = eh_frame.getRelocs(zld, object_id, fde_offset);
for (relocs) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = object_id,
.rel = rel,
.code = fde.data,
.base_offset = @as(i32, @intCast(fde_offset)) + 4,
switch (cpu_arch) {
.aarch64 => {
// Mark FDE references which should include any referenced LSDA record
const relocs = eh_frame.getRelocs(zld, object_id, fde_offset);
for (relocs) |rel| {
const target = Atom.parseRelocTarget(zld, .{
.object_id = object_id,
.rel = rel,
.code = fde.data,
.base_offset = @as(i32, @intCast(fde_offset)) + 4,
});
const target_sym = zld.getSymbol(target);
if (!target_sym.undf()) blk: {
const target_object = zld.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index) orelse
break :blk;
markLive(zld, target_atom_index, alive);
}
}
},
.x86_64 => {
const sect = object.getSourceSection(object.eh_frame_sect_id.?);
const lsda_ptr = try fde.getLsdaPointer(cie, .{
.base_addr = sect.addr,
.base_offset = fde_offset,
});
const target_sym = zld.getSymbol(target);
if (!target_sym.undf()) blk: {
const target_object = zld.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index) orelse
break :blk;
if (lsda_ptr) |lsda_address| {
// Mark LSDA record as live
const sym_index = object.getSymbolByAddress(lsda_address, null);
const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
markLive(zld, target_atom_index, alive);
}
}
},
.x86_64 => {
const sect = object.getSourceSection(object.eh_frame_sect_id.?);
const lsda_ptr = try fde.getLsdaPointer(cie, .{
.base_addr = sect.addr,
.base_offset = fde_offset,
});
if (lsda_ptr) |lsda_address| {
// Mark LSDA record as live
const sym_index = object.getSymbolByAddress(lsda_address, null);
const target_atom_index = object.getAtomIndexForSymbol(sym_index).?;
},
else => unreachable,
}
// Mark CIE references which should include any referenced personalities
// that are defined locally.
if (cie.getPersonalityPointerReloc(zld, object_id, cie_offset)) |target| {
const target_sym = zld.getSymbol(target);
if (!target_sym.undf()) {
const target_object = zld.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
markLive(zld, target_atom_index, alive);
}
},
else => unreachable,
}
// Mark CIE references which should include any referenced personalities
// that are defined locally.
if (cie.getPersonalityPointerReloc(zld, object_id, cie_offset)) |target| {
const target_sym = zld.getSymbol(target);
if (!target_sym.undf()) {
const target_object = zld.objects.items[target.getFile().?];
const target_atom_index = target_object.getAtomIndexForSymbol(target.sym_index).?;
markLive(zld, target_atom_index, alive);
}
}
}
@ -458,8 +465,8 @@ fn prune(zld: *Zld, alive: AtomTable) void {
section.last_atom_index = prev_index;
} else {
assert(section.header.size == 0);
section.first_atom_index = undefined;
section.last_atom_index = undefined;
section.first_atom_index = 0;
section.last_atom_index = 0;
}
}

View file

@ -24,19 +24,22 @@ pub fn scanRelocs(zld: *Zld) !void {
var it = object.getEhFrameRecordsIterator();
for (object.exec_atoms.items) |atom_index| {
const fde_offset = object.eh_frame_records_lookup.get(atom_index) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
it.seekTo(fde_offset);
const fde = (try it.next()).?;
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
while (inner_syms_it.next()) |sym| {
const fde_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_offset).?.dead) continue;
it.seekTo(fde_offset);
const fde = (try it.next()).?;
const cie_ptr = fde.getCiePointerSource(@intCast(object_id), zld, fde_offset);
const cie_offset = fde_offset + 4 - cie_ptr;
const cie_ptr = fde.getCiePointerSource(@intCast(object_id), zld, fde_offset);
const cie_offset = fde_offset + 4 - cie_ptr;
if (!cies.contains(cie_offset)) {
try cies.putNoClobber(cie_offset, {});
it.seekTo(cie_offset);
const cie = (try it.next()).?;
try cie.scanRelocs(zld, @as(u32, @intCast(object_id)), cie_offset);
if (!cies.contains(cie_offset)) {
try cies.putNoClobber(cie_offset, {});
it.seekTo(cie_offset);
const cie = (try it.next()).?;
try cie.scanRelocs(zld, @as(u32, @intCast(object_id)), cie_offset);
}
}
}
}
@ -59,35 +62,38 @@ pub fn calcSectionSize(zld: *Zld, unwind_info: *const UnwindInfo) !void {
var eh_it = object.getEhFrameRecordsIterator();
for (object.exec_atoms.items) |atom_index| {
const fde_record_offset = object.eh_frame_records_lookup.get(atom_index) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
while (inner_syms_it.next()) |sym| {
const fde_record_offset = object.eh_frame_records_lookup.get(sym) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
const record_id = unwind_info.records_lookup.get(atom_index) orelse continue;
const record = unwind_info.records.items[record_id];
const record_id = unwind_info.records_lookup.get(sym) orelse continue;
const record = unwind_info.records.items[record_id];
// TODO skip this check if no __compact_unwind is present
const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
if (!is_dwarf) continue;
// TODO skip this check if no __compact_unwind is present
const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
if (!is_dwarf) continue;
eh_it.seekTo(fde_record_offset);
const source_fde_record = (try eh_it.next()).?;
eh_it.seekTo(fde_record_offset);
const source_fde_record = (try eh_it.next()).?;
const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
const cie_offset = fde_record_offset + 4 - cie_ptr;
const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
const cie_offset = fde_record_offset + 4 - cie_ptr;
const gop = try cies.getOrPut(cie_offset);
if (!gop.found_existing) {
eh_it.seekTo(cie_offset);
const source_cie_record = (try eh_it.next()).?;
gop.value_ptr.* = size;
size += source_cie_record.getSize();
const gop = try cies.getOrPut(cie_offset);
if (!gop.found_existing) {
eh_it.seekTo(cie_offset);
const source_cie_record = (try eh_it.next()).?;
gop.value_ptr.* = size;
size += source_cie_record.getSize();
}
size += source_fde_record.getSize();
}
size += source_fde_record.getSize();
}
}
sect.size = size;
sect.size = size;
}
}
pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
@ -118,97 +124,99 @@ pub fn write(zld: *Zld, unwind_info: *UnwindInfo) !void {
var eh_it = object.getEhFrameRecordsIterator();
for (object.exec_atoms.items) |atom_index| {
const fde_record_offset = object.eh_frame_records_lookup.get(atom_index) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
var inner_syms_it = Atom.getInnerSymbolsIterator(zld, atom_index);
while (inner_syms_it.next()) |target| {
const fde_record_offset = object.eh_frame_records_lookup.get(target) orelse continue;
if (object.eh_frame_relocs_lookup.get(fde_record_offset).?.dead) continue;
const record_id = unwind_info.records_lookup.get(atom_index) orelse continue;
const record = &unwind_info.records.items[record_id];
const record_id = unwind_info.records_lookup.get(target) orelse continue;
const record = &unwind_info.records.items[record_id];
// TODO skip this check if no __compact_unwind is present
const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
if (!is_dwarf) continue;
// TODO skip this check if no __compact_unwind is present
const is_dwarf = UnwindInfo.UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch);
if (!is_dwarf) continue;
eh_it.seekTo(fde_record_offset);
const source_fde_record = (try eh_it.next()).?;
eh_it.seekTo(fde_record_offset);
const source_fde_record = (try eh_it.next()).?;
const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
const cie_offset = fde_record_offset + 4 - cie_ptr;
const cie_ptr = source_fde_record.getCiePointerSource(@intCast(object_id), zld, fde_record_offset);
const cie_offset = fde_record_offset + 4 - cie_ptr;
const gop = try cies.getOrPut(cie_offset);
if (!gop.found_existing) {
eh_it.seekTo(cie_offset);
const source_cie_record = (try eh_it.next()).?;
var cie_record = try source_cie_record.toOwned(gpa);
try cie_record.relocate(zld, @as(u32, @intCast(object_id)), .{
.source_offset = cie_offset,
const gop = try cies.getOrPut(cie_offset);
if (!gop.found_existing) {
eh_it.seekTo(cie_offset);
const source_cie_record = (try eh_it.next()).?;
var cie_record = try source_cie_record.toOwned(gpa);
try cie_record.relocate(zld, @as(u32, @intCast(object_id)), .{
.source_offset = cie_offset,
.out_offset = eh_frame_offset,
.sect_addr = sect.addr,
});
eh_records.putAssumeCapacityNoClobber(eh_frame_offset, cie_record);
gop.value_ptr.* = eh_frame_offset;
eh_frame_offset += cie_record.getSize();
}
var fde_record = try source_fde_record.toOwned(gpa);
try fde_record.relocate(zld, @as(u32, @intCast(object_id)), .{
.source_offset = fde_record_offset,
.out_offset = eh_frame_offset,
.sect_addr = sect.addr,
});
eh_records.putAssumeCapacityNoClobber(eh_frame_offset, cie_record);
gop.value_ptr.* = eh_frame_offset;
eh_frame_offset += cie_record.getSize();
}
fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*);
var fde_record = try source_fde_record.toOwned(gpa);
try fde_record.relocate(zld, @as(u32, @intCast(object_id)), .{
.source_offset = fde_record_offset,
.out_offset = eh_frame_offset,
.sect_addr = sect.addr,
});
fde_record.setCiePointer(eh_frame_offset + 4 - gop.value_ptr.*);
switch (cpu_arch) {
.aarch64 => {}, // relocs take care of LSDA pointers
.x86_64 => {
// We need to relocate target symbol address ourselves.
const atom = zld.getAtom(atom_index);
const atom_sym = zld.getSymbol(atom.getSymbolWithLoc());
try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{
.base_addr = sect.addr,
.base_offset = eh_frame_offset,
});
// We need to parse LSDA pointer and relocate ourselves.
const cie_record = eh_records.get(
eh_frame_offset + 4 - fde_record.getCiePointer(),
).?;
const eh_frame_sect = object.getSourceSection(object.eh_frame_sect_id.?);
const source_lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
.base_addr = eh_frame_sect.addr,
.base_offset = fde_record_offset,
});
if (source_lsda_ptr) |ptr| {
const sym_index = object.getSymbolByAddress(ptr, null);
const sym = object.symtab[sym_index];
try fde_record.setLsdaPointer(cie_record, sym.n_value, .{
switch (cpu_arch) {
.aarch64 => {}, // relocs take care of LSDA pointers
.x86_64 => {
// We need to relocate target symbol address ourselves.
const atom_sym = zld.getSymbol(target);
try fde_record.setTargetSymbolAddress(atom_sym.n_value, .{
.base_addr = sect.addr,
.base_offset = eh_frame_offset,
});
}
},
else => unreachable,
// We need to parse LSDA pointer and relocate ourselves.
const cie_record = eh_records.get(
eh_frame_offset + 4 - fde_record.getCiePointer(),
).?;
const eh_frame_sect = object.getSourceSection(object.eh_frame_sect_id.?);
const source_lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
.base_addr = eh_frame_sect.addr,
.base_offset = fde_record_offset,
});
if (source_lsda_ptr) |ptr| {
const sym_index = object.getSymbolByAddress(ptr, null);
const sym = object.symtab[sym_index];
try fde_record.setLsdaPointer(cie_record, sym.n_value, .{
.base_addr = sect.addr,
.base_offset = eh_frame_offset,
});
}
},
else => unreachable,
}
eh_records.putAssumeCapacityNoClobber(eh_frame_offset, fde_record);
UnwindInfo.UnwindEncoding.setDwarfSectionOffset(
&record.compactUnwindEncoding,
cpu_arch,
@as(u24, @intCast(eh_frame_offset)),
);
const cie_record = eh_records.get(
eh_frame_offset + 4 - fde_record.getCiePointer(),
).?;
const lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
.base_addr = sect.addr,
.base_offset = eh_frame_offset,
});
if (lsda_ptr) |ptr| {
record.lsda = ptr - seg.vmaddr;
}
eh_frame_offset += fde_record.getSize();
}
eh_records.putAssumeCapacityNoClobber(eh_frame_offset, fde_record);
UnwindInfo.UnwindEncoding.setDwarfSectionOffset(
&record.compactUnwindEncoding,
cpu_arch,
@as(u24, @intCast(eh_frame_offset)),
);
const cie_record = eh_records.get(
eh_frame_offset + 4 - fde_record.getCiePointer(),
).?;
const lsda_ptr = try fde_record.getLsdaPointer(cie_record, .{
.base_addr = sect.addr,
.base_offset = eh_frame_offset,
});
if (lsda_ptr) |ptr| {
record.lsda = ptr - seg.vmaddr;
}
eh_frame_offset += fde_record.getSize();
}
}

View file

@ -1492,6 +1492,42 @@ pub const Zld = struct {
try thunks.createThunks(self, @as(u8, @intCast(sect_id)));
}
}
// Update offsets of all symbols contained within each Atom.
// We need to do this since our unwind info synthesiser relies on
// traversing the symbols when synthesising unwind info and DWARF CFI records.
for (slice.items(.first_atom_index)) |first_atom_index| {
if (first_atom_index == 0) continue;
var atom_index = first_atom_index;
while (true) {
const atom = self.getAtom(atom_index);
const sym = self.getSymbol(atom.getSymbolWithLoc());
if (atom.getFile() != null) {
// Update each symbol contained within the atom
var it = Atom.getInnerSymbolsIterator(self, atom_index);
while (it.next()) |sym_loc| {
const inner_sym = self.getSymbolPtr(sym_loc);
inner_sym.n_value = sym.n_value + Atom.calcInnerSymbolOffset(
self,
atom_index,
sym_loc.sym_index,
);
}
// If there is a section alias, update it now too
if (Atom.getSectionAlias(self, atom_index)) |sym_loc| {
const alias = self.getSymbolPtr(sym_loc);
alias.n_value = sym.n_value;
}
}
if (atom.next_index) |next_index| {
atom_index = next_index;
} else break;
}
}
}
fn allocateSegments(self: *Zld) !void {

View file

@ -96,6 +96,10 @@ pub const cases = [_]Case{
.build_root = "test/link/macho/bugs/16308",
.import = @import("link/macho/bugs/16308/build.zig"),
},
.{
.build_root = "test/link/macho/bugs/16628",
.import = @import("link/macho/bugs/16628/build.zig"),
},
.{
.build_root = "test/link/macho/dead_strip",
.import = @import("link/macho/dead_strip/build.zig"),

View file

@ -0,0 +1,37 @@
.globl _foo
.align 4
_foo:
.cfi_startproc
stp x29, x30, [sp, #-32]!
.cfi_def_cfa_offset 32
.cfi_offset w30, -24
.cfi_offset w29, -32
mov x29, sp
.cfi_def_cfa w29, 32
bl _bar
ldp x29, x30, [sp], #32
.cfi_restore w29
.cfi_restore w30
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.globl _bar
.align 4
_bar:
.cfi_startproc
sub sp, sp, #32
.cfi_def_cfa_offset -32
stp x29, x30, [sp, #16]
.cfi_offset w30, -24
.cfi_offset w29, -32
mov x29, sp
.cfi_def_cfa w29, 32
mov w0, #4
ldp x29, x30, [sp, #16]
.cfi_restore w29
.cfi_restore w30
add sp, sp, #32
.cfi_def_cfa_offset 0
ret
.cfi_endproc

View file

@ -0,0 +1,29 @@
.globl _foo
_foo:
.cfi_startproc
push %rbp
.cfi_def_cfa_offset 8
.cfi_offset %rbp, -8
mov %rsp, %rbp
.cfi_def_cfa_register %rbp
call _bar
pop %rbp
.cfi_restore %rbp
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.globl _bar
_bar:
.cfi_startproc
push %rbp
.cfi_def_cfa_offset 8
.cfi_offset %rbp, -8
mov %rsp, %rbp
.cfi_def_cfa_register %rbp
mov $4, %rax
pop %rbp
.cfi_restore %rbp
.cfi_def_cfa_offset 0
ret
.cfi_endproc

View file

@ -0,0 +1,42 @@
const std = @import("std");
const builtin = @import("builtin");
pub const requires_symlinks = true;
pub const requires_macos_sdk = false;
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const target: std.zig.CrossTarget = .{ .os_tag = .macos };
const exe = b.addExecutable(.{
.name = "test",
.optimize = optimize,
.target = target,
});
exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
switch (builtin.cpu.arch) {
.aarch64 => {
exe.addCSourceFile(.{ .file = .{ .path = "a_arm64.s" }, .flags = &[0][]const u8{} });
},
.x86_64 => {
exe.addCSourceFile(.{ .file = .{ .path = "a_x64.s" }, .flags = &[0][]const u8{} });
},
else => unreachable,
}
exe.linkLibC();
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdOutEqual("4\n");
test_step.dependOn(&run.step);
}

View file

@ -0,0 +1,8 @@
#include <stdio.h>
int foo();
int main() {
printf("%d\n", foo());
return 0;
}