[llvm-pdbutil] Dig deeper into the PDB and DBI streams when explaining.

This will show more detail when using `llvm-pdbutil explain` on an
offset in the DBI or PDB streams.  Specifically, it will dig into
individual header fields and substreams to give a more precise
description of what the byte represents.

llvm-svn: 328878
diff --git a/llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp b/llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp
index c4fbde2..70deb2e 100644
--- a/llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp
+++ b/llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp
@@ -13,8 +13,14 @@
 #include "StreamUtil.h"
 #include "llvm-pdbutil.h"
 
+#include "llvm/DebugInfo/CodeView/Formatters.h"
 #include "llvm/DebugInfo/MSF/MappedBlockStream.h"
+#include "llvm/DebugInfo/PDB/Native/DbiStream.h"
+#include "llvm/DebugInfo/PDB/Native/InfoStream.h"
 #include "llvm/DebugInfo/PDB/Native/PDBFile.h"
+#include "llvm/DebugInfo/PDB/Native/RawTypes.h"
+#include "llvm/Support/BinaryStreamArray.h"
+#include "llvm/Support/Error.h"
 
 using namespace llvm;
 using namespace llvm::codeview;
@@ -201,6 +207,20 @@
   P.formatLine("Address is at offset {0}/{1} of Stream {2} ({3}){4}.",
                StreamOff, Layout.Length, Stream, S.getLongName(),
                (StreamOff > Layout.Length) ? " in unused space" : "");
