aboutsummaryrefslogtreecommitdiff
path: root/tools/llvm-pdbutil/Diff.cpp
blob: 75e8f34bda064e9ae755b4685c7c22b1b24eab7d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
//===- Diff.cpp - PDB diff utility ------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "Diff.h"

#include "DiffPrinter.h"
#include "FormatUtil.h"
#include "StreamUtil.h"
#include "llvm-pdbutil.h"

#include "llvm/ADT/StringSet.h"

#include "llvm/DebugInfo/PDB/Native/DbiStream.h"
#include "llvm/DebugInfo/PDB/Native/Formatters.h"
#include "llvm/DebugInfo/PDB/Native/InfoStream.h"
#include "llvm/DebugInfo/PDB/Native/PDBFile.h"
#include "llvm/DebugInfo/PDB/Native/PDBStringTable.h"
#include "llvm/DebugInfo/PDB/Native/RawConstants.h"

#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatAdapters.h"
#include "llvm/Support/FormatProviders.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"

using namespace llvm;
using namespace llvm::pdb;

namespace {
// Compare and format two stream numbers.  Stream numbers are considered
// identical if they contain the same value, equivalent if they are both
// the invalid stream or neither is the invalid stream, and different if
// one is the invalid stream and another isn't.
struct StreamNumberProvider {
  static DiffResult compare(uint16_t L, uint16_t R) {
    if (L == R)
      return DiffResult::IDENTICAL;
    bool LP = L != kInvalidStreamIndex;
    bool RP = R != kInvalidStreamIndex;
    if (LP != RP)
      return DiffResult::DIFFERENT;
    return DiffResult::EQUIVALENT;
  }

  static std::string format(uint16_t SN, bool Right) {
    if (SN == kInvalidStreamIndex)
      return "(not present)";
    return formatv("{0}", SN).str();
  }
};

// Compares and formats two module indices.  Modis are considered identical
// if they are identical, equivalent if they either both contain a value or
// both don't contain a value, and different if one contains a value and the
// other doesn't.
struct ModiProvider {
  DiffResult compare(Optional<uint32_t> L, Optional<uint32_t> R) {
    if (L == R)
      return DiffResult::IDENTICAL;
    if (L.hasValue() != R.hasValue())
      return DiffResult::DIFFERENT;
    return DiffResult::EQUIVALENT;
  }

  std::string format(Optional<uint32_t> Modi, bool Right) {
    if (!Modi.hasValue())
      return "(not present)";
    return formatv("{0}", *Modi).str();
  }
};

// Compares and formats two paths embedded in the PDB, ignoring the beginning
// of the path if the user specified it as a "root path" on the command line.
struct BinaryPathProvider {
  explicit BinaryPathProvider(uint32_t MaxLen) : MaxLen(MaxLen) {}

  DiffResult compare(StringRef L, StringRef R) {
    if (L == R)
      return DiffResult::IDENTICAL;

    SmallString<64> LN = removeRoot(L, false);
    SmallString<64> RN = removeRoot(R, true);

    return (LN.equals_lower(RN)) ? DiffResult::EQUIVALENT
                                 : DiffResult::DIFFERENT;
  }

  std::string format(StringRef S, bool Right) {
    if (S.empty())
      return "(empty)";

    SmallString<64> Native = removeRoot(S, Right);
    return truncateStringFront(Native.str(), MaxLen);
  }

  SmallString<64> removeRoot(StringRef Path, bool IsRight) const {
    SmallString<64> Native(Path);
    SmallString<64> Root =
        IsRight ? opts::diff::RightRoot : opts::diff::LeftRoot;
    // pdb paths always use windows syntax, convert slashes to backslashes.
    sys::path::native(Root, sys::path::Style::windows);
    if (sys::path::has_stem(Root, sys::path::Style::windows))
      sys::path::append(Root,
                        sys::path::get_separator(sys::path::Style::windows));

    sys::path::replace_path_prefix(Native, Root, "");
    return Native;
  }
  uint32_t MaxLen;
};

// Compare and format two stream purposes.  For general streams, this just
// compares the description.  For module streams it uses the path comparison
// algorithm taking into consideration the binary root, described above.
// Formatting stream purposes just prints the stream purpose, except for
// module streams and named streams, where it prefixes the name / module
// with an identifier.  Example:
//
//   Named Stream "\names"
//   Module Stream "foo.obj"
//
// If a named stream is too long to fit in a column, it is truncated at the
// end, and if a module is too long to fit in a column, it is truncated at the
// beginning.  Example:
//
//  Named Stream "\Really Long Str..."
//  Module Stream "...puts\foo.obj"
//
struct StreamPurposeProvider {
  explicit StreamPurposeProvider(uint32_t MaxLen) : MaxLen(MaxLen) {}

