Merge pull request #10251 from ziglang/fix-10225

macos: fix stack traces for Zig linked with any linker including Apple's ld64
This commit is contained in:
Andrew Kelley 2021-11-30 12:52:00 -08:00 committed by GitHub
commit 51df63a44e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 212 additions and 134 deletions

View file

@ -559,7 +559,7 @@ pub const TTY = struct {
fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
var min: usize = 0;
var max: usize = symbols.len - 1; // Exclude sentinel.
var max: usize = symbols.len;
while (min < max) {
const mid = min + (max - min) / 2;
const curr = &symbols[mid];
@ -850,51 +850,91 @@ fn readMachODebugInfo(allocator: *mem.Allocator, macho_file: File) !ModuleDebugI
} else {
return error.MissingDebugInfo;
};
const syms = @ptrCast([*]const macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff))[0..symtab.nsyms];
const syms = @ptrCast(
[*]const macho.nlist_64,
@alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff),
)[0..symtab.nsyms];
const strings = @ptrCast([*]const u8, hdr_base + symtab.stroff)[0 .. symtab.strsize - 1 :0];
const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
var ofile: ?*const macho.nlist_64 = null;
var reloc: u64 = 0;
var ofile: u32 = undefined;
var last_sym: MachoSymbol = undefined;
var symbol_index: usize = 0;
var last_len: u64 = 0;
var state: enum {
init,
oso_open,
oso_close,
bnsym,
fun_strx,
fun_size,
ensym,
} = .init;
for (syms) |*sym| {
if (sym.n_type & std.macho.N_STAB != 0) {
switch (sym.n_type) {
std.macho.N_OSO => {
ofile = sym;
reloc = 0;
},
std.macho.N_FUN => {
if (sym.n_sect == 0) {
last_len = sym.n_value;
} else {
symbols_buf[symbol_index] = MachoSymbol{
.nlist = sym,
if (!sym.stab()) continue;
// TODO handle globals N_GSYM, and statics N_STSYM
switch (sym.n_type) {
macho.N_OSO => {
switch (state) {
.init, .oso_close => {
state = .oso_open;
ofile = sym.n_strx;
},
else => return error.InvalidDebugInfo,
}
},
macho.N_BNSYM => {
switch (state) {
.oso_open, .ensym => {
state = .bnsym;
last_sym = .{
.strx = 0,
.addr = sym.n_value,
.size = 0,
.ofile = ofile,
.reloc = reloc,
};
},
else => return error.InvalidDebugInfo,
}
},
macho.N_FUN => {
switch (state) {
.bnsym => {
state = .fun_strx;
last_sym.strx = sym.n_strx;
},
.fun_strx => {
state = .fun_size;
last_sym.size = @intCast(u32, sym.n_value);
},
else => return error.InvalidDebugInfo,
}
},
macho.N_ENSYM => {
switch (state) {
.fun_size => {
state = .ensym;
symbols_buf[symbol_index] = last_sym;
symbol_index += 1;
}
},
std.macho.N_BNSYM => {
if (reloc == 0) {
reloc = sym.n_value;
}
},
else => continue,
}
},
else => return error.InvalidDebugInfo,
}
},
macho.N_SO => {
switch (state) {
.init, .oso_close => {},
.oso_open, .ensym => {
state = .oso_close;
},
else => return error.InvalidDebugInfo,
}
},
else => {},
}
}
const sentinel = try allocator.create(macho.nlist_64);
sentinel.* = macho.nlist_64{
.n_strx = 0,
.n_type = 36,
.n_sect = 0,
.n_desc = 0,
.n_value = symbols_buf[symbol_index - 1].nlist.n_value + last_len,
};
assert(state == .oso_close);
const symbols = allocator.shrink(symbols_buf, symbol_index);
@ -946,18 +986,19 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
}
const MachoSymbol = struct {
nlist: *const macho.nlist_64,
ofile: ?*const macho.nlist_64,
reloc: u64,
strx: u32,
addr: u64,
size: u32,
ofile: u32,
/// Returns the address from the macho file
fn address(self: MachoSymbol) u64 {
return self.nlist.n_value;
return self.addr;
}
fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
_ = context;
return lhs.address() < rhs.address();
return lhs.addr < rhs.addr;
}
};
@ -1231,13 +1272,17 @@ pub const ModuleDebugInfo = switch (native_os) {
strings: [:0]const u8,
ofiles: OFileTable,
const OFileTable = std.StringHashMap(DW.DwarfInfo);
const OFileTable = std.StringHashMap(OFileInfo);
const OFileInfo = struct {
di: DW.DwarfInfo,
addr_table: std.StringHashMap(u64),
};
pub fn allocator(self: @This()) *mem.Allocator {
return self.ofiles.allocator;
}
fn loadOFile(self: *@This(), o_file_path: []const u8) !DW.DwarfInfo {
fn loadOFile(self: *@This(), o_file_path: []const u8) !OFileInfo {
const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking });
const mapped_mem = try mapWholeFile(o_file);
@ -1250,22 +1295,54 @@ pub const ModuleDebugInfo = switch (native_os) {
const hdr_base = @ptrCast([*]const u8, hdr);
var ptr = hdr_base + @sizeOf(macho.mach_header_64);
var segptr = ptr;
var ncmd: u32 = hdr.ncmds;
const segcmd = while (ncmd != 0) : (ncmd -= 1) {
var segcmd: ?*const macho.segment_command_64 = null;
var symtabcmd: ?*const macho.symtab_command = null;
while (ncmd != 0) : (ncmd -= 1) {
const lc = @ptrCast(*const std.macho.load_command, ptr);
switch (lc.cmd) {
std.macho.LC_SEGMENT_64 => {
break @ptrCast(
segcmd = @ptrCast(
*const std.macho.segment_command_64,
@alignCast(@alignOf(std.macho.segment_command_64), ptr),
);
segptr = ptr;
},
std.macho.LC_SYMTAB => {
symtabcmd = @ptrCast(
*const std.macho.symtab_command,
@alignCast(@alignOf(std.macho.symtab_command), ptr),
);
},
else => {},
}
ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);
} else {
return error.MissingDebugInfo;
};
}
if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
// Parse symbols
const strtab = @ptrCast(
[*]const u8,
hdr_base + symtabcmd.?.stroff,
)[0 .. symtabcmd.?.strsize - 1 :0];
const symtab = @ptrCast(
[*]const macho.nlist_64,
@alignCast(@alignOf(macho.nlist_64), hdr_base + symtabcmd.?.symoff),
)[0..symtabcmd.?.nsyms];
// TODO handle tentative (common) symbols
var addr_table = std.StringHashMap(u64).init(self.allocator());
try addr_table.ensureTotalCapacity(@intCast(u32, symtab.len));
for (symtab) |sym| {
if (sym.n_strx == 0) continue;
if (sym.undf() or sym.tentative() or sym.abs()) continue;
const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
// TODO is it possible to have a symbol collision?
addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
}
var opt_debug_line: ?*const macho.section_64 = null;
var opt_debug_info: ?*const macho.section_64 = null;
@ -1275,8 +1352,8 @@ pub const ModuleDebugInfo = switch (native_os) {
const sections = @ptrCast(
[*]const macho.section_64,
@alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)),
)[0..segcmd.nsects];
@alignCast(@alignOf(macho.section_64), segptr + @sizeOf(std.macho.segment_command_64)),
)[0..segcmd.?.nsects];
for (sections) |*sect| {
// The section name may not exceed 16 chars and a trailing null may
// not be present
@ -1320,34 +1397,34 @@ pub const ModuleDebugInfo = switch (native_os) {
};
try DW.openDwarfDebugInfo(&di, self.allocator());
var info = OFileInfo{
.di = di,
.addr_table = addr_table,
};
// Add the debug info to the cache
try self.ofiles.putNoClobber(o_file_path, di);
try self.ofiles.putNoClobber(o_file_path, info);
return di;
return info;
}
pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
nosuspend {
// Translate the VA into an address into this object
const relocated_address = address - self.base_address;
assert(relocated_address >= 0x100000000);
// Find the .o file where this symbol is defined
const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse
return SymbolInfo{};
const addr_off = relocated_address - symbol.addr;
// Take the symbol name from the N_FUN STAB entry, we're going to
// use it if we fail to find the DWARF infos
const stab_symbol = mem.sliceTo(self.strings[symbol.nlist.n_strx..], 0);
if (symbol.ofile == null)
return SymbolInfo{ .symbol_name = stab_symbol };
const o_file_path = mem.sliceTo(self.strings[symbol.ofile.?.n_strx..], 0);
const stab_symbol = mem.sliceTo(self.strings[symbol.strx..], 0);
const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
// Check if its debug infos are already in the cache
var o_file_di = self.ofiles.get(o_file_path) orelse
var o_file_info = self.ofiles.get(o_file_path) orelse
(self.loadOFile(o_file_path) catch |err| switch (err) {
error.FileNotFound,
error.MissingDebugInfo,
@ -1357,19 +1434,22 @@ pub const ModuleDebugInfo = switch (native_os) {
},
else => return err,
});
const o_file_di = &o_file_info.di;
// Translate again the address, this time into an address inside the
// .o file
const relocated_address_o = relocated_address - symbol.reloc;
const relocated_address_o = o_file_info.addr_table.get(stab_symbol) orelse return SymbolInfo{
.symbol_name = "???",
};
if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
return SymbolInfo{
.symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
.compile_unit_name = compile_unit.die.getAttrString(&o_file_di, DW.AT.name) catch |err| switch (err) {
.compile_unit_name = compile_unit.die.getAttrString(o_file_di, DW.AT.name) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => "???",
else => return err,
},
.line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o) catch |err| switch (err) {
.line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o + addr_off) catch |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => null,
else => return err,
},

