[llvm-pdbutil] Support dumping CodeView from object files.

We have llvm-readobj for dumping CodeView from object files, and
llvm-pdbutil has always been more focused on PDB.  However,
llvm-pdbutil has a lot of useful options for summarizing debug
information in aggregate and presenting high level statistical
views.  Furthermore, it's arguably better as a testing tool since
we don't have to write tests to conform to a state-machine like
structure where you match multiple lines in succession, each
depending on a previous match.  llvm-pdbutil dumps much more
concisely, so it's possible to use single-line matches in many
cases where as with readobj tests you have to use multi-line
matches with an implicit state machine.

Because of this, I'm adding object file support to llvm-pdbutil.
In fact, this mirrors the cvdump tool from Microsoft, which also
supports both object files and pdb files.  In the future we could
perhaps rename this tool llvm-cvutil.

In the meantime, this allows us to deep dive into object files
the same way we already can with PDB files.

llvm-svn: 312358
diff --git a/llvm/tools/llvm-pdbutil/DumpOutputStyle.cpp b/llvm/tools/llvm-pdbutil/DumpOutputStyle.cpp
index bc3b5b9..e25d53c 100644
--- a/llvm/tools/llvm-pdbutil/DumpOutputStyle.cpp
+++ b/llvm/tools/llvm-pdbutil/DumpOutputStyle.cpp
@@ -10,6 +10,7 @@
 #include "DumpOutputStyle.h"
 
 #include "FormatUtil.h"
+#include "InputFile.h"
 #include "MinimalSymbolDumper.h"
 #include "MinimalTypeDumper.h"
 #include "StreamUtil.h"
@@ -67,9 +68,12 @@
 using namespace llvm::msf;
 using namespace llvm::pdb;
 
-DumpOutputStyle::DumpOutputStyle(PDBFile &File)
+DumpOutputStyle::DumpOutputStyle(InputFile &File)
     : File(File), P(2, false, outs()) {}
 