  DiffResult compare(const std::pair<StreamPurpose, std::string> &L,
                     const std::pair<StreamPurpose, std::string> &R) {
    if (L.first != R.first)
      return DiffResult::DIFFERENT;
    if (L.first == StreamPurpose::ModuleStream) {
      BinaryPathProvider PathProvider(MaxLen);
      return PathProvider.compare(L.second, R.second);
    }
    return (L.second == R.second) ? DiffResult::IDENTICAL
                                  : DiffResult::DIFFERENT;
  }

  std::string format(const std::pair<StreamPurpose, std::string> &P,
                     bool Right) {
    if (P.first == StreamPurpose::Other)
      return truncateStringBack(P.second, MaxLen);
    if (P.first == StreamPurpose::NamedStream)
      return truncateQuotedNameBack("Named Stream", P.second, MaxLen);

    assert(P.first == StreamPurpose::ModuleStream);
    uint32_t ExtraChars = strlen("Module \"\"");
    BinaryPathProvider PathProvider(MaxLen - ExtraChars);
    std::string Result = PathProvider.format(P.second, Right);
    return formatv("Module \"{0}\"", Result);
  }

  uint32_t MaxLen;
};
}

namespace llvm {
template <> struct format_provider<PdbRaw_FeatureSig> {
  static void format(const PdbRaw_FeatureSig &Sig, raw_ostream &Stream,
                     StringRef Style) {
    switch (Sig) {
    case PdbRaw_FeatureSig::MinimalDebugInfo:
      Stream << "MinimalDebugInfo";
      break;
    case PdbRaw_FeatureSig::NoTypeMerge:
      Stream << "NoTypeMerge";
      break;
    case PdbRaw_FeatureSig::VC110:
      Stream << "VC110";
      break;
    case PdbRaw_FeatureSig::VC140:
      Stream << "VC140";
      break;
    }
  }
};
}

template <typename R> using ValueOfRange = llvm::detail::ValueOfRange<R>;

DiffStyle::DiffStyle(PDBFile &File1, PDBFile &File2)
    : File1(File1), File2(File2) {}

Error DiffStyle::dump() {
  if (auto EC = diffSuperBlock())
    return EC;

  if (auto EC = diffFreePageMap())
    return EC;

  if (auto EC = diffStreamDirectory())
    return EC;

  if (auto EC = diffStringTable())
    return EC;

  if (auto EC = diffInfoStream())
    return EC;

  if (auto EC = diffDbiStream())
    return EC;

  if (auto EC = diffSectionContribs())
    return EC;

  if (auto EC = diffSectionMap())
    return EC;

  if (auto EC = diffFpoStream())
    return EC;

  if (auto EC = diffTpiStream(StreamTPI))
    return EC;

  if (auto EC = diffTpiStream(StreamIPI))
    return EC;

  if (auto EC = diffPublics())
    return EC;

  if (auto EC = diffGlobals())
    return EC;

  return Error::success();
}

Error DiffStyle::diffSuperBlock() {
  DiffPrinter D(2, "MSF Super Block", 16, 20, opts::diff::PrintResultColumn,
                opts::diff::PrintValueColumns, outs());
  D.printExplicit("File", DiffResult::UNSPECIFIED,
                  truncateStringFront(File1.getFilePath(), 18),
                  truncateStringFront(File2.getFilePath(), 18));
  D.print("Block Size", File1.getBlockSize(), File2.getBlockSize());
  D.print("Block Count", File1.getBlockCount(), File2.getBlockCount());
  D.print("Unknown 1", File1.getUnknown1(), File2.getUnknown1());
  D.print("Directory Size", File1.getNumDirectoryBytes(),
          File2.getNumDirectoryBytes());
  return Error::success();
}