+  switch (S.getPurpose()) {
+  case StreamPurpose::DBI:
+    explainDbiStream(Stream, StreamOff);
+    break;
+  case StreamPurpose::PDB:
+    explainPdbStream(Stream, StreamOff);
+    break;
+  case StreamPurpose::IPI:
+  case StreamPurpose::TPI:
+  case StreamPurpose::ModuleStream:
+  case StreamPurpose::NamedStream:
+  default:
+    break;
+  }
 }
 
 void ExplainOutputStyle::explainStreamDirectoryOffset() {
@@ -218,3 +238,183 @@
 void ExplainOutputStyle::explainUnknownBlock() {
   P.formatLine("Address has unknown purpose.");
 }
+
+template <typename T>
+static void printStructField(LinePrinter &P, StringRef Label, T Value) {
+  P.formatLine("which contains {0}.", Label);
+  P.formatLine("The current value is {0}.", Value);
+}
+
+static void explainDbiHeaderOffset(LinePrinter &P, DbiStream &Dbi,
+                                   uint32_t Offset) {
+  const DbiStreamHeader *Header = Dbi.getHeader();
+  assert(Header != nullptr);
+
+  if (Offset < endof(DbiStreamHeader, VersionSignature))
+    printStructField(P, "the DBI Stream Version Signature",
+                     int32_t(Header->VersionSignature));
+  else if (Offset < endof(DbiStreamHeader, VersionHeader))
+    printStructField(P, "the DBI Stream Version Header",
+                     uint32_t(Header->VersionHeader));
+  else if (Offset < endof(DbiStreamHeader, Age))
+    printStructField(P, "the age of the DBI Stream", uint32_t(Header->Age));
+  else if (Offset < endof(DbiStreamHeader, GlobalSymbolStreamIndex))
+    printStructField(P, "the index of the Global Symbol Stream",
+                     uint16_t(Header->GlobalSymbolStreamIndex));
+  else if (Offset < endof(DbiStreamHeader, BuildNumber))
+    printStructField(P, "the build number", uint16_t(Header->BuildNumber));
+  else if (Offset < endof(DbiStreamHeader, PublicSymbolStreamIndex))
+    printStructField(P, "the index of the Public Symbol Stream",
+                     uint16_t(Header->PublicSymbolStreamIndex));
+  else if (Offset < endof(DbiStreamHeader, PdbDllVersion))
+    printStructField(P, "the version of mspdb.dll",
+                     uint16_t(Header->PdbDllVersion));
+  else if (Offset < endof(DbiStreamHeader, SymRecordStreamIndex))
+    printStructField(P, "the index of the Symbol Record Stream",
+                     uint16_t(Header->SymRecordStreamIndex));
+  else if (Offset < endof(DbiStreamHeader, PdbDllRbld))
+    printStructField(P, "the rbld of mspdb.dll", uint16_t(Header->PdbDllRbld));
+  else if (Offset < endof(DbiStreamHeader, ModiSubstreamSize))
+    printStructField(P, "the size of the Module Info Substream",
+                     int32_t(Header->ModiSubstreamSize));
+  else if (Offset < endof(DbiStreamHeader, SecContrSubstreamSize))
+    printStructField(P, "the size of the Section Contribution Substream",
+                     int32_t(Header->SecContrSubstreamSize));
+  else if (Offset < endof(DbiStreamHeader, SectionMapSize))
+    printStructField(P, "the size of the Section Map Substream",
+                     int32_t(Header->SectionMapSize));
+  else if (Offset < endof(DbiStreamHeader, FileInfoSize))
+    printStructField(P, "the size of the File Info Substream",
+                     int32_t(Header->FileInfoSize));
+  else if (Offset < endof(DbiStreamHeader, TypeServerSize))
+    printStructField(P, "the size of the Type Server Map",
+                     int32_t(Header->TypeServerSize));
+  else if (Offset < endof(DbiStreamHeader, MFCTypeServerIndex))
+    printStructField(P, "the index of the MFC Type Server stream",
+                     uint32_t(Header->MFCTypeServerIndex));
+  else if (Offset < endof(DbiStreamHeader, OptionalDbgHdrSize))
+    printStructField(P, "the size of the Optional Debug Stream array",
+                     int32_t(Header->OptionalDbgHdrSize));
+  else if (Offset < endof(DbiStreamHeader, ECSubstreamSize))
+    printStructField(P, "the size of the Edit & Continue Substream",
+                     int32_t(Header->ECSubstreamSize));
+  else if (Offset < endof(DbiStreamHeader, Flags))
+    printStructField(P, "the DBI Stream flags", uint16_t(Header->Flags));
+  else if (Offset < endof(DbiStreamHeader, MachineType))
+    printStructField(P, "the machine type", uint16_t(Header->MachineType));
+  else if (Offset < endof(DbiStreamHeader, Reserved))
+    printStructField(P, "reserved data", uint32_t(Header->Reserved));
+}
+
+static void explainDbiModiSubstreamOffset(LinePrinter &P, DbiStream &Dbi,
+                                          uint32_t Offset) {
+  VarStreamArray<DbiModuleDescriptor> ModuleDescriptors;
+  BinaryStreamRef ModiSubstreamData = Dbi.getModiSubstreamData().StreamData;
+  BinaryStreamReader Reader(ModiSubstreamData);
+
+  cantFail(Reader.readArray(ModuleDescriptors, ModiSubstreamData.getLength()));
+  auto Prev = ModuleDescriptors.begin();
+  assert(Prev.offset() == 0);
+  auto Current = Prev;
+  uint32_t Index = 0;
+  while (true) {
+    Prev = Current;
+    ++Current;
+    if (Current == ModuleDescriptors.end() || Offset < Current.offset())
+      break;
+    ++Index;
+  }
+
+  DbiModuleDescriptor &Descriptor = *Prev;
+  P.formatLine("which contains the descriptor for module {0} ({1}).", Index,
+               Descriptor.getModuleName());
+}
+
+template <typename T>
+static void dontExplain(LinePrinter &Printer, T &Stream, uint32_t Offset) {}
+
+template <typename T, typename SubstreamRangeT>
+static void explainSubstreamOffset(LinePrinter &P, uint32_t OffsetInStream,
+                                   T &Stream,
+                                   const SubstreamRangeT &Substreams) {
+  uint32_t SubOffset = OffsetInStream;
+  for (const auto &Entry : Substreams) {
+    if (Entry.Size == 0)
+      continue;
+    if (SubOffset < Entry.Size) {
+      P.formatLine("address is at offset {0}/{1} of the {2}.", SubOffset,
+                   Entry.Size, Entry.Label);
+      Entry.Explain(P, Stream, SubOffset);
+      return;
+    }
+    SubOffset -= Entry.Size;
+  }
+}
+
+void ExplainOutputStyle::explainDbiStream(uint32_t StreamIdx,
+                                          uint32_t OffsetInStream) {
+  P.printLine("Within the DBI stream:");
+  DbiStream &Dbi = cantFail(File.getPDBDbiStream());
+  AutoIndent Indent(P);
+  const DbiStreamHeader *Header = Dbi.getHeader();
+  assert(Header != nullptr);
+
+  struct SubstreamInfo {
+    uint32_t Size;
+    StringRef Label;
+    void (*Explain)(LinePrinter &, DbiStream &, uint32_t);
+  } Substreams[] = {
+      {sizeof(DbiStreamHeader), "DBI Stream Header", explainDbiHeaderOffset},
+      {Header->ModiSubstreamSize, "Module Info Substream",
+       explainDbiModiSubstreamOffset},
+      {Header->SecContrSubstreamSize, "Section Contribution Substream",
+       dontExplain<DbiStream>},
+      {Header->SectionMapSize, "Section Map", dontExplain<DbiStream>},
+      {Header->FileInfoSize, "File Info Substream", dontExplain<DbiStream>},
+      {Header->TypeServerSize, "Type Server Map Substream",
+       dontExplain<DbiStream>},
+      {Header->ECSubstreamSize, "Edit & Continue Substream",
+       dontExplain<DbiStream>},
+      {Header->OptionalDbgHdrSize, "Optional Debug Stream Array",
+       dontExplain<DbiStream>},
+  };
+
+  explainSubstreamOffset(P, OffsetInStream, Dbi, Substreams);
+}
+
+static void explainPdbStreamHeaderOffset(LinePrinter &P, InfoStream &Info,
+                                         uint32_t Offset) {
+  const InfoStreamHeader *Header = Info.getHeader();
+  assert(Header != nullptr);
+
+  if (Offset < endof(InfoStreamHeader, Version))
+    printStructField(P, "the PDB Stream Version Signature",
+                     uint32_t(Header->Version));
+  else if (Offset < endof(InfoStreamHeader, Signature))
+    printStructField(P, "the signature of the PDB Stream",
+                     uint32_t(Header->Signature));
+  else if (Offset < endof(InfoStreamHeader, Age))
+    printStructField(P, "the age of the PDB", uint32_t(Header->Age));
+  else if (Offset < endof(InfoStreamHeader, Guid))
+    printStructField(P, "the guid of the PDB", fmt_guid(Header->Guid.Guid));
+}
+
+void ExplainOutputStyle::explainPdbStream(uint32_t StreamIdx,
+                                          uint32_t OffsetInStream) {
+  P.printLine("Within the PDB stream:");
+  InfoStream &Info = cantFail(File.getPDBInfoStream());
+  AutoIndent Indent(P);
+
+  struct SubstreamInfo {
+    uint32_t Size;
+    StringRef Label;
+    void (*Explain)(LinePrinter &, InfoStream &, uint32_t);
+  } Substreams[] = {{sizeof(InfoStreamHeader), "PDB Stream Header",
+                     explainPdbStreamHeaderOffset},
+                    {Info.getNamedStreamMapByteSize(), "Named Stream Map",
+                     dontExplain<InfoStream>},
+                    {Info.getStreamSize(), "PDB Feature Signatures",
+                     dontExplain<InfoStream>}};
+
+  explainSubstreamOffset(P, OffsetInStream, Info, Substreams);
+}
diff --git a/llvm/tools/llvm-pdbutil/ExplainOutputStyle.h b/llvm/tools/llvm-pdbutil/ExplainOutputStyle.h
index 386f615..383007b 100644
--- a/llvm/tools/llvm-pdbutil/ExplainOutputStyle.h
+++ b/llvm/tools/llvm-pdbutil/ExplainOutputStyle.h
@@ -19,6 +19,7 @@
 
 namespace pdb {
 
+class DbiStream;
 class PDBFile;
 
 class ExplainOutputStyle : public OutputStyle {
@@ -47,6 +48,9 @@
   void explainStreamOffset(uint32_t Stream);
   void explainUnknownBlock();
 
+  void explainDbiStream(uint32_t StreamIdx, uint32_t OffsetInStream);
+  void explainPdbStream(uint32_t StreamIdx, uint32_t OffsetInStream);
+
   PDBFile &File;
   const uint64_t FileOffset;
   const uint64_t BlockIndex;
diff --git a/llvm/tools/llvm-pdbutil/StreamUtil.cpp b/llvm/tools/llvm-pdbutil/StreamUtil.cpp
index 991c99a..367d947 100644
--- a/llvm/tools/llvm-pdbutil/StreamUtil.cpp
+++ b/llvm/tools/llvm-pdbutil/StreamUtil.cpp
@@ -49,16 +49,9 @@
   return Result;
 }
 
-static inline StreamInfo otherStream(StringRef Label, uint32_t Idx) {
-  return StreamInfo::createStream(StreamPurpose::Other, Label, Idx);
-}
-
-static inline StreamInfo namedStream(StringRef Label, uint32_t Idx) {
-  return StreamInfo::createStream(StreamPurpose::NamedStream, Label, Idx);
-}
-
-static inline StreamInfo symbolStream(StringRef Label, uint32_t Idx) {
-  return StreamInfo::createStream(StreamPurpose::Symbols, Label, Idx);
+static inline StreamInfo stream(StreamPurpose Purpose, StringRef Label,
+                                uint32_t Idx) {
+  return StreamInfo::createStream(Purpose, Label, Idx);
 }
 
 static inline StreamInfo moduleStream(StringRef Label, uint32_t StreamIdx,
@@ -105,60 +98,75 @@
   Streams.resize(StreamCount);
   for (uint16_t StreamIdx = 0; StreamIdx < StreamCount; ++StreamIdx) {
     if (StreamIdx == OldMSFDirectory)
-      Streams[StreamIdx] = otherStream("Old MSF Directory", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "Old MSF Directory", StreamIdx);
     else if (StreamIdx == StreamPDB)
-      Streams[StreamIdx] = otherStream("PDB Stream", StreamIdx);
+      Streams[StreamIdx] = stream(StreamPurpose::PDB, "PDB Stream", StreamIdx);
     else if (StreamIdx == StreamDBI)
-      Streams[StreamIdx] = otherStream("DBI Stream", StreamIdx);
+      Streams[StreamIdx] = stream(StreamPurpose::DBI, "DBI Stream", StreamIdx);
     else if (StreamIdx == StreamTPI)
-      Streams[StreamIdx] = otherStream("TPI Stream", StreamIdx);
+      Streams[StreamIdx] = stream(StreamPurpose::TPI, "TPI Stream", StreamIdx);
     else if (StreamIdx == StreamIPI)
-      Streams[StreamIdx] = otherStream("IPI Stream", StreamIdx);
+      Streams[StreamIdx] = stream(StreamPurpose::IPI, "IPI Stream", StreamIdx);
     else if (Dbi && StreamIdx == Dbi->getGlobalSymbolStreamIndex())
-      Streams[StreamIdx] = otherStream("Global Symbol Hash", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::GlobalHash, "Global Symbol Hash", StreamIdx);
     else if (Dbi && StreamIdx == Dbi->getPublicSymbolStreamIndex())
-      Streams[StreamIdx] = otherStream("Public Symbol Hash", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::PublicHash, "Public Symbol Hash", StreamIdx);
     else if (Dbi && StreamIdx == Dbi->getSymRecordStreamIndex())
-      Streams[StreamIdx] = symbolStream("Symbol Records", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Symbols, "Symbol Records", StreamIdx);
     else if (Tpi && StreamIdx == Tpi->getTypeHashStreamIndex())
-      Streams[StreamIdx] = otherStream("TPI Hash", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::TpiHash, "TPI Hash", StreamIdx);
     else if (Tpi && StreamIdx == Tpi->getTypeHashStreamAuxIndex())
-      Streams[StreamIdx] = otherStream("TPI Aux Hash", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "TPI Aux Hash", StreamIdx);
     else if (Ipi && StreamIdx == Ipi->getTypeHashStreamIndex())
-      Streams[StreamIdx] = otherStream("IPI Hash", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::IpiHash, "IPI Hash", StreamIdx);
     else if (Ipi && StreamIdx == Ipi->getTypeHashStreamAuxIndex())
-      Streams[StreamIdx] = otherStream("IPI Aux Hash", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "IPI Aux Hash", StreamIdx);
     else if (Dbi &&
              StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::Exception))
-      Streams[StreamIdx] = otherStream("Exception Data", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "Exception Data", StreamIdx);
     else if (Dbi && StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::Fixup))
-      Streams[StreamIdx] = otherStream("Fixup Data", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "Fixup Data", StreamIdx);
     else if (Dbi && StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::FPO))
-      Streams[StreamIdx] = otherStream("FPO Data", StreamIdx);
+      Streams[StreamIdx] = stream(StreamPurpose::Other, "FPO Data", StreamIdx);
     else if (Dbi &&
              StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::NewFPO))
-      Streams[StreamIdx] = otherStream("New FPO Data", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "New FPO Data", StreamIdx);
     else if (Dbi &&
              StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::OmapFromSrc))
