1 //===- ExplainOutputStyle.cpp --------------------------------- *- C++ --*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "ExplainOutputStyle.h"
10
11 #include "FormatUtil.h"
12 #include "InputFile.h"
13 #include "StreamUtil.h"
14 #include "llvm-pdbutil.h"
15
16 #include "llvm/DebugInfo/CodeView/Formatters.h"
17 #include "llvm/DebugInfo/MSF/MappedBlockStream.h"
18 #include "llvm/DebugInfo/PDB/Native/DbiStream.h"
19 #include "llvm/DebugInfo/PDB/Native/InfoStream.h"
20 #include "llvm/DebugInfo/PDB/Native/PDBFile.h"
21 #include "llvm/DebugInfo/PDB/Native/RawTypes.h"
22 #include "llvm/Support/BinaryByteStream.h"
23 #include "llvm/Support/BinaryStreamArray.h"
24 #include "llvm/Support/Error.h"
25
26 using namespace llvm;
27 using namespace llvm::codeview;
28 using namespace llvm::msf;
29 using namespace llvm::pdb;
30
ExplainOutputStyle(InputFile & File,uint64_t FileOffset)31 ExplainOutputStyle::ExplainOutputStyle(InputFile &File, uint64_t FileOffset)
32 : File(File), FileOffset(FileOffset), P(2, false, outs()) {}
33
dump()34 Error ExplainOutputStyle::dump() {
35 P.formatLine("Explaining file offset {0} of file '{1}'.", FileOffset,
36 File.getFilePath());
37
38 if (File.isPdb())
39 return explainPdbFile();
40
41 return explainBinaryFile();
42 }
43
explainPdbFile()44 Error ExplainOutputStyle::explainPdbFile() {
45 bool IsAllocated = explainPdbBlockStatus();
46 if (!IsAllocated)
47 return Error::success();
48
49 AutoIndent Indent(P);
50 if (isPdbSuperBlock())
51 explainPdbSuperBlockOffset();
52 else if (isPdbFpmBlock())
53 explainPdbFpmBlockOffset();
54 else if (isPdbBlockMapBlock())
55 explainPdbBlockMapOffset();
56 else if (isPdbStreamDirectoryBlock())
57 explainPdbStreamDirectoryOffset();
58 else if (auto Index = getPdbBlockStreamIndex())
59 explainPdbStreamOffset(*Index);
60 else
61 explainPdbUnknownBlock();
62 return Error::success();
63 }
64
explainBinaryFile()65 Error ExplainOutputStyle::explainBinaryFile() {
66 std::unique_ptr<BinaryByteStream> Stream =
67 std::make_unique<BinaryByteStream>(File.unknown().getBuffer(),
68 llvm::support::little);
69 switch (opts::explain::InputType) {
70 case opts::explain::InputFileType::DBIStream: {
71 DbiStream Dbi(std::move(Stream));
72 if (auto EC = Dbi.reload(nullptr))
73 return EC;
74 explainStreamOffset(Dbi, FileOffset);
75 break;
76 }
77 case opts::explain::InputFileType::PDBStream: {
78 InfoStream Info(std::move(Stream));
79 if (auto EC = Info.reload())
80 return EC;
81 explainStreamOffset(Info, FileOffset);
82 break;
83 }
84 default:
85 llvm_unreachable("Invalid input file type!");
86 }
87 return Error::success();
88 }
89
pdbBlockIndex() const90 uint32_t ExplainOutputStyle::pdbBlockIndex() const {
91 return FileOffset / File.pdb().getBlockSize();
92 }
93
pdbBlockOffset() const94 uint32_t ExplainOutputStyle::pdbBlockOffset() const {
95 uint64_t BlockStart = pdbBlockIndex() * File.pdb().getBlockSize();
96 assert(FileOffset >= BlockStart);
97 return FileOffset - BlockStart;
98 }
99
isPdbSuperBlock() const100 bool ExplainOutputStyle::isPdbSuperBlock() const {
101 return pdbBlockIndex() == 0;
102 }
103
isPdbFpm1() const104 bool ExplainOutputStyle::isPdbFpm1() const {
105 return ((pdbBlockIndex() - 1) % File.pdb().getBlockSize() == 0);
106 }
isPdbFpm2() const107 bool ExplainOutputStyle::isPdbFpm2() const {
108 return ((pdbBlockIndex() - 2) % File.pdb().getBlockSize() == 0);
109 }
110
isPdbFpmBlock() const111 bool ExplainOutputStyle::isPdbFpmBlock() const {
112 return isPdbFpm1() || isPdbFpm2();
113 }
114
isPdbBlockMapBlock() const115 bool ExplainOutputStyle::isPdbBlockMapBlock() const {
116 return pdbBlockIndex() == File.pdb().getBlockMapIndex();
117 }
118
isPdbStreamDirectoryBlock() const119 bool ExplainOutputStyle::isPdbStreamDirectoryBlock() const {
120 const auto &Layout = File.pdb().getMsfLayout();
121 return llvm::is_contained(Layout.DirectoryBlocks, pdbBlockIndex());
122 }
123
getPdbBlockStreamIndex() const124 Optional<uint32_t> ExplainOutputStyle::getPdbBlockStreamIndex() const {
125 const auto &Layout = File.pdb().getMsfLayout();
126 for (const auto &Entry : enumerate(Layout.StreamMap)) {
127 if (!llvm::is_contained(Entry.value(), pdbBlockIndex()))
128 continue;
129 return Entry.index();
130 }
131 return None;
132 }
133
explainPdbBlockStatus()134 bool ExplainOutputStyle::explainPdbBlockStatus() {
135 if (FileOffset >= File.pdb().getFileSize()) {
136 P.formatLine("Address {0} is not in the file (file size = {1}).",
137 FileOffset, File.pdb().getFileSize());
138 return false;
139 }
140 P.formatLine("Block:Offset = {2:X-}:{1:X-4}.", FileOffset, pdbBlockOffset(),
141 pdbBlockIndex());
142
143 bool IsFree = File.pdb().getMsfLayout().FreePageMap[pdbBlockIndex()];
144 P.formatLine("Address is in block {0} ({1}allocated).", pdbBlockIndex(),
145 IsFree ? "un" : "");
146 return !IsFree;
147 }
148
149 #define endof(Class, Field) (offsetof(Class, Field) + sizeof(Class::Field))
150
explainPdbSuperBlockOffset()151 void ExplainOutputStyle::explainPdbSuperBlockOffset() {
152 P.formatLine("This corresponds to offset {0} of the MSF super block, ",
153 pdbBlockOffset());
154 if (pdbBlockOffset() < endof(SuperBlock, MagicBytes))
155 P.printLine("which is part of the MSF file magic.");
156 else if (pdbBlockOffset() < endof(SuperBlock, BlockSize)) {
157 P.printLine("which contains the block size of the file.");
158 P.formatLine("The current value is {0}.",
159 uint32_t(File.pdb().getMsfLayout().SB->BlockSize));
160 } else if (pdbBlockOffset() < endof(SuperBlock, FreeBlockMapBlock)) {
161 P.printLine("which contains the index of the FPM block (e.g. 1 or 2).");
162 P.formatLine("The current value is {0}.",
163 uint32_t(File.pdb().getMsfLayout().SB->FreeBlockMapBlock));
164 } else if (pdbBlockOffset() < endof(SuperBlock, NumBlocks)) {
165 P.printLine("which contains the number of blocks in the file.");
166 P.formatLine("The current value is {0}.",
167 uint32_t(File.pdb().getMsfLayout().SB->NumBlocks));
168 } else if (pdbBlockOffset() < endof(SuperBlock, NumDirectoryBytes)) {
169 P.printLine("which contains the number of bytes in the stream directory.");
170 P.formatLine("The current value is {0}.",
171 uint32_t(File.pdb().getMsfLayout().SB->NumDirectoryBytes));
172 } else if (pdbBlockOffset() < endof(SuperBlock, Unknown1)) {
173 P.printLine("whose purpose is unknown.");
174 P.formatLine("The current value is {0}.",
175 uint32_t(File.pdb().getMsfLayout().SB->Unknown1));
176 } else if (pdbBlockOffset() < endof(SuperBlock, BlockMapAddr)) {
177 P.printLine("which contains the file offset of the block map.");
178 P.formatLine("The current value is {0}.",
179 uint32_t(File.pdb().getMsfLayout().SB->BlockMapAddr));
180 } else {
181 assert(pdbBlockOffset() > sizeof(SuperBlock));
182 P.printLine(
183 "which is outside the range of valid data for the super block.");
184 }
185 }
186
toBinaryString(uint8_t Byte)187 static std::string toBinaryString(uint8_t Byte) {
188 char Result[9] = {0};
189 for (int I = 0; I < 8; ++I) {
190 char C = (Byte & 1) ? '1' : '0';
191 Result[I] = C;
192 Byte >>= 1;
193 }
194 return std::string(Result);
195 }
196
explainPdbFpmBlockOffset()197 void ExplainOutputStyle::explainPdbFpmBlockOffset() {
198 const MSFLayout &Layout = File.pdb().getMsfLayout();
199 uint32_t MainFpm = Layout.mainFpmBlock();
200 uint32_t AltFpm = Layout.alternateFpmBlock();
201
202 assert(isPdbFpmBlock());
203 uint32_t Fpm = isPdbFpm1() ? 1 : 2;
204 uint32_t FpmChunk = pdbBlockIndex() / File.pdb().getBlockSize();
205 assert((Fpm == MainFpm) || (Fpm == AltFpm));
206 (void)AltFpm;
207 bool IsMain = (Fpm == MainFpm);
208 P.formatLine("Address is in FPM{0} ({1} FPM)", Fpm, IsMain ? "Main" : "Alt");
209 uint32_t DescribedBlockStart =
210 8 * (FpmChunk * File.pdb().getBlockSize() + pdbBlockOffset());
211 if (DescribedBlockStart > File.pdb().getBlockCount()) {
212 P.printLine("Address is in extraneous FPM space.");
213 return;
214 }
215
216 P.formatLine("Address describes the allocation status of blocks [{0},{1})",
217 DescribedBlockStart, DescribedBlockStart + 8);
218 ArrayRef<uint8_t> Bytes;
219 cantFail(File.pdb().getMsfBuffer().readBytes(FileOffset, 1, Bytes));
220 P.formatLine("Status = {0} (Note: 0 = allocated, 1 = free)",
221 toBinaryString(Bytes[0]));
222 }
223
explainPdbBlockMapOffset()224 void ExplainOutputStyle::explainPdbBlockMapOffset() {
225 uint64_t BlockMapOffset = File.pdb().getBlockMapOffset();
226 uint32_t OffsetInBlock = FileOffset - BlockMapOffset;
227 P.formatLine("Address is at offset {0} of the directory block list",
228 OffsetInBlock);
229 }
230
getOffsetInStream(ArrayRef<support::ulittle32_t> StreamBlocks,uint64_t FileOffset,uint32_t BlockSize)231 static uint32_t getOffsetInStream(ArrayRef<support::ulittle32_t> StreamBlocks,
232 uint64_t FileOffset, uint32_t BlockSize) {
233 uint32_t BlockIndex = FileOffset / BlockSize;
234 uint32_t OffsetInBlock = FileOffset - BlockIndex * BlockSize;
235
236 auto Iter = llvm::find(StreamBlocks, BlockIndex);
237 assert(Iter != StreamBlocks.end());
238 uint32_t StreamBlockIndex = std::distance(StreamBlocks.begin(), Iter);
239 return StreamBlockIndex * BlockSize + OffsetInBlock;
240 }
241
explainPdbStreamOffset(uint32_t Stream)242 void ExplainOutputStyle::explainPdbStreamOffset(uint32_t Stream) {
243 SmallVector<StreamInfo, 12> Streams;
244 discoverStreamPurposes(File.pdb(), Streams);
245
246 assert(Stream <= Streams.size());
247 const StreamInfo &S = Streams[Stream];
248 const auto &Layout = File.pdb().getStreamLayout(Stream);
249 uint32_t StreamOff =
250 getOffsetInStream(Layout.Blocks, FileOffset, File.pdb().getBlockSize());
251 P.formatLine("Address is at offset {0}/{1} of Stream {2} ({3}){4}.",
252 StreamOff, Layout.Length, Stream, S.getLongName(),
253 (StreamOff > Layout.Length) ? " in unused space" : "");
254 switch (S.getPurpose()) {
255 case StreamPurpose::DBI: {
256 DbiStream &Dbi = cantFail(File.pdb().getPDBDbiStream());
257 explainStreamOffset(Dbi, StreamOff);
258 break;
259 }
260 case StreamPurpose::PDB: {
261 InfoStream &Info = cantFail(File.pdb().getPDBInfoStream());
262 explainStreamOffset(Info, StreamOff);
263 break;
264 }
265 case StreamPurpose::IPI:
266 case StreamPurpose::TPI:
267 case StreamPurpose::ModuleStream:
268 case StreamPurpose::NamedStream:
269 default:
270 break;
271 }
272 }
273
explainPdbStreamDirectoryOffset()274 void ExplainOutputStyle::explainPdbStreamDirectoryOffset() {
275 auto DirectoryBlocks = File.pdb().getDirectoryBlockArray();
276 const auto &Layout = File.pdb().getMsfLayout();
277 uint32_t StreamOff =
278 getOffsetInStream(DirectoryBlocks, FileOffset, File.pdb().getBlockSize());
279 P.formatLine("Address is at offset {0}/{1} of Stream Directory{2}.",
280 StreamOff, uint32_t(Layout.SB->NumDirectoryBytes),
281 uint32_t(StreamOff > Layout.SB->NumDirectoryBytes)
282 ? " in unused space"
283 : "");
284 }
285
explainPdbUnknownBlock()286 void ExplainOutputStyle::explainPdbUnknownBlock() {
287 P.formatLine("Address has unknown purpose.");
288 }
289
290 template <typename T>
printStructField(LinePrinter & P,StringRef Label,T Value)291 static void printStructField(LinePrinter &P, StringRef Label, T Value) {
292 P.formatLine("which contains {0}.", Label);
293 P.formatLine("The current value is {0}.", Value);
294 }
295
explainDbiHeaderOffset(LinePrinter & P,DbiStream & Dbi,uint32_t Offset)296 static void explainDbiHeaderOffset(LinePrinter &P, DbiStream &Dbi,
297 uint32_t Offset) {
298 const DbiStreamHeader *Header = Dbi.getHeader();
299 assert(Header != nullptr);
300
301 if (Offset < endof(DbiStreamHeader, VersionSignature))
302 printStructField(P, "the DBI Stream Version Signature",
303 int32_t(Header->VersionSignature));
304 else if (Offset < endof(DbiStreamHeader, VersionHeader))
305 printStructField(P, "the DBI Stream Version Header",
306 uint32_t(Header->VersionHeader));
307 else if (Offset < endof(DbiStreamHeader, Age))
308 printStructField(P, "the age of the DBI Stream", uint32_t(Header->Age));
309 else if (Offset < endof(DbiStreamHeader, GlobalSymbolStreamIndex))
310 printStructField(P, "the index of the Global Symbol Stream",
311 uint16_t(Header->GlobalSymbolStreamIndex));
312 else if (Offset < endof(DbiStreamHeader, BuildNumber))
313 printStructField(P, "the build number", uint16_t(Header->BuildNumber));
314 else if (Offset < endof(DbiStreamHeader, PublicSymbolStreamIndex))
315 printStructField(P, "the index of the Public Symbol Stream",
316 uint16_t(Header->PublicSymbolStreamIndex));
317 else if (Offset < endof(DbiStreamHeader, PdbDllVersion))
318 printStructField(P, "the version of mspdb.dll",
319 uint16_t(Header->PdbDllVersion));
320 else if (Offset < endof(DbiStreamHeader, SymRecordStreamIndex))
321 printStructField(P, "the index of the Symbol Record Stream",
322 uint16_t(Header->SymRecordStreamIndex));
323 else if (Offset < endof(DbiStreamHeader, PdbDllRbld))
324 printStructField(P, "the rbld of mspdb.dll", uint16_t(Header->PdbDllRbld));
325 else if (Offset < endof(DbiStreamHeader, ModiSubstreamSize))
326 printStructField(P, "the size of the Module Info Substream",
327 int32_t(Header->ModiSubstreamSize));
328 else if (Offset < endof(DbiStreamHeader, SecContrSubstreamSize))
329 printStructField(P, "the size of the Section Contribution Substream",
330 int32_t(Header->SecContrSubstreamSize));
331 else if (Offset < endof(DbiStreamHeader, SectionMapSize))
332 printStructField(P, "the size of the Section Map Substream",
333 int32_t(Header->SectionMapSize));
334 else if (Offset < endof(DbiStreamHeader, FileInfoSize))
335 printStructField(P, "the size of the File Info Substream",
336 int32_t(Header->FileInfoSize));
337 else if (Offset < endof(DbiStreamHeader, TypeServerSize))
338 printStructField(P, "the size of the Type Server Map",
339 int32_t(Header->TypeServerSize));
340 else if (Offset < endof(DbiStreamHeader, MFCTypeServerIndex))
341 printStructField(P, "the index of the MFC Type Server stream",
342 uint32_t(Header->MFCTypeServerIndex));
343 else if (Offset < endof(DbiStreamHeader, OptionalDbgHdrSize))
344 printStructField(P, "the size of the Optional Debug Stream array",
345 int32_t(Header->OptionalDbgHdrSize));
346 else if (Offset < endof(DbiStreamHeader, ECSubstreamSize))
347 printStructField(P, "the size of the Edit & Continue Substream",
348 int32_t(Header->ECSubstreamSize));
349 else if (Offset < endof(DbiStreamHeader, Flags))
350 printStructField(P, "the DBI Stream flags", uint16_t(Header->Flags));
351 else if (Offset < endof(DbiStreamHeader, MachineType))
352 printStructField(P, "the machine type", uint16_t(Header->MachineType));
353 else if (Offset < endof(DbiStreamHeader, Reserved))
354 printStructField(P, "reserved data", uint32_t(Header->Reserved));
355 }
356
explainDbiModiSubstreamOffset(LinePrinter & P,DbiStream & Dbi,uint32_t Offset)357 static void explainDbiModiSubstreamOffset(LinePrinter &P, DbiStream &Dbi,
358 uint32_t Offset) {
359 VarStreamArray<DbiModuleDescriptor> ModuleDescriptors;
360 BinaryStreamRef ModiSubstreamData = Dbi.getModiSubstreamData().StreamData;
361 BinaryStreamReader Reader(ModiSubstreamData);
362
363 cantFail(Reader.readArray(ModuleDescriptors, ModiSubstreamData.getLength()));
364 auto Prev = ModuleDescriptors.begin();
365 assert(Prev.offset() == 0);
366 auto Current = Prev;
367 uint32_t Index = 0;
368 while (true) {
369 Prev = Current;
370 ++Current;
371 if (Current == ModuleDescriptors.end() || Offset < Current.offset())
372 break;
373 ++Index;
374 }
375
376 DbiModuleDescriptor &Descriptor = *Prev;
377 P.formatLine("which contains the descriptor for module {0} ({1}).", Index,
378 Descriptor.getModuleName());
379 }
380
381 template <typename T>
dontExplain(LinePrinter & Printer,T & Stream,uint32_t Offset)382 static void dontExplain(LinePrinter &Printer, T &Stream, uint32_t Offset) {}
383
384 template <typename T, typename SubstreamRangeT>
explainSubstreamOffset(LinePrinter & P,uint32_t OffsetInStream,T & Stream,const SubstreamRangeT & Substreams)385 static void explainSubstreamOffset(LinePrinter &P, uint32_t OffsetInStream,
386 T &Stream,
387 const SubstreamRangeT &Substreams) {
388 uint32_t SubOffset = OffsetInStream;
389 for (const auto &Entry : Substreams) {
390 if (Entry.Size <= 0)
391 continue;
392 uint32_t S = static_cast<uint32_t>(Entry.Size);
393 if (SubOffset < S) {
394 P.formatLine("address is at offset {0}/{1} of the {2}.", SubOffset, S,
395 Entry.Label);
396 Entry.Explain(P, Stream, SubOffset);
397 return;
398 }
399 SubOffset -= S;
400 }
401 }
402
explainStreamOffset(DbiStream & Dbi,uint32_t OffsetInStream)403 void ExplainOutputStyle::explainStreamOffset(DbiStream &Dbi,
404 uint32_t OffsetInStream) {
405 P.printLine("Within the DBI stream:");
406 AutoIndent Indent(P);
407 const DbiStreamHeader *Header = Dbi.getHeader();
408 assert(Header != nullptr);
409
410 struct SubstreamInfo {
411 int32_t Size;
412 StringRef Label;
413 void (*Explain)(LinePrinter &, DbiStream &, uint32_t);
414 } Substreams[] = {
415 {sizeof(DbiStreamHeader), "DBI Stream Header", explainDbiHeaderOffset},
416 {int32_t(Header->ModiSubstreamSize), "Module Info Substream",
417 explainDbiModiSubstreamOffset},
418 {int32_t(Header->SecContrSubstreamSize), "Section Contribution Substream",
419 dontExplain<DbiStream>},
420 {int32_t(Header->SectionMapSize), "Section Map", dontExplain<DbiStream>},
421 {int32_t(Header->FileInfoSize), "File Info Substream",
422 dontExplain<DbiStream>},
423 {int32_t(Header->TypeServerSize), "Type Server Map Substream",
424 dontExplain<DbiStream>},
425 {int32_t(Header->ECSubstreamSize), "Edit & Continue Substream",
426 dontExplain<DbiStream>},
427 {int32_t(Header->OptionalDbgHdrSize), "Optional Debug Stream Array",
428 dontExplain<DbiStream>},
429 };
430
431 explainSubstreamOffset(P, OffsetInStream, Dbi, Substreams);
432 }
433
explainPdbStreamHeaderOffset(LinePrinter & P,InfoStream & Info,uint32_t Offset)434 static void explainPdbStreamHeaderOffset(LinePrinter &P, InfoStream &Info,
435 uint32_t Offset) {
436 const InfoStreamHeader *Header = Info.getHeader();
437 assert(Header != nullptr);
438
439 if (Offset < endof(InfoStreamHeader, Version))
440 printStructField(P, "the PDB Stream Version Signature",
441 uint32_t(Header->Version));
442 else if (Offset < endof(InfoStreamHeader, Signature))
443 printStructField(P, "the signature of the PDB Stream",
444 uint32_t(Header->Signature));
445 else if (Offset < endof(InfoStreamHeader, Age))
446 printStructField(P, "the age of the PDB", uint32_t(Header->Age));
447 else if (Offset < endof(InfoStreamHeader, Guid))
448 printStructField(P, "the guid of the PDB", fmt_guid(Header->Guid.Guid));
449 }
450
explainStreamOffset(InfoStream & Info,uint32_t OffsetInStream)451 void ExplainOutputStyle::explainStreamOffset(InfoStream &Info,
452 uint32_t OffsetInStream) {
453 P.printLine("Within the PDB stream:");
454 AutoIndent Indent(P);
455
456 struct SubstreamInfo {
457 uint32_t Size;
458 StringRef Label;
459 void (*Explain)(LinePrinter &, InfoStream &, uint32_t);
460 } Substreams[] = {{sizeof(InfoStreamHeader), "PDB Stream Header",
461 explainPdbStreamHeaderOffset},
462 {Info.getNamedStreamMapByteSize(), "Named Stream Map",
463 dontExplain<InfoStream>},
464 {Info.getStreamSize(), "PDB Feature Signatures",
465 dontExplain<InfoStream>}};
466
467 explainSubstreamOffset(P, OffsetInStream, Info, Substreams);
468 }
469