Error DiffStyle::diffStreamDirectory() {
  DiffPrinter D(2, "Stream Directory", 30, 20, opts::diff::PrintResultColumn,
                opts::diff::PrintValueColumns, outs());
  D.printExplicit("File", DiffResult::UNSPECIFIED,
                  truncateStringFront(File1.getFilePath(), 18),
                  truncateStringFront(File2.getFilePath(), 18));

  SmallVector<std::pair<StreamPurpose, std::string>, 32> P;
  SmallVector<std::pair<StreamPurpose, std::string>, 32> Q;
  discoverStreamPurposes(File1, P);
  discoverStreamPurposes(File2, Q);
  D.print("Stream Count", File1.getNumStreams(), File2.getNumStreams());
  auto PI = to_vector<32>(enumerate(P));
  auto QI = to_vector<32>(enumerate(Q));

  // Scan all streams in the left hand side, looking for ones that are also
  // in the right.  Each time we find one, remove it.  When we're done, Q
  // should contain all the streams that are in the right but not in the left.
  StreamPurposeProvider StreamProvider(28);
  for (const auto &P : PI) {
    typedef decltype(PI) ContainerType;
    typedef typename ContainerType::value_type value_type;

    auto Iter = llvm::find_if(QI, [P, &StreamProvider](const value_type &V) {
      DiffResult Result = StreamProvider.compare(P.value(), V.value());
      return Result == DiffResult::EQUIVALENT ||
             Result == DiffResult::IDENTICAL;
    });

    if (Iter == QI.end()) {
      D.printExplicit(StreamProvider.format(P.value(), false),
                      DiffResult::DIFFERENT, P.index(), "(not present)");
      continue;
    }

    D.print<EquivalentDiffProvider>(StreamProvider.format(P.value(), false),
                                    P.index(), Iter->index());
    QI.erase(Iter);
  }

  for (const auto &Q : QI) {
    D.printExplicit(StreamProvider.format(Q.value(), true),
                    DiffResult::DIFFERENT, "(not present)", Q.index());
  }

  return Error::success();
}

Error DiffStyle::diffStringTable() {
  DiffPrinter D(2, "String Table", 30, 20, opts::diff::PrintResultColumn,
                opts::diff::PrintValueColumns, outs());
  D.printExplicit("File", DiffResult::UNSPECIFIED,
                  truncateStringFront(File1.getFilePath(), 18),
                  truncateStringFront(File2.getFilePath(), 18));

  auto ExpectedST1 = File1.getStringTable();
  auto ExpectedST2 = File2.getStringTable();
  bool Has1 = !!ExpectedST1;
  bool Has2 = !!ExpectedST2;
  std::string Count1 = Has1 ? llvm::utostr(ExpectedST1->getNameCount())
                            : "(string table not present)";
  std::string Count2 = Has2 ? llvm::utostr(ExpectedST2->getNameCount())
                            : "(string table not present)";
  D.print("Number of Strings", Count1, Count2);

  if (!Has1 || !Has2) {
    consumeError(ExpectedST1.takeError());
    consumeError(ExpectedST2.takeError());
    return Error::success();
  }

  auto &ST1 = *ExpectedST1;
  auto &ST2 = *ExpectedST2;

  D.print("Hash Version", ST1.getHashVersion(), ST2.getHashVersion());
  D.print("Byte Size", ST1.getByteSize(), ST2.getByteSize());
  D.print("Signature", ST1.getSignature(), ST2.getSignature());

  // Both have a valid string table, dive in and compare individual strings.

  auto IdList1 = ST1.name_ids();
  auto IdList2 = ST2.name_ids();
  StringSet<> LS;
  StringSet<> RS;
  uint32_t Empty1 = 0;
  uint32_t Empty2 = 0;
  for (auto ID : IdList1) {
    auto S = ST1.getStringForID(ID);
    if (!S)
      return S.takeError();
    if (S->empty())
      ++Empty1;
    else
      LS.insert(*S);
  }
  for (auto ID : IdList2) {
    auto S = ST2.getStringForID(ID);
    if (!S)
      return S.takeError();
    if (S->empty())
      ++Empty2;
    else
      RS.insert(*S);
  }
  D.print("Empty Strings", Empty1, Empty2);

  for (const auto &S : LS) {
    auto R = RS.find(S.getKey());
    std::string Truncated = truncateStringMiddle(S.getKey(), 28);
    uint32_t I = cantFail(ST1.getIDForString(S.getKey()));
    if (R == RS.end()) {
      D.printExplicit(Truncated, DiffResult::DIFFERENT, I, "(not present)");
      continue;
    }

    uint32_t J = cantFail(ST2.getIDForString(R->getKey()));
    D.print<EquivalentDiffProvider>(Truncated, I, J);
    RS.erase(R);
  }

  for (const auto &S : RS) {
    auto L = LS.find(S.getKey());
    std::string Truncated = truncateStringMiddle(S.getKey(), 28);
    uint32_t J = cantFail(ST2.getIDForString(S.getKey()));
    if (L == LS.end()) {
      D.printExplicit(Truncated, DiffResult::DIFFERENT, "(not present)", J);
      continue;
    }

    uint32_t I = cantFail(ST1.getIDForString(L->getKey()));
    D.print<EquivalentDiffProvider>(Truncated, I, J);
  }
  return Error::success();
}