View file

@ -744,6 +744,55 @@ pub const nlist_64 = extern struct {
n_sect: u8,
n_desc: u16,
n_value: u64,
pub fn stab(sym: nlist_64) bool {
return (N_STAB & sym.n_type) != 0;
}
pub fn pext(sym: nlist_64) bool {
return (N_PEXT & sym.n_type) != 0;
}
pub fn ext(sym: nlist_64) bool {
return (N_EXT & sym.n_type) != 0;
}
pub fn sect(sym: nlist_64) bool {
const type_ = N_TYPE & sym.n_type;
return type_ == N_SECT;
}
pub fn undf(sym: nlist_64) bool {
const type_ = N_TYPE & sym.n_type;
return type_ == N_UNDF;
}
pub fn indr(sym: nlist_64) bool {
const type_ = N_TYPE & sym.n_type;
return type_ == N_INDR;
}
pub fn abs(sym: nlist_64) bool {
const type_ = N_TYPE & sym.n_type;
return type_ == N_ABS;
}
pub fn weakDef(sym: nlist_64) bool {
return (sym.n_desc & N_WEAK_DEF) != 0;
}
pub fn weakRef(sym: nlist_64) bool {
return (sym.n_desc & N_WEAK_REF) != 0;
}
pub fn discarded(sym: nlist_64) bool {
return (sym.n_desc & N_DESC_DISCARDED) != 0;
}
pub fn tentative(sym: nlist_64) bool {
if (!sym.undf()) return false;
return sym.n_value != 0;
}
};
/// Format of a relocation entry of a Mach-O file. Modified from the 4.3BSD

