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