authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2021-06-11 11:44:55+02:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-06-12 10:51:43+03:00
log986a71234bf2b40284117cac0537e68071b29b33
treeb40c288f1f1efe2b5df5ef716b4308168ad12728
parentcaad5c49607a910e7f06864dca31ca7a68333ef3

std: Move PDB-related code into its own file

No functional changes are expected, this patch is only moving some code in order to slim the huge bowl of spaghetti that is debug.zig. The amount of memory leaked on error is much less than before but not zero, some further work is required to smooth the edges of this old part of the stdlib.

2 files changed, 416 insertions(+), 367 deletions(-)

lib/std/debug.zig+30-338
...@@ -30,17 +30,6 @@ pub const runtime_safety = switch (builtin.mode) {...@@ -30,17 +30,6 @@ pub const runtime_safety = switch (builtin.mode) {
30 .ReleaseFast, .ReleaseSmall => false,30 .ReleaseFast, .ReleaseSmall => false,
31};31};
3232
33const Module = struct {
34 mod_info: pdb.ModInfo,
35 module_name: []u8,
36 obj_file_name: []u8,
37
38 populated: bool,
39 symbols: []u8,
40 subsect_info: []u8,
41 checksum_offset: ?usize,
42};
43
44pub const LineInfo = struct {33pub const LineInfo = struct {
45 line: u64,34 line: u64,
46 column: u64,35 column: u64,
...@@ -53,6 +42,18 @@ pub const LineInfo = struct {...@@ -53,6 +42,18 @@ pub const LineInfo = struct {
53 }42 }
54};43};
5544
45pub const SymbolInfo = struct {
46 symbol_name: []const u8 = "???",
47 compile_unit_name: []const u8 = "???",
48 line_info: ?LineInfo = null,
49
50 fn deinit(self: @This()) void {
51 if (self.line_info) |li| {
52 li.deinit();
53 }
54 }
55};
56
56var stderr_mutex = std.Thread.Mutex{};57var stderr_mutex = std.Thread.Mutex{};
5758
58/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for59/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for
...@@ -528,53 +529,6 @@ pub const TTY = struct {...@@ -528,53 +529,6 @@ pub const TTY = struct {
528 };529 };
529};530};
530531
531/// TODO resources https://github.com/ziglang/zig/issues/4353
532fn populateModule(di: *ModuleDebugInfo, mod: *Module) !void {
533 if (mod.populated)
534 return;
535 const allocator = getDebugInfoAllocator();
536
537 // At most one can be non-zero.
538 if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
539 return error.InvalidDebugInfo;
540
541 if (mod.mod_info.C13ByteSize == 0)
542 return;
543
544 const modi = di.pdb.getStreamById(mod.mod_info.ModuleSymStream) orelse return error.MissingDebugInfo;
545
546 const signature = try modi.reader().readIntLittle(u32);
547 if (signature != 4)
548 return error.InvalidDebugInfo;
549
550 mod.symbols = try allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
551 try modi.reader().readNoEof(mod.symbols);
552
553 mod.subsect_info = try allocator.alloc(u8, mod.mod_info.C13ByteSize);
554 try modi.reader().readNoEof(mod.subsect_info);
555
556 var sect_offset: usize = 0;
557 var skip_len: usize = undefined;
558 while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) {
559 const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &mod.subsect_info[sect_offset]);
560 skip_len = subsect_hdr.Length;
561 sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
562
563 switch (subsect_hdr.Kind) {
564 .FileChecksums => {
565 mod.checksum_offset = sect_offset;
566 break;
567 },
568 else => {},
569 }
570
571 if (sect_offset > mod.subsect_info.len)
572 return error.InvalidDebugInfo;
573 }
574
575 mod.populated = true;
576}
577
578fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {532fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
579 var min: usize = 0;533 var min: usize = 0;
580 var max: usize = symbols.len - 1; // Exclude sentinel.534 var max: usize = symbols.len - 1; // Exclude sentinel.
...@@ -716,8 +670,6 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf...@@ -716,8 +670,6 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf
716 .base_address = undefined,670 .base_address = undefined,
717 .coff = coff_obj,671 .coff = coff_obj,
718 .pdb = undefined,672 .pdb = undefined,
719 .sect_contribs = undefined,
720 .modules = undefined,
721 };673 };
722674
723 try di.coff.loadHeader();675 try di.coff.loadHeader();
...@@ -727,157 +679,19 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf...@@ -727,157 +679,19 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf
727 const raw_path = path_buf[0..len];679 const raw_path = path_buf[0..len];
728680
729 const path = try fs.path.resolve(allocator, &[_][]const u8{raw_path});681 const path = try fs.path.resolve(allocator, &[_][]const u8{raw_path});
682 defer allocator.free(path);
730683
731 try di.pdb.openFile(di.coff, path);684 di.pdb = try pdb.Pdb.init(allocator, path);
732685 try di.pdb.parseInfoStream();
733 var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo;686 try di.pdb.parseDbiStream();
734 const version = try pdb_stream.reader().readIntLittle(u32);
735 const signature = try pdb_stream.reader().readIntLittle(u32);
736 const age = try pdb_stream.reader().readIntLittle(u32);
737 var guid: [16]u8 = undefined;
738 try pdb_stream.reader().readNoEof(&guid);
739 if (version != 20000404) // VC70, only value observed by LLVM team
740 return error.UnknownPDBVersion;
741 if (!mem.eql(u8, &di.coff.guid, &guid) or di.coff.age != age)
742 return error.PDBMismatch;
743 // We validated the executable and pdb match.
744
745 const string_table_index = str_tab_index: {
746 const name_bytes_len = try pdb_stream.reader().readIntLittle(u32);
747 const name_bytes = try allocator.alloc(u8, name_bytes_len);
748 try pdb_stream.reader().readNoEof(name_bytes);
749
750 const HashTableHeader = packed struct {
751 Size: u32,
752 Capacity: u32,
753
754 fn maxLoad(cap: u32) u32 {
755 return cap * 2 / 3 + 1;
756 }
757 };
758 const hash_tbl_hdr = try pdb_stream.reader().readStruct(HashTableHeader);
759 if (hash_tbl_hdr.Capacity == 0)
760 return error.InvalidDebugInfo;
761
762 if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
763 return error.InvalidDebugInfo;
764
765 const present = try readSparseBitVector(&pdb_stream.reader(), allocator);
766 if (present.len != hash_tbl_hdr.Size)
767 return error.InvalidDebugInfo;
768 const deleted = try readSparseBitVector(&pdb_stream.reader(), allocator);
769
770 const Bucket = struct {
771 first: u32,
772 second: u32,
773 };
774 const bucket_list = try allocator.alloc(Bucket, present.len);
775 for (present) |_| {
776 const name_offset = try pdb_stream.reader().readIntLittle(u32);
777 const name_index = try pdb_stream.reader().readIntLittle(u32);
778 const name = mem.spanZ(std.meta.assumeSentinel(name_bytes.ptr + name_offset, 0));
779 if (mem.eql(u8, name, "/names")) {
780 break :str_tab_index name_index;
781 }
782 }
783 return error.MissingDebugInfo;
784 };
785
786 di.pdb.string_table = di.pdb.getStreamById(string_table_index) orelse return error.MissingDebugInfo;
787 di.pdb.dbi = di.pdb.getStream(pdb.StreamType.Dbi) orelse return error.MissingDebugInfo;
788
789 const dbi = di.pdb.dbi;
790687
791 // Dbi Header688 if (!mem.eql(u8, &di.coff.guid, &di.pdb.guid) or di.coff.age != di.pdb.age)
792 const dbi_stream_header = try dbi.reader().readStruct(pdb.DbiStreamHeader);689 return error.InvalidDebugInfo;
793 if (dbi_stream_header.VersionHeader != 19990903) // V70, only value observed by LLVM team
794 return error.UnknownPDBVersion;
795 if (dbi_stream_header.Age != age)
796 return error.UnmatchingPDB;
797
798 const mod_info_size = dbi_stream_header.ModInfoSize;
799 const section_contrib_size = dbi_stream_header.SectionContributionSize;
800
801 var modules = ArrayList(Module).init(allocator);
802
803 // Module Info Substream
804 var mod_info_offset: usize = 0;
805 while (mod_info_offset != mod_info_size) {
806 const mod_info = try dbi.reader().readStruct(pdb.ModInfo);
807 var this_record_len: usize = @sizeOf(pdb.ModInfo);
808
809 const module_name = try dbi.readNullTermString(allocator);
810 this_record_len += module_name.len + 1;
811
812 const obj_file_name = try dbi.readNullTermString(allocator);
813 this_record_len += obj_file_name.len + 1;
814
815 if (this_record_len % 4 != 0) {
816 const round_to_next_4 = (this_record_len | 0x3) + 1;
817 const march_forward_bytes = round_to_next_4 - this_record_len;
818 try dbi.seekBy(@intCast(isize, march_forward_bytes));
819 this_record_len += march_forward_bytes;
820 }
821
822 try modules.append(Module{
823 .mod_info = mod_info,
824 .module_name = module_name,
825 .obj_file_name = obj_file_name,
826
827 .populated = false,
828 .symbols = undefined,
829 .subsect_info = undefined,
830 .checksum_offset = null,
831 });
832
833 mod_info_offset += this_record_len;
834 if (mod_info_offset > mod_info_size)
835 return error.InvalidDebugInfo;
836 }
837
838 di.modules = modules.toOwnedSlice();
839
840 // Section Contribution Substream
841 var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator);
842 var sect_cont_offset: usize = 0;
843 if (section_contrib_size != 0) {
844 const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.reader().readIntLittle(u32));
845 if (ver != pdb.SectionContrSubstreamVersion.Ver60)
846 return error.InvalidDebugInfo;
847 sect_cont_offset += @sizeOf(u32);
848 }
849 while (sect_cont_offset != section_contrib_size) {
850 const entry = try sect_contribs.addOne();
851 entry.* = try dbi.reader().readStruct(pdb.SectionContribEntry);
852 sect_cont_offset += @sizeOf(pdb.SectionContribEntry);
853
854 if (sect_cont_offset > section_contrib_size)
855 return error.InvalidDebugInfo;
856 }
857
858 di.sect_contribs = sect_contribs.toOwnedSlice();
859690
860 return di;691 return di;
861 }692 }
862}693}
863694
864fn readSparseBitVector(stream: anytype, allocator: *mem.Allocator) ![]usize {
865 const num_words = try stream.readIntLittle(u32);
866 var word_i: usize = 0;
867 var list = ArrayList(usize).init(allocator);
868 while (word_i != num_words) : (word_i += 1) {
869 const word = try stream.readIntLittle(u32);
870 var bit_i: u5 = 0;
871 while (true) : (bit_i += 1) {
872 if (word & (@as(u32, 1) << bit_i) != 0) {
873 try list.append(word_i * 32 + bit_i);
874 }
875 if (bit_i == maxInt(u5)) break;
876 }
877 }
878 return list.toOwnedSlice();
879}
880
881fn chopSlice(ptr: []const u8, offset: u64, size: u64) ![]const u8 {695fn chopSlice(ptr: []const u8, offset: u64, size: u64) ![]const u8 {
882 const start = try math.cast(usize, offset);696 const start = try math.cast(usize, offset);
883 const end = start + try math.cast(usize, size);697 const end = start + try math.cast(usize, size);
...@@ -1353,18 +1167,6 @@ pub const DebugInfo = struct {...@@ -1353,18 +1167,6 @@ pub const DebugInfo = struct {
1353 }1167 }
1354};1168};
13551169
1356const SymbolInfo = struct {
1357 symbol_name: []const u8 = "???",
1358 compile_unit_name: []const u8 = "???",
1359 line_info: ?LineInfo = null,
1360
1361 fn deinit(self: @This()) void {
1362 if (self.line_info) |li| {
1363 li.deinit();
1364 }
1365 }
1366};
1367
1368pub const ModuleDebugInfo = switch (native_os) {1170pub const ModuleDebugInfo = switch (native_os) {
1369 .macos, .ios, .watchos, .tvos => struct {1171 .macos, .ios, .watchos, .tvos => struct {
1370 base_address: usize,1172 base_address: usize,
...@@ -1531,8 +1333,6 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1531,8 +1333,6 @@ pub const ModuleDebugInfo = switch (native_os) {
1531 base_address: usize,1333 base_address: usize,
1532 pdb: pdb.Pdb,1334 pdb: pdb.Pdb,
1533 coff: *coff.Coff,1335 coff: *coff.Coff,
1534 sect_contribs: []pdb.SectionContribEntry,
1535 modules: []Module,
15361336
1537 pub fn allocator(self: @This()) *mem.Allocator {1337 pub fn allocator(self: @This()) *mem.Allocator {
1538 return self.coff.allocator;1338 return self.coff.allocator;
...@@ -1543,7 +1343,7 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1543,7 +1343,7 @@ pub const ModuleDebugInfo = switch (native_os) {
1543 const relocated_address = address - self.base_address;1343 const relocated_address = address - self.base_address;
15441344
1545 var coff_section: *coff.Section = undefined;1345 var coff_section: *coff.Section = undefined;
1546 const mod_index = for (self.sect_contribs) |sect_contrib| {1346 const mod_index = for (self.pdb.sect_contribs) |sect_contrib| {
1547 if (sect_contrib.Section > self.coff.sections.items.len) continue;1347 if (sect_contrib.Section > self.coff.sections.items.len) continue;
1548 // Remember that SectionContribEntry.Section is 1-based.1348 // Remember that SectionContribEntry.Section is 1-based.
1549 coff_section = &self.coff.sections.items[sect_contrib.Section - 1];1349 coff_section = &self.coff.sections.items[sect_contrib.Section - 1];
...@@ -1558,126 +1358,18 @@ pub const ModuleDebugInfo = switch (native_os) {...@@ -1558,126 +1358,18 @@ pub const ModuleDebugInfo = switch (native_os) {
1558 return SymbolInfo{};1358 return SymbolInfo{};
1559 };1359 };
15601360
1561 const mod = &self.modules[mod_index];1361 const module = (try self.pdb.getModule(mod_index)) orelse
1562 try populateModule(self, mod);1362 return error.InvalidDebugInfo;
1563 const obj_basename = fs.path.basename(mod.obj_file_name);1363 const obj_basename = fs.path.basename(module.obj_file_name);
1564
1565 var symbol_i: usize = 0;
1566 const symbol_name = if (!mod.populated) "???" else while (symbol_i != mod.symbols.len) {
1567 const prefix = @ptrCast(*pdb.RecordPrefix, &mod.symbols[symbol_i]);
1568 if (prefix.RecordLen < 2)
1569 return error.InvalidDebugInfo;
1570 switch (prefix.RecordKind) {
1571 .S_LPROC32, .S_GPROC32 => {
1572 const proc_sym = @ptrCast(*pdb.ProcSym, &mod.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)]);
1573 const vaddr_start = coff_section.header.virtual_address + proc_sym.CodeOffset;
1574 const vaddr_end = vaddr_start + proc_sym.CodeSize;
1575 if (relocated_address >= vaddr_start and relocated_address < vaddr_end) {
1576 break mem.spanZ(@ptrCast([*:0]u8, proc_sym) + @sizeOf(pdb.ProcSym));
1577 }
1578 },
1579 else => {},
1580 }
1581 symbol_i += prefix.RecordLen + @sizeOf(u16);
1582 if (symbol_i > mod.symbols.len)
1583 return error.InvalidDebugInfo;
1584 } else "???";
1585
1586 const subsect_info = mod.subsect_info;
1587
1588 var sect_offset: usize = 0;
1589 var skip_len: usize = undefined;
1590 const opt_line_info = subsections: {
1591 const checksum_offset = mod.checksum_offset orelse break :subsections null;
1592 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
1593 const subsect_hdr = @ptrCast(*pdb.DebugSubsectionHeader, &subsect_info[sect_offset]);
1594 skip_len = subsect_hdr.Length;
1595 sect_offset += @sizeOf(pdb.DebugSubsectionHeader);
1596
1597 switch (subsect_hdr.Kind) {
1598 .Lines => {
1599 var line_index = sect_offset;
1600
1601 const line_hdr = @ptrCast(*pdb.LineFragmentHeader, &subsect_info[line_index]);
1602 if (line_hdr.RelocSegment == 0)
1603 return error.MissingDebugInfo;
1604 line_index += @sizeOf(pdb.LineFragmentHeader);
1605 const frag_vaddr_start = coff_section.header.virtual_address + line_hdr.RelocOffset;
1606 const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
1607
1608 if (relocated_address >= frag_vaddr_start and relocated_address < frag_vaddr_end) {
1609 // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
1610 // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in,
1611 // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
1612 const subsection_end_index = sect_offset + subsect_hdr.Length;
1613
1614 while (line_index < subsection_end_index) {
1615 const block_hdr = @ptrCast(*pdb.LineBlockFragmentHeader, &subsect_info[line_index]);
1616 line_index += @sizeOf(pdb.LineBlockFragmentHeader);
1617 const start_line_index = line_index;
1618
1619 const has_column = line_hdr.Flags.LF_HaveColumns;
1620
1621 // All line entries are stored inside their line block by ascending start address.
1622 // Heuristic: we want to find the last line entry
1623 // that has a vaddr_start <= relocated_address.
1624 // This is done with a simple linear search.
1625 var line_i: u32 = 0;
1626 while (line_i < block_hdr.NumLines) : (line_i += 1) {
1627 const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[line_index]);
1628 line_index += @sizeOf(pdb.LineNumberEntry);
1629
1630 const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
1631 if (relocated_address < vaddr_start) {
1632 break;
1633 }
1634 }
1635
1636 // line_i == 0 would mean that no matching LineNumberEntry was found.
1637 if (line_i > 0) {
1638 const subsect_index = checksum_offset + block_hdr.NameIndex;
1639 const chksum_hdr = @ptrCast(*pdb.FileChecksumEntryHeader, &mod.subsect_info[subsect_index]);
1640 const strtab_offset = @sizeOf(pdb.PDBStringTableHeader) + chksum_hdr.FileNameOffset;
1641 try self.pdb.string_table.seekTo(strtab_offset);
1642 const source_file_name = try self.pdb.string_table.readNullTermString(self.allocator());
1643
1644 const line_entry_idx = line_i - 1;
1645
1646 const column = if (has_column) blk: {
1647 const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines;
1648 const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx;
1649 const col_num_entry = @ptrCast(*pdb.ColumnNumberEntry, &subsect_info[col_index]);
1650 break :blk col_num_entry.StartColumn;
1651 } else 0;
1652
1653 const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry);
1654 const line_num_entry = @ptrCast(*pdb.LineNumberEntry, &subsect_info[found_line_index]);
1655 const flags = @ptrCast(*pdb.LineNumberEntry.Flags, &line_num_entry.Flags);
1656
1657 break :subsections LineInfo{
1658 .allocator = self.allocator(),
1659 .file_name = source_file_name,
1660 .line = flags.Start,
1661 .column = column,
1662 };
1663 }
1664 }
1665
1666 // Checking that we are not reading garbage after the (possibly) multiple block fragments.
1667 if (line_index != subsection_end_index) {
1668 return error.InvalidDebugInfo;
1669 }
1670 }
1671 },
1672 else => {},
1673 }
16741364
1675 if (sect_offset > subsect_info.len)1365 const symbol_name = self.pdb.getSymbolName(
1676 return error.InvalidDebugInfo;1366 module,
1677 } else {1367 relocated_address - coff_section.header.virtual_address,
1678 break :subsections null;1368 ) orelse "???";
1679 }1369 const opt_line_info = try self.pdb.getLineNumberInfo(
1680 };1370 module,
1371 relocated_address - coff_section.header.virtual_address,
1372 );
16811373
1682 return SymbolInfo{1374 return SymbolInfo{
1683 .symbol_name = symbol_name,1375 .symbol_name = symbol_name,
lib/std/pdb.zig+386-29
...@@ -13,6 +13,7 @@ const warn = std.debug.warn;...@@ -13,6 +13,7 @@ const warn = std.debug.warn;
13const coff = std.coff;13const coff = std.coff;
14const fs = std.fs;14const fs = std.fs;
15const File = std.fs.File;15const File = std.fs.File;
16const debug = std.debug;
1617
17const ArrayList = std.ArrayList;18const ArrayList = std.ArrayList;
1819
...@@ -345,6 +346,7 @@ pub const ProcSymFlags = packed struct {...@@ -345,6 +346,7 @@ pub const ProcSymFlags = packed struct {
345pub const SectionContrSubstreamVersion = enum(u32) {346pub const SectionContrSubstreamVersion = enum(u32) {
346 Ver60 = 0xeffe0000 + 19970605,347 Ver60 = 0xeffe0000 + 19970605,
347 V2 = 0xeffe0000 + 20140516,348 V2 = 0xeffe0000 + 20140516,
349 _,
348};350};
349351
350pub const RecordPrefix = packed struct {352pub const RecordPrefix = packed struct {
...@@ -465,21 +467,382 @@ pub const PDBStringTableHeader = packed struct {...@@ -465,21 +467,382 @@ pub const PDBStringTableHeader = packed struct {
465 ByteSize: u32,467 ByteSize: u32,
466};468};
467469
470fn readSparseBitVector(stream: anytype, allocator: *mem.Allocator) ![]u32 {
471 const num_words = try stream.readIntLittle(u32);
472 var list = ArrayList(u32).init(allocator);
473 errdefer list.deinit();
474 var word_i: u32 = 0;
475 while (word_i != num_words) : (word_i += 1) {
476 const word = try stream.readIntLittle(u32);
477 var bit_i: u5 = 0;
478 while (true) : (bit_i += 1) {
479 if (word & (@as(u32, 1) << bit_i) != 0) {
480 try list.append(word_i * 32 + bit_i);
481 }
482 if (bit_i == std.math.maxInt(u5)) break;
483 }
484 }
485 return list.toOwnedSlice();
486}
487
468pub const Pdb = struct {488pub const Pdb = struct {
469 in_file: File,489 in_file: File,
490 msf: Msf,
470 allocator: *mem.Allocator,491 allocator: *mem.Allocator,
471 coff: *coff.Coff,492 string_table: ?*MsfStream,
472 string_table: *MsfStream,493 dbi: ?*MsfStream,
473 dbi: *MsfStream,494 modules: []Module,
495 sect_contribs: []SectionContribEntry,
496 guid: [16]u8,
497 age: u32,
498
499 pub const Module = struct {
500 mod_info: ModInfo,
501 module_name: []u8,
502 obj_file_name: []u8,
503 // The fields below are filled on demand.
504 populated: bool,
505 symbols: []u8,
506 subsect_info: []u8,
507 checksum_offset: ?usize,
508 };
474509
475 msf: Msf,510 pub fn init(allocator: *mem.Allocator, path: []const u8) !Pdb {
511 const file = try fs.cwd().openFile(path, .{ .intended_io_mode = .blocking });
512 errdefer file.close();
513
514 return Pdb{
515 .in_file = file,
516 .allocator = allocator,
517 .string_table = null,
518 .dbi = null,
519 .msf = try Msf.init(allocator, file),
520 .modules = &[_]Module{},
521 .sect_contribs = &[_]SectionContribEntry{},
522 .guid = undefined,
523 .age = undefined,
524 };
525 }
526
527 pub fn deinit(self: *Pdb) void {
528 self.in_file.close();
529 self.allocator.free(self.modules);
530 self.allocator.free(self.sect_contribs);
531 }
532
533 pub fn parseDbiStream(self: *Pdb) !void {
534 var stream = self.getStream(StreamType.Dbi) orelse
535 return error.InvalidDebugInfo;
536 const reader = stream.reader();
537
538 const header = try reader.readStruct(DbiStreamHeader);
539 if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team
540 return error.UnknownPDBVersion;
541 // if (header.Age != age)
542 // return error.UnmatchingPDB;
543
544 const mod_info_size = header.ModInfoSize;
545 const section_contrib_size = header.SectionContributionSize;
546
547 var modules = ArrayList(Module).init(self.allocator);
548 errdefer modules.deinit();
549
550 // Module Info Substream
551 var mod_info_offset: usize = 0;
552 while (mod_info_offset != mod_info_size) {
553 const mod_info = try reader.readStruct(ModInfo);
554 var this_record_len: usize = @sizeOf(ModInfo);
555
556 const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
557 errdefer self.allocator.free(module_name);
558 this_record_len += module_name.len + 1;
559
560 const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024);
561 errdefer self.allocator.free(obj_file_name);
562 this_record_len += obj_file_name.len + 1;
563
564 if (this_record_len % 4 != 0) {
565 const round_to_next_4 = (this_record_len | 0x3) + 1;
566 const march_forward_bytes = round_to_next_4 - this_record_len;
567 try stream.seekBy(@intCast(isize, march_forward_bytes));
568 this_record_len += march_forward_bytes;
569 }
570
571 try modules.append(Module{
572 .mod_info = mod_info,
573 .module_name = module_name,
574 .obj_file_name = obj_file_name,
575
576 .populated = false,
577 .symbols = undefined,
578 .subsect_info = undefined,
579 .checksum_offset = null,
580 });
581
582 mod_info_offset += this_record_len;
583 if (mod_info_offset > mod_info_size)
584 return error.InvalidDebugInfo;
585 }
586
587 // Section Contribution Substream
588 var sect_contribs = ArrayList(SectionContribEntry).init(self.allocator);
589 errdefer sect_contribs.deinit();
590
591 var sect_cont_offset: usize = 0;
592 if (section_contrib_size != 0) {
593 const version = reader.readEnum(SectionContrSubstreamVersion, .Little) catch |err| switch (err) {
594 error.InvalidValue => return error.InvalidDebugInfo,
595 else => |e| return e,
596 };
597 sect_cont_offset += @sizeOf(u32);
598 }
599 while (sect_cont_offset != section_contrib_size) {
600 const entry = try sect_contribs.addOne();
601 entry.* = try reader.readStruct(SectionContribEntry);
602 sect_cont_offset += @sizeOf(SectionContribEntry);
603
604 if (sect_cont_offset > section_contrib_size)
605 return error.InvalidDebugInfo;
606 }
607
608 self.modules = modules.toOwnedSlice();
609 self.sect_contribs = sect_contribs.toOwnedSlice();
610 }
611
612 pub fn parseInfoStream(self: *Pdb) !void {
613 var stream = self.getStream(StreamType.Pdb) orelse
614 return error.InvalidDebugInfo;
615 const reader = stream.reader();
616
617 // Parse the InfoStreamHeader.
618 const version = try reader.readIntLittle(u32);
619 const signature = try reader.readIntLittle(u32);
620 const age = try reader.readIntLittle(u32);
621 const guid = try reader.readBytesNoEof(16);
622
623 if (version != 20000404) // VC70, only value observed by LLVM team
624 return error.UnknownPDBVersion;
625
626 self.guid = guid;
627 self.age = age;
628
629 // Find the string table.
630 const string_table_index = str_tab_index: {
631 const name_bytes_len = try reader.readIntLittle(u32);
632 const name_bytes = try self.allocator.alloc(u8, name_bytes_len);
633 defer self.allocator.free(name_bytes);
634 try reader.readNoEof(name_bytes);
635
636 const HashTableHeader = extern struct {
637 Size: u32,
638 Capacity: u32,
639
640 fn maxLoad(cap: u32) u32 {
641 return cap * 2 / 3 + 1;
642 }
643 };
644 const hash_tbl_hdr = try reader.readStruct(HashTableHeader);
645 if (hash_tbl_hdr.Capacity == 0)
646 return error.InvalidDebugInfo;
647
648 if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity))
649 return error.InvalidDebugInfo;
650
651 const present = try readSparseBitVector(&reader, self.allocator);
652 defer self.allocator.free(present);
653 if (present.len != hash_tbl_hdr.Size)
654 return error.InvalidDebugInfo;
655 const deleted = try readSparseBitVector(&reader, self.allocator);
656 defer self.allocator.free(deleted);
657
658 for (present) |_| {
659 const name_offset = try reader.readIntLittle(u32);
660 const name_index = try reader.readIntLittle(u32);
661 if (name_offset > name_bytes.len)
662 return error.InvalidDebugInfo;
663 const name = mem.spanZ(std.meta.assumeSentinel(name_bytes.ptr + name_offset, 0));
664 if (mem.eql(u8, name, "/names")) {
665 break :str_tab_index name_index;
666 }
667 }
668 return error.MissingDebugInfo;
669 };
670
671 self.string_table = self.getStreamById(string_table_index) orelse
672 return error.MissingDebugInfo;
673 }
674
675 pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 {
676 std.debug.assert(module.populated);
677
678 var symbol_i: usize = 0;
679 while (symbol_i != module.symbols.len) {
680 const prefix = @ptrCast(*RecordPrefix, &module.symbols[symbol_i]);
681 if (prefix.RecordLen < 2)
682 return null;
683 switch (prefix.RecordKind) {
684 .S_LPROC32, .S_GPROC32 => {
685 const proc_sym = @ptrCast(*ProcSym, &module.symbols[symbol_i + @sizeOf(RecordPrefix)]);
686 if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) {
687 return mem.spanZ(@ptrCast([*:0]u8, proc_sym) + @sizeOf(ProcSym));
688 }
689 },
690 else => {},
691 }
692 symbol_i += prefix.RecordLen + @sizeOf(u16);
693 }
476694
477 pub fn openFile(self: *Pdb, coff_ptr: *coff.Coff, file_name: []const u8) !void {695 return null;
478 self.in_file = try fs.cwd().openFile(file_name, .{ .intended_io_mode = .blocking });696 }
479 self.allocator = coff_ptr.allocator;697
480 self.coff = coff_ptr;698 pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.LineInfo {
699 std.debug.assert(module.populated);
700 const subsect_info = module.subsect_info;
701
702 var sect_offset: usize = 0;
703 var skip_len: usize = undefined;
704 const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo;
705 while (sect_offset != subsect_info.len) : (sect_offset += skip_len) {
706 const subsect_hdr = @ptrCast(*DebugSubsectionHeader, &subsect_info[sect_offset]);
707 skip_len = subsect_hdr.Length;
708 sect_offset += @sizeOf(DebugSubsectionHeader);
709
710 switch (subsect_hdr.Kind) {
711 .Lines => {
712 var line_index = sect_offset;
713
714 const line_hdr = @ptrCast(*LineFragmentHeader, &subsect_info[line_index]);
715 if (line_hdr.RelocSegment == 0)
716 return error.MissingDebugInfo;
717 line_index += @sizeOf(LineFragmentHeader);
718 const frag_vaddr_start = line_hdr.RelocOffset;
719 const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize;
720
721 if (address >= frag_vaddr_start and address < frag_vaddr_end) {
722 // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records)
723 // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in,
724 // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection.
725 const subsection_end_index = sect_offset + subsect_hdr.Length;
726
727 while (line_index < subsection_end_index) {
728 const block_hdr = @ptrCast(*LineBlockFragmentHeader, &subsect_info[line_index]);
729 line_index += @sizeOf(LineBlockFragmentHeader);
730 const start_line_index = line_index;
731
732 const has_column = line_hdr.Flags.LF_HaveColumns;
733
734 // All line entries are stored inside their line block by ascending start address.
735 // Heuristic: we want to find the last line entry
736 // that has a vaddr_start <= address.
737 // This is done with a simple linear search.
738 var line_i: u32 = 0;
739 while (line_i < block_hdr.NumLines) : (line_i += 1) {
740 const line_num_entry = @ptrCast(*LineNumberEntry, &subsect_info[line_index]);
741 line_index += @sizeOf(LineNumberEntry);
742
743 const vaddr_start = frag_vaddr_start + line_num_entry.Offset;
744 if (address < vaddr_start) {
745 break;
746 }
747 }
748
749 // line_i == 0 would mean that no matching LineNumberEntry was found.
750 if (line_i > 0) {
751 const subsect_index = checksum_offset + block_hdr.NameIndex;
752 const chksum_hdr = @ptrCast(*FileChecksumEntryHeader, &module.subsect_info[subsect_index]);
753 const strtab_offset = @sizeOf(PDBStringTableHeader) + chksum_hdr.FileNameOffset;
754 try self.string_table.?.seekTo(strtab_offset);
755 const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024);
756
757 const line_entry_idx = line_i - 1;
758
759 const column = if (has_column) blk: {
760 const start_col_index = start_line_index + @sizeOf(LineNumberEntry) * block_hdr.NumLines;
761 const col_index = start_col_index + @sizeOf(ColumnNumberEntry) * line_entry_idx;
762 const col_num_entry = @ptrCast(*ColumnNumberEntry, &subsect_info[col_index]);
763 break :blk col_num_entry.StartColumn;
764 } else 0;
765
766 const found_line_index = start_line_index + line_entry_idx * @sizeOf(LineNumberEntry);
767 const line_num_entry = @ptrCast(*LineNumberEntry, &subsect_info[found_line_index]);
768 const flags = @ptrCast(*LineNumberEntry.Flags, &line_num_entry.Flags);
769
770 return debug.LineInfo{
771 .allocator = self.allocator,
772 .file_name = source_file_name,
773 .line = flags.Start,
774 .column = column,
775 };
776 }
777 }
778
779 // Checking that we are not reading garbage after the (possibly) multiple block fragments.
780 if (line_index != subsection_end_index) {
781 return error.InvalidDebugInfo;
782 }
783 }
784 },
785 else => {},
786 }
787
788 if (sect_offset > subsect_info.len)
789 return error.InvalidDebugInfo;
790 }
481791
482 try self.msf.openFile(self.allocator, self.in_file);792 return error.MissingDebugInfo;
793 }
794
795 pub fn getModule(self: *Pdb, index: usize) !?*Module {
796 if (index >= self.modules.len)
797 return null;
798
799 const mod = &self.modules[index];
800 if (mod.populated)
801 return mod;
802
803 // At most one can be non-zero.
804 if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0)
805 return error.InvalidDebugInfo;
806 if (mod.mod_info.C13ByteSize == 0)
807 return error.InvalidDebugInfo;
808
809 const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse
810 return error.MissingDebugInfo;
811 const reader = stream.reader();
812
813 const signature = try reader.readIntLittle(u32);
814 if (signature != 4)
815 return error.InvalidDebugInfo;
816
817 mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4);
818 errdefer self.allocator.free(mod.symbols);
819 try reader.readNoEof(mod.symbols);
820
821 mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize);
822 errdefer self.allocator.free(mod.subsect_info);
823 try reader.readNoEof(mod.subsect_info);
824
825 var sect_offset: usize = 0;
826 var skip_len: usize = undefined;
827 while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) {
828 const subsect_hdr = @ptrCast(*DebugSubsectionHeader, &mod.subsect_info[sect_offset]);
829 skip_len = subsect_hdr.Length;
830 sect_offset += @sizeOf(DebugSubsectionHeader);
831
832 switch (subsect_hdr.Kind) {
833 .FileChecksums => {
834 mod.checksum_offset = sect_offset;
835 break;
836 },
837 else => {},
838 }
839
840 if (sect_offset > mod.subsect_info.len)
841 return error.InvalidDebugInfo;
842 }
843
844 mod.populated = true;
845 return mod;
483 }846 }
484847
485 pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream {848 pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream {
...@@ -499,7 +862,7 @@ const Msf = struct {...@@ -499,7 +862,7 @@ const Msf = struct {
499 directory: MsfStream,862 directory: MsfStream,
500 streams: []MsfStream,863 streams: []MsfStream,
501864
502 fn openFile(self: *Msf, allocator: *mem.Allocator, file: File) !void {865 fn init(allocator: *mem.Allocator, file: File) !Msf {
503 const in = file.reader();866 const in = file.reader();
504867
505 const superblock = try in.readStruct(SuperBlock);868 const superblock = try in.readStruct(SuperBlock);
...@@ -526,14 +889,14 @@ const Msf = struct {...@@ -526,14 +889,14 @@ const Msf = struct {
526 for (dir_blocks) |*b| {889 for (dir_blocks) |*b| {
527 b.* = try in.readIntLittle(u32);890 b.* = try in.readIntLittle(u32);
528 }891 }
529 self.directory = MsfStream.init(892 var directory = MsfStream.init(
530 superblock.BlockSize,893 superblock.BlockSize,
531 file,894 file,
532 dir_blocks,895 dir_blocks,
533 );896 );
534897
535 const begin = self.directory.pos;898 const begin = directory.pos;
536 const stream_count = try self.directory.reader().readIntLittle(u32);899 const stream_count = try directory.reader().readIntLittle(u32);
537 const stream_sizes = try allocator.alloc(u32, stream_count);900 const stream_sizes = try allocator.alloc(u32, stream_count);
538 defer allocator.free(stream_sizes);901 defer allocator.free(stream_sizes);
539902
...@@ -542,12 +905,12 @@ const Msf = struct {...@@ -542,12 +905,12 @@ const Msf = struct {
542 // and must be taken into account when resolving stream indices.905 // and must be taken into account when resolving stream indices.
543 const Nil = 0xFFFFFFFF;906 const Nil = 0xFFFFFFFF;
544 for (stream_sizes) |*s, i| {907 for (stream_sizes) |*s, i| {
545 const size = try self.directory.reader().readIntLittle(u32);908 const size = try directory.reader().readIntLittle(u32);
546 s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize);909 s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize);
547 }910 }
548911
549 self.streams = try allocator.alloc(MsfStream, stream_count);912 const streams = try allocator.alloc(MsfStream, stream_count);
550 for (self.streams) |*stream, i| {913 for (streams) |*stream, i| {
551 const size = stream_sizes[i];914 const size = stream_sizes[i];
552 if (size == 0) {915 if (size == 0) {
553 stream.* = MsfStream{916 stream.* = MsfStream{
...@@ -557,7 +920,7 @@ const Msf = struct {...@@ -557,7 +920,7 @@ const Msf = struct {
557 var blocks = try allocator.alloc(u32, size);920 var blocks = try allocator.alloc(u32, size);
558 var j: u32 = 0;921 var j: u32 = 0;
559 while (j < size) : (j += 1) {922 while (j < size) : (j += 1) {
560 const block_id = try self.directory.reader().readIntLittle(u32);923 const block_id = try directory.reader().readIntLittle(u32);
561 const n = (block_id % superblock.BlockSize);924 const n = (block_id % superblock.BlockSize);
562 // 0 is for SuperBlock, 1 and 2 for FPMs.925 // 0 is for SuperBlock, 1 and 2 for FPMs.
563 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > try file.getEndPos())926 if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > try file.getEndPos())
...@@ -573,9 +936,14 @@ const Msf = struct {...@@ -573,9 +936,14 @@ const Msf = struct {
573 }936 }
574 }937 }
575938
576 const end = self.directory.pos;939 const end = directory.pos;
577 if (end - begin != superblock.NumDirectoryBytes)940 if (end - begin != superblock.NumDirectoryBytes)
578 return error.InvalidStreamDirectory;941 return error.InvalidStreamDirectory;
942
943 return Msf{
944 .directory = directory,
945 .streams = streams,
946 };
579 }947 }
580};948};
581949
...@@ -649,17 +1017,6 @@ const MsfStream = struct {...@@ -649,17 +1017,6 @@ const MsfStream = struct {
649 return stream;1017 return stream;
650 }1018 }
6511019
652 pub fn readNullTermString(self: *MsfStream, allocator: *mem.Allocator) ![]u8 {
653 var list = ArrayList(u8).init(allocator);
654 while (true) {
655 const byte = try self.reader().readByte();
656 if (byte == 0) {
657 return list.items;
658 }
659 try list.append(byte);
660 }
661 }
662
663 fn read(self: *MsfStream, buffer: []u8) !usize {1020 fn read(self: *MsfStream, buffer: []u8) !usize {
664 var block_id = @intCast(usize, self.pos / self.block_size);1021 var block_id = @intCast(usize, self.pos / self.block_size);
665 if (block_id >= self.blocks.len) return 0; // End of Stream1022 if (block_id >= self.blocks.len) return 0; // End of Stream