View file

@ -885,7 +885,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
const sym_name = self.getString(sym.n_strx);
const resolv = self.symbol_resolver.get(sym.n_strx) orelse unreachable;
if (symbolIsDiscarded(sym.*)) {
if (sym.discarded()) {
sym.* = .{
.n_strx = 0,
.n_type = macho.N_UNDF,
@ -2445,21 +2445,21 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
const sym_id = @intCast(u32, id);
const sym_name = object.getString(sym.n_strx);
if (symbolIsStab(sym)) {
if (sym.stab()) {
log.err("unhandled symbol type: stab", .{});
log.err(" symbol '{s}'", .{sym_name});
log.err(" first definition in '{s}'", .{object.name});
return error.UnhandledSymbolType;
}
if (symbolIsIndr(sym)) {
if (sym.indr()) {
log.err("unhandled symbol type: indirect", .{});
log.err(" symbol '{s}'", .{sym_name});
log.err(" first definition in '{s}'", .{object.name});
return error.UnhandledSymbolType;
}
if (symbolIsAbs(sym)) {
if (sym.abs()) {
log.err("unhandled symbol type: absolute", .{});
log.err(" symbol '{s}'", .{sym_name});
log.err(" first definition in '{s}'", .{object.name});
@ -2467,7 +2467,7 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
}
const n_strx = try self.makeString(sym_name);
if (symbolIsSect(sym)) {
if (sym.sect()) {
// Defined symbol regardless of scope lands in the locals symbol table.
const local_sym_index = @intCast(u32, self.locals.items.len);
try self.locals.append(self.base.allocator, .{
@ -2482,7 +2482,7 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
// 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;
if (!sym.ext()) continue;
const local = self.locals.items[local_sym_index];
const resolv = self.symbol_resolver.getPtr(n_strx) orelse {
@ -2507,18 +2507,16 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
.global => {
const global = &self.globals.items[resolv.where_index];
if (symbolIsTentative(global.*)) {
if (global.tentative()) {
assert(self.tentatives.swapRemove(resolv.where_index));
} else if (!(symbolIsWeakDef(sym) or symbolIsPext(sym)) and
!(symbolIsWeakDef(global.*) or symbolIsPext(global.*)))
{
} else if (!(sym.weakDef() or sym.pext()) and !(global.weakDef() or global.pext())) {
log.err("symbol '{s}' defined multiple times", .{sym_name});
if (resolv.file) |file| {
log.err(" first definition in '{s}'", .{self.objects.items[file].name});
}
log.err(" next definition in '{s}'", .{object.name});
return error.MultipleSymbolDefinitions;
} else if (symbolIsWeakDef(sym) or symbolIsPext(sym)) continue; // Current symbol is weak, so skip it.
} else if (sym.weakDef() or sym.pext()) continue; // Current symbol is weak, so skip it.
// Otherwise, update the resolver and the global symbol.
global.n_type = sym.n_type;
@ -2554,7 +2552,7 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
.local_sym_index = local_sym_index,
.file = object_id,
};
} else if (symbolIsTentative(sym)) {
} else if (sym.tentative()) {
// Symbol is a tentative definition.
const resolv = self.symbol_resolver.getPtr(n_strx) orelse {
const global_sym_index = @intCast(u32, self.globals.items.len);
@ -2577,7 +2575,7 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void {
switch (resolv.where) {
.global => {
const global = &self.globals.items[resolv.where_index];
if (!symbolIsTentative(global.*)) continue;
if (!global.tentative()) continue;
if (global.n_value >= sym.n_value) continue;
global.n_desc = sym.n_desc;
@ -3575,9 +3573,9 @@ pub fn updateDeclExports(
const sym = &self.globals.items[resolv.where_index];
if (symbolIsTentative(sym.*)) {
if (sym.tentative()) {
assert(self.tentatives.swapRemove(resolv.where_index));
} else if (!is_weak and !(symbolIsWeakDef(sym.*) or symbolIsPext(sym.*))) {
} else if (!is_weak and !(sym.weakDef() or sym.pext())) {
_ = try module.failed_exports.put(
module.gpa,
exp,
@ -5316,58 +5314,9 @@ pub fn getString(self: *MachO, off: u32) []const u8 {
return mem.sliceTo(@ptrCast([*:0]const u8, self.strtab.items.ptr + off), 0);
}
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 symbolIsDiscarded(sym: macho.nlist_64) bool {
return (sym.n_desc & macho.N_DESC_DISCARDED) != 0;
}
pub fn symbolIsTentative(sym: macho.nlist_64) bool {
if (!symbolIsUndf(sym)) return false;
return sym.n_value != 0;
}
pub fn symbolIsTemp(sym: macho.nlist_64, sym_name: []const u8) bool {
if (!symbolIsSect(sym)) return false;
if (symbolIsExt(sym)) return false;
if (!sym.sect()) return false;
if (sym.ext()) return false;
return mem.startsWith(u8, sym_name, "l") or mem.startsWith(u8, sym_name, "L");
}

View file

@ -295,7 +295,7 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC
assert(subtractor == null);
const sym = context.object.symtab.items[rel.r_symbolnum];
if (MachO.symbolIsSect(sym) and !MachO.symbolIsExt(sym)) {
if (sym.sect() and !sym.ext()) {
subtractor = context.object.symbol_mapping.get(rel.r_symbolnum).?;
} else {
const sym_name = context.object.getString(sym.n_strx);
@ -362,7 +362,7 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC
const sym = context.object.symtab.items[rel.r_symbolnum];
const sym_name = context.object.getString(sym.n_strx);
if (MachO.symbolIsSect(sym) and !MachO.symbolIsExt(sym)) {
if (sym.sect() and !sym.ext()) {
const sym_index = context.object.symbol_mapping.get(rel.r_symbolnum) orelse unreachable;
break :target Relocation.Target{ .local = sym_index };
}

View file

@ -226,7 +226,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {
_ = try self.file.preadAll(strtab, symtab_cmd.stroff + self.library_offset);
for (slice) |sym| {
const add_to_symtab = MachO.symbolIsExt(sym) and (MachO.symbolIsSect(sym) or MachO.symbolIsIndr(sym));
const add_to_symtab = sym.ext() and (sym.sect() or sym.indr());
if (!add_to_symtab) continue;

View file

@ -338,8 +338,8 @@ const NlistWithIndex = struct {
// afterwards by address in each group. Normally, dysymtab should
// be enough to guarantee the sort, but turns out not every compiler
// is kind enough to specify the symbols in the correct order.
if (MachO.symbolIsSect(lhs.nlist)) {
if (MachO.symbolIsSect(rhs.nlist)) {
if (lhs.nlist.sect()) {
if (rhs.nlist.sect()) {
// Same group, sort by address.
return lhs.nlist.n_value < rhs.nlist.n_value;
} else {
@ -414,7 +414,7 @@ pub fn parseIntoAtoms(self: *Object, allocator: *Allocator, macho_file: *MachO)
var iundefsym: usize = sorted_all_nlists.items.len;
while (iundefsym > 0) : (iundefsym -= 1) {
const nlist = sorted_all_nlists.items[iundefsym];
if (MachO.symbolIsSect(nlist.nlist)) break;
if (nlist.nlist.sect()) break;
}
break :blk iundefsym;
};