//===- DumpOutputStyle.cpp ------------------------------------ *- C++ --*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "DumpOutputStyle.h" #include "MinimalSymbolDumper.h" #include "MinimalTypeDumper.h" #include "StreamUtil.h" #include "TypeReferenceTracker.h" #include "llvm-pdbutil.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/DebugInfo/CodeView/CVSymbolVisitor.h" #include "llvm/DebugInfo/CodeView/CVTypeVisitor.h" #include "llvm/DebugInfo/CodeView/DebugChecksumsSubsection.h" #include "llvm/DebugInfo/CodeView/DebugCrossExSubsection.h" #include "llvm/DebugInfo/CodeView/DebugCrossImpSubsection.h" #include "llvm/DebugInfo/CodeView/DebugFrameDataSubsection.h" #include "llvm/DebugInfo/CodeView/DebugInlineeLinesSubsection.h" #include "llvm/DebugInfo/CodeView/DebugLinesSubsection.h" #include "llvm/DebugInfo/CodeView/DebugStringTableSubsection.h" #include "llvm/DebugInfo/CodeView/DebugSymbolsSubsection.h" #include "llvm/DebugInfo/CodeView/Formatters.h" #include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h" #include "llvm/DebugInfo/CodeView/Line.h" #include "llvm/DebugInfo/CodeView/SymbolDeserializer.h" #include "llvm/DebugInfo/CodeView/SymbolVisitorCallbackPipeline.h" #include "llvm/DebugInfo/CodeView/SymbolVisitorCallbacks.h" #include "llvm/DebugInfo/CodeView/TypeHashing.h" #include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h" #include "llvm/DebugInfo/MSF/MappedBlockStream.h" #include "llvm/DebugInfo/PDB/Native/DbiModuleDescriptor.h" #include "llvm/DebugInfo/PDB/Native/DbiStream.h" #include "llvm/DebugInfo/PDB/Native/FormatUtil.h" #include "llvm/DebugInfo/PDB/Native/GlobalsStream.h" #include "llvm/DebugInfo/PDB/Native/ISectionContribVisitor.h" #include "llvm/DebugInfo/PDB/Native/InfoStream.h" #include "llvm/DebugInfo/PDB/Native/InputFile.h" #include "llvm/DebugInfo/PDB/Native/ModuleDebugStream.h" #include "llvm/DebugInfo/PDB/Native/NativeSession.h" #include "llvm/DebugInfo/PDB/Native/PDBFile.h" #include "llvm/DebugInfo/PDB/Native/PublicsStream.h" #include "llvm/DebugInfo/PDB/Native/RawError.h" #include "llvm/DebugInfo/PDB/Native/SymbolStream.h" #include "llvm/DebugInfo/PDB/Native/TpiHashing.h" #include "llvm/DebugInfo/PDB/Native/TpiStream.h" #include "llvm/Object/COFF.h" #include "llvm/Support/BinaryStreamReader.h" #include "llvm/Support/FormatAdapters.h" #include "llvm/Support/FormatVariadic.h" #include using namespace llvm; using namespace llvm::codeview; using namespace llvm::msf; using namespace llvm::pdb; DumpOutputStyle::DumpOutputStyle(InputFile &File) : File(File), P(2, false, outs(), opts::Filters) { if (opts::dump::DumpTypeRefStats) RefTracker.reset(new TypeReferenceTracker(File)); } DumpOutputStyle::~DumpOutputStyle() {} PDBFile &DumpOutputStyle::getPdb() { return File.pdb(); } object::COFFObjectFile &DumpOutputStyle::getObj() { return File.obj(); } void DumpOutputStyle::printStreamNotValidForObj() { AutoIndent Indent(P, 4); P.formatLine("Dumping this stream is not valid for object files"); } void DumpOutputStyle::printStreamNotPresent(StringRef StreamName) { AutoIndent Indent(P, 4); P.formatLine("{0} stream not present", StreamName); } Error DumpOutputStyle::dump() { // Walk symbols & globals if we are supposed to mark types referenced. if (opts::dump::DumpTypeRefStats) RefTracker->mark(); if (opts::dump::DumpSummary) { if (auto EC = dumpFileSummary()) return EC; P.NewLine(); } if (opts::dump::DumpStreams) { if (auto EC = dumpStreamSummary()) return EC; P.NewLine(); } if (opts::dump::DumpSymbolStats) { ExitOnError Err("Unexpected error processing module stats: "); Err(dumpSymbolStats()); P.NewLine(); } if (opts::dump::DumpUdtStats) { if (auto EC = dumpUdtStats()) return EC; P.NewLine(); } if (opts::dump::DumpTypeStats || opts::dump::DumpIDStats) { if (auto EC = dumpTypeStats()) return EC; P.NewLine(); } if (opts::dump::DumpNamedStreams) { if (auto EC = dumpNamedStreams()) return EC; P.NewLine(); } if (opts::dump::DumpStringTable || opts::dump::DumpStringTableDetails) { if (auto EC = dumpStringTable()) return EC; P.NewLine(); } if (opts::dump::DumpModules) { ExitOnError Err("Unexpected error processing modules: "); Err(dumpModules()); } if (opts::dump::DumpModuleFiles) { ExitOnError Err("Unexpected error processing files: "); Err(dumpModuleFiles()); } if (opts::dump::DumpLines) { ExitOnError Err("Unexpected error processing lines: "); Err(dumpLines()); } if (opts::dump::DumpInlineeLines) { ExitOnError Err("Unexpected error processing inlinee lines: "); Err(dumpInlineeLines()); } if (opts::dump::DumpXmi) { ExitOnError Err("Unexpected error processing cross module imports: "); Err(dumpXmi()); } if (opts::dump::DumpXme) { ExitOnError Err("Unexpected error processing cross module exports: "); Err(dumpXme()); } if (opts::dump::DumpFpo) { if (auto EC = dumpFpo()) return EC; } if (File.isObj()) { if (opts::dump::DumpTypes || !opts::dump::DumpTypeIndex.empty() || opts::dump::DumpTypeExtras) if (auto EC = dumpTypesFromObjectFile()) return EC; } else { if (opts::dump::DumpTypes || !opts::dump::DumpTypeIndex.empty() || opts::dump::DumpTypeExtras) { if (auto EC = dumpTpiStream(StreamTPI)) return EC; } if (opts::dump::DumpIds || !opts::dump::DumpIdIndex.empty() || opts::dump::DumpIdExtras) { if (auto EC = dumpTpiStream(StreamIPI)) return EC; } } if (opts::dump::DumpGSIRecords) { if (auto EC = dumpGSIRecords()) return EC; } if (opts::dump::DumpGlobals) { if (auto EC = dumpGlobals()) return EC; } if (opts::dump::DumpPublics) { if (auto EC = dumpPublics()) return EC; } if (opts::dump::DumpSymbols) { ExitOnError Err("Unexpected error processing symbols: "); Err(File.isPdb() ? dumpModuleSymsForPdb() : dumpModuleSymsForObj()); } if (opts::dump::DumpTypeRefStats) { if (auto EC = dumpTypeRefStats()) return EC; } if (opts::dump::DumpSectionHeaders) { if (auto EC = dumpSectionHeaders()) return EC; } if (opts::dump::DumpSectionContribs) { if (auto EC = dumpSectionContribs()) return EC; } if (opts::dump::DumpSectionMap) { if (auto EC = dumpSectionMap()) return EC; } P.NewLine(); return Error::success(); } static void printHeader(LinePrinter &P, const Twine &S) { P.NewLine(); P.formatLine("{0,=60}", S); P.formatLine("{0}", fmt_repeat('=', 60)); } Error DumpOutputStyle::dumpFileSummary() { printHeader(P, "Summary"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Invalid PDB Format: "); P.formatLine("Block Size: {0}", getPdb().getBlockSize()); P.formatLine("Number of blocks: {0}", getPdb().getBlockCount()); P.formatLine("Number of streams: {0}", getPdb().getNumStreams()); auto &PS = Err(getPdb().getPDBInfoStream()); P.formatLine("Signature: {0}", PS.getSignature()); P.formatLine("Age: {0}", PS.getAge()); P.formatLine("GUID: {0}", fmt_guid(PS.getGuid().Guid)); P.formatLine("Features: {0:x+}", static_cast(PS.getFeatures())); P.formatLine("Has Debug Info: {0}", getPdb().hasPDBDbiStream()); P.formatLine("Has Types: {0}", getPdb().hasPDBTpiStream()); P.formatLine("Has IDs: {0}", getPdb().hasPDBIpiStream()); P.formatLine("Has Globals: {0}", getPdb().hasPDBGlobalsStream()); P.formatLine("Has Publics: {0}", getPdb().hasPDBPublicsStream()); if (getPdb().hasPDBDbiStream()) { DbiStream &DBI = Err(getPdb().getPDBDbiStream()); P.formatLine("Is incrementally linked: {0}", DBI.isIncrementallyLinked()); P.formatLine("Has conflicting types: {0}", DBI.hasCTypes()); P.formatLine("Is stripped: {0}", DBI.isStripped()); } return Error::success(); } static StatCollection getSymbolStats(const SymbolGroup &SG, StatCollection &CumulativeStats) { StatCollection Stats; if (SG.getFile().isPdb() && SG.hasDebugStream()) { // For PDB files, all symbols are packed into one stream. for (const auto &S : SG.getPdbModuleStream().symbols(nullptr)) { Stats.update(S.kind(), S.length()); CumulativeStats.update(S.kind(), S.length()); } return Stats; } for (const auto &SS : SG.getDebugSubsections()) { // For object files, all symbols are spread across multiple Symbol // subsections of a given .debug$S section. if (SS.kind() != DebugSubsectionKind::Symbols) continue; DebugSymbolsSubsectionRef Symbols; BinaryStreamReader Reader(SS.getRecordData()); cantFail(Symbols.initialize(Reader)); for (const auto &S : Symbols) { Stats.update(S.kind(), S.length()); CumulativeStats.update(S.kind(), S.length()); } } return Stats; } static StatCollection getChunkStats(const SymbolGroup &SG, StatCollection &CumulativeStats) { StatCollection Stats; for (const auto &Chunk : SG.getDebugSubsections()) { Stats.update(uint32_t(Chunk.kind()), Chunk.getRecordLength()); CumulativeStats.update(uint32_t(Chunk.kind()), Chunk.getRecordLength()); } return Stats; } static inline std::string formatModuleDetailKind(DebugSubsectionKind K) { return formatChunkKind(K, false); } static inline std::string formatModuleDetailKind(SymbolKind K) { return formatSymbolKind(K); } // Get the stats sorted by size, descending. std::vector StatCollection::getStatsSortedBySize() const { std::vector SortedStats(Individual.begin(), Individual.end()); llvm::stable_sort(SortedStats, [](const KindAndStat &LHS, const KindAndStat &RHS) { return LHS.second.Size > RHS.second.Size; }); return SortedStats; } template static void printModuleDetailStats(LinePrinter &P, StringRef Label, const StatCollection &Stats) { P.NewLine(); P.formatLine(" {0}", Label); AutoIndent Indent(P); P.formatLine("{0,40}: {1,7} entries ({2,12:N} bytes)", "Total", Stats.Totals.Count, Stats.Totals.Size); P.formatLine("{0}", fmt_repeat('-', 74)); for (const auto &K : Stats.getStatsSortedBySize()) { std::string KindName = formatModuleDetailKind(Kind(K.first)); P.formatLine("{0,40}: {1,7} entries ({2,12:N} bytes)", KindName, K.second.Count, K.second.Size); } } Error DumpOutputStyle::dumpStreamSummary() { printHeader(P, "Streams"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } AutoIndent Indent(P); if (StreamPurposes.empty()) discoverStreamPurposes(getPdb(), StreamPurposes); uint32_t StreamCount = getPdb().getNumStreams(); uint32_t MaxStreamSize = getPdb().getMaxStreamSize(); for (uint32_t StreamIdx = 0; StreamIdx < StreamCount; ++StreamIdx) { P.formatLine( "Stream {0} ({1} bytes): [{2}]", fmt_align(StreamIdx, AlignStyle::Right, NumDigits(StreamCount)), fmt_align(getPdb().getStreamByteSize(StreamIdx), AlignStyle::Right, NumDigits(MaxStreamSize)), StreamPurposes[StreamIdx].getLongName()); if (opts::dump::DumpStreamBlocks) { auto Blocks = getPdb().getStreamBlockList(StreamIdx); std::vector BV(Blocks.begin(), Blocks.end()); P.formatLine(" {0} Blocks: [{1}]", fmt_repeat(' ', NumDigits(StreamCount)), make_range(BV.begin(), BV.end())); } } return Error::success(); } static Expected, ArrayRef>> loadSectionHeaders(PDBFile &File, DbgHeaderType Type) { if (!File.hasPDBDbiStream()) return make_error( "Section headers require a DBI Stream, which could not be loaded", inconvertibleErrorCode()); DbiStream &Dbi = cantFail(File.getPDBDbiStream()); uint32_t SI = Dbi.getDebugStreamIndex(Type); if (SI == kInvalidStreamIndex) return make_error( "PDB does not contain the requested image section header type", inconvertibleErrorCode()); auto Stream = File.createIndexedStream(SI); if (!Stream) return make_error("Could not load the required stream data", inconvertibleErrorCode()); ArrayRef Headers; if (Stream->getLength() % sizeof(object::coff_section) != 0) return make_error( "Section header array size is not a multiple of section header size", inconvertibleErrorCode()); uint32_t NumHeaders = Stream->getLength() / sizeof(object::coff_section); BinaryStreamReader Reader(*Stream); cantFail(Reader.readArray(Headers, NumHeaders)); return std::make_pair(std::move(Stream), Headers); } static Expected> getSectionNames(PDBFile &File) { auto ExpectedHeaders = loadSectionHeaders(File, DbgHeaderType::SectionHdr); if (!ExpectedHeaders) return ExpectedHeaders.takeError(); std::unique_ptr Stream; ArrayRef Headers; std::tie(Stream, Headers) = std::move(*ExpectedHeaders); std::vector Names; for (const auto &H : Headers) Names.push_back(H.Name); return Names; } static void dumpSectionContrib(LinePrinter &P, const SectionContrib &SC, ArrayRef SectionNames, uint32_t FieldWidth) { std::string NameInsert; if (SC.ISect > 0 && SC.ISect <= SectionNames.size()) { StringRef SectionName = SectionNames[SC.ISect - 1]; NameInsert = formatv("[{0}]", SectionName).str(); } else NameInsert = "[???]"; P.formatLine("SC{5} | mod = {2}, {0}, size = {1}, data crc = {3}, reloc " "crc = {4}", formatSegmentOffset(SC.ISect, SC.Off), fmtle(SC.Size), fmtle(SC.Imod), fmtle(SC.DataCrc), fmtle(SC.RelocCrc), fmt_align(NameInsert, AlignStyle::Left, FieldWidth + 2)); AutoIndent Indent(P, FieldWidth + 2); P.formatLine(" {0}", formatSectionCharacteristics(P.getIndentLevel() + 6, SC.Characteristics, 3, " | ")); } static void dumpSectionContrib(LinePrinter &P, const SectionContrib2 &SC, ArrayRef SectionNames, uint32_t FieldWidth) { P.formatLine("SC2[{6}] | mod = {2}, {0}, size = {1}, data crc = {3}, reloc " "crc = {4}, coff section = {5}", formatSegmentOffset(SC.Base.ISect, SC.Base.Off), fmtle(SC.Base.Size), fmtle(SC.Base.Imod), fmtle(SC.Base.DataCrc), fmtle(SC.Base.RelocCrc), fmtle(SC.ISectCoff)); P.formatLine(" {0}", formatSectionCharacteristics(P.getIndentLevel() + 6, SC.Base.Characteristics, 3, " | ")); } Error DumpOutputStyle::dumpModules() { printHeader(P, "Modules"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } AutoIndent Indent(P); Expected StreamOrErr = getPdb().getPDBDbiStream(); if (!StreamOrErr) return StreamOrErr.takeError(); DbiStream &Stream = *StreamOrErr; const DbiModuleList &Modules = Stream.modules(); return iterateSymbolGroups( File, PrintScope{P, 11}, [&](uint32_t Modi, const SymbolGroup &Strings) -> Error { auto Desc = Modules.getModuleDescriptor(Modi); if (opts::dump::DumpSectionContribs) { auto SectionsOrErr = getSectionNames(getPdb()); if (!SectionsOrErr) return SectionsOrErr.takeError(); ArrayRef Sections = *SectionsOrErr; dumpSectionContrib(P, Desc.getSectionContrib(), Sections, 0); } P.formatLine("Obj: `{0}`: ", Desc.getObjFileName()); P.formatLine("debug stream: {0}, # files: {1}, has ec info: {2}", Desc.getModuleStreamIndex(), Desc.getNumberOfFiles(), Desc.hasECInfo()); auto PdbPathOrErr = Stream.getECName(Desc.getPdbFilePathNameIndex()); if (!PdbPathOrErr) return PdbPathOrErr.takeError(); StringRef PdbFilePath = *PdbPathOrErr; auto SrcPathOrErr = Stream.getECName(Desc.getSourceFileNameIndex()); if (!SrcPathOrErr) return SrcPathOrErr.takeError(); StringRef SrcFilePath = *SrcPathOrErr; P.formatLine("pdb file ni: {0} `{1}`, src file ni: {2} `{3}`", Desc.getPdbFilePathNameIndex(), PdbFilePath, Desc.getSourceFileNameIndex(), SrcFilePath); return Error::success(); }); } Error DumpOutputStyle::dumpModuleFiles() { printHeader(P, "Files"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } return iterateSymbolGroups( File, PrintScope{P, 11}, [this](uint32_t Modi, const SymbolGroup &Strings) -> Error { Expected StreamOrErr = getPdb().getPDBDbiStream(); if (!StreamOrErr) return StreamOrErr.takeError(); DbiStream &Stream = *StreamOrErr; const DbiModuleList &Modules = Stream.modules(); for (const auto &F : Modules.source_files(Modi)) { Strings.formatFromFileName(P, F); } return Error::success(); }); } Error DumpOutputStyle::dumpSymbolStats() { printHeader(P, "Module Stats"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } StatCollection SymStats; StatCollection ChunkStats; PrintScope Scope(P, 2); if (Error Err = iterateSymbolGroups( File, Scope, [&](uint32_t Modi, const SymbolGroup &SG) -> Error { StatCollection SS = getSymbolStats(SG, SymStats); StatCollection CS = getChunkStats(SG, ChunkStats); if (!SG.getFile().isPdb()) return Error::success(); AutoIndent Indent(P); auto Modules = cantFail(File.pdb().getPDBDbiStream()).modules(); uint32_t ModCount = Modules.getModuleCount(); DbiModuleDescriptor Desc = Modules.getModuleDescriptor(Modi); uint32_t StreamIdx = Desc.getModuleStreamIndex(); if (StreamIdx == kInvalidStreamIndex) { P.formatLine( "Mod {0} (debug info not present): [{1}]", fmt_align(Modi, AlignStyle::Right, NumDigits(ModCount)), Desc.getModuleName()); return Error::success(); } P.formatLine("Stream {0}, {1} bytes", StreamIdx, getPdb().getStreamByteSize(StreamIdx)); printModuleDetailStats(P, "Symbols", SS); printModuleDetailStats(P, "Chunks", CS); return Error::success(); })) return Err; if (SymStats.Totals.Count > 0) { P.printLine(" Summary |"); AutoIndent Indent(P, 4); printModuleDetailStats(P, "Symbols", SymStats); printModuleDetailStats(P, "Chunks", ChunkStats); } return Error::success(); } Error DumpOutputStyle::dumpTypeStats() { printHeader(P, "Type Record Stats"); // Iterate the types, categorize by kind, accumulate size stats. StatCollection TypeStats; LazyRandomTypeCollection &Types = opts::dump::DumpTypeStats ? File.types() : File.ids(); for (std::optional TI = Types.getFirst(); TI; TI = Types.getNext(*TI)) { CVType Type = Types.getType(*TI); TypeStats.update(uint32_t(Type.kind()), Type.length()); } P.NewLine(); P.formatLine(" Types"); AutoIndent Indent(P); P.formatLine("{0,16}: {1,7} entries ({2,12:N} bytes, {3,7} avg)", "Total", TypeStats.Totals.Count, TypeStats.Totals.Size, (double)TypeStats.Totals.Size / TypeStats.Totals.Count); P.formatLine("{0}", fmt_repeat('-', 74)); for (const auto &K : TypeStats.getStatsSortedBySize()) { P.formatLine("{0,16}: {1,7} entries ({2,12:N} bytes, {3,7} avg)", formatTypeLeafKind(TypeLeafKind(K.first)), K.second.Count, K.second.Size, (double)K.second.Size / K.second.Count); } return Error::success(); } static bool isValidNamespaceIdentifier(StringRef S) { if (S.empty()) return false; if (std::isdigit(S[0])) return false; return llvm::all_of(S, [](char C) { return std::isalnum(C); }); } namespace { constexpr uint32_t kNoneUdtKind = 0; constexpr uint32_t kSimpleUdtKind = 1; constexpr uint32_t kUnknownUdtKind = 2; } // namespace static std::string getUdtStatLabel(uint32_t Kind) { if (Kind == kNoneUdtKind) return ""; if (Kind == kSimpleUdtKind) return ""; if (Kind == kUnknownUdtKind) return ""; return formatTypeLeafKind(static_cast(Kind)); } static uint32_t getLongestTypeLeafName(const StatCollection &Stats) { size_t L = 0; for (const auto &Stat : Stats.Individual) { std::string Label = getUdtStatLabel(Stat.first); L = std::max(L, Label.size()); } return static_cast(L); } Error DumpOutputStyle::dumpUdtStats() { printHeader(P, "S_UDT Record Stats"); if (File.isPdb() && !getPdb().hasPDBGlobalsStream()) { printStreamNotPresent("Globals"); return Error::success(); } StatCollection UdtStats; StatCollection UdtTargetStats; AutoIndent Indent(P, 4); auto &TpiTypes = File.types(); StringMap NamespacedStats; size_t LongestNamespace = 0; auto HandleOneSymbol = [&](const CVSymbol &Sym) { if (Sym.kind() != SymbolKind::S_UDT) return; UdtStats.update(SymbolKind::S_UDT, Sym.length()); UDTSym UDT = cantFail(SymbolDeserializer::deserializeAs(Sym)); uint32_t Kind = 0; uint32_t RecordSize = 0; if (UDT.Type.isNoneType()) Kind = kNoneUdtKind; else if (UDT.Type.isSimple()) Kind = kSimpleUdtKind; else if (std::optional T = TpiTypes.tryGetType(UDT.Type)) { Kind = T->kind(); RecordSize = T->length(); } else Kind = kUnknownUdtKind; UdtTargetStats.update(Kind, RecordSize); size_t Pos = UDT.Name.find("::"); if (Pos == StringRef::npos) return; StringRef Scope = UDT.Name.take_front(Pos); if (Scope.empty() || !isValidNamespaceIdentifier(Scope)) return; LongestNamespace = std::max(LongestNamespace, Scope.size()); NamespacedStats[Scope].update(RecordSize); }; P.NewLine(); if (File.isPdb()) { auto &SymbolRecords = cantFail(getPdb().getPDBSymbolStream()); auto ExpGlobals = getPdb().getPDBGlobalsStream(); if (!ExpGlobals) return ExpGlobals.takeError(); for (uint32_t PubSymOff : ExpGlobals->getGlobalsTable()) { CVSymbol Sym = SymbolRecords.readRecord(PubSymOff); HandleOneSymbol(Sym); } } else { for (const auto &Sec : File.symbol_groups()) { for (const auto &SS : Sec.getDebugSubsections()) { if (SS.kind() != DebugSubsectionKind::Symbols) continue; DebugSymbolsSubsectionRef Symbols; BinaryStreamReader Reader(SS.getRecordData()); cantFail(Symbols.initialize(Reader)); for (const auto &S : Symbols) HandleOneSymbol(S); } } } LongestNamespace += StringRef(" namespace ''").size(); size_t LongestTypeLeafKind = getLongestTypeLeafName(UdtTargetStats); size_t FieldWidth = std::max(LongestNamespace, LongestTypeLeafKind); // Compute the max number of digits for count and size fields, including comma // separators. StringRef CountHeader("Count"); StringRef SizeHeader("Size"); size_t CD = NumDigits(UdtStats.Totals.Count); CD += (CD - 1) / 3; CD = std::max(CD, CountHeader.size()); size_t SD = NumDigits(UdtStats.Totals.Size); SD += (SD - 1) / 3; SD = std::max(SD, SizeHeader.size()); uint32_t TableWidth = FieldWidth + 3 + CD + 2 + SD + 1; P.formatLine("{0} | {1} {2}", fmt_align("Record Kind", AlignStyle::Right, FieldWidth), fmt_align(CountHeader, AlignStyle::Right, CD), fmt_align(SizeHeader, AlignStyle::Right, SD)); P.formatLine("{0}", fmt_repeat('-', TableWidth)); for (const auto &Stat : UdtTargetStats.getStatsSortedBySize()) { std::string Label = getUdtStatLabel(Stat.first); P.formatLine("{0} | {1:N} {2:N}", fmt_align(Label, AlignStyle::Right, FieldWidth), fmt_align(Stat.second.Count, AlignStyle::Right, CD), fmt_align(Stat.second.Size, AlignStyle::Right, SD)); } P.formatLine("{0}", fmt_repeat('-', TableWidth)); P.formatLine("{0} | {1:N} {2:N}", fmt_align("Total (S_UDT)", AlignStyle::Right, FieldWidth), fmt_align(UdtStats.Totals.Count, AlignStyle::Right, CD), fmt_align(UdtStats.Totals.Size, AlignStyle::Right, SD)); P.formatLine("{0}", fmt_repeat('-', TableWidth)); struct StrAndStat { StringRef Key; StatCollection::Stat Stat; }; // Print namespace stats in descending order of size. std::vector NamespacedStatsSorted; for (const auto &Stat : NamespacedStats) NamespacedStatsSorted.push_back({Stat.getKey(), Stat.second}); llvm::stable_sort(NamespacedStatsSorted, [](const StrAndStat &L, const StrAndStat &R) { return L.Stat.Size > R.Stat.Size; }); for (const auto &Stat : NamespacedStatsSorted) { std::string Label = std::string(formatv("namespace '{0}'", Stat.Key)); P.formatLine("{0} | {1:N} {2:N}", fmt_align(Label, AlignStyle::Right, FieldWidth), fmt_align(Stat.Stat.Count, AlignStyle::Right, CD), fmt_align(Stat.Stat.Size, AlignStyle::Right, SD)); } return Error::success(); } static void typesetLinesAndColumns(LinePrinter &P, uint32_t Start, const LineColumnEntry &E) { const uint32_t kMaxCharsPerLineNumber = 4; // 4 digit line number uint32_t MinColumnWidth = kMaxCharsPerLineNumber + 5; // Let's try to keep it under 100 characters constexpr uint32_t kMaxRowLength = 100; // At least 3 spaces between columns. uint32_t ColumnsPerRow = kMaxRowLength / (MinColumnWidth + 3); uint32_t ItemsLeft = E.LineNumbers.size(); auto LineIter = E.LineNumbers.begin(); while (ItemsLeft != 0) { uint32_t RowColumns = std::min(ItemsLeft, ColumnsPerRow); for (uint32_t I = 0; I < RowColumns; ++I) { LineInfo Line(LineIter->Flags); std::string LineStr; if (Line.isAlwaysStepInto()) LineStr = "ASI"; else if (Line.isNeverStepInto()) LineStr = "NSI"; else LineStr = utostr(Line.getStartLine()); char Statement = Line.isStatement() ? ' ' : '!'; P.format("{0} {1:X-} {2} ", fmt_align(LineStr, AlignStyle::Right, kMaxCharsPerLineNumber), fmt_align(Start + LineIter->Offset, AlignStyle::Right, 8, '0'), Statement); ++LineIter; --ItemsLeft; } P.NewLine(); } } Error DumpOutputStyle::dumpLines() { printHeader(P, "Lines"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } uint32_t LastModi = UINT32_MAX; uint32_t LastNameIndex = UINT32_MAX; return iterateModuleSubsections( File, PrintScope{P, 4}, [this, &LastModi, &LastNameIndex](uint32_t Modi, const SymbolGroup &Strings, DebugLinesSubsectionRef &Lines) -> Error { uint16_t Segment = Lines.header()->RelocSegment; uint32_t Begin = Lines.header()->RelocOffset; uint32_t End = Begin + Lines.header()->CodeSize; for (const auto &Block : Lines) { if (LastModi != Modi || LastNameIndex != Block.NameIndex) { LastModi = Modi; LastNameIndex = Block.NameIndex; Strings.formatFromChecksumsOffset(P, Block.NameIndex); } AutoIndent Indent(P, 2); P.formatLine("{0:X-4}:{1:X-8}-{2:X-8}, ", Segment, Begin, End); uint32_t Count = Block.LineNumbers.size(); if (Lines.hasColumnInfo()) P.format("line/column/addr entries = {0}", Count); else P.format("line/addr entries = {0}", Count); P.NewLine(); typesetLinesAndColumns(P, Begin, Block); } return Error::success(); }); } Error DumpOutputStyle::dumpInlineeLines() { printHeader(P, "Inlinee Lines"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } return iterateModuleSubsections( File, PrintScope{P, 2}, [this](uint32_t Modi, const SymbolGroup &Strings, DebugInlineeLinesSubsectionRef &Lines) -> Error { P.formatLine("{0,+8} | {1,+5} | {2}", "Inlinee", "Line", "Source File"); for (const auto &Entry : Lines) { P.formatLine("{0,+8} | {1,+5} | ", Entry.Header->Inlinee, fmtle(Entry.Header->SourceLineNum)); Strings.formatFromChecksumsOffset(P, Entry.Header->FileID, true); for (const auto &ExtraFileID : Entry.ExtraFiles) { P.formatLine(" "); Strings.formatFromChecksumsOffset(P, ExtraFileID, true); } } P.NewLine(); return Error::success(); }); } Error DumpOutputStyle::dumpXmi() { printHeader(P, "Cross Module Imports"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } return iterateModuleSubsections( File, PrintScope{P, 2}, [this](uint32_t Modi, const SymbolGroup &Strings, DebugCrossModuleImportsSubsectionRef &Imports) -> Error { P.formatLine("{0,=32} | {1}", "Imported Module", "Type IDs"); for (const auto &Xmi : Imports) { auto ExpectedModule = Strings.getNameFromStringTable(Xmi.Header->ModuleNameOffset); StringRef Module; SmallString<32> ModuleStorage; if (!ExpectedModule) { Module = "(unknown module)"; consumeError(ExpectedModule.takeError()); } else Module = *ExpectedModule; if (Module.size() > 32) { ModuleStorage = "..."; ModuleStorage += Module.take_back(32 - 3); Module = ModuleStorage; } std::vector TIs; for (const auto I : Xmi.Imports) TIs.push_back(std::string(formatv("{0,+10:X+}", fmtle(I)))); std::string Result = typesetItemList(TIs, P.getIndentLevel() + 35, 12, " "); P.formatLine("{0,+32} | {1}", Module, Result); } return Error::success(); }); } Error DumpOutputStyle::dumpXme() { printHeader(P, "Cross Module Exports"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } return iterateModuleSubsections( File, PrintScope{P, 2}, [this](uint32_t Modi, const SymbolGroup &Strings, DebugCrossModuleExportsSubsectionRef &Exports) -> Error { P.formatLine("{0,-10} | {1}", "Local ID", "Global ID"); for (const auto &Export : Exports) { P.formatLine("{0,+10:X+} | {1}", TypeIndex(Export.Local), TypeIndex(Export.Global)); } return Error::success(); }); } std::string formatFrameType(object::frame_type FT) { switch (FT) { case object::frame_type::Fpo: return "FPO"; case object::frame_type::NonFpo: return "Non-FPO"; case object::frame_type::Trap: return "Trap"; case object::frame_type::Tss: return "TSS"; } return ""; } Error DumpOutputStyle::dumpOldFpo(PDBFile &File) { printHeader(P, "Old FPO Data"); ExitOnError Err("Error dumping old fpo data:"); DbiStream &Dbi = Err(File.getPDBDbiStream()); if (!Dbi.hasOldFpoRecords()) { printStreamNotPresent("FPO"); return Error::success(); } const FixedStreamArray& Records = Dbi.getOldFpoRecords(); P.printLine(" RVA | Code | Locals | Params | Prolog | Saved Regs | Use " "BP | Has SEH | Frame Type"); for (const object::FpoData &FD : Records) { P.formatLine("{0:X-8} | {1,4} | {2,6} | {3,6} | {4,6} | {5,10} | {6,6} | " "{7,7} | {8,9}", uint32_t(FD.Offset), uint32_t(FD.Size), uint32_t(FD.NumLocals), uint32_t(FD.NumParams), FD.getPrologSize(), FD.getNumSavedRegs(), FD.useBP(), FD.hasSEH(), formatFrameType(FD.getFP())); } return Error::success(); } Error DumpOutputStyle::dumpNewFpo(PDBFile &File) { printHeader(P, "New FPO Data"); ExitOnError Err("Error dumping new fpo data:"); DbiStream &Dbi = Err(File.getPDBDbiStream()); if (!Dbi.hasNewFpoRecords()) { printStreamNotPresent("New FPO"); return Error::success(); } const DebugFrameDataSubsectionRef& FDS = Dbi.getNewFpoRecords(); P.printLine(" RVA | Code | Locals | Params | Stack | Prolog | Saved Regs " "| Has SEH | Has C++EH | Start | Program"); for (const FrameData &FD : FDS) { bool IsFuncStart = FD.Flags & FrameData::IsFunctionStart; bool HasEH = FD.Flags & FrameData::HasEH; bool HasSEH = FD.Flags & FrameData::HasSEH; auto &StringTable = Err(File.getStringTable()); auto Program = Err(StringTable.getStringForID(FD.FrameFunc)); P.formatLine("{0:X-8} | {1,4} | {2,6} | {3,6} | {4,5} | {5,6} | {6,10} | " "{7,7} | {8,9} | {9,5} | {10}", uint32_t(FD.RvaStart), uint32_t(FD.CodeSize), uint32_t(FD.LocalSize), uint32_t(FD.ParamsSize), uint32_t(FD.MaxStackSize), uint16_t(FD.PrologSize), uint16_t(FD.SavedRegsSize), HasSEH, HasEH, IsFuncStart, Program); } return Error::success(); } Error DumpOutputStyle::dumpFpo() { if (!File.isPdb()) { printStreamNotValidForObj(); return Error::success(); } PDBFile &File = getPdb(); if (!File.hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } if (auto EC = dumpOldFpo(File)) return EC; if (auto EC = dumpNewFpo(File)) return EC; return Error::success(); } Error DumpOutputStyle::dumpStringTableFromPdb() { AutoIndent Indent(P); auto IS = getPdb().getStringTable(); if (!IS) { P.formatLine("Not present in file"); consumeError(IS.takeError()); return Error::success(); } if (opts::dump::DumpStringTable) { if (IS->name_ids().empty()) P.formatLine("Empty"); else { auto MaxID = llvm::max_element(IS->name_ids()); uint32_t Digits = NumDigits(*MaxID); P.formatLine("{0} | {1}", fmt_align("ID", AlignStyle::Right, Digits), "String"); std::vector SortedIDs(IS->name_ids().begin(), IS->name_ids().end()); llvm::sort(SortedIDs); for (uint32_t I : SortedIDs) { auto ES = IS->getStringForID(I); llvm::SmallString<32> Str; if (!ES) { consumeError(ES.takeError()); Str = "Error reading string"; } else if (!ES->empty()) { Str.append("'"); Str.append(*ES); Str.append("'"); } if (!Str.empty()) P.formatLine("{0} | {1}", fmt_align(I, AlignStyle::Right, Digits), Str); } } } if (opts::dump::DumpStringTableDetails) { P.NewLine(); { P.printLine("String Table Header:"); AutoIndent Indent(P); P.formatLine("Signature: {0}", IS->getSignature()); P.formatLine("Hash Version: {0}", IS->getHashVersion()); P.formatLine("Name Buffer Size: {0}", IS->getByteSize()); P.NewLine(); } BinaryStreamRef NameBuffer = IS->getStringTable().getBuffer(); ArrayRef Contents; cantFail(NameBuffer.readBytes(0, NameBuffer.getLength(), Contents)); P.formatBinary("Name Buffer", Contents, 0); P.NewLine(); { P.printLine("Hash Table:"); AutoIndent Indent(P); P.formatLine("Bucket Count: {0}", IS->name_ids().size()); for (const auto &Entry : enumerate(IS->name_ids())) P.formatLine("Bucket[{0}] : {1}", Entry.index(), uint32_t(Entry.value())); P.formatLine("Name Count: {0}", IS->getNameCount()); } } return Error::success(); } Error DumpOutputStyle::dumpStringTableFromObj() { return iterateModuleSubsections( File, PrintScope{P, 4}, [&](uint32_t Modi, const SymbolGroup &Strings, DebugStringTableSubsectionRef &Strings2) -> Error { BinaryStreamRef StringTableBuffer = Strings2.getBuffer(); BinaryStreamReader Reader(StringTableBuffer); while (Reader.bytesRemaining() > 0) { StringRef Str; uint32_t Offset = Reader.getOffset(); cantFail(Reader.readCString(Str)); if (Str.empty()) continue; P.formatLine("{0} | {1}", fmt_align(Offset, AlignStyle::Right, 4), Str); } return Error::success(); }); } Error DumpOutputStyle::dumpNamedStreams() { printHeader(P, "Named Streams"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Invalid PDB File: "); auto &IS = Err(File.pdb().getPDBInfoStream()); const NamedStreamMap &NS = IS.getNamedStreams(); for (const auto &Entry : NS.entries()) { P.printLine(Entry.getKey()); AutoIndent Indent2(P, 2); P.formatLine("Index: {0}", Entry.getValue()); P.formatLine("Size in bytes: {0}", File.pdb().getStreamByteSize(Entry.getValue())); } return Error::success(); } Error DumpOutputStyle::dumpStringTable() { printHeader(P, "String Table"); if (File.isPdb()) return dumpStringTableFromPdb(); return dumpStringTableFromObj(); } static void buildDepSet(LazyRandomTypeCollection &Types, ArrayRef Indices, std::map &DepSet) { SmallVector DepList; for (const auto &I : Indices) { TypeIndex TI(I); if (DepSet.find(TI) != DepSet.end() || TI.isSimple() || TI.isNoneType()) continue; CVType Type = Types.getType(TI); DepSet[TI] = Type; codeview::discoverTypeIndices(Type, DepList); buildDepSet(Types, DepList, DepSet); } } static void dumpFullTypeStream(LinePrinter &Printer, LazyRandomTypeCollection &Types, TypeReferenceTracker *RefTracker, uint32_t NumTypeRecords, uint32_t NumHashBuckets, FixedStreamArray HashValues, TpiStream *Stream, bool Bytes, bool Extras) { Printer.formatLine("Showing {0:N} records", NumTypeRecords); uint32_t Width = NumDigits(TypeIndex::FirstNonSimpleIndex + NumTypeRecords); MinimalTypeDumpVisitor V(Printer, Width + 2, Bytes, Extras, Types, RefTracker, NumHashBuckets, HashValues, Stream); if (auto EC = codeview::visitTypeStream(Types, V)) { Printer.formatLine("An error occurred dumping type records: {0}", toString(std::move(EC))); } } static void dumpPartialTypeStream(LinePrinter &Printer, LazyRandomTypeCollection &Types, TypeReferenceTracker *RefTracker, TpiStream &Stream, ArrayRef TiList, bool Bytes, bool Extras, bool Deps) { uint32_t Width = NumDigits(TypeIndex::FirstNonSimpleIndex + Stream.getNumTypeRecords()); MinimalTypeDumpVisitor V(Printer, Width + 2, Bytes, Extras, Types, RefTracker, Stream.getNumHashBuckets(), Stream.getHashValues(), &Stream); if (opts::dump::DumpTypeDependents) { // If we need to dump all dependents, then iterate each index and find // all dependents, adding them to a map ordered by TypeIndex. std::map DepSet; buildDepSet(Types, TiList, DepSet); Printer.formatLine( "Showing {0:N} records and their dependents ({1:N} records total)", TiList.size(), DepSet.size()); for (auto &Dep : DepSet) { if (auto EC = codeview::visitTypeRecord(Dep.second, Dep.first, V)) Printer.formatLine("An error occurred dumping type record {0}: {1}", Dep.first, toString(std::move(EC))); } } else { Printer.formatLine("Showing {0:N} records.", TiList.size()); for (const auto &I : TiList) { TypeIndex TI(I); if (TI.isSimple()) { Printer.formatLine("{0} | {1}", fmt_align(I, AlignStyle::Right, Width), Types.getTypeName(TI)); } else if (std::optional Type = Types.tryGetType(TI)) { if (auto EC = codeview::visitTypeRecord(*Type, TI, V)) Printer.formatLine("An error occurred dumping type record {0}: {1}", TI, toString(std::move(EC))); } else { Printer.formatLine("Type {0} doesn't exist in TPI stream", TI); } } } } Error DumpOutputStyle::dumpTypesFromObjectFile() { LazyRandomTypeCollection Types(100); for (const auto &S : getObj().sections()) { Expected NameOrErr = S.getName(); if (!NameOrErr) return NameOrErr.takeError(); StringRef SectionName = *NameOrErr; // .debug$T is a standard CodeView type section, while .debug$P is the same // format but used for MSVC precompiled header object files. if (SectionName == ".debug$T") printHeader(P, "Types (.debug$T)"); else if (SectionName == ".debug$P") printHeader(P, "Precompiled Types (.debug$P)"); else continue; Expected ContentsOrErr = S.getContents(); if (!ContentsOrErr) return ContentsOrErr.takeError(); uint32_t Magic; BinaryStreamReader Reader(*ContentsOrErr, llvm::endianness::little); if (auto EC = Reader.readInteger(Magic)) return EC; if (Magic != COFF::DEBUG_SECTION_MAGIC) return make_error("Invalid CodeView debug section.", inconvertibleErrorCode()); Types.reset(Reader, 100); if (opts::dump::DumpTypes) { dumpFullTypeStream(P, Types, RefTracker.get(), 0, 0, {}, nullptr, opts::dump::DumpTypeData, false); } else if (opts::dump::DumpTypeExtras) { auto LocalHashes = LocallyHashedType::hashTypeCollection(Types); auto GlobalHashes = GloballyHashedType::hashTypeCollection(Types); assert(LocalHashes.size() == GlobalHashes.size()); P.formatLine("Local / Global hashes:"); TypeIndex TI(TypeIndex::FirstNonSimpleIndex); for (auto H : zip(LocalHashes, GlobalHashes)) { AutoIndent Indent2(P); LocallyHashedType &L = std::get<0>(H); GloballyHashedType &G = std::get<1>(H); P.formatLine("TI: {0}, LocalHash: {1:X}, GlobalHash: {2}", TI, L, G); ++TI; } P.NewLine(); } } return Error::success(); } Error DumpOutputStyle::dumpTpiStream(uint32_t StreamIdx) { assert(StreamIdx == StreamTPI || StreamIdx == StreamIPI); if (StreamIdx == StreamTPI) { printHeader(P, "Types (TPI Stream)"); } else if (StreamIdx == StreamIPI) { printHeader(P, "Types (IPI Stream)"); } assert(!File.isObj()); bool Present = false; bool DumpTypes = false; bool DumpBytes = false; bool DumpExtras = false; std::vector Indices; if (StreamIdx == StreamTPI) { Present = getPdb().hasPDBTpiStream(); DumpTypes = opts::dump::DumpTypes; DumpBytes = opts::dump::DumpTypeData; DumpExtras = opts::dump::DumpTypeExtras; Indices.assign(opts::dump::DumpTypeIndex.begin(), opts::dump::DumpTypeIndex.end()); } else if (StreamIdx == StreamIPI) { Present = getPdb().hasPDBIpiStream(); DumpTypes = opts::dump::DumpIds; DumpBytes = opts::dump::DumpIdData; DumpExtras = opts::dump::DumpIdExtras; Indices.assign(opts::dump::DumpIdIndex.begin(), opts::dump::DumpIdIndex.end()); } if (!Present) { printStreamNotPresent(StreamIdx == StreamTPI ? "TPI" : "IPI"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Unexpected error processing types: "); auto &Stream = Err((StreamIdx == StreamTPI) ? getPdb().getPDBTpiStream() : getPdb().getPDBIpiStream()); auto &Types = (StreamIdx == StreamTPI) ? File.types() : File.ids(); // Only emit notes about referenced/unreferenced for types. TypeReferenceTracker *MaybeTracker = (StreamIdx == StreamTPI) ? RefTracker.get() : nullptr; // Enable resolving forward decls. Stream.buildHashMap(); if (DumpTypes || !Indices.empty()) { if (Indices.empty()) dumpFullTypeStream(P, Types, MaybeTracker, Stream.getNumTypeRecords(), Stream.getNumHashBuckets(), Stream.getHashValues(), &Stream, DumpBytes, DumpExtras); else { std::vector TiList(Indices.begin(), Indices.end()); dumpPartialTypeStream(P, Types, MaybeTracker, Stream, TiList, DumpBytes, DumpExtras, opts::dump::DumpTypeDependents); } } if (DumpExtras) { P.NewLine(); P.formatLine("Header Version: {0}", static_cast(Stream.getTpiVersion())); P.formatLine("Hash Stream Index: {0}", Stream.getTypeHashStreamIndex()); P.formatLine("Aux Hash Stream Index: {0}", Stream.getTypeHashStreamAuxIndex()); P.formatLine("Hash Key Size: {0}", Stream.getHashKeySize()); P.formatLine("Num Hash Buckets: {0}", Stream.getNumHashBuckets()); auto IndexOffsets = Stream.getTypeIndexOffsets(); P.formatLine("Type Index Offsets:"); for (const auto &IO : IndexOffsets) { AutoIndent Indent2(P); P.formatLine("TI: {0}, Offset: {1}", IO.Type, fmtle(IO.Offset)); } if (getPdb().hasPDBStringTable()) { P.NewLine(); P.formatLine("Hash Adjusters:"); auto &Adjusters = Stream.getHashAdjusters(); auto &Strings = Err(getPdb().getStringTable()); for (const auto &A : Adjusters) { AutoIndent Indent2(P); auto ExpectedStr = Strings.getStringForID(A.first); TypeIndex TI(A.second); if (ExpectedStr) P.formatLine("`{0}` -> {1}", *ExpectedStr, TI); else { P.formatLine("unknown str id ({0}) -> {1}", A.first, TI); consumeError(ExpectedStr.takeError()); } } } } return Error::success(); } Error DumpOutputStyle::dumpModuleSymsForObj() { printHeader(P, "Symbols"); AutoIndent Indent(P); auto &Types = File.types(); SymbolVisitorCallbackPipeline Pipeline; SymbolDeserializer Deserializer(nullptr, CodeViewContainer::ObjectFile); MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Types, Types); Pipeline.addCallbackToPipeline(Deserializer); Pipeline.addCallbackToPipeline(Dumper); CVSymbolVisitor Visitor(Pipeline); return iterateModuleSubsections( File, PrintScope{P, 2}, [&](uint32_t Modi, const SymbolGroup &Strings, DebugSymbolsSubsectionRef &Symbols) -> Error { Dumper.setSymbolGroup(&Strings); for (auto Symbol : Symbols) { if (auto EC = Visitor.visitSymbolRecord(Symbol)) { return EC; } } return Error::success(); }); } Error DumpOutputStyle::dumpModuleSymsForPdb() { printHeader(P, "Symbols"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } AutoIndent Indent(P); auto &Ids = File.ids(); auto &Types = File.types(); return iterateSymbolGroups( File, PrintScope{P, 2}, [&](uint32_t I, const SymbolGroup &Strings) -> Error { auto ExpectedModS = getModuleDebugStream(File.pdb(), I); if (!ExpectedModS) { P.formatLine("Error loading module stream {0}. {1}", I, toString(ExpectedModS.takeError())); return Error::success(); } ModuleDebugStreamRef &ModS = *ExpectedModS; SymbolVisitorCallbackPipeline Pipeline; SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb); MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Strings, Ids, Types); Pipeline.addCallbackToPipeline(Deserializer); Pipeline.addCallbackToPipeline(Dumper); CVSymbolVisitor Visitor(Pipeline); auto SS = ModS.getSymbolsSubstream(); if (opts::Filters.SymbolOffset) { CVSymbolVisitor::FilterOptions Filter; Filter.SymbolOffset = opts::Filters.SymbolOffset; Filter.ParentRecursiveDepth = opts::Filters.ParentRecurseDepth; Filter.ChildRecursiveDepth = opts::Filters.ChildrenRecurseDepth; if (auto EC = Visitor.visitSymbolStreamFiltered(ModS.getSymbolArray(), Filter)) { P.formatLine("Error while processing symbol records. {0}", toStringWithoutConsuming(EC)); return EC; } } else if (auto EC = Visitor.visitSymbolStream(ModS.getSymbolArray(), SS.Offset)) { P.formatLine("Error while processing symbol records. {0}", toStringWithoutConsuming(EC)); return EC; } return Error::success(); }); } Error DumpOutputStyle::dumpTypeRefStats() { printHeader(P, "Type Reference Statistics"); AutoIndent Indent(P); // Sum the byte size of all type records, and the size and count of all // referenced records. size_t TotalRecs = File.types().size(); size_t RefRecs = 0; size_t TotalBytes = 0; size_t RefBytes = 0; auto &Types = File.types(); for (std::optional TI = Types.getFirst(); TI; TI = Types.getNext(*TI)) { CVType Type = File.types().getType(*TI); TotalBytes += Type.length(); if (RefTracker->isTypeReferenced(*TI)) { ++RefRecs; RefBytes += Type.length(); } } P.formatLine("Records referenced: {0:N} / {1:N} {2:P}", RefRecs, TotalRecs, (double)RefRecs / TotalRecs); P.formatLine("Bytes referenced: {0:N} / {1:N} {2:P}", RefBytes, TotalBytes, (double)RefBytes / TotalBytes); return Error::success(); } Error DumpOutputStyle::dumpGSIRecords() { printHeader(P, "GSI Records"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBSymbolStream()) { printStreamNotPresent("GSI Common Symbol"); return Error::success(); } AutoIndent Indent(P); auto &Records = cantFail(getPdb().getPDBSymbolStream()); auto &Types = File.types(); auto &Ids = File.ids(); P.printLine("Records"); SymbolVisitorCallbackPipeline Pipeline; SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb); MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Ids, Types); Pipeline.addCallbackToPipeline(Deserializer); Pipeline.addCallbackToPipeline(Dumper); CVSymbolVisitor Visitor(Pipeline); BinaryStreamRef SymStream = Records.getSymbolArray().getUnderlyingStream(); if (auto E = Visitor.visitSymbolStream(Records.getSymbolArray(), 0)) return E; return Error::success(); } Error DumpOutputStyle::dumpGlobals() { printHeader(P, "Global Symbols"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBGlobalsStream()) { printStreamNotPresent("Globals"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Error dumping globals stream: "); auto &Globals = Err(getPdb().getPDBGlobalsStream()); if (opts::dump::DumpGlobalNames.empty()) { const GSIHashTable &Table = Globals.getGlobalsTable(); Err(dumpSymbolsFromGSI(Table, opts::dump::DumpGlobalExtras)); } else { SymbolStream &SymRecords = cantFail(getPdb().getPDBSymbolStream()); auto &Types = File.types(); auto &Ids = File.ids(); SymbolVisitorCallbackPipeline Pipeline; SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb); MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Ids, Types); Pipeline.addCallbackToPipeline(Deserializer); Pipeline.addCallbackToPipeline(Dumper); CVSymbolVisitor Visitor(Pipeline); using ResultEntryType = std::pair; for (StringRef Name : opts::dump::DumpGlobalNames) { AutoIndent Indent(P); P.formatLine("Global Name `{0}`", Name); std::vector Results = Globals.findRecordsByName(Name, SymRecords); if (Results.empty()) { AutoIndent Indent(P); P.printLine("(no matching records found)"); continue; } for (ResultEntryType Result : Results) { if (auto E = Visitor.visitSymbolRecord(Result.second, Result.first)) return E; } } } return Error::success(); } Error DumpOutputStyle::dumpPublics() { printHeader(P, "Public Symbols"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBPublicsStream()) { printStreamNotPresent("Publics"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Error dumping publics stream: "); auto &Publics = Err(getPdb().getPDBPublicsStream()); const GSIHashTable &PublicsTable = Publics.getPublicsTable(); if (opts::dump::DumpPublicExtras) { P.printLine("Publics Header"); AutoIndent Indent(P); P.formatLine("sym hash = {0}, thunk table addr = {1}", Publics.getSymHash(), formatSegmentOffset(Publics.getThunkTableSection(), Publics.getThunkTableOffset())); } Err(dumpSymbolsFromGSI(PublicsTable, opts::dump::DumpPublicExtras)); // Skip the rest if we aren't dumping extras. if (!opts::dump::DumpPublicExtras) return Error::success(); P.formatLine("Address Map"); { // These are offsets into the publics stream sorted by secidx:secrel. AutoIndent Indent2(P); for (uint32_t Addr : Publics.getAddressMap()) P.formatLine("off = {0}", Addr); } // The thunk map is optional debug info used for ILT thunks. if (!Publics.getThunkMap().empty()) { P.formatLine("Thunk Map"); AutoIndent Indent2(P); for (uint32_t Addr : Publics.getThunkMap()) P.formatLine("{0:x8}", Addr); } // The section offsets table appears to be empty when incremental linking // isn't in use. if (!Publics.getSectionOffsets().empty()) { P.formatLine("Section Offsets"); AutoIndent Indent2(P); for (const SectionOffset &SO : Publics.getSectionOffsets()) P.formatLine("{0:x4}:{1:x8}", uint16_t(SO.Isect), uint32_t(SO.Off)); } return Error::success(); } Error DumpOutputStyle::dumpSymbolsFromGSI(const GSIHashTable &Table, bool HashExtras) { auto ExpectedSyms = getPdb().getPDBSymbolStream(); if (!ExpectedSyms) return ExpectedSyms.takeError(); auto &Types = File.types(); auto &Ids = File.ids(); if (HashExtras) { P.printLine("GSI Header"); AutoIndent Indent(P); P.formatLine("sig = {0:X}, hdr = {1:X}, hr size = {2}, num buckets = {3}", Table.getVerSignature(), Table.getVerHeader(), Table.getHashRecordSize(), Table.getNumBuckets()); } { P.printLine("Records"); SymbolVisitorCallbackPipeline Pipeline; SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb); MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Ids, Types); Pipeline.addCallbackToPipeline(Deserializer); Pipeline.addCallbackToPipeline(Dumper); CVSymbolVisitor Visitor(Pipeline); BinaryStreamRef SymStream = ExpectedSyms->getSymbolArray().getUnderlyingStream(); for (uint32_t PubSymOff : Table) { Expected Sym = readSymbolFromStream(SymStream, PubSymOff); if (!Sym) return Sym.takeError(); if (auto E = Visitor.visitSymbolRecord(*Sym, PubSymOff)) return E; } } // Return early if we aren't dumping public hash table and address map info. if (HashExtras) { P.formatLine("Hash Entries"); { AutoIndent Indent2(P); for (const PSHashRecord &HR : Table.HashRecords) P.formatLine("off = {0}, refcnt = {1}", uint32_t(HR.Off), uint32_t(HR.CRef)); } P.formatLine("Hash Buckets"); { AutoIndent Indent2(P); for (uint32_t Hash : Table.HashBuckets) P.formatLine("{0:x8}", Hash); } } return Error::success(); } static std::string formatSegMapDescriptorFlag(uint32_t IndentLevel, OMFSegDescFlags Flags) { std::vector Opts; if (Flags == OMFSegDescFlags::None) return "none"; PUSH_FLAG(OMFSegDescFlags, Read, Flags, "read"); PUSH_FLAG(OMFSegDescFlags, Write, Flags, "write"); PUSH_FLAG(OMFSegDescFlags, Execute, Flags, "execute"); PUSH_FLAG(OMFSegDescFlags, AddressIs32Bit, Flags, "32 bit addr"); PUSH_FLAG(OMFSegDescFlags, IsSelector, Flags, "selector"); PUSH_FLAG(OMFSegDescFlags, IsAbsoluteAddress, Flags, "absolute addr"); PUSH_FLAG(OMFSegDescFlags, IsGroup, Flags, "group"); return typesetItemList(Opts, IndentLevel, 4, " | "); } Error DumpOutputStyle::dumpSectionHeaders() { dumpSectionHeaders("Section Headers", DbgHeaderType::SectionHdr); dumpSectionHeaders("Original Section Headers", DbgHeaderType::SectionHdrOrig); return Error::success(); } void DumpOutputStyle::dumpSectionHeaders(StringRef Label, DbgHeaderType Type) { printHeader(P, Label); if (File.isObj()) { printStreamNotValidForObj(); return; } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return; } AutoIndent Indent(P); ExitOnError Err("Error dumping section headers: "); std::unique_ptr Stream; ArrayRef Headers; auto ExpectedHeaders = loadSectionHeaders(getPdb(), Type); if (!ExpectedHeaders) { P.printLine(toString(ExpectedHeaders.takeError())); return; } std::tie(Stream, Headers) = std::move(*ExpectedHeaders); uint32_t I = 1; for (const auto &Header : Headers) { P.NewLine(); P.formatLine("SECTION HEADER #{0}", I); P.formatLine("{0,8} name", Header.Name); P.formatLine("{0,8:X-} virtual size", uint32_t(Header.VirtualSize)); P.formatLine("{0,8:X-} virtual address", uint32_t(Header.VirtualAddress)); P.formatLine("{0,8:X-} size of raw data", uint32_t(Header.SizeOfRawData)); P.formatLine("{0,8:X-} file pointer to raw data", uint32_t(Header.PointerToRawData)); P.formatLine("{0,8:X-} file pointer to relocation table", uint32_t(Header.PointerToRelocations)); P.formatLine("{0,8:X-} file pointer to line numbers", uint32_t(Header.PointerToLinenumbers)); P.formatLine("{0,8:X-} number of relocations", uint32_t(Header.NumberOfRelocations)); P.formatLine("{0,8:X-} number of line numbers", uint32_t(Header.NumberOfLinenumbers)); P.formatLine("{0,8:X-} flags", uint32_t(Header.Characteristics)); AutoIndent IndentMore(P, 9); P.formatLine("{0}", formatSectionCharacteristics( P.getIndentLevel(), Header.Characteristics, 1, "")); ++I; } } Error DumpOutputStyle::dumpSectionContribs() { printHeader(P, "Section Contributions"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Error dumping section contributions: "); DbiStream &Dbi = Err(getPdb().getPDBDbiStream()); class Visitor : public ISectionContribVisitor { public: Visitor(LinePrinter &P, ArrayRef Names) : P(P), Names(Names) { auto Max = llvm::max_element(Names, [](StringRef S1, StringRef S2) { return S1.size() < S2.size(); }); MaxNameLen = (Max == Names.end() ? 0 : Max->size()); } void visit(const SectionContrib &SC) override { dumpSectionContrib(P, SC, Names, MaxNameLen); } void visit(const SectionContrib2 &SC) override { dumpSectionContrib(P, SC, Names, MaxNameLen); } private: LinePrinter &P; uint32_t MaxNameLen; ArrayRef Names; }; auto NamesOrErr = getSectionNames(getPdb()); if (!NamesOrErr) return NamesOrErr.takeError(); ArrayRef Names = *NamesOrErr; Visitor V(P, Names); Dbi.visitSectionContributions(V); return Error::success(); } Error DumpOutputStyle::dumpSectionMap() { printHeader(P, "Section Map"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Error dumping section map: "); DbiStream &Dbi = Err(getPdb().getPDBDbiStream()); uint32_t I = 0; for (auto &M : Dbi.getSectionMap()) { P.formatLine( "Section {0:4} | ovl = {1}, group = {2}, frame = {3}, name = {4}", I, fmtle(M.Ovl), fmtle(M.Group), fmtle(M.Frame), fmtle(M.SecName)); P.formatLine(" class = {0}, offset = {1}, size = {2}", fmtle(M.ClassName), fmtle(M.Offset), fmtle(M.SecByteLength)); P.formatLine(" flags = {0}", formatSegMapDescriptorFlag( P.getIndentLevel() + 13, static_cast(uint16_t(M.Flags)))); ++I; } return Error::success(); }