+PDBFile &DumpOutputStyle::getPdb() { return File.pdb(); }
+object::COFFObjectFile &DumpOutputStyle::getObj() { return File.obj(); }
+
 Error DumpOutputStyle::dump() {
   if (opts::dump::DumpSummary) {
     if (auto EC = dumpFileSummary())
@@ -83,13 +87,13 @@
     P.NewLine();
   }
 
-  if (opts::dump::DumpSymbolStats.getNumOccurrences() > 0) {
+  if (opts::dump::DumpSymbolStats) {
     if (auto EC = dumpSymbolStats())
       return EC;
     P.NewLine();
   }
 
-  if (opts::dump::DumpUdtStats.getNumOccurrences() > 0) {
+  if (opts::dump::DumpUdtStats) {
     if (auto EC = dumpUdtStats())
       return EC;
     P.NewLine();
@@ -154,7 +158,8 @@
   }
 
   if (opts::dump::DumpSymbols) {
-    if (auto EC = dumpModuleSyms())
+    auto EC = File.isPdb() ? dumpModuleSymsForPdb() : dumpModuleSymsForObj();
+    if (EC)
       return EC;
   }
 
@@ -188,22 +193,27 @@
   ExitOnError Err("Invalid PDB Format: ");
 
   AutoIndent Indent(P);
-  P.formatLine("Block Size: {0}", File.getBlockSize());
-  P.formatLine("Number of blocks: {0}", File.getBlockCount());
-  P.formatLine("Number of streams: {0}", File.getNumStreams());
+  if (File.isObj()) {
+    P.formatLine("Dumping File summary is not valid for object files");
+    return Error::success();
+  }
 
-  auto &PS = Err(File.getPDBInfoStream());
+  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<uint32_t>(PS.getFeatures()));
-  P.formatLine("Has Debug Info: {0}", File.hasPDBDbiStream());
-  P.formatLine("Has Types: {0}", File.hasPDBTpiStream());
-  P.formatLine("Has IDs: {0}", File.hasPDBIpiStream());
-  P.formatLine("Has Globals: {0}", File.hasPDBGlobalsStream());
-  P.formatLine("Has Publics: {0}", File.hasPDBPublicsStream());
-  if (File.hasPDBDbiStream()) {
-    auto &DBI = Err(File.getPDBDbiStream());
+  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()) {
+    auto &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());
@@ -212,20 +222,38 @@
   return Error::success();
 }
 
-static StatCollection getSymbolStats(ModuleDebugStreamRef MDS,
+static StatCollection getSymbolStats(const SymbolGroup &SG,
                                      StatCollection &CumulativeStats) {
   StatCollection Stats;
-  for (const auto &S : MDS.symbols(nullptr)) {
-    Stats.update(S.kind(), S.length());
-    CumulativeStats.update(S.kind(), S.length());
+  if (SG.getFile().isPdb()) {
+    // 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(ModuleDebugStreamRef MDS,
+static StatCollection getChunkStats(const SymbolGroup &SG,
                                     StatCollection &CumulativeStats) {
   StatCollection Stats;
-  for (const auto &Chunk : MDS.subsections()) {
+  for (const auto &Chunk : SG.getDebugSubsections()) {
     Stats.update(uint32_t(Chunk.kind()), Chunk.getRecordLength());
     CumulativeStats.update(uint32_t(Chunk.kind()), Chunk.getRecordLength());
   }
@@ -256,8 +284,11 @@
   }
 }
 
-static bool isMyCode(const DbiModuleDescriptor &Desc) {
-  StringRef Name = Desc.getModuleName();
+static bool isMyCode(const SymbolGroup &Group) {
+  if (Group.getFile().isObj())
+    return true;
+
+  StringRef Name = Group.name();
   if (Name.startswith("Import:"))
     return false;
   if (Name.endswith_lower(".dll"))
@@ -271,8 +302,8 @@
   return true;
 }
 
-static bool shouldDumpModule(uint32_t Modi, const DbiModuleDescriptor &Desc) {
-  if (opts::dump::JustMyCode && !isMyCode(Desc))
+static bool shouldDumpSymbolGroup(uint32_t Idx, const SymbolGroup &Group) {
+  if (opts::dump::JustMyCode && !isMyCode(Group))
     return false;
 
   // If the arg was not specified on the command line, always dump all modules.
@@ -280,29 +311,34 @@
     return true;
 
   // Otherwise, only dump if this is the same module specified.
-  return (opts::dump::DumpModi == Modi);
+  return (opts::dump::DumpModi == Idx);
 }
 
 Error DumpOutputStyle::dumpStreamSummary() {
   printHeader(P, "Streams");
 
-  if (StreamPurposes.empty())
-    discoverStreamPurposes(File, StreamPurposes);
-
   AutoIndent Indent(P);
-  uint32_t StreamCount = File.getNumStreams();
-  uint32_t MaxStreamSize = File.getMaxStreamSize();
+  if (File.isObj()) {
+    P.formatLine("Dumping streams is not valid for object files");
+    return Error::success();
+  }
+
+  if (StreamPurposes.empty())
+    discoverStreamPurposes(getPdb(), StreamPurposes);
+
+  uint32_t StreamCount = getPdb().getNumStreams();
+  uint32_t MaxStreamSize = getPdb().getMaxStreamSize();
 
   for (uint16_t StreamIdx = 0; StreamIdx < StreamCount; ++StreamIdx) {
     P.formatLine(
         "Stream {0} ({1} bytes): [{2}]",
         fmt_align(StreamIdx, AlignStyle::Right, NumDigits(StreamCount)),
-        fmt_align(File.getStreamByteSize(StreamIdx), AlignStyle::Right,
+        fmt_align(getPdb().getStreamByteSize(StreamIdx), AlignStyle::Right,
                   NumDigits(MaxStreamSize)),
         StreamPurposes[StreamIdx].getLongName());
 
     if (opts::dump::DumpStreamBlocks) {
-      auto Blocks = File.getStreamBlockList(StreamIdx);
+      auto Blocks = getPdb().getStreamBlockList(StreamIdx);
       std::vector<uint32_t> BV(Blocks.begin(), Blocks.end());
       P.formatLine("       {0}  Blocks: [{1}]",
                    fmt_repeat(' ', NumDigits(StreamCount)),
@@ -326,9 +362,7 @@
     return make_error<RawError>(raw_error_code::no_stream,
                                 "Module stream not present");
 
-  auto ModStreamData = MappedBlockStream::createIndexedStream(
-      File.getMsfLayout(), File.getMsfBuffer(), ModiStream,
-      File.getAllocator());
+  auto ModStreamData = File.createIndexedStream(ModiStream);
 
   ModuleDebugStreamRef ModS(Modi, std::move(ModStreamData));
   if (auto EC = ModS.reload())
@@ -338,198 +372,91 @@
   return std::move(ModS);
 }
 
-static std::string formatChecksumKind(FileChecksumKind Kind) {
-  switch (Kind) {
-    RETURN_CASE(FileChecksumKind, None, "None");
-    RETURN_CASE(FileChecksumKind, MD5, "MD5");
-    RETURN_CASE(FileChecksumKind, SHA1, "SHA-1");
-    RETURN_CASE(FileChecksumKind, SHA256, "SHA-256");
-  }
-  return formatUnknownEnum(Kind);
-}
-
-namespace {
-class StringsAndChecksumsPrinter {
-  const DebugStringTableSubsectionRef &extractStringTable(PDBFile &File) {
-    ExitOnError Err("Unexpected error processing modules: ");
-    return Err(File.getStringTable()).getStringTable();
-  }
-
-  template <typename... Args>
-  void formatInternal(LinePrinter &Printer, bool Append,
-                      Args &&... args) const {
-    if (Append)
-      Printer.format(std::forward<Args>(args)...);
-    else
-      Printer.formatLine(std::forward<Args>(args)...);
-  }
-
-public:
-  StringsAndChecksumsPrinter(PDBFile &File, uint32_t Modi)
-      : Records(extractStringTable(File)) {
-    auto MDS = getModuleDebugStream(File, Modi);
-    if (!MDS) {
-      consumeError(MDS.takeError());
-      return;
-    }
-
-    DebugStream = llvm::make_unique<ModuleDebugStreamRef>(std::move(*MDS));
-    Records.initialize(MDS->subsections());
-    if (Records.hasChecksums()) {
-      for (const auto &Entry : Records.checksums()) {
-        auto S = Records.strings().getString(Entry.FileNameOffset);
-        if (!S)
-          continue;
-        ChecksumsByFile[*S] = Entry;
-      }
-    }
-  }
-
-  Expected<StringRef> getNameFromStringTable(uint32_t Offset) const {
-    return Records.strings().getString(Offset);
-  }
-
-  void formatFromFileName(LinePrinter &Printer, StringRef File,
-                          bool Append = false) const {
-    auto FC = ChecksumsByFile.find(File);
-    if (FC == ChecksumsByFile.end()) {
-      formatInternal(Printer, Append, "- (no checksum) {0}", File);
-      return;
-    }
-
-    formatInternal(Printer, Append, "- ({0}: {1}) {2}",
-                   formatChecksumKind(FC->getValue().Kind),
-                   toHex(FC->getValue().Checksum), File);
-  }
-
-  void formatFromChecksumsOffset(LinePrinter &Printer, uint32_t Offset,
-                                 bool Append = false) const {
-    if (!Records.hasChecksums()) {
-      formatInternal(Printer, Append, "(unknown file name offset {0})", Offset);
-      return;
-    }
-
-    auto Iter = Records.checksums().getArray().at(Offset);
-    if (Iter == Records.checksums().getArray().end()) {
-      formatInternal(Printer, Append, "(unknown file name offset {0})", Offset);
-      return;
-    }
-
-    uint32_t FO = Iter->FileNameOffset;
-    auto ExpectedFile = getNameFromStringTable(FO);
-    if (!ExpectedFile) {
-      formatInternal(Printer, Append, "(unknown file name offset {0})", Offset);
-      consumeError(ExpectedFile.takeError());
-      return;
-    }
-    if (Iter->Kind == FileChecksumKind::None) {
-      formatInternal(Printer, Append, "{0} (no checksum)", *ExpectedFile);
-    } else {
-      formatInternal(Printer, Append, "{0} ({1}: {2})", *ExpectedFile,
-                     formatChecksumKind(Iter->Kind), toHex(Iter->Checksum));
-    }
-  }
-
-  std::unique_ptr<ModuleDebugStreamRef> DebugStream;
-  StringsAndChecksumsRef Records;
-  StringMap<FileChecksumEntry> ChecksumsByFile;
-};
-} // namespace
-
 template <typename CallbackT>
-static void iterateOneModule(PDBFile &File, LinePrinter &P,
-                             const DbiModuleDescriptor &Descriptor,
-                             uint32_t Modi, uint32_t IndentLevel,
-                             uint32_t Digits, CallbackT Callback) {
-  P.formatLine(
-      "Mod {0:4} | `{1}`: ", fmt_align(Modi, AlignStyle::Right, Digits),
-      Descriptor.getModuleName());
+static void
+iterateOneModule(InputFile &File, const Optional<PrintScope> &HeaderScope,
+                 const SymbolGroup &SG, uint32_t Modi, CallbackT Callback) {
+  if (HeaderScope) {
+    HeaderScope->P.formatLine(
+        "Mod {0:4} | `{1}`: ",
+        fmt_align(Modi, AlignStyle::Right, HeaderScope->LabelWidth), SG.name());
+  }
 
-  StringsAndChecksumsPrinter Strings(File, Modi);
-  AutoIndent Indent2(P, IndentLevel);
-  Callback(Modi, Strings);
+  AutoIndent Indent(HeaderScope);
+  Callback(Modi, SG);
 }
 
 template <typename CallbackT>
-static void iterateModules(PDBFile &File, LinePrinter &P, uint32_t IndentLevel,
-                           CallbackT Callback) {
-  AutoIndent Indent(P);
-  if (!File.hasPDBDbiStream()) {
-    P.formatLine("DBI Stream not present");
-    return;
-  }
+static void iterateSymbolGroups(InputFile &Input,
+                                const Optional<PrintScope> &HeaderScope,
+                                CallbackT Callback) {
+  AutoIndent Indent(HeaderScope);
 
   ExitOnError Err("Unexpected error processing modules: ");
 
-  auto &Stream = Err(File.getPDBDbiStream());
-
-  const DbiModuleList &Modules = Stream.modules();
-
   if (opts::dump::DumpModi.getNumOccurrences() > 0) {
     assert(opts::dump::DumpModi.getNumOccurrences() == 1);
     uint32_t Modi = opts::dump::DumpModi;
-    auto Descriptor = Modules.getModuleDescriptor(Modi);
-    iterateOneModule(File, P, Descriptor, Modi, IndentLevel, NumDigits(Modi),
-                     Callback);
+    SymbolGroup SG(&Input, Modi);
+    iterateOneModule(Input, withLabelWidth(HeaderScope, NumDigits(Modi)), SG,
+                     Modi, Callback);
     return;
   }
 
-  uint32_t Count = Modules.getModuleCount();
-  uint32_t Digits = NumDigits(Count);
-  for (uint32_t I = 0; I < Count; ++I) {
-    auto Desc = Modules.getModuleDescriptor(I);
-    if (!shouldDumpModule(I, Desc))
-      continue;
-    iterateOneModule(File, P, Desc, I, IndentLevel, Digits, Callback);
+  uint32_t I = 0;
+
+  for (const auto &SG : Input.symbol_groups()) {
+    if (shouldDumpSymbolGroup(I, SG))
+      iterateOneModule(Input, withLabelWidth(HeaderScope, NumDigits(I)), SG, I,
+                       Callback);
+
+    ++I;
   }
 }
 
 template <typename SubsectionT>
 static void iterateModuleSubsections(
-    PDBFile &File, LinePrinter &P, uint32_t IndentLevel,
-    llvm::function_ref<void(uint32_t, StringsAndChecksumsPrinter &,
-                            SubsectionT &)>
+    InputFile &File, const Optional<PrintScope> &HeaderScope,
+    llvm::function_ref<void(uint32_t, const SymbolGroup &, SubsectionT &)>
         Callback) {
 
-  iterateModules(
-      File, P, IndentLevel,
-      [&File, &Callback](uint32_t Modi, StringsAndChecksumsPrinter &Strings) {
-        auto MDS = getModuleDebugStream(File, Modi);
-        if (!MDS) {
-          consumeError(MDS.takeError());
-          return;
-        }
+  iterateSymbolGroups(File, HeaderScope,
+                      [&](uint32_t Modi, const SymbolGroup &SG) {
+                        for (const auto &SS : SG.getDebugSubsections()) {
+                          SubsectionT Subsection;
 
-        for (const auto &SS : MDS->subsections()) {
-          SubsectionT Subsection;
+                          if (SS.kind() != Subsection.kind())
+                            continue;
 
-          if (SS.kind() != Subsection.kind())
-            continue;
-
-          BinaryStreamReader Reader(SS.getRecordData());
-          if (auto EC = Subsection.initialize(Reader))
-            continue;
-          Callback(Modi, Strings, Subsection);
-        }
-      });
+                          BinaryStreamReader Reader(SS.getRecordData());
+                          if (auto EC = Subsection.initialize(Reader))
+                            continue;
+                          Callback(Modi, SG, Subsection);
+                        }
+                      });
 }
 
 Error DumpOutputStyle::dumpModules() {
   printHeader(P, "Modules");
-
   AutoIndent Indent(P);
-  if (!File.hasPDBDbiStream()) {
+
+  if (File.isObj()) {
+    P.formatLine("Dumping modules is not supported for object files");
+    return Error::success();
+  }
+
+  if (!getPdb().hasPDBDbiStream()) {
     P.formatLine("DBI Stream not present");
     return Error::success();
   }
 
   ExitOnError Err("Unexpected error processing modules: ");
 
-  auto &Stream = Err(File.getPDBDbiStream());
+  auto &Stream = Err(getPdb().getPDBDbiStream());
 
   const DbiModuleList &Modules = Stream.modules();
-  iterateModules(
-      File, P, 11, [&](uint32_t Modi, StringsAndChecksumsPrinter &Strings) {
+  iterateSymbolGroups(
+      File, PrintScope{P, 11}, [&](uint32_t Modi, const SymbolGroup &Strings) {
         auto Desc = Modules.getModuleDescriptor(Modi);
         P.formatLine("Obj: `{0}`: ", Desc.getObjFileName());
         P.formatLine("debug stream: {0}, # files: {1}, has ec info: {2}",
@@ -549,18 +476,22 @@
 Error DumpOutputStyle::dumpModuleFiles() {
   printHeader(P, "Files");
 
+  if (File.isObj()) {
+    P.formatLine("Dumping files is not valid for object files");
+    return Error::success();
+  }
+
   ExitOnError Err("Unexpected error processing modules: ");
 
-  iterateModules(
-      File, P, 11,
-      [this, &Err](uint32_t Modi, StringsAndChecksumsPrinter &Strings) {
-        auto &Stream = Err(File.getPDBDbiStream());
+  iterateSymbolGroups(File, PrintScope{P, 11},
+                      [this, &Err](uint32_t Modi, const SymbolGroup &Strings) {
+                        auto &Stream = Err(getPdb().getPDBDbiStream());
 
-        const DbiModuleList &Modules = Stream.modules();
-        for (const auto &F : Modules.source_files(Modi)) {
-          Strings.formatFromFileName(P, F);
-        }
-      });
+                        const DbiModuleList &Modules = Stream.modules();
+                        for (const auto &F : Modules.source_files(Modi)) {
+                          Strings.formatFromFileName(P, F);
+                        }
+                      });
   return Error::success();
 }
 
@@ -571,37 +502,30 @@
 
   StatCollection SymStats;
   StatCollection ChunkStats;
-  auto &Stream = Err(File.getPDBDbiStream());
 
-  const DbiModuleList &Modules = Stream.modules();
-  uint32_t ModCount = Modules.getModuleCount();
+  iterateSymbolGroups(File, None, [&](uint32_t Modi, const SymbolGroup &SG) {
+    StatCollection SS = getSymbolStats(SG, SymStats);
+    StatCollection CS = getChunkStats(SG, ChunkStats);
 
-  iterateModules(File, P, 0, [&](uint32_t Modi,
-                                 StringsAndChecksumsPrinter &Strings) {
-    DbiModuleDescriptor Desc = Modules.getModuleDescriptor(Modi);
-    uint32_t StreamIdx = Desc.getModuleStreamIndex();
+    if (SG.getFile().isPdb()) {
+      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;
+      if (StreamIdx == kInvalidStreamIndex) {
+        P.formatLine("Mod {0} (debug info not present): [{1}]",
+                     fmt_align(Modi, AlignStyle::Right, NumDigits(ModCount)),
+                     Desc.getModuleName());
+        return;
+      }
+
+      P.formatLine("Stream {0}, {1} bytes", StreamIdx,
+                   getPdb().getStreamByteSize(StreamIdx));
+
+      printModuleDetailStats<SymbolKind>(P, "Symbols", SS);
+      printModuleDetailStats<DebugSubsectionKind>(P, "Chunks", CS);
     }
-
-    P.formatLine("Stream {0}, {1} bytes", StreamIdx,
-                 File.getStreamByteSize(StreamIdx));
-
-    ModuleDebugStreamRef MDS(Desc, File.createIndexedStream(StreamIdx));
-    if (auto EC = MDS.reload()) {
-      P.printLine("- Error parsing debug info stream");
-      consumeError(std::move(EC));
-      return;
-    }
-
-    printModuleDetailStats<SymbolKind>(P, "Symbols",
-                                       getSymbolStats(MDS, SymStats));
-    printModuleDetailStats<DebugSubsectionKind>(P, "Chunks",
-                                                getChunkStats(MDS, ChunkStats));
   });
 
   P.printLine("  Summary |");
@@ -661,58 +585,75 @@
 
   StatCollection UdtStats;
   StatCollection UdtTargetStats;
-  if (!File.hasPDBGlobalsStream()) {
-    P.printLine("- Error: globals stream not present");
-    return Error::success();
-  }
-
   AutoIndent Indent(P, 4);
 
-  auto &SymbolRecords = cantFail(File.getPDBSymbolStream());
-  auto &Globals = cantFail(File.getPDBGlobalsStream());
-  auto &TpiTypes = cantFail(initializeTypes(StreamTPI));
+  auto &TpiTypes = File.types();
 
   StringMap<StatCollection::Stat> NamespacedStats;
 
-  P.NewLine();
-
   size_t LongestNamespace = 0;
-  for (uint32_t PubSymOff : Globals.getGlobalsTable()) {
-    CVSymbol Sym = SymbolRecords.readRecord(PubSymOff);
+  auto HandleOneSymbol = [&](const CVSymbol &Sym) {
     if (Sym.kind() != SymbolKind::S_UDT)
-      continue;
+      return;
     UdtStats.update(SymbolKind::S_UDT, Sym.length());
 
     UDTSym UDT = cantFail(SymbolDeserializer::deserializeAs<UDTSym>(Sym));
 
     uint32_t Kind = 0;
     uint32_t RecordSize = 0;
-    if (UDT.Type.isSimple() ||
-        (UDT.Type.toArrayIndex() >= TpiTypes.capacity())) {
-      if (UDT.Type.isNoneType())
-        Kind = kNoneUdtKind;
-      else if (UDT.Type.isSimple())
-        Kind = kSimpleUdtKind;
-      else
-        Kind = kUnknownUdtKind;
-    } else {
-      CVType T = TpiTypes.getType(UDT.Type);
-      Kind = T.kind();
-      RecordSize = T.length();
-    }
+
+    if (UDT.Type.isNoneType())
+      Kind = kNoneUdtKind;
+    else if (UDT.Type.isSimple())
+      Kind = kSimpleUdtKind;
+    else if (Optional<CVType> 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)
-      continue;
+      return;
 
     StringRef Scope = UDT.Name.take_front(Pos);
     if (Scope.empty() || !isValidNamespaceIdentifier(Scope))
-      continue;
+      return;
 
     LongestNamespace = std::max(LongestNamespace, Scope.size());
     NamespacedStats[Scope].update(RecordSize);
+  };
+
+  P.NewLine();
+
+  if (File.isPdb()) {
+    if (!getPdb().hasPDBGlobalsStream()) {
+      P.printLine("- Error: globals stream not present");
+      return Error::success();
+    }
+
+    auto &SymbolRecords = cantFail(getPdb().getPDBSymbolStream());
+    auto &Globals = cantFail(getPdb().getPDBGlobalsStream());
+
+    for (uint32_t PubSymOff : Globals.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();
@@ -762,8 +703,8 @@
   return Error::success();
 }
 
-static void typesetLinesAndColumns(PDBFile &File, LinePrinter &P,
-                                   uint32_t Start, const LineColumnEntry &E) {
+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;
 
@@ -802,9 +743,9 @@
   uint32_t LastModi = UINT32_MAX;
   uint32_t LastNameIndex = UINT32_MAX;
   iterateModuleSubsections<DebugLinesSubsectionRef>(
-      File, P, 4,
+      File, PrintScope{P, 4},
       [this, &LastModi, &LastNameIndex](uint32_t Modi,
-                                        StringsAndChecksumsPrinter &Strings,
+                                        const SymbolGroup &Strings,
                                         DebugLinesSubsectionRef &Lines) {
         uint16_t Segment = Lines.header()->RelocSegment;
         uint32_t Begin = Lines.header()->RelocOffset;
@@ -825,7 +766,7 @@
             P.format("line/addr entries = {0}", Count);
 
           P.NewLine();
-          typesetLinesAndColumns(File, P, Begin, Block);
+          typesetLinesAndColumns(P, Begin, Block);
         }
       });
 
@@ -836,8 +777,8 @@
   printHeader(P, "Inlinee Lines");
 
   iterateModuleSubsections<DebugInlineeLinesSubsectionRef>(
-      File, P, 2,
-      [this](uint32_t Modi, StringsAndChecksumsPrinter &Strings,
+      File, PrintScope{P, 2},
+      [this](uint32_t Modi, const SymbolGroup &Strings,
              DebugInlineeLinesSubsectionRef &Lines) {
         P.formatLine("{0,+8} | {1,+5} | {2}", "Inlinee", "Line", "Source File");
         for (const auto &Entry : Lines) {
@@ -854,8 +795,8 @@
 Error DumpOutputStyle::dumpXmi() {
   printHeader(P, "Cross Module Imports");
   iterateModuleSubsections<DebugCrossModuleImportsSubsectionRef>(
-      File, P, 2,
-      [this](uint32_t Modi, StringsAndChecksumsPrinter &Strings,
+      File, PrintScope{P, 2},
+      [this](uint32_t Modi, const SymbolGroup &Strings,
              DebugCrossModuleImportsSubsectionRef &Imports) {
         P.formatLine("{0,=32} | {1}", "Imported Module", "Type IDs");
 
@@ -890,8 +831,8 @@
   printHeader(P, "Cross Module Exports");
 
   iterateModuleSubsections<DebugCrossModuleExportsSubsectionRef>(
-      File, P, 2,
-      [this](uint32_t Modi, StringsAndChecksumsPrinter &Strings,
+      File, PrintScope{P, 2},
+      [this](uint32_t Modi, const SymbolGroup &Strings,
              DebugCrossModuleExportsSubsectionRef &Exports) {
         P.formatLine("{0,-10} | {1}", "Local ID", "Global ID");
         for (const auto &Export : Exports) {
@@ -906,8 +847,13 @@
 Error DumpOutputStyle::dumpStringTable() {
   printHeader(P, "String Table");
 
+  if (File.isObj()) {
+    P.formatLine("Dumping string table is not supported for object files");
+    return Error::success();
+  }
+
   AutoIndent Indent(P);
-  auto IS = File.getStringTable();
+  auto IS = getPdb().getStringTable();
   if (!IS) {
     P.formatLine("Not present in file");
     consumeError(IS.takeError());
@@ -1018,22 +964,32 @@
 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)");
+  }
+
+  AutoIndent Indent(P);
+  if (File.isObj()) {
+    P.formatLine("Dumping types is not supported for object files");
+    return Error::success();
+  }
+
   bool Present = false;
   bool DumpTypes = false;
   bool DumpBytes = false;
   bool DumpExtras = false;
   std::vector<uint32_t> Indices;
   if (StreamIdx == StreamTPI) {
-    printHeader(P, "Types (TPI Stream)");
-    Present = File.hasPDBTpiStream();
+    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) {
-    printHeader(P, "Types (IPI Stream)");
-    Present = File.hasPDBIpiStream();
+    Present = getPdb().hasPDBIpiStream();
     DumpTypes = opts::dump::DumpIds;
     DumpBytes = opts::dump::DumpIdData;
     DumpExtras = opts::dump::DumpIdExtras;
@@ -1041,7 +997,6 @@
                    opts::dump::DumpIdIndex.end());
   }
 
-  AutoIndent Indent(P);
   if (!Present) {
     P.formatLine("Stream not present");
     return Error::success();
@@ -1049,10 +1004,10 @@
 
   ExitOnError Err("Unexpected error processing types: ");
 
-  auto &Stream = Err((StreamIdx == StreamTPI) ? File.getPDBTpiStream()
-                                              : File.getPDBIpiStream());
+  auto &Stream = Err((StreamIdx == StreamTPI) ? getPdb().getPDBTpiStream()
+                                              : getPdb().getPDBIpiStream());
 
-  auto &Types = Err(initializeTypes(StreamIdx));
+  auto &Types = (StreamIdx == StreamTPI) ? File.types() : File.ids();
 
   if (DumpTypes || !Indices.empty()) {
     if (Indices.empty())
@@ -1076,7 +1031,7 @@
     P.NewLine();
     P.formatLine("Hash Adjusters:");
     auto &Adjusters = Stream.getHashAdjusters();
-    auto &Strings = Err(File.getStringTable());
+    auto &Strings = Err(getPdb().getStringTable());
     for (const auto &A : Adjusters) {
       AutoIndent Indent2(P);
       auto ExpectedStr = Strings.getStringForID(A.first);
@@ -1092,42 +1047,60 @@
   return Error::success();
 }
 
-Expected<codeview::LazyRandomTypeCollection &>
-DumpOutputStyle::initializeTypes(uint32_t SN) {
-  auto &TypeCollection = (SN == StreamTPI) ? TpiTypes : IpiTypes;
-  auto Tpi =
-      (SN == StreamTPI) ? File.getPDBTpiStream() : File.getPDBIpiStream();
-  if (!Tpi)
-    return Tpi.takeError();
-
-  if (!TypeCollection) {
-    auto &Types = Tpi->typeArray();
-    uint32_t Count = Tpi->getNumTypeRecords();
-    auto Offsets = Tpi->getTypeIndexOffsets();
-    TypeCollection =
-        llvm::make_unique<LazyRandomTypeCollection>(Types, Count, Offsets);
-  }
-
-  return *TypeCollection;
-}
-
-Error DumpOutputStyle::dumpModuleSyms() {
+Error DumpOutputStyle::dumpModuleSymsForObj() {
   printHeader(P, "Symbols");
 
   AutoIndent Indent(P);
-  if (!File.hasPDBDbiStream()) {
+
+  ExitOnError Err("Unexpected error processing symbols: ");
+
+  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);
+
+  std::unique_ptr<llvm::Error> SymbolError;
+
+  iterateModuleSubsections<DebugSymbolsSubsectionRef>(
+      File, PrintScope{P, 2},
+      [&](uint32_t Modi, const SymbolGroup &Strings,
+          DebugSymbolsSubsectionRef &Symbols) {
+        for (auto Symbol : Symbols) {
+          if (auto EC = Visitor.visitSymbolRecord(Symbol)) {
+            SymbolError = llvm::make_unique<Error>(std::move(EC));
+            return;
+          }
+        }
+      });
+
+  if (SymbolError)
+    return std::move(*SymbolError);
+
+  return Error::success();
+}
+
+Error DumpOutputStyle::dumpModuleSymsForPdb() {
+  printHeader(P, "Symbols");
+
+  AutoIndent Indent(P);
+  if (!getPdb().hasPDBDbiStream()) {
     P.formatLine("DBI Stream not present");
     return Error::success();
   }
 
   ExitOnError Err("Unexpected error processing symbols: ");
 
-  auto &Ids = Err(initializeTypes(StreamIPI));
-  auto &Types = Err(initializeTypes(StreamTPI));
+  auto &Ids = File.ids();
+  auto &Types = File.types();
 
-  iterateModules(
-      File, P, 2, [&](uint32_t I, StringsAndChecksumsPrinter &Strings) {
-        auto ExpectedModS = getModuleDebugStream(File, I);
+  iterateSymbolGroups(
+      File, PrintScope{P, 2}, [&](uint32_t I, const SymbolGroup &Strings) {
+        auto ExpectedModS = getModuleDebugStream(File.pdb(), I);
         if (!ExpectedModS) {
           P.formatLine("Error loading module stream {0}.  {1}", I,
                        toString(ExpectedModS.takeError()));
@@ -1158,12 +1131,18 @@
 Error DumpOutputStyle::dumpGlobals() {
   printHeader(P, "Global Symbols");
   AutoIndent Indent(P);
-  if (!File.hasPDBGlobalsStream()) {
+
+  if (File.isObj()) {
+    P.formatLine("Dumping Globals is not supported for object files");
+    return Error::success();
+  }
+
+  if (!getPdb().hasPDBGlobalsStream()) {
     P.formatLine("Globals stream not present");
     return Error::success();
   }
   ExitOnError Err("Error dumping globals stream: ");
-  auto &Globals = Err(File.getPDBGlobalsStream());
+  auto &Globals = Err(getPdb().getPDBGlobalsStream());
 
   const GSIHashTable &Table = Globals.getGlobalsTable();
   Err(dumpSymbolsFromGSI(Table, opts::dump::DumpGlobalExtras));
@@ -1173,12 +1152,18 @@
 Error DumpOutputStyle::dumpPublics() {
   printHeader(P, "Public Symbols");
   AutoIndent Indent(P);
-  if (!File.hasPDBPublicsStream()) {
+
+  if (File.isObj()) {
+    P.formatLine("Dumping Globals is not supported for object files");
+    return Error::success();
+  }
+
+  if (!getPdb().hasPDBPublicsStream()) {
     P.formatLine("Publics stream not present");
     return Error::success();
   }
   ExitOnError Err("Error dumping publics stream: ");
-  auto &Publics = Err(File.getPDBPublicsStream());
+  auto &Publics = Err(getPdb().getPDBPublicsStream());
 
   const GSIHashTable &PublicsTable = Publics.getPublicsTable();
   if (opts::dump::DumpPublicExtras) {
@@ -1224,15 +1209,11 @@
 
 Error DumpOutputStyle::dumpSymbolsFromGSI(const GSIHashTable &Table,
                                           bool HashExtras) {
-  auto ExpectedSyms = File.getPDBSymbolStream();
+  auto ExpectedSyms = getPdb().getPDBSymbolStream();
   if (!ExpectedSyms)
     return ExpectedSyms.takeError();
-  auto ExpectedTypes = initializeTypes(StreamTPI);
-  if (!ExpectedTypes)
-    return ExpectedTypes.takeError();
-  auto ExpectedIds = initializeTypes(StreamIPI);
-  if (!ExpectedIds)
-    return ExpectedIds.takeError();
+  auto &Types = File.types();
+  auto &Ids = File.ids();
 
   if (HashExtras) {
     P.printLine("GSI Header");
@@ -1246,8 +1227,7 @@
     P.printLine("Records");
     SymbolVisitorCallbackPipeline Pipeline;
     SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb);
-    MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, *ExpectedIds,
-                               *ExpectedTypes);
+    MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Ids, Types);
 
     Pipeline.addCallbackToPipeline(Deserializer);
     Pipeline.addCallbackToPipeline(Dumper);
@@ -1326,8 +1306,7 @@
         "PDB does not contain the requested image section header type",
         inconvertibleErrorCode());
 
-  auto Stream = MappedBlockStream::createIndexedStream(
-      File.getMsfLayout(), File.getMsfBuffer(), SI, File.getAllocator());
+  auto Stream = File.createIndexedStream(SI);
   if (!Stream)
     return make_error<StringError>("Could not load the required stream data",
                                    inconvertibleErrorCode());
@@ -1346,12 +1325,17 @@
 
 void DumpOutputStyle::dumpSectionHeaders(StringRef Label, DbgHeaderType Type) {
   printHeader(P, Label);
-  ExitOnError Err("Error dumping publics stream: ");
 
   AutoIndent Indent(P);
+  if (File.isObj()) {
+    P.formatLine("Dumping Section Headers is not supported for object files");
+    return;
+  }
+
+  ExitOnError Err("Error dumping section headers: ");
   std::unique_ptr<MappedBlockStream> Stream;
   ArrayRef<object::coff_section> Headers;
-  auto ExpectedHeaders = loadSectionHeaders(File, Type);
+  auto ExpectedHeaders = loadSectionHeaders(getPdb(), Type);
   if (!ExpectedHeaders) {
     P.printLine(toString(ExpectedHeaders.takeError()));
     return;
@@ -1401,16 +1385,22 @@
 
 Error DumpOutputStyle::dumpSectionContribs() {
   printHeader(P, "Section Contributions");
-  ExitOnError Err("Error dumping publics stream: ");
 
   AutoIndent Indent(P);
-  if (!File.hasPDBDbiStream()) {
+  if (File.isObj()) {
+    P.formatLine(
+        "Dumping section contributions is not supported for object files");
+    return Error::success();
+  }
+
+  ExitOnError Err("Error dumping section contributions: ");
+  if (!getPdb().hasPDBDbiStream()) {
     P.formatLine(
         "Section contribs require a DBI Stream, which could not be loaded");
     return Error::success();
   }
 
-  auto &Dbi = Err(File.getPDBDbiStream());
+  auto &Dbi = Err(getPdb().getPDBDbiStream());
 
   class Visitor : public ISectionContribVisitor {
   public:
@@ -1456,7 +1446,7 @@
     ArrayRef<std::string> Names;
   };
 
-  std::vector<std::string> Names = getSectionNames(File);
+  std::vector<std::string> Names = getSectionNames(getPdb());
   Visitor V(P, makeArrayRef(Names));
   Dbi.visitSectionContributions(V);
   return Error::success();
@@ -1464,16 +1454,22 @@
 
 Error DumpOutputStyle::dumpSectionMap() {
   printHeader(P, "Section Map");
+  AutoIndent Indent(P);
+
+  if (File.isObj()) {
+    P.formatLine("Dumping section map is not supported for object files");
+    return Error::success();
+  }
+
   ExitOnError Err("Error dumping section map: ");
 
-  AutoIndent Indent(P);
-  if (!File.hasPDBDbiStream()) {
+  if (!getPdb().hasPDBDbiStream()) {
     P.formatLine("Dumping the section map requires a DBI Stream, which could "
                  "not be loaded");
     return Error::success();
   }
 
-  auto &Dbi = Err(File.getPDBDbiStream());
+  auto &Dbi = Err(getPdb().getPDBDbiStream());
 
   uint32_t I = 0;
   for (auto &M : Dbi.getSectionMap()) {