-      Streams[StreamIdx] = otherStream("Omap From Source Data", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "Omap From Source Data", StreamIdx);
     else if (Dbi &&
              StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::OmapToSrc))
-      Streams[StreamIdx] = otherStream("Omap To Source Data", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "Omap To Source Data", StreamIdx);
     else if (Dbi && StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::Pdata))
-      Streams[StreamIdx] = otherStream("Pdata", StreamIdx);
+      Streams[StreamIdx] = stream(StreamPurpose::Other, "Pdata", StreamIdx);
     else if (Dbi &&
              StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::SectionHdr))
-      Streams[StreamIdx] = otherStream("Section Header Data", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "Section Header Data", StreamIdx);
     else if (Dbi &&
              StreamIdx ==
                  Dbi->getDebugStreamIndex(DbgHeaderType::SectionHdrOrig))
-      Streams[StreamIdx] =
-          otherStream("Section Header Original Data", StreamIdx);
+      Streams[StreamIdx] = stream(StreamPurpose::Other,
+                                  "Section Header Original Data", StreamIdx);
     else if (Dbi &&
              StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::TokenRidMap))
-      Streams[StreamIdx] = otherStream("Token Rid Data", StreamIdx);
+      Streams[StreamIdx] =
+          stream(StreamPurpose::Other, "Token Rid Data", StreamIdx);
     else if (Dbi && StreamIdx == Dbi->getDebugStreamIndex(DbgHeaderType::Xdata))