Error DiffStyle::diffFreePageMap() { return Error::success(); }

Error DiffStyle::diffInfoStream() {
  DiffPrinter D(2, "PDB Stream", 22, 40, opts::diff::PrintResultColumn,
                opts::diff::PrintValueColumns, outs());
  D.printExplicit("File", DiffResult::UNSPECIFIED,
                  truncateStringFront(File1.getFilePath(), 38),
                  truncateStringFront(File2.getFilePath(), 38));

  auto ExpectedInfo1 = File1.getPDBInfoStream();
  auto ExpectedInfo2 = File2.getPDBInfoStream();

  bool Has1 = !!ExpectedInfo1;
  bool Has2 = !!ExpectedInfo2;
  if (!(Has1 && Has2)) {
    std::string L = Has1 ? "(present)" : "(not present)";
    std::string R = Has2 ? "(present)" : "(not present)";
    D.print("Stream", L, R);

    consumeError(ExpectedInfo1.takeError());
    consumeError(ExpectedInfo2.takeError());
    return Error::success();
  }

  auto &IS1 = *ExpectedInfo1;
  auto &IS2 = *ExpectedInfo2;
  D.print("Stream Size", IS1.getStreamSize(), IS2.getStreamSize());
  D.print("Age", IS1.getAge(), IS2.getAge());
  D.print("Guid", IS1.getGuid(), IS2.getGuid());
  D.print("Signature", IS1.getSignature(), IS2.getSignature());
  D.print("Version", IS1.getVersion(), IS2.getVersion());
  D.diffUnorderedArray("Feature", IS1.getFeatureSignatures(),
                       IS2.getFeatureSignatures());
  D.print("Named Stream Size", IS1.getNamedStreamMapByteSize(),
          IS2.getNamedStreamMapByteSize());
  StringMap<uint32_t> NSL = IS1.getNamedStreams().getStringMap();
  StringMap<uint32_t> NSR = IS2.getNamedStreams().getStringMap();
  D.diffUnorderedMap<EquivalentDiffProvider>("Named Stream", NSL, NSR);
  return Error::success();
}

static std::vector<std::pair<uint32_t, DbiModuleDescriptor>>
getModuleDescriptors(const DbiModuleList &ML) {
  std::vector<std::pair<uint32_t, DbiModuleDescriptor>> List;
  List.reserve(ML.getModuleCount());
  for (uint32_t I = 0; I < ML.getModuleCount(); ++I)
    List.emplace_back(I, ML.getModuleDescriptor(I));
  return List;
}

