authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-01 14:18:16-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-01 22:11:23-07:00
log1ba6b56c817777c5f504a5975591da6de68dd361
treeff62d373b92d99ab7a71456a55d136400033c135
parent2e26cf83cf06b6204a1ea300403e7cf19e1e91e8

std.debug.Info: extract to separate file


4 files changed, 1390 insertions(+), 1376 deletions(-)

lib/std/debug.zig+7-1370
......@@ -1,5 +1,5 @@
1const std = @import("std.zig");
21const builtin = @import("builtin");
2const std = @import("std.zig");
33const math = std.math;
44const mem = std.mem;
55const io = std.io;
......@@ -19,6 +19,7 @@ const native_os = builtin.os.tag;
1919const native_endian = native_arch.endian();
2020
2121pub const Dwarf = @import("debug/Dwarf.zig");
22pub const Info = @import("debug/Info.zig");
2223
2324pub const runtime_safety = switch (builtin.mode) {
2425 .Debug, .ReleaseSafe => true,
......@@ -46,39 +47,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
4647 else => true,
4748};
4849
49pub const LineInfo = struct {
50 line: u64,
51 column: u64,
52 file_name: []const u8,
53
54 pub fn deinit(self: LineInfo, allocator: mem.Allocator) void {
55 allocator.free(self.file_name);
56 }
57};
58
59pub const SymbolInfo = struct {
60 symbol_name: []const u8 = "???",
61 compile_unit_name: []const u8 = "???",
62 line_info: ?LineInfo = null,
63
64 pub fn deinit(self: SymbolInfo, allocator: mem.Allocator) void {
65 if (self.line_info) |li| {
66 li.deinit(allocator);
67 }
68 }
69};
70const PdbOrDwarf = union(enum) {
71 pdb: pdb.Pdb,
72 dwarf: Dwarf,
73
74 fn deinit(self: *PdbOrDwarf, allocator: mem.Allocator) void {
75 switch (self.*) {
76 .pdb => |*inner| inner.deinit(),
77 .dwarf => |*inner| inner.deinit(allocator),
78 }
79 }
80};
81
8250/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
8351///
8452/// During the lock, any `std.Progress` information is cleared from the terminal.
......@@ -110,7 +78,7 @@ pub fn getSelfDebugInfo() !*Info {
11078 if (self_debug_info) |*info| {
11179 return info;
11280 } else {
113 self_debug_info = try openSelfDebugInfo(getDebugInfoAllocator());
81 self_debug_info = try Info.openSelf(getDebugInfoAllocator());
11482 return &self_debug_info.?;
11583 }
11684}
......@@ -957,51 +925,6 @@ pub fn writeStackTraceWindows(
957925 }
958926}
959927
960fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
961 var min: usize = 0;
962 var max: usize = symbols.len - 1;
963 while (min < max) {
964 const mid = min + (max - min) / 2;
965 const curr = &symbols[mid];
966 const next = &symbols[mid + 1];
967 if (address >= next.address()) {
968 min = mid + 1;
969 } else if (address < curr.address()) {
970 max = mid;
971 } else {
972 return curr;
973 }
974 }
975
976 const max_sym = &symbols[symbols.len - 1];
977 if (address >= max_sym.address())
978 return max_sym;
979
980 return null;
981}
982
983test machoSearchSymbols {
984 const symbols = [_]MachoSymbol{
985 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
986 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
987 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
988 };
989
990 try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
991 try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
992 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
993 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
994 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
995
996 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?);
997 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?);
998 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?);
999
1000 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?);
1001 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?);
1002 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
1003}
1004
1005928fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
1006929 const module_name = debug_info.getModuleNameForAddress(address);
1007930 return printLineInfo(
......@@ -1058,7 +981,7 @@ pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usi
1058981
1059982fn printLineInfo(
1060983 out_stream: anytype,
1061 line_info: ?LineInfo,
984 line_info: ?Info.SourceLocation,
1062985 address: usize,
1063986 symbol_name: []const u8,
1064987 compile_unit_name: []const u8,
......@@ -1104,428 +1027,7 @@ fn printLineInfo(
11041027 }
11051028}
11061029
1107pub const OpenSelfDebugInfoError = error{
1108 MissingDebugInfo,
1109 UnsupportedOperatingSystem,
1110} || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set;
1111
1112pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!Info {
1113 nosuspend {
1114 if (builtin.strip_debug_info)
1115 return error.MissingDebugInfo;
1116 if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) {
1117 return root.os.debug.openSelfDebugInfo(allocator);
1118 }
1119 switch (native_os) {
1120 .linux,
1121 .freebsd,
1122 .netbsd,
1123 .dragonfly,
1124 .openbsd,
1125 .macos,
1126 .solaris,
1127 .illumos,
1128 .windows,
1129 => return try Info.init(allocator),
1130 else => return error.UnsupportedOperatingSystem,
1131 }
1132 }
1133}
1134
1135fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo {
1136 nosuspend {
1137 var di = ModuleDebugInfo{
1138 .base_address = undefined,
1139 .coff_image_base = coff_obj.getImageBase(),
1140 .coff_section_headers = undefined,
1141 };
1142
1143 if (coff_obj.getSectionByName(".debug_info")) |_| {
1144 // This coff file has embedded DWARF debug info
1145 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1146 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1147
1148 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1149 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
1150 break :blk .{
1151 .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
1152 .virtual_address = section_header.virtual_address,
1153 .owned = true,
1154 };
1155 } else null;
1156 }
1157
1158 var dwarf = Dwarf{
1159 .endian = native_endian,
1160 .sections = sections,
1161 .is_macho = false,
1162 };
1163
1164 try Dwarf.open(&dwarf, allocator);
1165 di.dwarf = dwarf;
1166 }
1167
1168 const raw_path = try coff_obj.getPdbPath() orelse return di;
1169 const path = blk: {
1170 if (fs.path.isAbsolute(raw_path)) {
1171 break :blk raw_path;
1172 } else {
1173 const self_dir = try fs.selfExeDirPathAlloc(allocator);
1174 defer allocator.free(self_dir);
1175 break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
1176 }
1177 };
1178 defer if (path.ptr != raw_path.ptr) allocator.free(path);
1179
1180 di.pdb = pdb.Pdb.init(allocator, path) catch |err| switch (err) {
1181 error.FileNotFound, error.IsDir => {
1182 if (di.dwarf == null) return error.MissingDebugInfo;
1183 return di;
1184 },
1185 else => return err,
1186 };
1187 try di.pdb.?.parseInfoStream();
1188 try di.pdb.?.parseDbiStream();
1189
1190 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
1191 return error.InvalidDebugInfo;
1192
1193 // Only used by the pdb path
1194 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
1195 errdefer allocator.free(di.coff_section_headers);
1196
1197 return di;
1198 }
1199}
1200
1201fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
1202 const start = math.cast(usize, offset) orelse return error.Overflow;
1203 const end = start + (math.cast(usize, size) orelse return error.Overflow);
1204 return ptr[start..end];
1205}
1206
1207/// Reads debug info from an ELF file, or the current binary if none in specified.
1208/// If the required sections aren't present but a reference to external debug info is,
1209/// then this this function will recurse to attempt to load the debug sections from
1210/// an external file.
1211pub fn readElfDebugInfo(
1212 allocator: mem.Allocator,
1213 elf_filename: ?[]const u8,
1214 build_id: ?[]const u8,
1215 expected_crc: ?u32,
1216 parent_sections: *Dwarf.SectionArray,
1217 parent_mapped_mem: ?[]align(mem.page_size) const u8,
1218) !ModuleDebugInfo {
1219 nosuspend {
1220 const elf_file = (if (elf_filename) |filename| blk: {
1221 break :blk fs.cwd().openFile(filename, .{});
1222 } else fs.openSelfExe(.{})) catch |err| switch (err) {
1223 error.FileNotFound => return error.MissingDebugInfo,
1224 else => return err,
1225 };
1226
1227 const mapped_mem = try mapWholeFile(elf_file);
1228 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
1229
1230 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
1231 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
1232 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
1233
1234 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
1235 elf.ELFDATA2LSB => .little,
1236 elf.ELFDATA2MSB => .big,
1237 else => return error.InvalidElfEndian,
1238 };
1239 assert(endian == native_endian); // this is our own debug info
1240
1241 const shoff = hdr.e_shoff;
1242 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
1243 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
1244 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
1245 const shdrs = @as(
1246 [*]const elf.Shdr,
1247 @ptrCast(@alignCast(&mapped_mem[shoff])),
1248 )[0..hdr.e_shnum];
1249
1250 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1251
1252 // Combine section list. This takes ownership over any owned sections from the parent scope.
1253 for (parent_sections, &sections) |*parent, *section| {
1254 if (parent.*) |*p| {
1255 section.* = p.*;
1256 p.owned = false;
1257 }
1258 }
1259 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1260
1261 var separate_debug_filename: ?[]const u8 = null;
1262 var separate_debug_crc: ?u32 = null;
1263
1264 for (shdrs) |*shdr| {
1265 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
1266 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
1267
1268 if (mem.eql(u8, name, ".gnu_debuglink")) {
1269 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1270 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1271 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
1272 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
1273 separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
1274 separate_debug_filename = debug_filename;
1275 continue;
1276 }
1277
1278 var section_index: ?usize = null;
1279 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1280 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
1281 }
1282 if (section_index == null) continue;
1283 if (sections[section_index.?] != null) continue;
1284
1285 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1286 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1287 var section_stream = io.fixedBufferStream(section_bytes);
1288 var section_reader = section_stream.reader();
1289 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
1290 if (chdr.ch_type != .ZLIB) continue;
1291
1292 var zlib_stream = std.compress.zlib.decompressor(section_stream.reader());
1293
1294 const decompressed_section = try allocator.alloc(u8, chdr.ch_size);
1295 errdefer allocator.free(decompressed_section);
1296
1297 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
1298 assert(read == decompressed_section.len);
1299
1300 break :blk .{
1301 .data = decompressed_section,
1302 .virtual_address = shdr.sh_addr,
1303 .owned = true,
1304 };
1305 } else .{
1306 .data = section_bytes,
1307 .virtual_address = shdr.sh_addr,
1308 .owned = false,
1309 };
1310 }
1311
1312 const missing_debug_info =
1313 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
1314 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
1315 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
1316 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
1317
1318 // Attempt to load debug info from an external file
1319 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1320 if (missing_debug_info) {
1321
1322 // Only allow one level of debug info nesting
1323 if (parent_mapped_mem) |_| {
1324 return error.MissingDebugInfo;
1325 }
1326
1327 const global_debug_directories = [_][]const u8{
1328 "/usr/lib/debug",
1329 };
1330
1331 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
1332 if (build_id) |id| blk: {
1333 if (id.len < 3) break :blk;
1334
1335 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
1336 const extension = ".debug";
1337 var id_prefix_buf: [2]u8 = undefined;
1338 var filename_buf: [38 + extension.len]u8 = undefined;
1339
1340 _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
1341 const filename = std.fmt.bufPrint(
1342 &filename_buf,
1343 "{s}" ++ extension,
1344 .{std.fmt.fmtSliceHexLower(id[1..])},
1345 ) catch break :blk;
1346
1347 for (global_debug_directories) |global_directory| {
1348 const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
1349 defer allocator.free(path);
1350
1351 return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1352 }
1353 }
1354
1355 // use the path from .gnu_debuglink, in the same search order as gdb
1356 if (separate_debug_filename) |separate_filename| blk: {
1357 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
1358
1359 // <cwd>/<gnu_debuglink>
1360 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1361
1362 // <cwd>/.debug/<gnu_debuglink>
1363 {
1364 const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
1365 defer allocator.free(path);
1366
1367 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1368 }
1369
1370 var cwd_buf: [fs.max_path_bytes]u8 = undefined;
1371 const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk;
1372
1373 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
1374 for (global_debug_directories) |global_directory| {
1375 const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
1376 defer allocator.free(path);
1377 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1378 }
1379 }
1380
1381 return error.MissingDebugInfo;
1382 }
1383
1384 var di = Dwarf{
1385 .endian = endian,
1386 .sections = sections,
1387 .is_macho = false,
1388 };
1389
1390 try Dwarf.open(&di, allocator);
1391
1392 return ModuleDebugInfo{
1393 .base_address = undefined,
1394 .dwarf = di,
1395 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1396 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
1397 };
1398 }
1399}
1400
1401/// This takes ownership of macho_file: users of this function should not close
1402/// it themselves, even on error.
1403/// TODO it's weird to take ownership even on error, rework this code.
1404fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugInfo {
1405 const mapped_mem = try mapWholeFile(macho_file);
1406
1407 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
1408 if (hdr.magic != macho.MH_MAGIC_64)
1409 return error.InvalidDebugInfo;
1410
1411 var it = macho.LoadCommandIterator{
1412 .ncmds = hdr.ncmds,
1413 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
1414 };
1415 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
1416 .SYMTAB => break cmd.cast(macho.symtab_command).?,
1417 else => {},
1418 } else return error.MissingDebugInfo;
1419
1420 const syms = @as(
1421 [*]const macho.nlist_64,
1422 @ptrCast(@alignCast(&mapped_mem[symtab.symoff])),
1423 )[0..symtab.nsyms];
1424 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
1425
1426 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
1427
1428 var ofile: u32 = undefined;
1429 var last_sym: MachoSymbol = undefined;
1430 var symbol_index: usize = 0;
1431 var state: enum {
1432 init,
1433 oso_open,
1434 oso_close,
1435 bnsym,
1436 fun_strx,
1437 fun_size,
1438 ensym,
1439 } = .init;
1440
1441 for (syms) |*sym| {
1442 if (!sym.stab()) continue;
1443
1444 // TODO handle globals N_GSYM, and statics N_STSYM
1445 switch (sym.n_type) {
1446 macho.N_OSO => {
1447 switch (state) {
1448 .init, .oso_close => {
1449 state = .oso_open;
1450 ofile = sym.n_strx;
1451 },
1452 else => return error.InvalidDebugInfo,
1453 }
1454 },
1455 macho.N_BNSYM => {
1456 switch (state) {
1457 .oso_open, .ensym => {
1458 state = .bnsym;
1459 last_sym = .{
1460 .strx = 0,
1461 .addr = sym.n_value,
1462 .size = 0,
1463 .ofile = ofile,
1464 };
1465 },
1466 else => return error.InvalidDebugInfo,
1467 }
1468 },
1469 macho.N_FUN => {
1470 switch (state) {
1471 .bnsym => {
1472 state = .fun_strx;
1473 last_sym.strx = sym.n_strx;
1474 },
1475 .fun_strx => {
1476 state = .fun_size;
1477 last_sym.size = @as(u32, @intCast(sym.n_value));
1478 },
1479 else => return error.InvalidDebugInfo,
1480 }
1481 },
1482 macho.N_ENSYM => {
1483 switch (state) {
1484 .fun_size => {
1485 state = .ensym;
1486 symbols_buf[symbol_index] = last_sym;
1487 symbol_index += 1;
1488 },
1489 else => return error.InvalidDebugInfo,
1490 }
1491 },
1492 macho.N_SO => {
1493 switch (state) {
1494 .init, .oso_close => {},
1495 .oso_open, .ensym => {
1496 state = .oso_close;
1497 },
1498 else => return error.InvalidDebugInfo,
1499 }
1500 },
1501 else => {},
1502 }
1503 }
1504
1505 switch (state) {
1506 .init => return error.MissingDebugInfo,
1507 .oso_close => {},
1508 else => return error.InvalidDebugInfo,
1509 }
1510
1511 const symbols = try allocator.realloc(symbols_buf, symbol_index);
1512
1513 // Even though lld emits symbols in ascending order, this debug code
1514 // should work for programs linked in any valid way.
1515 // This sort is so that we can binary search later.
1516 mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
1517
1518 return ModuleDebugInfo{
1519 .base_address = undefined,
1520 .vmaddr_slide = undefined,
1521 .mapped_memory = mapped_mem,
1522 .ofiles = ModuleDebugInfo.OFileTable.init(allocator),
1523 .symbols = symbols,
1524 .strings = strings,
1525 };
1526}
1527
1528fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void {
1030fn printLineFromFileAnyOs(out_stream: anytype, line_info: Info.SourceLocation) !void {
15291031 // Need this to always block even in async I/O mode, because this could potentially
15301032 // be called from e.g. the event loop code crashing.
15311033 var f = try fs.cwd().openFile(line_info.file_name, .{});
......@@ -1591,7 +1093,7 @@ test printLineFromFileAnyOs {
15911093
15921094 var test_dir = std.testing.tmpDir(.{});
15931095 defer test_dir.cleanup();
1594 // Relies on testing.tmpDir internals which is not ideal, but LineInfo requires paths.
1096 // Relies on testing.tmpDir internals which is not ideal, but Info.SourceLocation requires paths.
15951097 const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] });
15961098 defer allocator.free(test_dir_path);
15971099
......@@ -1702,871 +1204,6 @@ test printLineFromFileAnyOs {
17021204 }
17031205}
17041206
1705const MachoSymbol = struct {
1706 strx: u32,
1707 addr: u64,
1708 size: u32,
1709 ofile: u32,
1710
1711 /// Returns the address from the macho file
1712 fn address(self: MachoSymbol) u64 {
1713 return self.addr;
1714 }
1715
1716 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
1717 _ = context;
1718 return lhs.addr < rhs.addr;
1719 }
1720};
1721
1722/// Takes ownership of file, even on error.
1723/// TODO it's weird to take ownership even on error, rework this code.
1724fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
1725 nosuspend {
1726 defer file.close();
1727
1728 const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
1729 const mapped_mem = try posix.mmap(
1730 null,
1731 file_len,
1732 posix.PROT.READ,
1733 .{ .TYPE = .SHARED },
1734 file.handle,
1735 0,
1736 );
1737 errdefer posix.munmap(mapped_mem);
1738
1739 return mapped_mem;
1740 }
1741}
1742
1743pub const WindowsModuleInfo = struct {
1744 base_address: usize,
1745 size: u32,
1746 name: []const u8,
1747 handle: windows.HMODULE,
1748
1749 // Set when the image file needed to be mapped from disk
1750 mapped_file: ?struct {
1751 file: File,
1752 section_handle: windows.HANDLE,
1753 section_view: []const u8,
1754
1755 pub fn deinit(self: @This()) void {
1756 const process_handle = windows.GetCurrentProcess();
1757 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS);
1758 windows.CloseHandle(self.section_handle);
1759 self.file.close();
1760 }
1761 } = null,
1762};
1763
1764pub const Info = struct {
1765 allocator: mem.Allocator,
1766 address_map: std.AutoHashMap(usize, *ModuleDebugInfo),
1767 modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void,
1768
1769 pub fn init(allocator: mem.Allocator) !Info {
1770 var debug_info = Info{
1771 .allocator = allocator,
1772 .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator),
1773 .modules = if (native_os == .windows) .{} else {},
1774 };
1775
1776 if (native_os == .windows) {
1777 errdefer debug_info.modules.deinit(allocator);
1778
1779 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
1780 if (handle == windows.INVALID_HANDLE_VALUE) {
1781 switch (windows.GetLastError()) {
1782 else => |err| return windows.unexpectedError(err),
1783 }
1784 }
1785 defer windows.CloseHandle(handle);
1786
1787 var module_entry: windows.MODULEENTRY32 = undefined;
1788 module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
1789 if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
1790 return error.MissingDebugInfo;
1791 }
1792
1793 var module_valid = true;
1794 while (module_valid) {
1795 const module_info = try debug_info.modules.addOne(allocator);
1796 const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
1797 errdefer allocator.free(name);
1798
1799 module_info.* = .{
1800 .base_address = @intFromPtr(module_entry.modBaseAddr),
1801 .size = module_entry.modBaseSize,
1802 .name = name,
1803 .handle = module_entry.hModule,
1804 };
1805
1806 module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
1807 }
1808 }
1809
1810 return debug_info;
1811 }
1812
1813 pub fn deinit(self: *Info) void {
1814 var it = self.address_map.iterator();
1815 while (it.next()) |entry| {
1816 const mdi = entry.value_ptr.*;
1817 mdi.deinit(self.allocator);
1818 self.allocator.destroy(mdi);
1819 }
1820 self.address_map.deinit();
1821 if (native_os == .windows) {
1822 for (self.modules.items) |module| {
1823 self.allocator.free(module.name);
1824 if (module.mapped_file) |mapped_file| mapped_file.deinit();
1825 }
1826 self.modules.deinit(self.allocator);
1827 }
1828 }
1829
1830 pub fn getModuleForAddress(self: *Info, address: usize) !*ModuleDebugInfo {
1831 if (comptime builtin.target.isDarwin()) {
1832 return self.lookupModuleDyld(address);
1833 } else if (native_os == .windows) {
1834 return self.lookupModuleWin32(address);
1835 } else if (native_os == .haiku) {
1836 return self.lookupModuleHaiku(address);
1837 } else if (comptime builtin.target.isWasm()) {
1838 return self.lookupModuleWasm(address);
1839 } else {
1840 return self.lookupModuleDl(address);
1841 }
1842 }
1843
1844 // Returns the module name for a given address.
1845 // This can be called when getModuleForAddress fails, so implementations should provide
1846 // a path that doesn't rely on any side-effects of a prior successful module lookup.
1847 pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 {
1848 if (comptime builtin.target.isDarwin()) {
1849 return self.lookupModuleNameDyld(address);
1850 } else if (native_os == .windows) {
1851 return self.lookupModuleNameWin32(address);
1852 } else if (native_os == .haiku) {
1853 return null;
1854 } else if (comptime builtin.target.isWasm()) {
1855 return null;
1856 } else {
1857 return self.lookupModuleNameDl(address);
1858 }
1859 }
1860
1861 fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo {
1862 const image_count = std.c._dyld_image_count();
1863
1864 var i: u32 = 0;
1865 while (i < image_count) : (i += 1) {
1866 const header = std.c._dyld_get_image_header(i) orelse continue;
1867 const base_address = @intFromPtr(header);
1868 if (address < base_address) continue;
1869 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
1870
1871 var it = macho.LoadCommandIterator{
1872 .ncmds = header.ncmds,
1873 .buffer = @alignCast(@as(
1874 [*]u8,
1875 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
1876 )[0..header.sizeofcmds]),
1877 };
1878
1879 var unwind_info: ?[]const u8 = null;
1880 var eh_frame: ?[]const u8 = null;
1881 while (it.next()) |cmd| switch (cmd.cmd()) {
1882 .SEGMENT_64 => {
1883 const segment_cmd = cmd.cast(macho.segment_command_64).?;
1884 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
1885
1886 const seg_start = segment_cmd.vmaddr + vmaddr_slide;
1887 const seg_end = seg_start + segment_cmd.vmsize;
1888 if (address >= seg_start and address < seg_end) {
1889 if (self.address_map.get(base_address)) |obj_di| {
1890 return obj_di;
1891 }
1892
1893 for (cmd.getSections()) |sect| {
1894 if (mem.eql(u8, "__unwind_info", sect.sectName())) {
1895 unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
1896 } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
1897 eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
1898 }
1899 }
1900
1901 const obj_di = try self.allocator.create(ModuleDebugInfo);
1902 errdefer self.allocator.destroy(obj_di);
1903
1904 const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
1905 const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
1906 error.FileNotFound => return error.MissingDebugInfo,
1907 else => return err,
1908 };
1909 obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
1910 obj_di.base_address = base_address;
1911 obj_di.vmaddr_slide = vmaddr_slide;
1912 obj_di.unwind_info = unwind_info;
1913 obj_di.eh_frame = eh_frame;
1914
1915 try self.address_map.putNoClobber(base_address, obj_di);
1916
1917 return obj_di;
1918 }
1919 },
1920 else => {},
1921 };
1922 }
1923
1924 return error.MissingDebugInfo;
1925 }
1926
1927 fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 {
1928 _ = self;
1929 const image_count = std.c._dyld_image_count();
1930
1931 var i: u32 = 0;
1932 while (i < image_count) : (i += 1) {
1933 const header = std.c._dyld_get_image_header(i) orelse continue;
1934 const base_address = @intFromPtr(header);
1935 if (address < base_address) continue;
1936 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
1937
1938 var it = macho.LoadCommandIterator{
1939 .ncmds = header.ncmds,
1940 .buffer = @alignCast(@as(
1941 [*]u8,
1942 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
1943 )[0..header.sizeofcmds]),
1944 };
1945
1946 while (it.next()) |cmd| switch (cmd.cmd()) {
1947 .SEGMENT_64 => {
1948 const segment_cmd = cmd.cast(macho.segment_command_64).?;
1949 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
1950
1951 const original_address = address - vmaddr_slide;
1952 const seg_start = segment_cmd.vmaddr;
1953 const seg_end = seg_start + segment_cmd.vmsize;
1954 if (original_address >= seg_start and original_address < seg_end) {
1955 return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
1956 }
1957 },
1958 else => {},
1959 };
1960 }
1961
1962 return null;
1963 }
1964
1965 fn lookupModuleWin32(self: *Info, address: usize) !*ModuleDebugInfo {
1966 for (self.modules.items) |*module| {
1967 if (address >= module.base_address and address < module.base_address + module.size) {
1968 if (self.address_map.get(module.base_address)) |obj_di| {
1969 return obj_di;
1970 }
1971
1972 const obj_di = try self.allocator.create(ModuleDebugInfo);
1973 errdefer self.allocator.destroy(obj_di);
1974
1975 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
1976 var coff_obj = try coff.Coff.init(mapped_module, true);
1977
1978 // The string table is not mapped into memory by the loader, so if a section name is in the
1979 // string table then we have to map the full image file from disk. This can happen when
1980 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
1981 if (coff_obj.strtabRequired()) {
1982 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
1983 // openFileAbsoluteW requires the prefix to be present
1984 @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
1985
1986 const process_handle = windows.GetCurrentProcess();
1987 const len = windows.kernel32.GetModuleFileNameExW(
1988 process_handle,
1989 module.handle,
1990 @ptrCast(&name_buffer[4]),
1991 windows.PATH_MAX_WIDE,
1992 );
1993
1994 if (len == 0) return error.MissingDebugInfo;
1995 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
1996 error.FileNotFound => return error.MissingDebugInfo,
1997 else => return err,
1998 };
1999 errdefer coff_file.close();
2000
2001 var section_handle: windows.HANDLE = undefined;
2002 const create_section_rc = windows.ntdll.NtCreateSection(
2003 &section_handle,
2004 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
2005 null,
2006 null,
2007 windows.PAGE_READONLY,
2008 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
2009 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
2010 windows.SEC_COMMIT,
2011 coff_file.handle,
2012 );
2013 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
2014 errdefer windows.CloseHandle(section_handle);
2015
2016 var coff_len: usize = 0;
2017 var base_ptr: usize = 0;
2018 const map_section_rc = windows.ntdll.NtMapViewOfSection(
2019 section_handle,
2020 process_handle,
2021 @ptrCast(&base_ptr),
2022 null,
2023 0,
2024 null,
2025 &coff_len,
2026 .ViewUnmap,
2027 0,
2028 windows.PAGE_READONLY,
2029 );
2030 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
2031 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
2032
2033 const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
2034 coff_obj = try coff.Coff.init(section_view, false);
2035
2036 module.mapped_file = .{
2037 .file = coff_file,
2038 .section_handle = section_handle,
2039 .section_view = section_view,
2040 };
2041 }
2042 errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
2043
2044 obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
2045 obj_di.base_address = module.base_address;
2046
2047 try self.address_map.putNoClobber(module.base_address, obj_di);
2048 return obj_di;
2049 }
2050 }
2051
2052 return error.MissingDebugInfo;
2053 }
2054
2055 fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 {
2056 for (self.modules.items) |module| {
2057 if (address >= module.base_address and address < module.base_address + module.size) {
2058 return module.name;
2059 }
2060 }
2061 return null;
2062 }
2063
2064 fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 {
2065 _ = self;
2066
2067 var ctx: struct {
2068 // Input
2069 address: usize,
2070 // Output
2071 name: []const u8 = "",
2072 } = .{ .address = address };
2073 const CtxTy = @TypeOf(ctx);
2074
2075 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
2076 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
2077 _ = size;
2078 if (context.address < info.addr) return;
2079 const phdrs = info.phdr[0..info.phnum];
2080 for (phdrs) |*phdr| {
2081 if (phdr.p_type != elf.PT_LOAD) continue;
2082
2083 const seg_start = info.addr +% phdr.p_vaddr;
2084 const seg_end = seg_start + phdr.p_memsz;
2085 if (context.address >= seg_start and context.address < seg_end) {
2086 context.name = mem.sliceTo(info.name, 0) orelse "";
2087 break;
2088 }
2089 } else return;
2090
2091 return error.Found;
2092 }
2093 }.callback)) {
2094 return null;
2095 } else |err| switch (err) {
2096 error.Found => return fs.path.basename(ctx.name),
2097 }
2098
2099 return null;
2100 }
2101
2102 fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo {
2103 var ctx: struct {
2104 // Input
2105 address: usize,
2106 // Output
2107 base_address: usize = undefined,
2108 name: []const u8 = undefined,
2109 build_id: ?[]const u8 = null,
2110 gnu_eh_frame: ?[]const u8 = null,
2111 } = .{ .address = address };
2112 const CtxTy = @TypeOf(ctx);
2113
2114 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
2115 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
2116 _ = size;
2117 // The base address is too high
2118 if (context.address < info.addr)
2119 return;
2120
2121 const phdrs = info.phdr[0..info.phnum];
2122 for (phdrs) |*phdr| {
2123 if (phdr.p_type != elf.PT_LOAD) continue;
2124
2125 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
2126 const seg_start = info.addr +% phdr.p_vaddr;
2127 const seg_end = seg_start + phdr.p_memsz;
2128 if (context.address >= seg_start and context.address < seg_end) {
2129 // Android libc uses NULL instead of an empty string to mark the
2130 // main program
2131 context.name = mem.sliceTo(info.name, 0) orelse "";
2132 context.base_address = info.addr;
2133 break;
2134 }
2135 } else return;
2136
2137 for (info.phdr[0..info.phnum]) |phdr| {
2138 switch (phdr.p_type) {
2139 elf.PT_NOTE => {
2140 // Look for .note.gnu.build-id
2141 const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
2142 const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);
2143 if (name_size != 4) continue;
2144 const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);
2145 const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
2146 if (note_type != elf.NT_GNU_BUILD_ID) continue;
2147 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
2148 context.build_id = note_bytes[16..][0..desc_size];
2149 },
2150 elf.PT_GNU_EH_FRAME => {
2151 context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
2152 },
2153 else => {},
2154 }
2155 }
2156
2157 // Stop the iteration
2158 return error.Found;
2159 }
2160 }.callback)) {
2161 return error.MissingDebugInfo;
2162 } else |err| switch (err) {
2163 error.Found => {},
2164 }
2165
2166 if (self.address_map.get(ctx.base_address)) |obj_di| {
2167 return obj_di;
2168 }
2169
2170 const obj_di = try self.allocator.create(ModuleDebugInfo);
2171 errdefer self.allocator.destroy(obj_di);
2172
2173 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
2174 if (ctx.gnu_eh_frame) |eh_frame_hdr| {
2175 // This is a special case - pointer offsets inside .eh_frame_hdr
2176 // are encoded relative to its base address, so we must use the
2177 // version that is already memory mapped, and not the one that
2178 // will be mapped separately from the ELF file.
2179 sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{
2180 .data = eh_frame_hdr,
2181 .owned = false,
2182 };
2183 }
2184
2185 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
2186 obj_di.base_address = ctx.base_address;
2187
2188 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
2189 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
2190
2191 try self.address_map.putNoClobber(ctx.base_address, obj_di);
2192
2193 return obj_di;
2194 }
2195
2196 fn lookupModuleHaiku(self: *Info, address: usize) !*ModuleDebugInfo {
2197 _ = self;
2198 _ = address;
2199 @panic("TODO implement lookup module for Haiku");
2200 }
2201
2202 fn lookupModuleWasm(self: *Info, address: usize) !*ModuleDebugInfo {
2203 _ = self;
2204 _ = address;
2205 @panic("TODO implement lookup module for Wasm");
2206 }
2207};
2208
2209pub const ModuleDebugInfo = switch (native_os) {
2210 .macos, .ios, .watchos, .tvos, .visionos => struct {
2211 base_address: usize,
2212 vmaddr_slide: usize,
2213 mapped_memory: []align(mem.page_size) const u8,
2214 symbols: []const MachoSymbol,
2215 strings: [:0]const u8,
2216 ofiles: OFileTable,
2217
2218 // Backed by the in-memory sections mapped by the loader
2219 unwind_info: ?[]const u8 = null,
2220 eh_frame: ?[]const u8 = null,
2221
2222 const OFileTable = std.StringHashMap(OFileInfo);
2223 const OFileInfo = struct {
2224 di: Dwarf,
2225 addr_table: std.StringHashMap(u64),
2226 };
2227
2228 pub fn deinit(self: *@This(), allocator: mem.Allocator) void {
2229 var it = self.ofiles.iterator();
2230 while (it.next()) |entry| {
2231 const ofile = entry.value_ptr;
2232 ofile.di.deinit(allocator);
2233 ofile.addr_table.deinit();
2234 }
2235 self.ofiles.deinit();
2236 allocator.free(self.symbols);
2237 posix.munmap(self.mapped_memory);
2238 }
2239
2240 fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo {
2241 const o_file = try fs.cwd().openFile(o_file_path, .{});
2242 const mapped_mem = try mapWholeFile(o_file);
2243
2244 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
2245 if (hdr.magic != std.macho.MH_MAGIC_64)
2246 return error.InvalidDebugInfo;
2247
2248 var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;
2249 var symtabcmd: ?macho.symtab_command = null;
2250 var it = macho.LoadCommandIterator{
2251 .ncmds = hdr.ncmds,
2252 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
2253 };
2254 while (it.next()) |cmd| switch (cmd.cmd()) {
2255 .SEGMENT_64 => segcmd = cmd,
2256 .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?,
2257 else => {},
2258 };
2259
2260 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
2261
2262 // Parse symbols
2263 const strtab = @as(
2264 [*]const u8,
2265 @ptrCast(&mapped_mem[symtabcmd.?.stroff]),
2266 )[0 .. symtabcmd.?.strsize - 1 :0];
2267 const symtab = @as(
2268 [*]const macho.nlist_64,
2269 @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),
2270 )[0..symtabcmd.?.nsyms];
2271
2272 // TODO handle tentative (common) symbols
2273 var addr_table = std.StringHashMap(u64).init(allocator);
2274 try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));
2275 for (symtab) |sym| {
2276 if (sym.n_strx == 0) continue;
2277 if (sym.undf() or sym.tentative() or sym.abs()) continue;
2278 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
2279 // TODO is it possible to have a symbol collision?
2280 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
2281 }
2282
2283 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
2284 if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
2285 .data = eh_frame,
2286 .owned = false,
2287 };
2288
2289 for (segcmd.?.getSections()) |sect| {
2290 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
2291
2292 var section_index: ?usize = null;
2293 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
2294 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
2295 }
2296 if (section_index == null) continue;
2297
2298 const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);
2299 sections[section_index.?] = .{
2300 .data = section_bytes,
2301 .virtual_address = sect.addr,
2302 .owned = false,
2303 };
2304 }
2305
2306 const missing_debug_info =
2307 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
2308 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
2309 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
2310 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
2311 if (missing_debug_info) return error.MissingDebugInfo;
2312
2313 var di = Dwarf{
2314 .endian = .little,
2315 .sections = sections,
2316 .is_macho = true,
2317 };
2318
2319 try Dwarf.open(&di, allocator);
2320 const info = OFileInfo{
2321 .di = di,
2322 .addr_table = addr_table,
2323 };
2324
2325 // Add the debug info to the cache
2326 const result = try self.ofiles.getOrPut(o_file_path);
2327 assert(!result.found_existing);
2328 result.value_ptr.* = info;
2329
2330 return result.value_ptr;
2331 }
2332
2333 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
2334 nosuspend {
2335 const result = try self.getOFileInfoForAddress(allocator, address);
2336 if (result.symbol == null) return .{};
2337
2338 // Take the symbol name from the N_FUN STAB entry, we're going to
2339 // use it if we fail to find the DWARF infos
2340 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
2341 if (result.o_file_info == null) return .{ .symbol_name = stab_symbol };
2342
2343 // Translate again the address, this time into an address inside the
2344 // .o file
2345 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
2346 .symbol_name = "???",
2347 };
2348
2349 const addr_off = result.relocated_address - result.symbol.?.addr;
2350 const o_file_di = &result.o_file_info.?.di;
2351 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
2352 return SymbolInfo{
2353 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
2354 .compile_unit_name = compile_unit.die.getAttrString(
2355 o_file_di,
2356 DW.AT.name,
2357 o_file_di.section(.debug_str),
2358 compile_unit.*,
2359 ) catch |err| switch (err) {
2360 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
2361 },
2362 .line_info = o_file_di.getLineNumberInfo(
2363 allocator,
2364 compile_unit.*,
2365 relocated_address_o + addr_off,
2366 ) catch |err| switch (err) {
2367 error.MissingDebugInfo, error.InvalidDebugInfo => null,
2368 else => return err,
2369 },
2370 };
2371 } else |err| switch (err) {
2372 error.MissingDebugInfo, error.InvalidDebugInfo => {
2373 return SymbolInfo{ .symbol_name = stab_symbol };
2374 },
2375 else => return err,
2376 }
2377 }
2378 }
2379
2380 pub fn getOFileInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !struct {
2381 relocated_address: usize,
2382 symbol: ?*const MachoSymbol = null,
2383 o_file_info: ?*OFileInfo = null,
2384 } {
2385 nosuspend {
2386 // Translate the VA into an address into this object
2387 const relocated_address = address - self.vmaddr_slide;
2388
2389 // Find the .o file where this symbol is defined
2390 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
2391 .relocated_address = relocated_address,
2392 };
2393
2394 // Check if its debug infos are already in the cache
2395 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
2396 const o_file_info = self.ofiles.getPtr(o_file_path) orelse
2397 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
2398 error.FileNotFound,
2399 error.MissingDebugInfo,
2400 error.InvalidDebugInfo,
2401 => return .{
2402 .relocated_address = relocated_address,
2403 .symbol = symbol,
2404 },
2405 else => return err,
2406 });
2407
2408 return .{
2409 .relocated_address = relocated_address,
2410 .symbol = symbol,
2411 .o_file_info = o_file_info,
2412 };
2413 }
2414 }
2415
2416 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf {
2417 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
2418 }
2419 },
2420 .uefi, .windows => struct {
2421 base_address: usize,
2422 pdb: ?pdb.Pdb = null,
2423 dwarf: ?Dwarf = null,
2424 coff_image_base: u64,
2425
2426 /// Only used if pdb is non-null
2427 coff_section_headers: []coff.SectionHeader,
2428
2429 pub fn deinit(self: *@This(), allocator: mem.Allocator) void {
2430 if (self.dwarf) |*dwarf| {
2431 dwarf.deinit(allocator);
2432 }
2433
2434 if (self.pdb) |*p| {
2435 p.deinit();
2436 allocator.free(self.coff_section_headers);
2437 }
2438 }
2439
2440 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo {
2441 var coff_section: *align(1) const coff.SectionHeader = undefined;
2442 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
2443 if (sect_contrib.Section > self.coff_section_headers.len) continue;
2444 // Remember that SectionContribEntry.Section is 1-based.
2445 coff_section = &self.coff_section_headers[sect_contrib.Section - 1];
2446
2447 const vaddr_start = coff_section.virtual_address + sect_contrib.Offset;
2448 const vaddr_end = vaddr_start + sect_contrib.Size;
2449 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
2450 break sect_contrib.ModuleIndex;
2451 }
2452 } else {
2453 // we have no information to add to the address
2454 return null;
2455 };
2456
2457 const module = (try self.pdb.?.getModule(mod_index)) orelse
2458 return error.InvalidDebugInfo;
2459 const obj_basename = fs.path.basename(module.obj_file_name);
2460
2461 const symbol_name = self.pdb.?.getSymbolName(
2462 module,
2463 relocated_address - coff_section.virtual_address,
2464 ) orelse "???";
2465 const opt_line_info = try self.pdb.?.getLineNumberInfo(
2466 module,
2467 relocated_address - coff_section.virtual_address,
2468 );
2469
2470 return SymbolInfo{
2471 .symbol_name = symbol_name,
2472 .compile_unit_name = obj_basename,
2473 .line_info = opt_line_info,
2474 };
2475 }
2476
2477 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
2478 // Translate the VA into an address into this object
2479 const relocated_address = address - self.base_address;
2480
2481 if (self.pdb != null) {
2482 if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;
2483 }
2484
2485 if (self.dwarf) |*dwarf| {
2486 const dwarf_address = relocated_address + self.coff_image_base;
2487 return getSymbolFromDwarf(allocator, dwarf_address, dwarf);
2488 }
2489
2490 return SymbolInfo{};
2491 }
2492
2493 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf {
2494 _ = allocator;
2495 _ = address;
2496
2497 return switch (self.debug_data) {
2498 .dwarf => |*dwarf| dwarf,
2499 else => null,
2500 };
2501 }
2502 },
2503 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
2504 base_address: usize,
2505 dwarf: Dwarf,
2506 mapped_memory: []align(mem.page_size) const u8,
2507 external_mapped_memory: ?[]align(mem.page_size) const u8,
2508
2509 pub fn deinit(self: *@This(), allocator: mem.Allocator) void {
2510 self.dwarf.deinit(allocator);
2511 posix.munmap(self.mapped_memory);
2512 if (self.external_mapped_memory) |m| posix.munmap(m);
2513 }
2514
2515 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
2516 // Translate the VA into an address into this object
2517 const relocated_address = address - self.base_address;
2518 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
2519 }
2520
2521 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf {
2522 _ = allocator;
2523 _ = address;
2524 return &self.dwarf;
2525 }
2526 },
2527 .wasi, .emscripten => struct {
2528 pub fn deinit(self: *@This(), allocator: mem.Allocator) void {
2529 _ = self;
2530 _ = allocator;
2531 }
2532
2533 pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
2534 _ = self;
2535 _ = allocator;
2536 _ = address;
2537 return SymbolInfo{};
2538 }
2539
2540 pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf {
2541 _ = self;
2542 _ = allocator;
2543 _ = address;
2544 return null;
2545 }
2546 },
2547 else => Dwarf,
2548};
2549
2550fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *Dwarf) !SymbolInfo {
2551 if (nosuspend di.findCompileUnit(address)) |compile_unit| {
2552 return SymbolInfo{
2553 .symbol_name = nosuspend di.getSymbolName(address) orelse "???",
2554 .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
2555 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
2556 },
2557 .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
2558 error.MissingDebugInfo, error.InvalidDebugInfo => null,
2559 else => return err,
2560 },
2561 };
2562 } else |err| switch (err) {
2563 error.MissingDebugInfo, error.InvalidDebugInfo => {
2564 return SymbolInfo{};
2565 },
2566 else => return err,
2567 }
2568}
2569
25701207/// TODO multithreaded awareness
25711208var debug_info_allocator: ?mem.Allocator = null;
25721209var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined;
......@@ -2802,7 +1439,7 @@ test "manage resources correctly" {
28021439 }
28031440
28041441 const writer = std.io.null_writer;
2805 var di = try openSelfDebugInfo(testing.allocator);
1442 var di = try Info.openSelf(testing.allocator);
28061443 defer di.deinit();
28071444 try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr()));
28081445}
lib/std/debug/Dwarf.zig+3-3
......@@ -1353,7 +1353,7 @@ pub fn getLineNumberInfo(
13531353 allocator: Allocator,
13541354 compile_unit: CompileUnit,
13551355 target_address: u64,
1356) !std.debug.LineInfo {
1356) !std.debug.Info.SourceLocation {
13571357 const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit);
13581358 const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list);
13591359
......@@ -2084,7 +2084,7 @@ const LineNumberProgram = struct {
20842084 self: *LineNumberProgram,
20852085 allocator: Allocator,
20862086 file_entries: []const FileEntry,
2087 ) !?std.debug.LineInfo {
2087 ) !?std.debug.Info.SourceLocation {
20882088 if (self.prev_valid and
20892089 self.target_address >= self.prev_address and
20902090 self.target_address < self.address)
......@@ -2104,7 +2104,7 @@ const LineNumberProgram = struct {
21042104 dir_name, file_entry.path,
21052105 });
21062106
2107 return std.debug.LineInfo{
2107 return std.debug.Info.SourceLocation{
21082108 .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0,
21092109 .column = self.prev_column,
21102110 .file_name = file_name,
lib/std/debug/Info.zig created+1377
......@@ -0,0 +1,1377 @@
1//! Cross-platform abstraction for debug information.
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const native_endian = native_arch.endian();
6const native_arch = builtin.cpu.arch;
7
8const std = @import("../std.zig");
9const mem = std.mem;
10const Allocator = std.mem.Allocator;
11const windows = std.os.windows;
12const macho = std.macho;
13const fs = std.fs;
14const coff = std.coff;
15const pdb = std.pdb;
16const assert = std.debug.assert;
17const posix = std.posix;
18const elf = std.elf;
19const Dwarf = std.debug.Dwarf;
20const File = std.fs.File;
21const math = std.math;
22const testing = std.testing;
23
24const Info = @This();
25
26const root = @import("root");
27
28allocator: Allocator,
29address_map: std.AutoHashMap(usize, *ModuleDebugInfo),
30modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void,
31
32pub const OpenSelfError = error{
33 MissingDebugInfo,
34 UnsupportedOperatingSystem,
35} || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set;
36
37pub fn openSelf(allocator: Allocator) OpenSelfError!Info {
38 nosuspend {
39 if (builtin.strip_debug_info)
40 return error.MissingDebugInfo;
41 if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) {
42 return root.os.debug.openSelfDebugInfo(allocator);
43 }
44 switch (native_os) {
45 .linux,
46 .freebsd,
47 .netbsd,
48 .dragonfly,
49 .openbsd,
50 .macos,
51 .solaris,
52 .illumos,
53 .windows,
54 => return try Info.init(allocator),
55 else => return error.UnsupportedOperatingSystem,
56 }
57 }
58}
59
60pub fn init(allocator: Allocator) !Info {
61 var debug_info = Info{
62 .allocator = allocator,
63 .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator),
64 .modules = if (native_os == .windows) .{} else {},
65 };
66
67 if (native_os == .windows) {
68 errdefer debug_info.modules.deinit(allocator);
69
70 const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
71 if (handle == windows.INVALID_HANDLE_VALUE) {
72 switch (windows.GetLastError()) {
73 else => |err| return windows.unexpectedError(err),
74 }
75 }
76 defer windows.CloseHandle(handle);
77
78 var module_entry: windows.MODULEENTRY32 = undefined;
79 module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
80 if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
81 return error.MissingDebugInfo;
82 }
83
84 var module_valid = true;
85 while (module_valid) {
86 const module_info = try debug_info.modules.addOne(allocator);
87 const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{};
88 errdefer allocator.free(name);
89
90 module_info.* = .{
91 .base_address = @intFromPtr(module_entry.modBaseAddr),
92 .size = module_entry.modBaseSize,
93 .name = name,
94 .handle = module_entry.hModule,
95 };
96
97 module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
98 }
99 }
100
101 return debug_info;
102}
103
104pub fn deinit(self: *Info) void {
105 var it = self.address_map.iterator();
106 while (it.next()) |entry| {
107 const mdi = entry.value_ptr.*;
108 mdi.deinit(self.allocator);
109 self.allocator.destroy(mdi);
110 }
111 self.address_map.deinit();
112 if (native_os == .windows) {
113 for (self.modules.items) |module| {
114 self.allocator.free(module.name);
115 if (module.mapped_file) |mapped_file| mapped_file.deinit();
116 }
117 self.modules.deinit(self.allocator);
118 }
119}
120
121pub fn getModuleForAddress(self: *Info, address: usize) !*ModuleDebugInfo {
122 if (comptime builtin.target.isDarwin()) {
123 return self.lookupModuleDyld(address);
124 } else if (native_os == .windows) {
125 return self.lookupModuleWin32(address);
126 } else if (native_os == .haiku) {
127 return self.lookupModuleHaiku(address);
128 } else if (comptime builtin.target.isWasm()) {
129 return self.lookupModuleWasm(address);
130 } else {
131 return self.lookupModuleDl(address);
132 }
133}
134
135// Returns the module name for a given address.
136// This can be called when getModuleForAddress fails, so implementations should provide
137// a path that doesn't rely on any side-effects of a prior successful module lookup.
138pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 {
139 if (comptime builtin.target.isDarwin()) {
140 return self.lookupModuleNameDyld(address);
141 } else if (native_os == .windows) {
142 return self.lookupModuleNameWin32(address);
143 } else if (native_os == .haiku) {
144 return null;
145 } else if (comptime builtin.target.isWasm()) {
146 return null;
147 } else {
148 return self.lookupModuleNameDl(address);
149 }
150}
151
152fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo {
153 const image_count = std.c._dyld_image_count();
154
155 var i: u32 = 0;
156 while (i < image_count) : (i += 1) {
157 const header = std.c._dyld_get_image_header(i) orelse continue;
158 const base_address = @intFromPtr(header);
159 if (address < base_address) continue;
160 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
161
162 var it = macho.LoadCommandIterator{
163 .ncmds = header.ncmds,
164 .buffer = @alignCast(@as(
165 [*]u8,
166 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
167 )[0..header.sizeofcmds]),
168 };
169
170 var unwind_info: ?[]const u8 = null;
171 var eh_frame: ?[]const u8 = null;
172 while (it.next()) |cmd| switch (cmd.cmd()) {
173 .SEGMENT_64 => {
174 const segment_cmd = cmd.cast(macho.segment_command_64).?;
175 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
176
177 const seg_start = segment_cmd.vmaddr + vmaddr_slide;
178 const seg_end = seg_start + segment_cmd.vmsize;
179 if (address >= seg_start and address < seg_end) {
180 if (self.address_map.get(base_address)) |obj_di| {
181 return obj_di;
182 }
183
184 for (cmd.getSections()) |sect| {
185 if (mem.eql(u8, "__unwind_info", sect.sectName())) {
186 unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
187 } else if (mem.eql(u8, "__eh_frame", sect.sectName())) {
188 eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size];
189 }
190 }
191
192 const obj_di = try self.allocator.create(ModuleDebugInfo);
193 errdefer self.allocator.destroy(obj_di);
194
195 const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0);
196 const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) {
197 error.FileNotFound => return error.MissingDebugInfo,
198 else => return err,
199 };
200 obj_di.* = try readMachODebugInfo(self.allocator, macho_file);
201 obj_di.base_address = base_address;
202 obj_di.vmaddr_slide = vmaddr_slide;
203 obj_di.unwind_info = unwind_info;
204 obj_di.eh_frame = eh_frame;
205
206 try self.address_map.putNoClobber(base_address, obj_di);
207
208 return obj_di;
209 }
210 },
211 else => {},
212 };
213 }
214
215 return error.MissingDebugInfo;
216}
217
218fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 {
219 _ = self;
220 const image_count = std.c._dyld_image_count();
221
222 var i: u32 = 0;
223 while (i < image_count) : (i += 1) {
224 const header = std.c._dyld_get_image_header(i) orelse continue;
225 const base_address = @intFromPtr(header);
226 if (address < base_address) continue;
227 const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i);
228
229 var it = macho.LoadCommandIterator{
230 .ncmds = header.ncmds,
231 .buffer = @alignCast(@as(
232 [*]u8,
233 @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)),
234 )[0..header.sizeofcmds]),
235 };
236
237 while (it.next()) |cmd| switch (cmd.cmd()) {
238 .SEGMENT_64 => {
239 const segment_cmd = cmd.cast(macho.segment_command_64).?;
240 if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue;
241
242 const original_address = address - vmaddr_slide;
243 const seg_start = segment_cmd.vmaddr;
244 const seg_end = seg_start + segment_cmd.vmsize;
245 if (original_address >= seg_start and original_address < seg_end) {
246 return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0));
247 }
248 },
249 else => {},
250 };
251 }
252
253 return null;
254}
255
256fn lookupModuleWin32(self: *Info, address: usize) !*ModuleDebugInfo {
257 for (self.modules.items) |*module| {
258 if (address >= module.base_address and address < module.base_address + module.size) {
259 if (self.address_map.get(module.base_address)) |obj_di| {
260 return obj_di;
261 }
262
263 const obj_di = try self.allocator.create(ModuleDebugInfo);
264 errdefer self.allocator.destroy(obj_di);
265
266 const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size];
267 var coff_obj = try coff.Coff.init(mapped_module, true);
268
269 // The string table is not mapped into memory by the loader, so if a section name is in the
270 // string table then we have to map the full image file from disk. This can happen when
271 // a binary is produced with -gdwarf, since the section names are longer than 8 bytes.
272 if (coff_obj.strtabRequired()) {
273 var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
274 // openFileAbsoluteW requires the prefix to be present
275 @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
276
277 const process_handle = windows.GetCurrentProcess();
278 const len = windows.kernel32.GetModuleFileNameExW(
279 process_handle,
280 module.handle,
281 @ptrCast(&name_buffer[4]),
282 windows.PATH_MAX_WIDE,
283 );
284
285 if (len == 0) return error.MissingDebugInfo;
286 const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
287 error.FileNotFound => return error.MissingDebugInfo,
288 else => return err,
289 };
290 errdefer coff_file.close();
291
292 var section_handle: windows.HANDLE = undefined;
293 const create_section_rc = windows.ntdll.NtCreateSection(
294 &section_handle,
295 windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ,
296 null,
297 null,
298 windows.PAGE_READONLY,
299 // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default.
300 // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6.
301 windows.SEC_COMMIT,
302 coff_file.handle,
303 );
304 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
305 errdefer windows.CloseHandle(section_handle);
306
307 var coff_len: usize = 0;
308 var base_ptr: usize = 0;
309 const map_section_rc = windows.ntdll.NtMapViewOfSection(
310 section_handle,
311 process_handle,
312 @ptrCast(&base_ptr),
313 null,
314 0,
315 null,
316 &coff_len,
317 .ViewUnmap,
318 0,
319 windows.PAGE_READONLY,
320 );
321 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
322 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS);
323
324 const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len];
325 coff_obj = try coff.Coff.init(section_view, false);
326
327 module.mapped_file = .{
328 .file = coff_file,
329 .section_handle = section_handle,
330 .section_view = section_view,
331 };
332 }
333 errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit();
334
335 obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj);
336 obj_di.base_address = module.base_address;
337
338 try self.address_map.putNoClobber(module.base_address, obj_di);
339 return obj_di;
340 }
341 }
342
343 return error.MissingDebugInfo;
344}
345
346fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 {
347 for (self.modules.items) |module| {
348 if (address >= module.base_address and address < module.base_address + module.size) {
349 return module.name;
350 }
351 }
352 return null;
353}
354
355fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 {
356 _ = self;
357
358 var ctx: struct {
359 // Input
360 address: usize,
361 // Output
362 name: []const u8 = "",
363 } = .{ .address = address };
364 const CtxTy = @TypeOf(ctx);
365
366 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
367 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
368 _ = size;
369 if (context.address < info.addr) return;
370 const phdrs = info.phdr[0..info.phnum];
371 for (phdrs) |*phdr| {
372 if (phdr.p_type != elf.PT_LOAD) continue;
373
374 const seg_start = info.addr +% phdr.p_vaddr;
375 const seg_end = seg_start + phdr.p_memsz;
376 if (context.address >= seg_start and context.address < seg_end) {
377 context.name = mem.sliceTo(info.name, 0) orelse "";
378 break;
379 }
380 } else return;
381
382 return error.Found;
383 }
384 }.callback)) {
385 return null;
386 } else |err| switch (err) {
387 error.Found => return fs.path.basename(ctx.name),
388 }
389
390 return null;
391}
392
393fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo {
394 var ctx: struct {
395 // Input
396 address: usize,
397 // Output
398 base_address: usize = undefined,
399 name: []const u8 = undefined,
400 build_id: ?[]const u8 = null,
401 gnu_eh_frame: ?[]const u8 = null,
402 } = .{ .address = address };
403 const CtxTy = @TypeOf(ctx);
404
405 if (posix.dl_iterate_phdr(&ctx, error{Found}, struct {
406 fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void {
407 _ = size;
408 // The base address is too high
409 if (context.address < info.addr)
410 return;
411
412 const phdrs = info.phdr[0..info.phnum];
413 for (phdrs) |*phdr| {
414 if (phdr.p_type != elf.PT_LOAD) continue;
415
416 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
417 const seg_start = info.addr +% phdr.p_vaddr;
418 const seg_end = seg_start + phdr.p_memsz;
419 if (context.address >= seg_start and context.address < seg_end) {
420 // Android libc uses NULL instead of an empty string to mark the
421 // main program
422 context.name = mem.sliceTo(info.name, 0) orelse "";
423 context.base_address = info.addr;
424 break;
425 }
426 } else return;
427
428 for (info.phdr[0..info.phnum]) |phdr| {
429 switch (phdr.p_type) {
430 elf.PT_NOTE => {
431 // Look for .note.gnu.build-id
432 const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
433 const name_size = mem.readInt(u32, note_bytes[0..4], native_endian);
434 if (name_size != 4) continue;
435 const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian);
436 const note_type = mem.readInt(u32, note_bytes[8..12], native_endian);
437 if (note_type != elf.NT_GNU_BUILD_ID) continue;
438 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
439 context.build_id = note_bytes[16..][0..desc_size];
440 },
441 elf.PT_GNU_EH_FRAME => {
442 context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz];
443 },
444 else => {},
445 }
446 }
447
448 // Stop the iteration
449 return error.Found;
450 }
451 }.callback)) {
452 return error.MissingDebugInfo;
453 } else |err| switch (err) {
454 error.Found => {},
455 }
456
457 if (self.address_map.get(ctx.base_address)) |obj_di| {
458 return obj_di;
459 }
460
461 const obj_di = try self.allocator.create(ModuleDebugInfo);
462 errdefer self.allocator.destroy(obj_di);
463
464 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
465 if (ctx.gnu_eh_frame) |eh_frame_hdr| {
466 // This is a special case - pointer offsets inside .eh_frame_hdr
467 // are encoded relative to its base address, so we must use the
468 // version that is already memory mapped, and not the one that
469 // will be mapped separately from the ELF file.
470 sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{
471 .data = eh_frame_hdr,
472 .owned = false,
473 };
474 }
475
476 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
477 obj_di.base_address = ctx.base_address;
478
479 // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding
480 obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {};
481
482 try self.address_map.putNoClobber(ctx.base_address, obj_di);
483
484 return obj_di;
485}
486
487fn lookupModuleHaiku(self: *Info, address: usize) !*ModuleDebugInfo {
488 _ = self;
489 _ = address;
490 @panic("TODO implement lookup module for Haiku");
491}
492
493fn lookupModuleWasm(self: *Info, address: usize) !*ModuleDebugInfo {
494 _ = self;
495 _ = address;
496 @panic("TODO implement lookup module for Wasm");
497}
498
499pub const ModuleDebugInfo = switch (native_os) {
500 .macos, .ios, .watchos, .tvos, .visionos => struct {
501 base_address: usize,
502 vmaddr_slide: usize,
503 mapped_memory: []align(mem.page_size) const u8,
504 symbols: []const MachoSymbol,
505 strings: [:0]const u8,
506 ofiles: OFileTable,
507
508 // Backed by the in-memory sections mapped by the loader
509 unwind_info: ?[]const u8 = null,
510 eh_frame: ?[]const u8 = null,
511
512 const OFileTable = std.StringHashMap(OFileInfo);
513 const OFileInfo = struct {
514 di: Dwarf,
515 addr_table: std.StringHashMap(u64),
516 };
517
518 pub fn deinit(self: *@This(), allocator: Allocator) void {
519 var it = self.ofiles.iterator();
520 while (it.next()) |entry| {
521 const ofile = entry.value_ptr;
522 ofile.di.deinit(allocator);
523 ofile.addr_table.deinit();
524 }
525 self.ofiles.deinit();
526 allocator.free(self.symbols);
527 posix.munmap(self.mapped_memory);
528 }
529
530 fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo {
531 const o_file = try fs.cwd().openFile(o_file_path, .{});
532 const mapped_mem = try mapWholeFile(o_file);
533
534 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
535 if (hdr.magic != std.macho.MH_MAGIC_64)
536 return error.InvalidDebugInfo;
537
538 var segcmd: ?macho.LoadCommandIterator.LoadCommand = null;
539 var symtabcmd: ?macho.symtab_command = null;
540 var it = macho.LoadCommandIterator{
541 .ncmds = hdr.ncmds,
542 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
543 };
544 while (it.next()) |cmd| switch (cmd.cmd()) {
545 .SEGMENT_64 => segcmd = cmd,
546 .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?,
547 else => {},
548 };
549
550 if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo;
551
552 // Parse symbols
553 const strtab = @as(
554 [*]const u8,
555 @ptrCast(&mapped_mem[symtabcmd.?.stroff]),
556 )[0 .. symtabcmd.?.strsize - 1 :0];
557 const symtab = @as(
558 [*]const macho.nlist_64,
559 @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])),
560 )[0..symtabcmd.?.nsyms];
561
562 // TODO handle tentative (common) symbols
563 var addr_table = std.StringHashMap(u64).init(allocator);
564 try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len)));
565 for (symtab) |sym| {
566 if (sym.n_strx == 0) continue;
567 if (sym.undf() or sym.tentative() or sym.abs()) continue;
568 const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0);
569 // TODO is it possible to have a symbol collision?
570 addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value);
571 }
572
573 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
574 if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{
575 .data = eh_frame,
576 .owned = false,
577 };
578
579 for (segcmd.?.getSections()) |sect| {
580 if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue;
581
582 var section_index: ?usize = null;
583 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
584 if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i;
585 }
586 if (section_index == null) continue;
587
588 const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size);
589 sections[section_index.?] = .{
590 .data = section_bytes,
591 .virtual_address = sect.addr,
592 .owned = false,
593 };
594 }
595
596 const missing_debug_info =
597 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
598 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
599 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
600 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
601 if (missing_debug_info) return error.MissingDebugInfo;
602
603 var di = Dwarf{
604 .endian = .little,
605 .sections = sections,
606 .is_macho = true,
607 };
608
609 try Dwarf.open(&di, allocator);
610 const info = OFileInfo{
611 .di = di,
612 .addr_table = addr_table,
613 };
614
615 // Add the debug info to the cache
616 const result = try self.ofiles.getOrPut(o_file_path);
617 assert(!result.found_existing);
618 result.value_ptr.* = info;
619
620 return result.value_ptr;
621 }
622
623 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
624 nosuspend {
625 const result = try self.getOFileInfoForAddress(allocator, address);
626 if (result.symbol == null) return .{};
627
628 // Take the symbol name from the N_FUN STAB entry, we're going to
629 // use it if we fail to find the DWARF infos
630 const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0);
631 if (result.o_file_info == null) return .{ .symbol_name = stab_symbol };
632
633 // Translate again the address, this time into an address inside the
634 // .o file
635 const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{
636 .symbol_name = "???",
637 };
638
639 const addr_off = result.relocated_address - result.symbol.?.addr;
640 const o_file_di = &result.o_file_info.?.di;
641 if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| {
642 return SymbolInfo{
643 .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???",
644 .compile_unit_name = compile_unit.die.getAttrString(
645 o_file_di,
646 std.dwarf.AT.name,
647 o_file_di.section(.debug_str),
648 compile_unit.*,
649 ) catch |err| switch (err) {
650 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
651 },
652 .line_info = o_file_di.getLineNumberInfo(
653 allocator,
654 compile_unit.*,
655 relocated_address_o + addr_off,
656 ) catch |err| switch (err) {
657 error.MissingDebugInfo, error.InvalidDebugInfo => null,
658 else => return err,
659 },
660 };
661 } else |err| switch (err) {
662 error.MissingDebugInfo, error.InvalidDebugInfo => {
663 return SymbolInfo{ .symbol_name = stab_symbol };
664 },
665 else => return err,
666 }
667 }
668 }
669
670 pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct {
671 relocated_address: usize,
672 symbol: ?*const MachoSymbol = null,
673 o_file_info: ?*OFileInfo = null,
674 } {
675 nosuspend {
676 // Translate the VA into an address into this object
677 const relocated_address = address - self.vmaddr_slide;
678
679 // Find the .o file where this symbol is defined
680 const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{
681 .relocated_address = relocated_address,
682 };
683
684 // Check if its debug infos are already in the cache
685 const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0);
686 const o_file_info = self.ofiles.getPtr(o_file_path) orelse
687 (self.loadOFile(allocator, o_file_path) catch |err| switch (err) {
688 error.FileNotFound,
689 error.MissingDebugInfo,
690 error.InvalidDebugInfo,
691 => return .{
692 .relocated_address = relocated_address,
693 .symbol = symbol,
694 },
695 else => return err,
696 });
697
698 return .{
699 .relocated_address = relocated_address,
700 .symbol = symbol,
701 .o_file_info = o_file_info,
702 };
703 }
704 }
705
706 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
707 return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null;
708 }
709 },
710 .uefi, .windows => struct {
711 base_address: usize,
712 pdb: ?pdb.Pdb = null,
713 dwarf: ?Dwarf = null,
714 coff_image_base: u64,
715
716 /// Only used if pdb is non-null
717 coff_section_headers: []coff.SectionHeader,
718
719 pub fn deinit(self: *@This(), allocator: Allocator) void {
720 if (self.dwarf) |*dwarf| {
721 dwarf.deinit(allocator);
722 }
723
724 if (self.pdb) |*p| {
725 p.deinit();
726 allocator.free(self.coff_section_headers);
727 }
728 }
729
730 fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo {
731 var coff_section: *align(1) const coff.SectionHeader = undefined;
732 const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| {
733 if (sect_contrib.Section > self.coff_section_headers.len) continue;
734 // Remember that SectionContribEntry.Section is 1-based.
735 coff_section = &self.coff_section_headers[sect_contrib.Section - 1];
736
737 const vaddr_start = coff_section.virtual_address + sect_contrib.Offset;
738 const vaddr_end = vaddr_start + sect_contrib.Size;
739 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
740 break sect_contrib.ModuleIndex;
741 }
742 } else {
743 // we have no information to add to the address
744 return null;
745 };
746
747 const module = (try self.pdb.?.getModule(mod_index)) orelse
748 return error.InvalidDebugInfo;
749 const obj_basename = fs.path.basename(module.obj_file_name);
750
751 const symbol_name = self.pdb.?.getSymbolName(
752 module,
753 relocated_address - coff_section.virtual_address,
754 ) orelse "???";
755 const opt_line_info = try self.pdb.?.getLineNumberInfo(
756 module,
757 relocated_address - coff_section.virtual_address,
758 );
759
760 return SymbolInfo{
761 .symbol_name = symbol_name,
762 .compile_unit_name = obj_basename,
763 .line_info = opt_line_info,
764 };
765 }
766
767 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
768 // Translate the VA into an address into this object
769 const relocated_address = address - self.base_address;
770
771 if (self.pdb != null) {
772 if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol;
773 }
774
775 if (self.dwarf) |*dwarf| {
776 const dwarf_address = relocated_address + self.coff_image_base;
777 return getSymbolFromDwarf(allocator, dwarf_address, dwarf);
778 }
779
780 return SymbolInfo{};
781 }
782
783 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
784 _ = allocator;
785 _ = address;
786
787 return switch (self.debug_data) {
788 .dwarf => |*dwarf| dwarf,
789 else => null,
790 };
791 }
792 },
793 .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct {
794 base_address: usize,
795 dwarf: Dwarf,
796 mapped_memory: []align(mem.page_size) const u8,
797 external_mapped_memory: ?[]align(mem.page_size) const u8,
798
799 pub fn deinit(self: *@This(), allocator: Allocator) void {
800 self.dwarf.deinit(allocator);
801 posix.munmap(self.mapped_memory);
802 if (self.external_mapped_memory) |m| posix.munmap(m);
803 }
804
805 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
806 // Translate the VA into an address into this object
807 const relocated_address = address - self.base_address;
808 return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf);
809 }
810
811 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
812 _ = allocator;
813 _ = address;
814 return &self.dwarf;
815 }
816 },
817 .wasi, .emscripten => struct {
818 pub fn deinit(self: *@This(), allocator: Allocator) void {
819 _ = self;
820 _ = allocator;
821 }
822
823 pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo {
824 _ = self;
825 _ = allocator;
826 _ = address;
827 return SymbolInfo{};
828 }
829
830 pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf {
831 _ = self;
832 _ = allocator;
833 _ = address;
834 return null;
835 }
836 },
837 else => Dwarf,
838};
839
840pub const WindowsModuleInfo = struct {
841 base_address: usize,
842 size: u32,
843 name: []const u8,
844 handle: windows.HMODULE,
845
846 // Set when the image file needed to be mapped from disk
847 mapped_file: ?struct {
848 file: File,
849 section_handle: windows.HANDLE,
850 section_view: []const u8,
851
852 pub fn deinit(self: @This()) void {
853 const process_handle = windows.GetCurrentProcess();
854 assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS);
855 windows.CloseHandle(self.section_handle);
856 self.file.close();
857 }
858 } = null,
859};
860
861/// This takes ownership of macho_file: users of this function should not close
862/// it themselves, even on error.
863/// TODO it's weird to take ownership even on error, rework this code.
864fn readMachODebugInfo(allocator: Allocator, macho_file: File) !ModuleDebugInfo {
865 const mapped_mem = try mapWholeFile(macho_file);
866
867 const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));
868 if (hdr.magic != macho.MH_MAGIC_64)
869 return error.InvalidDebugInfo;
870
871 var it = macho.LoadCommandIterator{
872 .ncmds = hdr.ncmds,
873 .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds],
874 };
875 const symtab = while (it.next()) |cmd| switch (cmd.cmd()) {
876 .SYMTAB => break cmd.cast(macho.symtab_command).?,
877 else => {},
878 } else return error.MissingDebugInfo;
879
880 const syms = @as(
881 [*]const macho.nlist_64,
882 @ptrCast(@alignCast(&mapped_mem[symtab.symoff])),
883 )[0..symtab.nsyms];
884 const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0];
885
886 const symbols_buf = try allocator.alloc(MachoSymbol, syms.len);
887
888 var ofile: u32 = undefined;
889 var last_sym: MachoSymbol = undefined;
890 var symbol_index: usize = 0;
891 var state: enum {
892 init,
893 oso_open,
894 oso_close,
895 bnsym,
896 fun_strx,
897 fun_size,
898 ensym,
899 } = .init;
900
901 for (syms) |*sym| {
902 if (!sym.stab()) continue;
903
904 // TODO handle globals N_GSYM, and statics N_STSYM
905 switch (sym.n_type) {
906 macho.N_OSO => {
907 switch (state) {
908 .init, .oso_close => {
909 state = .oso_open;
910 ofile = sym.n_strx;
911 },
912 else => return error.InvalidDebugInfo,
913 }
914 },
915 macho.N_BNSYM => {
916 switch (state) {
917 .oso_open, .ensym => {
918 state = .bnsym;
919 last_sym = .{
920 .strx = 0,
921 .addr = sym.n_value,
922 .size = 0,
923 .ofile = ofile,
924 };
925 },
926 else => return error.InvalidDebugInfo,
927 }
928 },
929 macho.N_FUN => {
930 switch (state) {
931 .bnsym => {
932 state = .fun_strx;
933 last_sym.strx = sym.n_strx;
934 },
935 .fun_strx => {
936 state = .fun_size;
937 last_sym.size = @as(u32, @intCast(sym.n_value));
938 },
939 else => return error.InvalidDebugInfo,
940 }
941 },
942 macho.N_ENSYM => {
943 switch (state) {
944 .fun_size => {
945 state = .ensym;
946 symbols_buf[symbol_index] = last_sym;
947 symbol_index += 1;
948 },
949 else => return error.InvalidDebugInfo,
950 }
951 },
952 macho.N_SO => {
953 switch (state) {
954 .init, .oso_close => {},
955 .oso_open, .ensym => {
956 state = .oso_close;
957 },
958 else => return error.InvalidDebugInfo,
959 }
960 },
961 else => {},
962 }
963 }
964
965 switch (state) {
966 .init => return error.MissingDebugInfo,
967 .oso_close => {},
968 else => return error.InvalidDebugInfo,
969 }
970
971 const symbols = try allocator.realloc(symbols_buf, symbol_index);
972
973 // Even though lld emits symbols in ascending order, this debug code
974 // should work for programs linked in any valid way.
975 // This sort is so that we can binary search later.
976 mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
977
978 return ModuleDebugInfo{
979 .base_address = undefined,
980 .vmaddr_slide = undefined,
981 .mapped_memory = mapped_mem,
982 .ofiles = ModuleDebugInfo.OFileTable.init(allocator),
983 .symbols = symbols,
984 .strings = strings,
985 };
986}
987
988fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo {
989 nosuspend {
990 var di = ModuleDebugInfo{
991 .base_address = undefined,
992 .coff_image_base = coff_obj.getImageBase(),
993 .coff_section_headers = undefined,
994 };
995
996 if (coff_obj.getSectionByName(".debug_info")) |_| {
997 // This coff file has embedded DWARF debug info
998 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
999 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1000
1001 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1002 sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: {
1003 break :blk .{
1004 .data = try coff_obj.getSectionDataAlloc(section_header, allocator),
1005 .virtual_address = section_header.virtual_address,
1006 .owned = true,
1007 };
1008 } else null;
1009 }
1010
1011 var dwarf = Dwarf{
1012 .endian = native_endian,
1013 .sections = sections,
1014 .is_macho = false,
1015 };
1016
1017 try Dwarf.open(&dwarf, allocator);
1018 di.dwarf = dwarf;
1019 }
1020
1021 const raw_path = try coff_obj.getPdbPath() orelse return di;
1022 const path = blk: {
1023 if (fs.path.isAbsolute(raw_path)) {
1024 break :blk raw_path;
1025 } else {
1026 const self_dir = try fs.selfExeDirPathAlloc(allocator);
1027 defer allocator.free(self_dir);
1028 break :blk try fs.path.join(allocator, &.{ self_dir, raw_path });
1029 }
1030 };
1031 defer if (path.ptr != raw_path.ptr) allocator.free(path);
1032
1033 di.pdb = pdb.Pdb.init(allocator, path) catch |err| switch (err) {
1034 error.FileNotFound, error.IsDir => {
1035 if (di.dwarf == null) return error.MissingDebugInfo;
1036 return di;
1037 },
1038 else => return err,
1039 };
1040 try di.pdb.?.parseInfoStream();
1041 try di.pdb.?.parseDbiStream();
1042
1043 if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age)
1044 return error.InvalidDebugInfo;
1045
1046 // Only used by the pdb path
1047 di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
1048 errdefer allocator.free(di.coff_section_headers);
1049
1050 return di;
1051 }
1052}
1053
1054/// Reads debug info from an ELF file, or the current binary if none in specified.
1055/// If the required sections aren't present but a reference to external debug info is,
1056/// then this this function will recurse to attempt to load the debug sections from
1057/// an external file.
1058pub fn readElfDebugInfo(
1059 allocator: Allocator,
1060 elf_filename: ?[]const u8,
1061 build_id: ?[]const u8,
1062 expected_crc: ?u32,
1063 parent_sections: *Dwarf.SectionArray,
1064 parent_mapped_mem: ?[]align(mem.page_size) const u8,
1065) !ModuleDebugInfo {
1066 nosuspend {
1067 const elf_file = (if (elf_filename) |filename| blk: {
1068 break :blk fs.cwd().openFile(filename, .{});
1069 } else fs.openSelfExe(.{})) catch |err| switch (err) {
1070 error.FileNotFound => return error.MissingDebugInfo,
1071 else => return err,
1072 };
1073
1074 const mapped_mem = try mapWholeFile(elf_file);
1075 if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo;
1076
1077 const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]);
1078 if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic;
1079 if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion;
1080
1081 const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) {
1082 elf.ELFDATA2LSB => .little,
1083 elf.ELFDATA2MSB => .big,
1084 else => return error.InvalidElfEndian,
1085 };
1086 assert(endian == native_endian); // this is our own debug info
1087
1088 const shoff = hdr.e_shoff;
1089 const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx);
1090 const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow]));
1091 const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size];
1092 const shdrs = @as(
1093 [*]const elf.Shdr,
1094 @ptrCast(@alignCast(&mapped_mem[shoff])),
1095 )[0..hdr.e_shnum];
1096
1097 var sections: Dwarf.SectionArray = Dwarf.null_section_array;
1098
1099 // Combine section list. This takes ownership over any owned sections from the parent scope.
1100 for (parent_sections, &sections) |*parent, *section| {
1101 if (parent.*) |*p| {
1102 section.* = p.*;
1103 p.owned = false;
1104 }
1105 }
1106 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
1107
1108 var separate_debug_filename: ?[]const u8 = null;
1109 var separate_debug_crc: ?u32 = null;
1110
1111 for (shdrs) |*shdr| {
1112 if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue;
1113 const name = mem.sliceTo(header_strings[shdr.sh_name..], 0);
1114
1115 if (mem.eql(u8, name, ".gnu_debuglink")) {
1116 const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1117 const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0);
1118 const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr);
1119 const crc_bytes = gnu_debuglink[crc_offset..][0..4];
1120 separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian);
1121 separate_debug_filename = debug_filename;
1122 continue;
1123 }
1124
1125 var section_index: ?usize = null;
1126 inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| {
1127 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
1128 }
1129 if (section_index == null) continue;
1130 if (sections[section_index.?] != null) continue;
1131
1132 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
1133 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
1134 var section_stream = std.io.fixedBufferStream(section_bytes);
1135 var section_reader = section_stream.reader();
1136 const chdr = section_reader.readStruct(elf.Chdr) catch continue;
1137 if (chdr.ch_type != .ZLIB) continue;
1138
1139 var zlib_stream = std.compress.zlib.decompressor(section_stream.reader());
1140
1141 const decompressed_section = try allocator.alloc(u8, chdr.ch_size);
1142 errdefer allocator.free(decompressed_section);
1143
1144 const read = zlib_stream.reader().readAll(decompressed_section) catch continue;
1145 assert(read == decompressed_section.len);
1146
1147 break :blk .{
1148 .data = decompressed_section,
1149 .virtual_address = shdr.sh_addr,
1150 .owned = true,
1151 };
1152 } else .{
1153 .data = section_bytes,
1154 .virtual_address = shdr.sh_addr,
1155 .owned = false,
1156 };
1157 }
1158
1159 const missing_debug_info =
1160 sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or
1161 sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or
1162 sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or
1163 sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null;
1164
1165 // Attempt to load debug info from an external file
1166 // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
1167 if (missing_debug_info) {
1168
1169 // Only allow one level of debug info nesting
1170 if (parent_mapped_mem) |_| {
1171 return error.MissingDebugInfo;
1172 }
1173
1174 const global_debug_directories = [_][]const u8{
1175 "/usr/lib/debug",
1176 };
1177
1178 // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug
1179 if (build_id) |id| blk: {
1180 if (id.len < 3) break :blk;
1181
1182 // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice
1183 const extension = ".debug";
1184 var id_prefix_buf: [2]u8 = undefined;
1185 var filename_buf: [38 + extension.len]u8 = undefined;
1186
1187 _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable;
1188 const filename = std.fmt.bufPrint(
1189 &filename_buf,
1190 "{s}" ++ extension,
1191 .{std.fmt.fmtSliceHexLower(id[1..])},
1192 ) catch break :blk;
1193
1194 for (global_debug_directories) |global_directory| {
1195 const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename });
1196 defer allocator.free(path);
1197
1198 return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue;
1199 }
1200 }
1201
1202 // use the path from .gnu_debuglink, in the same search order as gdb
1203 if (separate_debug_filename) |separate_filename| blk: {
1204 if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo;
1205
1206 // <cwd>/<gnu_debuglink>
1207 if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1208
1209 // <cwd>/.debug/<gnu_debuglink>
1210 {
1211 const path = try fs.path.join(allocator, &.{ ".debug", separate_filename });
1212 defer allocator.free(path);
1213
1214 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1215 }
1216
1217 var cwd_buf: [fs.max_path_bytes]u8 = undefined;
1218 const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk;
1219
1220 // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink>
1221 for (global_debug_directories) |global_directory| {
1222 const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename });
1223 defer allocator.free(path);
1224 if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {}
1225 }
1226 }
1227
1228 return error.MissingDebugInfo;
1229 }
1230
1231 var di = Dwarf{
1232 .endian = endian,
1233 .sections = sections,
1234 .is_macho = false,
1235 };
1236
1237 try Dwarf.open(&di, allocator);
1238
1239 return ModuleDebugInfo{
1240 .base_address = undefined,
1241 .dwarf = di,
1242 .mapped_memory = parent_mapped_mem orelse mapped_mem,
1243 .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null,
1244 };
1245 }
1246}
1247
1248const MachoSymbol = struct {
1249 strx: u32,
1250 addr: u64,
1251 size: u32,
1252 ofile: u32,
1253
1254 /// Returns the address from the macho file
1255 fn address(self: MachoSymbol) u64 {
1256 return self.addr;
1257 }
1258
1259 fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
1260 _ = context;
1261 return lhs.addr < rhs.addr;
1262 }
1263};
1264
1265/// Takes ownership of file, even on error.
1266/// TODO it's weird to take ownership even on error, rework this code.
1267fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
1268 nosuspend {
1269 defer file.close();
1270
1271 const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize);
1272 const mapped_mem = try posix.mmap(
1273 null,
1274 file_len,
1275 posix.PROT.READ,
1276 .{ .TYPE = .SHARED },
1277 file.handle,
1278 0,
1279 );
1280 errdefer posix.munmap(mapped_mem);
1281
1282 return mapped_mem;
1283 }
1284}
1285
1286fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 {
1287 const start = math.cast(usize, offset) orelse return error.Overflow;
1288 const end = start + (math.cast(usize, size) orelse return error.Overflow);
1289 return ptr[start..end];
1290}
1291
1292pub const SymbolInfo = struct {
1293 symbol_name: []const u8 = "???",
1294 compile_unit_name: []const u8 = "???",
1295 line_info: ?SourceLocation = null,
1296
1297 pub fn deinit(self: SymbolInfo, allocator: Allocator) void {
1298 if (self.line_info) |li| {
1299 li.deinit(allocator);
1300 }
1301 }
1302};
1303
1304pub const SourceLocation = struct {
1305 line: u64,
1306 column: u64,
1307 file_name: []const u8,
1308
1309 pub fn deinit(self: SourceLocation, allocator: Allocator) void {
1310 allocator.free(self.file_name);
1311 }
1312};
1313
1314fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
1315 var min: usize = 0;
1316 var max: usize = symbols.len - 1;
1317 while (min < max) {
1318 const mid = min + (max - min) / 2;
1319 const curr = &symbols[mid];
1320 const next = &symbols[mid + 1];
1321 if (address >= next.address()) {
1322 min = mid + 1;
1323 } else if (address < curr.address()) {
1324 max = mid;
1325 } else {
1326 return curr;
1327 }
1328 }
1329
1330 const max_sym = &symbols[symbols.len - 1];
1331 if (address >= max_sym.address())
1332 return max_sym;
1333
1334 return null;
1335}
1336
1337test machoSearchSymbols {
1338 const symbols = [_]MachoSymbol{
1339 .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined },
1340 .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined },
1341 .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined },
1342 };
1343
1344 try testing.expectEqual(null, machoSearchSymbols(&symbols, 0));
1345 try testing.expectEqual(null, machoSearchSymbols(&symbols, 99));
1346 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?);
1347 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?);
1348 try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?);
1349
1350 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?);
1351 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?);
1352 try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?);
1353
1354 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?);
1355 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?);
1356 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
1357}
1358
1359fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo {
1360 if (nosuspend di.findCompileUnit(address)) |compile_unit| {
1361 return SymbolInfo{
1362 .symbol_name = nosuspend di.getSymbolName(address) orelse "???",
1363 .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) {
1364 error.MissingDebugInfo, error.InvalidDebugInfo => "???",
1365 },
1366 .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) {
1367 error.MissingDebugInfo, error.InvalidDebugInfo => null,
1368 else => return err,
1369 },
1370 };
1371 } else |err| switch (err) {
1372 error.MissingDebugInfo, error.InvalidDebugInfo => {
1373 return SymbolInfo{};
1374 },
1375 else => return err,
1376 }
1377}
lib/std/pdb.zig+3-3
......@@ -706,7 +706,7 @@ pub const Pdb = struct {
706706 return null;
707707 }
708708
709 pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.LineInfo {
709 pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.Info.SourceLocation {
710710 std.debug.assert(module.populated);
711711 const subsect_info = module.subsect_info;
712712
......@@ -731,7 +731,7 @@ pub const Pdb = struct {
731731
732732 if (address >= frag_vaddr_start and address < frag_vaddr_end) {
733733 // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
734 // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in,
734 // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in,
735735 // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
736736 const subsection_end_index = sect_offset + subsect_hdr.Length;
737737
......@@ -778,7 +778,7 @@ pub const Pdb = struct {
778778 const line_num_entry: *align(1) LineNumberEntry = @ptrCast(&subsect_info[found_line_index]);
779779 const flags: *align(1) LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags);
780780
781 return debug.LineInfo{
781 return debug.Info.SourceLocation{
782782 .file_name = source_file_name,
783783 .line = flags.Start,
784784 .column = column,