authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-11-30 15:02:10+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-11-30 16:13:01+01:00
loga56749b654bbe432752a57e130c7cf9dda370a15
treec7db2ed86bfcf8c16c7e43f35329d5ec82f5213c
parent86fe47235eafc03ee4808f8c8b34b7903b816023

macos: rewrite logic for generating stack traces on macOS

In order to be linker-independent, when parsing debug info in each linked OSO, we also create a quick lookup table for symbols defined within the OSO. We then use this lookup to map symbol from the EXE to its defined address within the original OSO which we can then use to extract its associated DWARF info (if any).

1 files changed, 142 insertions(+), 61 deletions(-)

lib/std/debug.zig+142-61
......@@ -559,7 +559,7 @@ pub const TTY = struct {
559559
560560fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
561561 var min: usize = 0;
562 var max: usize = symbols.len - 1; // Exclude sentinel.
562 var max: usize = symbols.len;
563563 while (min < max) {
564564 const mid = min + (max - min) / 2;
565565 const curr = &symbols[mid];
......@@ -850,51 +850,91 @@ fn readMachODebugInfo(allocator: *mem.Allocator, macho_file: File) !ModuleDebugI
850850 } else {
851851 return error.MissingDebugInfo;
852852 };
853 const syms = @ptrCast([*]const macho.nlist_64, @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff))[0..symtab.nsyms];
853 const syms = @ptrCast(
854 [*]const macho.nlist_64,
855 @alignCast(@alignOf(macho.nlist_64), hdr_base + symtab.symoff),
856 )[0..symtab.nsyms];
854857 const strings = @ptrCast([*]const u8, hdr_base + symtab.stroff)[0 .. symtab.strsize - 1 :0];
855858
856859 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
857860
858 var ofile: ?*const macho.nlist_64 = null;
859 var reloc: u64 = 0;
861 var ofile: u32 = undefined;
862 var last_sym: MachoSymbol = undefined;
860863 var symbol_index: usize = 0;
861 var last_len: u64 = 0;
864 var state: enum {
865 init,
866 oso_open,
867 oso_close,
868 bnsym,
869 fun_strx,
870 fun_size,
871 ensym,
872 } = .init;
873
862874 for (syms) |*sym| {
863 if (sym.n_type & std.macho.N_STAB != 0) {
864 switch (sym.n_type) {
865 std.macho.N_OSO => {
866 ofile = sym;
867 reloc = 0;
868 },
869 std.macho.N_FUN => {
870 if (sym.n_sect == 0) {
871 last_len = sym.n_value;
872 } else {
873 symbols_buf[symbol_index] = MachoSymbol{
874 .nlist = sym,
875 if (!sym.stab()) continue;
876
877 // TODO handle globals N_GSYM, and statics N_STSYM
878 switch (sym.n_type) {
879 macho.N_OSO => {
880 switch (state) {
881 .init, .oso_close => {
882 state = .oso_open;
883 ofile = sym.n_strx;
884 },
885 else => return error.InvalidDebugInfo,
886 }
887 },
888 macho.N_BNSYM => {
889 switch (state) {
890 .oso_open, .ensym => {
891 state = .bnsym;
892 last_sym = .{
893 .strx = 0,
894 .addr = sym.n_value,
895 .size = 0,
875896 .ofile = ofile,
876 .reloc = reloc,
877897 };
898 },
899 else => return error.InvalidDebugInfo,
900 }
901 },
902 macho.N_FUN => {
903 switch (state) {
904 .bnsym => {
905 state = .fun_strx;
906 last_sym.strx = sym.n_strx;
907 },
908 .fun_strx => {
909 state = .fun_size;
910 last_sym.size = @intCast(u32, sym.n_value);
911 },
912 else => return error.InvalidDebugInfo,
913 }
914 },
915 macho.N_ENSYM => {
916 switch (state) {
917 .fun_size => {
918 state = .ensym;
919 symbols_buf[symbol_index] = last_sym;
878920 symbol_index += 1;
879 }
880 },
881 std.macho.N_BNSYM => {
882 if (reloc == 0) {
883 reloc = sym.n_value;
884 }
885 },
886 else => continue,
887 }
921 },
922 else => return error.InvalidDebugInfo,
923 }
924 },
925 macho.N_SO => {
926 switch (state) {
927 .init, .oso_close => {},
928 .oso_open, .ensym => {
929 state = .oso_close;
930 },
931 else => return error.InvalidDebugInfo,
932 }
933 },
934 else => {},
888935 }
889936 }
890 const sentinel = try allocator.create(macho.nlist_64);
891 sentinel.* = macho.nlist_64{
892 .n_strx = 0,
893 .n_type = 36,
894 .n_sect = 0,
895 .n_desc = 0,
896 .n_value = symbols_buf[symbol_index - 1].nlist.n_value + last_len,
897 };
937 assert(state == .oso_close);
898938
899939 const symbols = allocator.shrink(symbols_buf, symbol_index);
900940
......@@ -946,18 +986,19 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
946986}
947987
948988const MachoSymbol = struct {
949 nlist: *const macho.nlist_64,
950 ofile: ?*const macho.nlist_64,
951 reloc: u64,
989 strx: u32,
990 addr: u64,
991 size: u32,
992 ofile: u32,
952993
953994 /// Returns the address from the macho file
954995 fn address(self: MachoSymbol) u64 {
955 return self.nlist.n_value;
996 return self.addr;
956997 }
957998
958999 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
9591000 _ = context;
960 return lhs.address() < rhs.address();
1001 return lhs.addr < rhs.addr;
9611002 }
9621003};
9631004
......@@ -1231,13 +1272,17 @@ pub const ModuleDebugInfo = switch (native_os) {
12311272 strings: [:0]const u8,
12321273 ofiles: OFileTable,
12331274
1234 const OFileTable = std.StringHashMap(DW.DwarfInfo);
1275 const OFileTable = std.StringHashMap(OFileInfo);
1276 const OFileInfo = struct {
1277 di: DW.DwarfInfo,
1278 addr_table: std.StringHashMap(u64),
1279 };
12351280
12361281 pub fn allocator(self: @This()) *mem.Allocator {
12371282 return self.ofiles.allocator;
12381283 }
12391284
1240 fn loadOFile(self: *@This(), o_file_path: []const u8) !DW.DwarfInfo {
1285 fn loadOFile(self: *@This(), o_file_path: []const u8) !OFileInfo {
12411286 const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking });
12421287 const mapped_mem = try mapWholeFile(o_file);
12431288
......@@ -1250,22 +1295,54 @@ pub const ModuleDebugInfo = switch (native_os) {
12501295
12511296 const hdr_base = @ptrCast([*]const u8, hdr);
12521297 var ptr = hdr_base + @sizeOf(macho.mach_header_64);
1298 var segptr = ptr;
12531299 var ncmd: u32 = hdr.ncmds;
1254 const segcmd = while (ncmd != 0) : (ncmd -= 1) {
1300 var segcmd: ?*const macho.segment_command_64 = null;
1301 var symtabcmd: ?*const macho.symtab_command = null;
1302
1303 while (ncmd != 0) : (ncmd -= 1) {
12551304 const lc = @ptrCast(*const std.macho.load_command, ptr);
12561305 switch (lc.cmd) {
12571306 std.macho.LC_SEGMENT_64 => {
1258 break @ptrCast(
1307 segcmd = @ptrCast(
12591308 *const std.macho.segment_command_64,
12601309 @alignCast(@alignOf(std.macho.segment_command_64), ptr),
12611310 );
1311 segptr = ptr;
1312 },
1313 std.macho.LC_SYMTAB => {
1314 symtabcmd = @ptrCast(
1315 *const std.macho.symtab_command,
1316 @alignCast(@alignOf(std.macho.symtab_command), ptr),
1317 );
12621318 },
12631319 else => {},
12641320 }
12651321 ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);
1266 } else {
1267 return error.MissingDebugInfo;
1268 };
1322 }
1323
1324 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
1325
1326 // Parse symbols
1327 const strtab = @ptrCast(
1328 [*]const u8,
1329 hdr_base + symtabcmd.?.stroff,
1330 )[0 .. symtabcmd.?.strsize - 1 :0];
1331 const symtab = @ptrCast(
1332 [*]const macho.nlist_64,
1333 @alignCast(@alignOf(macho.nlist_64), hdr_base + symtabcmd.?.symoff),
1334 )[0..symtabcmd.?.nsyms];
1335
1336 // TODO handle tentative (common) symbols
1337 var addr_table = std.StringHashMap(u64).init(self.allocator());
1338 try addr_table.ensureTotalCapacity(@intCast(u32, symtab.len));
1339 for (symtab) |sym| {
1340 if (sym.n_strx == 0) continue;
1341 if (sym.undf() or sym.tentative() or sym.abs()) continue;
1342 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
1343 // TODO is it possible to have a symbol collision?
1344 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
1345 }
12691346
12701347 var opt_debug_line: ?*const macho.section_64 = null;
12711348 var opt_debug_info: ?*const macho.section_64 = null;
......@@ -1275,8 +1352,8 @@ pub const ModuleDebugInfo = switch (native_os) {
12751352
12761353 const sections = @ptrCast(
12771354 [*]const macho.section_64,
1278 @alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)),
1279 )[0..segcmd.nsects];
1355 @alignCast(@alignOf(macho.section_64), segptr + @sizeOf(std.macho.segment_command_64)),
1356 )[0..segcmd.?.nsects];
12801357 for (sections) |*sect| {
12811358 // The section name may not exceed 16 chars and a trailing null may
12821359 // not be present
......@@ -1320,11 +1397,15 @@ pub const ModuleDebugInfo = switch (native_os) {
13201397 };
13211398
13221399 try DW.openDwarfDebugInfo(&di, self.allocator());
1400 var info = OFileInfo{
1401 .di = di,
1402 .addr_table = addr_table,
1403 };
13231404
13241405 // Add the debug info to the cache
1325 try self.ofiles.putNoClobber(o_file_path, di);
1406 try self.ofiles.putNoClobber(o_file_path, info);
13261407
1327 return di;
1408 return info;
13281409 }
13291410
13301411 pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
......@@ -1336,18 +1417,15 @@ pub const ModuleDebugInfo = switch (native_os) {
13361417 // Find the .o file where this symbol is defined
13371418 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse
13381419 return SymbolInfo{};
1420 const addr_off = relocated_address - symbol.addr;
13391421
13401422 // Take the symbol name from the N_FUN STAB entry, we're going to
13411423 // use it if we fail to find the DWARF infos
1342 const stab_symbol = mem.sliceTo(self.strings[symbol.nlist.n_strx..], 0);
1343
1344 if (symbol.ofile == null)
1345 return SymbolInfo{ .symbol_name = stab_symbol };
1346
1347 const o_file_path = mem.sliceTo(self.strings[symbol.ofile.?.n_strx..], 0);
1424 const stab_symbol = mem.sliceTo(self.strings[symbol.strx..], 0);
1425 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
13481426
13491427 // Check if its debug infos are already in the cache
1350 var o_file_di = self.ofiles.get(o_file_path) orelse
1428 var o_file_info = self.ofiles.get(o_file_path) orelse
13511429 (self.loadOFile(o_file_path) catch |err| switch (err) {
13521430 error.FileNotFound,
13531431 error.MissingDebugInfo,
......@@ -1357,19 +1435,22 @@ pub const ModuleDebugInfo = switch (native_os) {
13571435 },
13581436 else => return err,
13591437 });
1438 const o_file_di = &o_file_info.di;
13601439
13611440 // Translate again the address, this time into an address inside the
13621441 // .o file
1363 const relocated_address_o = relocated_address - symbol.reloc;
1442 const relocated_address_o = o_file_info.addr_table.get(stab_symbol) orelse return SymbolInfo{
1443 .symbol_name = "???",
1444 };
13641445
13651446 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
13661447 return SymbolInfo{
13671448 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
1368 .compile_unit_name = compile_unit.die.getAttrString(&o_file_di, DW.AT.name) catch |err| switch (err) {
1449 .compile_unit_name = compile_unit.die.getAttrString(o_file_di, DW.AT.name) catch |err| switch (err) {
13691450 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
13701451 else => return err,
13711452 },
1372 .line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o) catch |err| switch (err) {
1453 .line_info = o_file_di.getLineNumberInfo(compile_unit.*, relocated_address_o + addr_off) catch |err| switch (err) {
13731454 error.MissingDebugInfo, error.InvalidDebugInfo => null,
13741455 else => return err,
13751456 },