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 {...@@ -559,7 +559,7 @@ pub const TTY = struct {
559559
560fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {560fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
561 var min: usize = 0;561 var min: usize = 0;
562 var max: usize = symbols.len - 1; // Exclude sentinel.562 var max: usize = symbols.len;
563 while (min < max) {563 while (min < max) {
564 const mid = min + (max - min) / 2;564 const mid = min + (max - min) / 2;
565 const curr = &symbols[mid];565 const curr = &symbols[mid];
...@@ -850,51 +850,91 @@ fn readMachODebugInfo(allocator: *mem.Allocator, macho_file: File) !ModuleDebugI...@@ -850,51 +850,91 @@ fn readMachODebugInfo(allocator: *mem.Allocator, macho_file: File) !ModuleDebugI
850 } else {850 } else {
851 return error.MissingDebugInfo;851 return error.MissingDebugInfo;
852 };852 };
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];
854 const strings = @ptrCast([*]const u8, hdr_base + symtab.stroff)[0 .. symtab.strsize - 1 :0];857 const strings = @ptrCast([*]const u8, hdr_base + symtab.stroff)[0 .. symtab.strsize - 1 :0];
855858
856 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);859 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
857860
858 var ofile: ?*const macho.nlist_64 = null;861 var ofile: u32 = undefined;
859 var reloc: u64 = 0;862 var last_sym: MachoSymbol = undefined;
860 var symbol_index: usize = 0;863 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
862 for (syms) |*sym| {874 for (syms) |*sym| {
863 if (sym.n_type & std.macho.N_STAB != 0) {875 if (!sym.stab()) continue;
864 switch (sym.n_type) {876
865 std.macho.N_OSO => {877 // TODO handle globals N_GSYM, and statics N_STSYM
866 ofile = sym;878 switch (sym.n_type) {
867 reloc = 0;879 macho.N_OSO => {
868 },880 switch (state) {
869 std.macho.N_FUN => {881 .init, .oso_close => {
870 if (sym.n_sect == 0) {882 state = .oso_open;
871 last_len = sym.n_value;883 ofile = sym.n_strx;
872 } else {884 },
873 symbols_buf[symbol_index] = MachoSymbol{885 else => return error.InvalidDebugInfo,
874 .nlist = sym,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,
875 .ofile = ofile,896 .ofile = ofile,
876 .reloc = reloc,
877 };897 };
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;
878 symbol_index += 1;920 symbol_index += 1;
879 }921 },
880 },922 else => return error.InvalidDebugInfo,
881 std.macho.N_BNSYM => {923 }
882 if (reloc == 0) {924 },
883 reloc = sym.n_value;925 macho.N_SO => {
884 }926 switch (state) {
885 },927 .init, .oso_close => {},
886 else => continue,928 .oso_open, .ensym => {
887 }929 state = .oso_close;
930 },
931 else => return error.InvalidDebugInfo,
932 }
933 },
934 else => {},
888 }935 }
889 }936 }
890 const sentinel = try allocator.create(macho.nlist_64);937 assert(state == .oso_close);
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 };
898938
899 const symbols = allocator.shrink(symbols_buf, symbol_index);939 const symbols = allocator.shrink(symbols_buf, symbol_index);
900940
...@@ -946,18 +986,19 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {...@@ -946,18 +986,19 @@ fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
946}986}
947987
948const MachoSymbol = struct {988const MachoSymbol = struct {
949 nlist: *const macho.nlist_64,989 strx: u32,
950 ofile: ?*const macho.nlist_64,990 addr: u64,
951 reloc: u64,991 size: u32,
992 ofile: u32,
952993
953 /// Returns the address from the macho file994 /// Returns the address from the macho file
954 fn address(self: MachoSymbol) u64 {995 fn address(self: MachoSymbol) u64 {
955 return self.nlist.n_value;996 return self.addr;
956 }997 }
957998
958 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {999 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
959 _ = context;1000 _ = context;
960 return lhs.address() < rhs.address();1001 return lhs.addr < rhs.addr;
961 }1002 }
962};1003};
9631004
...@@ -1231,13 +1272,17 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1231,13 +1272,17 @@ pub const ModuleDebugInfo = switch (native_os) {
1231 strings: [:0]const u8,1272 strings: [:0]const u8,
1232 ofiles: OFileTable,1273 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
1236 pub fn allocator(self: @This()) *mem.Allocator {1281 pub fn allocator(self: @This()) *mem.Allocator {
1237 return self.ofiles.allocator;1282 return self.ofiles.allocator;
1238 }1283 }
12391284
1240 fn loadOFile(self: *@This(), o_file_path: []const u8) !DW.DwarfInfo {1285 fn loadOFile(self: *@This(), o_file_path: []const u8) !OFileInfo {
1241 const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking });1286 const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking });
1242 const mapped_mem = try mapWholeFile(o_file);1287 const mapped_mem = try mapWholeFile(o_file);
12431288
...@@ -1250,22 +1295,54 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1250,22 +1295,54 @@ pub const ModuleDebugInfo = switch (native_os) {
12501295
1251 const hdr_base = @ptrCast([*]const u8, hdr);1296 const hdr_base = @ptrCast([*]const u8, hdr);
1252 var ptr = hdr_base + @sizeOf(macho.mach_header_64);1297 var ptr = hdr_base + @sizeOf(macho.mach_header_64);
1298 var segptr = ptr;
1253 var ncmd: u32 = hdr.ncmds;1299 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) {
1255 const lc = @ptrCast(*const std.macho.load_command, ptr);1304 const lc = @ptrCast(*const std.macho.load_command, ptr);
1256 switch (lc.cmd) {1305 switch (lc.cmd) {
1257 std.macho.LC_SEGMENT_64 => {1306 std.macho.LC_SEGMENT_64 => {
1258 break @ptrCast(1307 segcmd = @ptrCast(
1259 *const std.macho.segment_command_64,1308 *const std.macho.segment_command_64,
1260 @alignCast(@alignOf(std.macho.segment_command_64), ptr),1309 @alignCast(@alignOf(std.macho.segment_command_64), ptr),
1261 );1310 );
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 );
1262 },1318 },
1263 else => {},1319 else => {},
1264 }1320 }
1265 ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);1321 ptr = @alignCast(@alignOf(std.macho.load_command), ptr + lc.cmdsize);
1266 } else {1322 }
1267 return error.MissingDebugInfo;1323
1268 };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
1270 var opt_debug_line: ?*const macho.section_64 = null;1347 var opt_debug_line: ?*const macho.section_64 = null;
1271 var opt_debug_info: ?*const macho.section_64 = null;1348 var opt_debug_info: ?*const macho.section_64 = null;
...@@ -1275,8 +1352,8 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1275,8 +1352,8 @@ pub const ModuleDebugInfo = switch (native_os) {
12751352
1276 const sections = @ptrCast(1353 const sections = @ptrCast(
1277 [*]const macho.section_64,1354 [*]const macho.section_64,
1278 @alignCast(@alignOf(macho.section_64), ptr + @sizeOf(std.macho.segment_command_64)),1355 @alignCast(@alignOf(macho.section_64), segptr + @sizeOf(std.macho.segment_command_64)),
1279 )[0..segcmd.nsects];1356 )[0..segcmd.?.nsects];
1280 for (sections) |*sect| {1357 for (sections) |*sect| {
1281 // The section name may not exceed 16 chars and a trailing null may1358 // The section name may not exceed 16 chars and a trailing null may
1282 // not be present1359 // not be present
...@@ -1320,11 +1397,15 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1320,11 +1397,15 @@ pub const ModuleDebugInfo = switch (native_os) {
1320 };1397 };
13211398
1322 try DW.openDwarfDebugInfo(&di, self.allocator());1399 try DW.openDwarfDebugInfo(&di, self.allocator());
1400 var info = OFileInfo{
1401 .di = di,
1402 .addr_table = addr_table,
1403 };
13231404
1324 // Add the debug info to the cache1405 // 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;
1328 }1409 }
13291410
1330 pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {1411 pub fn getSymbolAtAddress(self: *@This(), address: usize) !SymbolInfo {
...@@ -1336,18 +1417,15 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1336,18 +1417,15 @@ pub const ModuleDebugInfo = switch (native_os) {
1336 // Find the .o file where this symbol is defined1417 // Find the .o file where this symbol is defined
1337 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse1418 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse
1338 return SymbolInfo{};1419 return SymbolInfo{};
1420 const addr_off = relocated_address - symbol.addr;
13391421
1340 // Take the symbol name from the N_FUN STAB entry, we're going to1422 // Take the symbol name from the N_FUN STAB entry, we're going to
1341 // use it if we fail to find the DWARF infos1423 // use it if we fail to find the DWARF infos
1342 const stab_symbol = mem.sliceTo(self.strings[symbol.nlist.n_strx..], 0);1424 const stab_symbol = mem.sliceTo(self.strings[symbol.strx..], 0);
13431425 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
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);
13481426
1349 // Check if its debug infos are already in the cache1427 // Check if its debug infos are already in the cache
1350 var o_file_di = self.ofiles.get(o_file_path) orelse1428 var o_file_info = self.ofiles.get(o_file_path) orelse
1351 (self.loadOFile(o_file_path) catch |err| switch (err) {1429 (self.loadOFile(o_file_path) catch |err| switch (err) {
1352 error.FileNotFound,1430 error.FileNotFound,
1353 error.MissingDebugInfo,1431 error.MissingDebugInfo,
...@@ -1357,19 +1435,22 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1357,19 +1435,22 @@ pub const ModuleDebugInfo = switch (native_os) {
1357 },1435 },
1358 else => return err,1436 else => return err,
1359 });1437 });
1438 const o_file_di = &o_file_info.di;
13601439
1361 // Translate again the address, this time into an address inside the1440 // Translate again the address, this time into an address inside the
1362 // .o file1441 // .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
1365 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {1446 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
1366 return SymbolInfo{1447 return SymbolInfo{
1367 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",1448 .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) {
1369 error.MissingDebugInfo, error.InvalidDebugInfo => "???",1450 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
1370 else => return err,1451 else => return err,
1371 },1452 },
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) {
1373 error.MissingDebugInfo, error.InvalidDebugInfo => null,1454 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1374 else => return err,1455 else => return err,
1375 },1456 },