-      Streams[StreamIdx] = otherStream("Xdata", StreamIdx);
+      Streams[StreamIdx] = stream(StreamPurpose::Other, "Xdata", StreamIdx);
     else {
       auto ModIter = ModStreams.find(StreamIdx);
       auto NSIter = NamedStreams.find(StreamIdx);
@@ -167,9 +175,10 @@
             moduleStream(ModIter->second.Descriptor.getModuleName(), StreamIdx,
                          ModIter->second.Modi);
       } else if (NSIter != NamedStreams.end()) {
-        Streams[StreamIdx] = namedStream(NSIter->second, StreamIdx);
+        Streams[StreamIdx] =
+            stream(StreamPurpose::NamedStream, NSIter->second, StreamIdx);
       } else {
-        Streams[StreamIdx] = otherStream("???", StreamIdx);
+        Streams[StreamIdx] = stream(StreamPurpose::Other, "???", StreamIdx);
       }
     }
   }
diff --git a/llvm/tools/llvm-pdbutil/StreamUtil.h b/llvm/tools/llvm-pdbutil/StreamUtil.h
index 443267c..0e2e807 100644
--- a/llvm/tools/llvm-pdbutil/StreamUtil.h
+++ b/llvm/tools/llvm-pdbutil/StreamUtil.h
@@ -19,7 +19,20 @@
 namespace llvm {
 namespace pdb {
 class PDBFile;
-enum class StreamPurpose { NamedStream, ModuleStream, Symbols, Other };
+enum class StreamPurpose {
+  NamedStream,
+  ModuleStream,
+  Symbols,
+  PDB,
+  DBI,
+  TPI,
+  IPI,
+  GlobalHash,
+  PublicHash,
+  TpiHash,
+  IpiHash,
+  Other
+};
 
 struct StreamInfo {
 public:
diff --git a/llvm/tools/llvm-pdbutil/llvm-pdbutil.cpp b/llvm/tools/llvm-pdbutil/llvm-pdbutil.cpp
index f472bfa..c96761f 100644
--- a/llvm/tools/llvm-pdbutil/llvm-pdbutil.cpp
+++ b/llvm/tools/llvm-pdbutil/llvm-pdbutil.cpp
@@ -615,9 +615,8 @@
                                     cl::desc("<input PDB file>"), cl::Required,
                                     cl::sub(ExplainSubcommand));
 
-cl::opt<uint64_t> Offset("offset", cl::desc("The file offset to explain"),
-                         cl::sub(ExplainSubcommand), cl::Required,
-                         cl::OneOrMore);
+cl::list<uint64_t> Offsets("offset", cl::desc("The file offset to explain"),
+                           cl::sub(ExplainSubcommand), cl::OneOrMore);
 } // namespace explain
 }
 
@@ -1091,9 +1090,12 @@
 static void explain() {
   std::unique_ptr<IPDBSession> Session;
   PDBFile &File = loadPDB(opts::explain::InputFilename.front(), Session);
-  auto O = llvm::make_unique<ExplainOutputStyle>(File, opts::explain::Offset);
 
-  ExitOnErr(O->dump());
+  for (uint64_t Off : opts::explain::Offsets) {
+    auto O = llvm::make_unique<ExplainOutputStyle>(File, Off);
+
+    ExitOnErr(O->dump());
+  }
 }
 
 static bool parseRange(StringRef Str,
diff --git a/llvm/tools/llvm-pdbutil/llvm-pdbutil.h b/llvm/tools/llvm-pdbutil/llvm-pdbutil.h
index 6765b93..e7ad269 100644
--- a/llvm/tools/llvm-pdbutil/llvm-pdbutil.h
+++ b/llvm/tools/llvm-pdbutil/llvm-pdbutil.h
@@ -191,7 +191,7 @@
 
 namespace explain {
 extern llvm::cl::list<std::string> InputFilename;
-extern llvm::cl::opt<uint64_t> Offset;
+extern llvm::cl::list<uint64_t> Offsets;
 } // namespace explain
 }