static void
diffOneModule(DiffPrinter &D,
              const std::pair<uint32_t, DbiModuleDescriptor> Item,
              std::vector<std::pair<uint32_t, DbiModuleDescriptor>> &Other,
              bool ItemIsRight) {
  StreamPurposeProvider HeaderProvider(70);
  std::pair<StreamPurpose, std::string> Header;
  Header.first = StreamPurpose::ModuleStream;
  Header.second = Item.second.getModuleName();
  D.printFullRow(HeaderProvider.format(Header, ItemIsRight));

  const auto *L = &Item;

  BinaryPathProvider PathProvider(28);
  auto Iter = llvm::find_if(
      Other, [&Item, &PathProvider, ItemIsRight,
              L](const std::pair<uint32_t, DbiModuleDescriptor> &Other) {
        const auto *Left = L;
        const auto *Right = &Other;
        if (ItemIsRight)
          std::swap(Left, Right);
        DiffResult Result = PathProvider.compare(Left->second.getModuleName(),
                                                 Right->second.getModuleName());
        return Result == DiffResult::EQUIVALENT ||
               Result == DiffResult::IDENTICAL;
      });
  if (Iter == Other.end()) {
    // We didn't find this module at all on the other side.  Just print one row
    // and continue.
    D.print<ModiProvider>("- Modi", Item.first, None);
    return;
  }

  // We did find this module.  Go through and compare each field.
  const auto *R = &*Iter;
  if (ItemIsRight)
    std::swap(L, R);

  D.print<ModiProvider>("- Modi", L->first, R->first);
  D.print<BinaryPathProvider>("- Obj File Name", L->second.getObjFileName(),
                              R->second.getObjFileName(), PathProvider);
  D.print<StreamNumberProvider>("- Debug Stream",
                                L->second.getModuleStreamIndex(),
                                R->second.getModuleStreamIndex());
  D.print("- C11 Byte Size", L->second.getC11LineInfoByteSize(),
          R->second.getC11LineInfoByteSize());
  D.print("- C13 Byte Size", L->second.getC13LineInfoByteSize(),
          R->second.getC13LineInfoByteSize());
  D.print("- # of files", L->second.getNumberOfFiles(),
          R->second.getNumberOfFiles());
  D.print("- Pdb File Path Index", L->second.getPdbFilePathNameIndex(),
          R->second.getPdbFilePathNameIndex());
  D.print("- Source File Name Index", L->second.getSourceFileNameIndex(),
          R->second.getSourceFileNameIndex());
  D.print("- Symbol Byte Size", L->second.getSymbolDebugInfoByteSize(),
          R->second.getSymbolDebugInfoByteSize());
  Other.erase(Iter);
}

