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 31 ExplainOutputStyle::ExplainOutputStyle(InputFile &File, uint64_t FileOffset) 32 : File(File), FileOffset(FileOffset), P(2, false, outs()) {} 33 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 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 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 90 uint32_t ExplainOutputStyle::pdbBlockIndex() const { 91 return FileOffset / File.pdb().getBlockSize(); 92 } 93 94 uint32_t ExplainOutputStyle::pdbBlockOffset() const { 95 uint64_t BlockStart = pdbBlockIndex() * File.pdb().getBlockSize(); 96 assert(FileOffset >= BlockStart); 97 return FileOffset - BlockStart; 98 } 99 100 bool ExplainOutputStyle::isPdbSuperBlock() const { 101 return pdbBlockIndex() == 0; 102 } 103 104 bool ExplainOutputStyle::isPdbFpm1() const { 105 return ((pdbBlockIndex() - 1) % File.pdb().getBlockSize() == 0); 106 } 107 bool ExplainOutputStyle::isPdbFpm2() const { 108 return ((pdbBlockIndex() - 2) % File.pdb().getBlockSize() == 0); 109 } 110 111 bool ExplainOutputStyle::isPdbFpmBlock() const { 112 return isPdbFpm1() || isPdbFpm2(); 113 } 114 115 bool ExplainOutputStyle::isPdbBlockMapBlock() const { 116 return pdbBlockIndex() == File.pdb().getBlockMapIndex(); 117 } 118 119 bool ExplainOutputStyle::isPdbStreamDirectoryBlock() const { 120 const auto &Layout = File.pdb().getMsfLayout(); 121 return llvm::is_contained(Layout.DirectoryBlocks, pdbBlockIndex()); 122 } 123 124 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 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 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 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 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 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 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 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 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 286 void ExplainOutputStyle::explainPdbUnknownBlock() { 287 P.formatLine("Address has unknown purpose."); 288 } 289 290 template <typename T> 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 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 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> 382 static void dontExplain(LinePrinter &Printer, T &Stream, uint32_t Offset) {} 383 384 template <typename T, typename SubstreamRangeT> 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 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 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 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