Error DiffStyle::diffDbiStream() {
  DiffPrinter D(2, "DBI Stream", 40, 30, opts::diff::PrintResultColumn,
                opts::diff::PrintValueColumns, outs());
  D.printExplicit("File", DiffResult::UNSPECIFIED,
                  truncateStringFront(File1.getFilePath(), 28),
                  truncateStringFront(File2.getFilePath(), 28));

  auto ExpectedDbi1 = File1.getPDBDbiStream();
  auto ExpectedDbi2 = File2.getPDBDbiStream();

  bool Has1 = !!ExpectedDbi1;
  bool Has2 = !!ExpectedDbi2;
  if (!(Has1 && Has2)) {
    std::string L = Has1 ? "(present)" : "(not present)";
    std::string R = Has2 ? "(present)" : "(not present)";
    D.print("Stream", L, R);

    consumeError(ExpectedDbi1.takeError());
    consumeError(ExpectedDbi2.takeError());
    return Error::success();
  }

  auto &DL = *ExpectedDbi1;
  auto &DR = *ExpectedDbi2;

  D.print("Dbi Version", (uint32_t)DL.getDbiVersion(),
          (uint32_t)DR.getDbiVersion());
  D.print("Age", DL.getAge(), DR.getAge());
  D.print("Machine", (uint16_t)DL.getMachineType(),
          (uint16_t)DR.getMachineType());
  D.print("Flags", DL.getFlags(), DR.getFlags());
  D.print("Build Major", DL.getBuildMajorVersion(), DR.getBuildMajorVersion());
  D.print("Build Minor", DL.getBuildMinorVersion(), DR.getBuildMinorVersion());
  D.print("Build Number", DL.getBuildNumber(), DR.getBuildNumber());
  D.print("PDB DLL Version", DL.getPdbDllVersion(), DR.getPdbDllVersion());
  D.print("PDB DLL RBLD", DL.getPdbDllRbld(), DR.getPdbDllRbld());
  D.print<StreamNumberProvider>("DBG (FPO)",
                                DL.getDebugStreamIndex(DbgHeaderType::FPO),
                                DR.getDebugStreamIndex(DbgHeaderType::FPO));
  D.print<StreamNumberProvider>(
      "DBG (Exception)", DL.getDebugStreamIndex(DbgHeaderType::Exception),
      DR.getDebugStreamIndex(DbgHeaderType::Exception));
  D.print<StreamNumberProvider>("DBG (Fixup)",
                                DL.getDebugStreamIndex(DbgHeaderType::Fixup),
                                DR.getDebugStreamIndex(DbgHeaderType::Fixup));
  D.print<StreamNumberProvider>(
      "DBG (OmapToSrc)", DL.getDebugStreamIndex(DbgHeaderType::OmapToSrc),
      DR.getDebugStreamIndex(DbgHeaderType::OmapToSrc));
  D.print<StreamNumberProvider>(
      "DBG (OmapFromSrc)", DL.getDebugStreamIndex(DbgHeaderType::OmapFromSrc),
      DR.getDebugStreamIndex(DbgHeaderType::OmapFromSrc));
  D.print<StreamNumberProvider>(
      "DBG (SectionHdr)", DL.getDebugStreamIndex(DbgHeaderType::SectionHdr),
      DR.getDebugStreamIndex(DbgHeaderType::SectionHdr));
  D.print<StreamNumberProvider>(
      "DBG (TokenRidMap)", DL.getDebugStreamIndex(DbgHeaderType::TokenRidMap),
      DR.getDebugStreamIndex(DbgHeaderType::TokenRidMap));
  D.print<StreamNumberProvider>("DBG (Xdata)",
                                DL.getDebugStreamIndex(DbgHeaderType::Xdata),
                                DR.getDebugStreamIndex(DbgHeaderType::Xdata));
  D.print<StreamNumberProvider>("DBG (Pdata)",
                                DL.getDebugStreamIndex(DbgHeaderType::Pdata),
                                DR.getDebugStreamIndex(DbgHeaderType::Pdata));
  D.print<StreamNumberProvider>("DBG (NewFPO)",
                                DL.getDebugStreamIndex(DbgHeaderType::NewFPO),
                                DR.getDebugStreamIndex(DbgHeaderType::NewFPO));
  D.print<StreamNumberProvider>(
      "DBG (SectionHdrOrig)",
      DL.getDebugStreamIndex(DbgHeaderType::SectionHdrOrig),
      DR.getDebugStreamIndex(DbgHeaderType::SectionHdrOrig));
  D.print<StreamNumberProvider>("Globals Stream",
                                DL.getGlobalSymbolStreamIndex(),
                                DR.getGlobalSymbolStreamIndex());
  D.print<StreamNumberProvider>("Publics Stream",
                                DL.getPublicSymbolStreamIndex(),
                                DR.getPublicSymbolStreamIndex());
  D.print<StreamNumberProvider>("Symbol Records", DL.getSymRecordStreamIndex(),
                                DR.getSymRecordStreamIndex());
  D.print("Has CTypes", DL.hasCTypes(), DR.hasCTypes());
  D.print("Is Incrementally Linked", DL.isIncrementallyLinked(),
          DR.isIncrementallyLinked());
  D.print("Is Stripped", DL.isStripped(), DR.isStripped());
  const DbiModuleList &ML = DL.modules();
  const DbiModuleList &MR = DR.modules();
  D.print("Module Count", ML.getModuleCount(), MR.getModuleCount());
  D.print("Source File Count", ML.getSourceFileCount(),
          MR.getSourceFileCount());
  auto MDL = getModuleDescriptors(ML);
  auto MDR = getModuleDescriptors(MR);
  // Scan all module descriptors from the left, and look for corresponding
  // module descriptors on the right.
  for (const auto &L : MDL)
    diffOneModule(D, L, MDR, false);

  for (const auto &R : MDR)
    diffOneModule(D, R, MDL, true);

  return Error::success();
}

Error DiffStyle::diffSectionContribs() { return Error::success(); }

Error DiffStyle::diffSectionMap() { return Error::success(); }

Error DiffStyle::diffFpoStream() { return Error::success(); }

Error DiffStyle::diffTpiStream(int Index) { return Error::success(); }

Error DiffStyle::diffModuleInfoStream(int Index) { return Error::success(); }

Error DiffStyle::diffPublics() { return Error::success(); }

Error DiffStyle::diffGlobals() { return Error::success(); }