Index: llvm/trunk/include/llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h =================================================================== --- llvm/trunk/include/llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h (revision 342079) +++ llvm/trunk/include/llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h (revision 342080) @@ -1,140 +1,143 @@ //===- DbiStreamBuilder.h - PDB Dbi Stream Creation -------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #ifndef LLVM_DEBUGINFO_PDB_RAW_PDBDBISTREAMBUILDER_H #define LLVM_DEBUGINFO_PDB_RAW_PDBDBISTREAMBUILDER_H #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringSet.h" #include "llvm/BinaryFormat/COFF.h" #include "llvm/Support/Error.h" #include "llvm/DebugInfo/CodeView/DebugFrameDataSubsection.h" #include "llvm/DebugInfo/PDB/Native/PDBFile.h" #include "llvm/DebugInfo/PDB/Native/PDBStringTableBuilder.h" #include "llvm/DebugInfo/PDB/Native/RawConstants.h" #include "llvm/DebugInfo/PDB/PDBTypes.h" #include "llvm/Support/BinaryByteStream.h" #include "llvm/Support/BinaryStreamReader.h" #include "llvm/Support/Endian.h" namespace llvm { namespace codeview { struct FrameData; } namespace msf { class MSFBuilder; } namespace object { struct coff_section; +struct FpoData; } namespace pdb { class DbiStream; struct DbiStreamHeader; class DbiModuleDescriptorBuilder; class PDBFile; class DbiStreamBuilder { public: DbiStreamBuilder(msf::MSFBuilder &Msf); ~DbiStreamBuilder(); DbiStreamBuilder(const DbiStreamBuilder &) = delete; DbiStreamBuilder &operator=(const DbiStreamBuilder &) = delete; void setVersionHeader(PdbRaw_DbiVer V); void setAge(uint32_t A); void setBuildNumber(uint16_t B); void setBuildNumber(uint8_t Major, uint8_t Minor); void setPdbDllVersion(uint16_t V); void setPdbDllRbld(uint16_t R); void setFlags(uint16_t F); void setMachineType(PDB_Machine M); void setMachineType(COFF::MachineTypes M); void setSectionMap(ArrayRef SecMap); // Add given bytes as a new stream. Error addDbgStream(pdb::DbgHeaderType Type, ArrayRef Data); uint32_t addECName(StringRef Name); uint32_t calculateSerializedLength() const; void setGlobalsStreamIndex(uint32_t Index); void setPublicsStreamIndex(uint32_t Index); void setSymbolRecordStreamIndex(uint32_t Index); - void addFrameData(const codeview::FrameData &FD); + void addNewFpoData(const codeview::FrameData &FD); + void addOldFpoData(const object::FpoData &Fpo); Expected addModuleInfo(StringRef ModuleName); Error addModuleSourceFile(DbiModuleDescriptorBuilder &Module, StringRef File); Expected getSourceFileNameIndex(StringRef FileName); Error finalizeMsfLayout(); Error commit(const msf::MSFLayout &Layout, WritableBinaryStreamRef MsfBuffer); void addSectionContrib(const SectionContrib &SC) { SectionContribs.emplace_back(SC); } // A helper function to create a Section Map from a COFF section header. static std::vector createSectionMap(ArrayRef SecHdrs); private: struct DebugStream { std::function WriteFn; uint32_t Size = 0; uint16_t StreamNumber = kInvalidStreamIndex; }; Error finalize(); uint32_t calculateModiSubstreamSize() const; uint32_t calculateNamesOffset() const; uint32_t calculateSectionContribsStreamSize() const; uint32_t calculateSectionMapStreamSize() const; uint32_t calculateFileInfoSubstreamSize() const; uint32_t calculateNamesBufferSize() const; uint32_t calculateDbgStreamsSize() const; Error generateFileInfoSubstream(); msf::MSFBuilder &Msf; BumpPtrAllocator &Allocator; Optional VerHeader; uint32_t Age; uint16_t BuildNumber; uint16_t PdbDllVersion; uint16_t PdbDllRbld; uint16_t Flags; PDB_Machine MachineType; uint32_t GlobalsStreamIndex = kInvalidStreamIndex; uint32_t PublicsStreamIndex = kInvalidStreamIndex; uint32_t SymRecordStreamIndex = kInvalidStreamIndex; const DbiStreamHeader *Header; std::vector> ModiList; - Optional FrameData; + Optional NewFpoData; + std::vector OldFpoData; StringMap SourceFileNames; PDBStringTableBuilder ECNamesBuilder; WritableBinaryStreamRef NamesBuffer; MutableBinaryByteStream FileInfoBuffer; std::vector SectionContribs; ArrayRef SectionMap; std::array, (int)DbgHeaderType::Max> DbgStreams; }; } } #endif Index: llvm/trunk/include/llvm/Object/COFF.h =================================================================== --- llvm/trunk/include/llvm/Object/COFF.h (revision 342079) +++ llvm/trunk/include/llvm/Object/COFF.h (revision 342080) @@ -1,1238 +1,1240 @@ //===- COFF.h - COFF object file implementation -----------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file declares the COFFObjectFile class. // //===----------------------------------------------------------------------===// #ifndef LLVM_OBJECT_COFF_H #define LLVM_OBJECT_COFF_H #include "llvm/ADT/iterator_range.h" #include "llvm/BinaryFormat/COFF.h" #include "llvm/MC/SubtargetFeature.h" #include "llvm/Object/Binary.h" #include "llvm/Object/CVDebugRecord.h" #include "llvm/Object/Error.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/BinaryByteStream.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Endian.h" #include "llvm/Support/ErrorHandling.h" #include #include #include #include namespace llvm { template class ArrayRef; namespace object { class BaseRelocRef; class DelayImportDirectoryEntryRef; class ExportDirectoryEntryRef; class ImportDirectoryEntryRef; class ImportedSymbolRef; class ResourceSectionRef; using import_directory_iterator = content_iterator; using delay_import_directory_iterator = content_iterator; using export_directory_iterator = content_iterator; using imported_symbol_iterator = content_iterator; using base_reloc_iterator = content_iterator; /// The DOS compatible header at the front of all PE/COFF executables. struct dos_header { char Magic[2]; support::ulittle16_t UsedBytesInTheLastPage; support::ulittle16_t FileSizeInPages; support::ulittle16_t NumberOfRelocationItems; support::ulittle16_t HeaderSizeInParagraphs; support::ulittle16_t MinimumExtraParagraphs; support::ulittle16_t MaximumExtraParagraphs; support::ulittle16_t InitialRelativeSS; support::ulittle16_t InitialSP; support::ulittle16_t Checksum; support::ulittle16_t InitialIP; support::ulittle16_t InitialRelativeCS; support::ulittle16_t AddressOfRelocationTable; support::ulittle16_t OverlayNumber; support::ulittle16_t Reserved[4]; support::ulittle16_t OEMid; support::ulittle16_t OEMinfo; support::ulittle16_t Reserved2[10]; support::ulittle32_t AddressOfNewExeHeader; }; struct coff_file_header { support::ulittle16_t Machine; support::ulittle16_t NumberOfSections; support::ulittle32_t TimeDateStamp; support::ulittle32_t PointerToSymbolTable; support::ulittle32_t NumberOfSymbols; support::ulittle16_t SizeOfOptionalHeader; support::ulittle16_t Characteristics; bool isImportLibrary() const { return NumberOfSections == 0xffff; } }; struct coff_bigobj_file_header { support::ulittle16_t Sig1; support::ulittle16_t Sig2; support::ulittle16_t Version; support::ulittle16_t Machine; support::ulittle32_t TimeDateStamp; uint8_t UUID[16]; support::ulittle32_t unused1; support::ulittle32_t unused2; support::ulittle32_t unused3; support::ulittle32_t unused4; support::ulittle32_t NumberOfSections; support::ulittle32_t PointerToSymbolTable; support::ulittle32_t NumberOfSymbols; }; /// The 32-bit PE header that follows the COFF header. struct pe32_header { support::ulittle16_t Magic; uint8_t MajorLinkerVersion; uint8_t MinorLinkerVersion; support::ulittle32_t SizeOfCode; support::ulittle32_t SizeOfInitializedData; support::ulittle32_t SizeOfUninitializedData; support::ulittle32_t AddressOfEntryPoint; support::ulittle32_t BaseOfCode; support::ulittle32_t BaseOfData; support::ulittle32_t ImageBase; support::ulittle32_t SectionAlignment; support::ulittle32_t FileAlignment; support::ulittle16_t MajorOperatingSystemVersion; support::ulittle16_t MinorOperatingSystemVersion; support::ulittle16_t MajorImageVersion; support::ulittle16_t MinorImageVersion; support::ulittle16_t MajorSubsystemVersion; support::ulittle16_t MinorSubsystemVersion; support::ulittle32_t Win32VersionValue; support::ulittle32_t SizeOfImage; support::ulittle32_t SizeOfHeaders; support::ulittle32_t CheckSum; support::ulittle16_t Subsystem; // FIXME: This should be DllCharacteristics. support::ulittle16_t DLLCharacteristics; support::ulittle32_t SizeOfStackReserve; support::ulittle32_t SizeOfStackCommit; support::ulittle32_t SizeOfHeapReserve; support::ulittle32_t SizeOfHeapCommit; support::ulittle32_t LoaderFlags; // FIXME: This should be NumberOfRvaAndSizes. support::ulittle32_t NumberOfRvaAndSize; }; /// The 64-bit PE header that follows the COFF header. struct pe32plus_header { support::ulittle16_t Magic; uint8_t MajorLinkerVersion; uint8_t MinorLinkerVersion; support::ulittle32_t SizeOfCode; support::ulittle32_t SizeOfInitializedData; support::ulittle32_t SizeOfUninitializedData; support::ulittle32_t AddressOfEntryPoint; support::ulittle32_t BaseOfCode; support::ulittle64_t ImageBase; support::ulittle32_t SectionAlignment; support::ulittle32_t FileAlignment; support::ulittle16_t MajorOperatingSystemVersion; support::ulittle16_t MinorOperatingSystemVersion; support::ulittle16_t MajorImageVersion; support::ulittle16_t MinorImageVersion; support::ulittle16_t MajorSubsystemVersion; support::ulittle16_t MinorSubsystemVersion; support::ulittle32_t Win32VersionValue; support::ulittle32_t SizeOfImage; support::ulittle32_t SizeOfHeaders; support::ulittle32_t CheckSum; support::ulittle16_t Subsystem; support::ulittle16_t DLLCharacteristics; support::ulittle64_t SizeOfStackReserve; support::ulittle64_t SizeOfStackCommit; support::ulittle64_t SizeOfHeapReserve; support::ulittle64_t SizeOfHeapCommit; support::ulittle32_t LoaderFlags; support::ulittle32_t NumberOfRvaAndSize; }; struct data_directory { support::ulittle32_t RelativeVirtualAddress; support::ulittle32_t Size; }; struct debug_directory { support::ulittle32_t Characteristics; support::ulittle32_t TimeDateStamp; support::ulittle16_t MajorVersion; support::ulittle16_t MinorVersion; support::ulittle32_t Type; support::ulittle32_t SizeOfData; support::ulittle32_t AddressOfRawData; support::ulittle32_t PointerToRawData; }; template struct import_lookup_table_entry { IntTy Data; bool isOrdinal() const { return Data < 0; } uint16_t getOrdinal() const { assert(isOrdinal() && "ILT entry is not an ordinal!"); return Data & 0xFFFF; } uint32_t getHintNameRVA() const { assert(!isOrdinal() && "ILT entry is not a Hint/Name RVA!"); return Data & 0xFFFFFFFF; } }; using import_lookup_table_entry32 = import_lookup_table_entry; using import_lookup_table_entry64 = import_lookup_table_entry; struct delay_import_directory_table_entry { // dumpbin reports this field as "Characteristics" instead of "Attributes". support::ulittle32_t Attributes; support::ulittle32_t Name; support::ulittle32_t ModuleHandle; support::ulittle32_t DelayImportAddressTable; support::ulittle32_t DelayImportNameTable; support::ulittle32_t BoundDelayImportTable; support::ulittle32_t UnloadDelayImportTable; support::ulittle32_t TimeStamp; }; struct export_directory_table_entry { support::ulittle32_t ExportFlags; support::ulittle32_t TimeDateStamp; support::ulittle16_t MajorVersion; support::ulittle16_t MinorVersion; support::ulittle32_t NameRVA; support::ulittle32_t OrdinalBase; support::ulittle32_t AddressTableEntries; support::ulittle32_t NumberOfNamePointers; support::ulittle32_t ExportAddressTableRVA; support::ulittle32_t NamePointerRVA; support::ulittle32_t OrdinalTableRVA; }; union export_address_table_entry { support::ulittle32_t ExportRVA; support::ulittle32_t ForwarderRVA; }; using export_name_pointer_table_entry = support::ulittle32_t; using export_ordinal_table_entry = support::ulittle16_t; struct StringTableOffset { support::ulittle32_t Zeroes; support::ulittle32_t Offset; }; template struct coff_symbol { union { char ShortName[COFF::NameSize]; StringTableOffset Offset; } Name; support::ulittle32_t Value; SectionNumberType SectionNumber; support::ulittle16_t Type; uint8_t StorageClass; uint8_t NumberOfAuxSymbols; }; using coff_symbol16 = coff_symbol; using coff_symbol32 = coff_symbol; // Contains only common parts of coff_symbol16 and coff_symbol32. struct coff_symbol_generic { union { char ShortName[COFF::NameSize]; StringTableOffset Offset; } Name; support::ulittle32_t Value; }; struct coff_aux_section_definition; struct coff_aux_weak_external; class COFFSymbolRef { public: COFFSymbolRef() = default; COFFSymbolRef(const coff_symbol16 *CS) : CS16(CS) {} COFFSymbolRef(const coff_symbol32 *CS) : CS32(CS) {} const void *getRawPtr() const { return CS16 ? static_cast(CS16) : CS32; } const coff_symbol_generic *getGeneric() const { if (CS16) return reinterpret_cast(CS16); return reinterpret_cast(CS32); } friend bool operator<(COFFSymbolRef A, COFFSymbolRef B) { return A.getRawPtr() < B.getRawPtr(); } bool isBigObj() const { if (CS16) return false; if (CS32) return true; llvm_unreachable("COFFSymbolRef points to nothing!"); } const char *getShortName() const { return CS16 ? CS16->Name.ShortName : CS32->Name.ShortName; } const StringTableOffset &getStringTableOffset() const { assert(isSet() && "COFFSymbolRef points to nothing!"); return CS16 ? CS16->Name.Offset : CS32->Name.Offset; } uint32_t getValue() const { return CS16 ? CS16->Value : CS32->Value; } int32_t getSectionNumber() const { assert(isSet() && "COFFSymbolRef points to nothing!"); if (CS16) { // Reserved sections are returned as negative numbers. if (CS16->SectionNumber <= COFF::MaxNumberOfSections16) return CS16->SectionNumber; return static_cast(CS16->SectionNumber); } return static_cast(CS32->SectionNumber); } uint16_t getType() const { assert(isSet() && "COFFSymbolRef points to nothing!"); return CS16 ? CS16->Type : CS32->Type; } uint8_t getStorageClass() const { assert(isSet() && "COFFSymbolRef points to nothing!"); return CS16 ? CS16->StorageClass : CS32->StorageClass; } uint8_t getNumberOfAuxSymbols() const { assert(isSet() && "COFFSymbolRef points to nothing!"); return CS16 ? CS16->NumberOfAuxSymbols : CS32->NumberOfAuxSymbols; } uint8_t getBaseType() const { return getType() & 0x0F; } uint8_t getComplexType() const { return (getType() & 0xF0) >> COFF::SCT_COMPLEX_TYPE_SHIFT; } template const T *getAux() const { return CS16 ? reinterpret_cast(CS16 + 1) : reinterpret_cast(CS32 + 1); } const coff_aux_section_definition *getSectionDefinition() const { if (!getNumberOfAuxSymbols() || getStorageClass() != COFF::IMAGE_SYM_CLASS_STATIC) return nullptr; return getAux(); } const coff_aux_weak_external *getWeakExternal() const { if (!getNumberOfAuxSymbols() || getStorageClass() != COFF::IMAGE_SYM_CLASS_WEAK_EXTERNAL) return nullptr; return getAux(); } bool isAbsolute() const { return getSectionNumber() == -1; } bool isExternal() const { return getStorageClass() == COFF::IMAGE_SYM_CLASS_EXTERNAL; } bool isCommon() const { return isExternal() && getSectionNumber() == COFF::IMAGE_SYM_UNDEFINED && getValue() != 0; } bool isUndefined() const { return isExternal() && getSectionNumber() == COFF::IMAGE_SYM_UNDEFINED && getValue() == 0; } bool isWeakExternal() const { return getStorageClass() == COFF::IMAGE_SYM_CLASS_WEAK_EXTERNAL; } bool isFunctionDefinition() const { return isExternal() && getBaseType() == COFF::IMAGE_SYM_TYPE_NULL && getComplexType() == COFF::IMAGE_SYM_DTYPE_FUNCTION && !COFF::isReservedSectionNumber(getSectionNumber()); } bool isFunctionLineInfo() const { return getStorageClass() == COFF::IMAGE_SYM_CLASS_FUNCTION; } bool isAnyUndefined() const { return isUndefined() || isWeakExternal(); } bool isFileRecord() const { return getStorageClass() == COFF::IMAGE_SYM_CLASS_FILE; } bool isSection() const { return getStorageClass() == COFF::IMAGE_SYM_CLASS_SECTION; } bool isSectionDefinition() const { // C++/CLI creates external ABS symbols for non-const appdomain globals. // These are also followed by an auxiliary section definition. bool isAppdomainGlobal = getStorageClass() == COFF::IMAGE_SYM_CLASS_EXTERNAL && getSectionNumber() == COFF::IMAGE_SYM_ABSOLUTE; bool isOrdinarySection = getStorageClass() == COFF::IMAGE_SYM_CLASS_STATIC; if (!getNumberOfAuxSymbols()) return false; return isAppdomainGlobal || isOrdinarySection; } bool isCLRToken() const { return getStorageClass() == COFF::IMAGE_SYM_CLASS_CLR_TOKEN; } private: bool isSet() const { return CS16 || CS32; } const coff_symbol16 *CS16 = nullptr; const coff_symbol32 *CS32 = nullptr; }; struct coff_section { char Name[COFF::NameSize]; support::ulittle32_t VirtualSize; support::ulittle32_t VirtualAddress; support::ulittle32_t SizeOfRawData; support::ulittle32_t PointerToRawData; support::ulittle32_t PointerToRelocations; support::ulittle32_t PointerToLinenumbers; support::ulittle16_t NumberOfRelocations; support::ulittle16_t NumberOfLinenumbers; support::ulittle32_t Characteristics; // Returns true if the actual number of relocations is stored in // VirtualAddress field of the first relocation table entry. bool hasExtendedRelocations() const { return (Characteristics & COFF::IMAGE_SCN_LNK_NRELOC_OVFL) && NumberOfRelocations == UINT16_MAX; } uint32_t getAlignment() const { // The IMAGE_SCN_TYPE_NO_PAD bit is a legacy way of getting to // IMAGE_SCN_ALIGN_1BYTES. if (Characteristics & COFF::IMAGE_SCN_TYPE_NO_PAD) return 1; // Bit [20:24] contains section alignment. 0 means use a default alignment // of 16. uint32_t Shift = (Characteristics >> 20) & 0xF; if (Shift > 0) return 1U << (Shift - 1); return 16; } }; struct coff_relocation { support::ulittle32_t VirtualAddress; support::ulittle32_t SymbolTableIndex; support::ulittle16_t Type; }; struct coff_aux_function_definition { support::ulittle32_t TagIndex; support::ulittle32_t TotalSize; support::ulittle32_t PointerToLinenumber; support::ulittle32_t PointerToNextFunction; char Unused1[2]; }; static_assert(sizeof(coff_aux_function_definition) == 18, "auxiliary entry must be 18 bytes"); struct coff_aux_bf_and_ef_symbol { char Unused1[4]; support::ulittle16_t Linenumber; char Unused2[6]; support::ulittle32_t PointerToNextFunction; char Unused3[2]; }; static_assert(sizeof(coff_aux_bf_and_ef_symbol) == 18, "auxiliary entry must be 18 bytes"); struct coff_aux_weak_external { support::ulittle32_t TagIndex; support::ulittle32_t Characteristics; char Unused1[10]; }; static_assert(sizeof(coff_aux_weak_external) == 18, "auxiliary entry must be 18 bytes"); struct coff_aux_section_definition { support::ulittle32_t Length; support::ulittle16_t NumberOfRelocations; support::ulittle16_t NumberOfLinenumbers; support::ulittle32_t CheckSum; support::ulittle16_t NumberLowPart; uint8_t Selection; uint8_t Unused; support::ulittle16_t NumberHighPart; int32_t getNumber(bool IsBigObj) const { uint32_t Number = static_cast(NumberLowPart); if (IsBigObj) Number |= static_cast(NumberHighPart) << 16; return static_cast(Number); } }; static_assert(sizeof(coff_aux_section_definition) == 18, "auxiliary entry must be 18 bytes"); struct coff_aux_clr_token { uint8_t AuxType; uint8_t Reserved; support::ulittle32_t SymbolTableIndex; char MBZ[12]; }; static_assert(sizeof(coff_aux_clr_token) == 18, "auxiliary entry must be 18 bytes"); struct coff_import_header { support::ulittle16_t Sig1; support::ulittle16_t Sig2; support::ulittle16_t Version; support::ulittle16_t Machine; support::ulittle32_t TimeDateStamp; support::ulittle32_t SizeOfData; support::ulittle16_t OrdinalHint; support::ulittle16_t TypeInfo; int getType() const { return TypeInfo & 0x3; } int getNameType() const { return (TypeInfo >> 2) & 0x7; } }; struct coff_import_directory_table_entry { support::ulittle32_t ImportLookupTableRVA; support::ulittle32_t TimeDateStamp; support::ulittle32_t ForwarderChain; support::ulittle32_t NameRVA; support::ulittle32_t ImportAddressTableRVA; bool isNull() const { return ImportLookupTableRVA == 0 && TimeDateStamp == 0 && ForwarderChain == 0 && NameRVA == 0 && ImportAddressTableRVA == 0; } }; template struct coff_tls_directory { IntTy StartAddressOfRawData; IntTy EndAddressOfRawData; IntTy AddressOfIndex; IntTy AddressOfCallBacks; support::ulittle32_t SizeOfZeroFill; support::ulittle32_t Characteristics; uint32_t getAlignment() const { // Bit [20:24] contains section alignment. uint32_t Shift = (Characteristics & 0x00F00000) >> 20; if (Shift > 0) return 1U << (Shift - 1); return 0; } }; using coff_tls_directory32 = coff_tls_directory; using coff_tls_directory64 = coff_tls_directory; /// Bits in control flow guard flags as we understand them. enum class coff_guard_flags : uint32_t { CFInstrumented = 0x00000100, HasFidTable = 0x00000400, ProtectDelayLoadIAT = 0x00001000, DelayLoadIATSection = 0x00002000, // Delay load in separate section HasLongJmpTable = 0x00010000, FidTableHasFlags = 0x10000000, // Indicates that fid tables are 5 bytes }; +enum class frame_type : uint16_t { Fpo = 0, Trap = 1, Tss = 2, NonFpo = 3 }; + struct coff_load_config_code_integrity { support::ulittle16_t Flags; support::ulittle16_t Catalog; support::ulittle32_t CatalogOffset; support::ulittle32_t Reserved; }; /// 32-bit load config (IMAGE_LOAD_CONFIG_DIRECTORY32) struct coff_load_configuration32 { support::ulittle32_t Size; support::ulittle32_t TimeDateStamp; support::ulittle16_t MajorVersion; support::ulittle16_t MinorVersion; support::ulittle32_t GlobalFlagsClear; support::ulittle32_t GlobalFlagsSet; support::ulittle32_t CriticalSectionDefaultTimeout; support::ulittle32_t DeCommitFreeBlockThreshold; support::ulittle32_t DeCommitTotalFreeThreshold; support::ulittle32_t LockPrefixTable; support::ulittle32_t MaximumAllocationSize; support::ulittle32_t VirtualMemoryThreshold; support::ulittle32_t ProcessAffinityMask; support::ulittle32_t ProcessHeapFlags; support::ulittle16_t CSDVersion; support::ulittle16_t DependentLoadFlags; support::ulittle32_t EditList; support::ulittle32_t SecurityCookie; support::ulittle32_t SEHandlerTable; support::ulittle32_t SEHandlerCount; // Added in MSVC 2015 for /guard:cf. support::ulittle32_t GuardCFCheckFunction; support::ulittle32_t GuardCFCheckDispatch; support::ulittle32_t GuardCFFunctionTable; support::ulittle32_t GuardCFFunctionCount; support::ulittle32_t GuardFlags; // coff_guard_flags // Added in MSVC 2017 coff_load_config_code_integrity CodeIntegrity; support::ulittle32_t GuardAddressTakenIatEntryTable; support::ulittle32_t GuardAddressTakenIatEntryCount; support::ulittle32_t GuardLongJumpTargetTable; support::ulittle32_t GuardLongJumpTargetCount; support::ulittle32_t DynamicValueRelocTable; support::ulittle32_t CHPEMetadataPointer; support::ulittle32_t GuardRFFailureRoutine; support::ulittle32_t GuardRFFailureRoutineFunctionPointer; support::ulittle32_t DynamicValueRelocTableOffset; support::ulittle16_t DynamicValueRelocTableSection; support::ulittle16_t Reserved2; support::ulittle32_t GuardRFVerifyStackPointerFunctionPointer; support::ulittle32_t HotPatchTableOffset; }; /// 64-bit load config (IMAGE_LOAD_CONFIG_DIRECTORY64) struct coff_load_configuration64 { support::ulittle32_t Size; support::ulittle32_t TimeDateStamp; support::ulittle16_t MajorVersion; support::ulittle16_t MinorVersion; support::ulittle32_t GlobalFlagsClear; support::ulittle32_t GlobalFlagsSet; support::ulittle32_t CriticalSectionDefaultTimeout; support::ulittle64_t DeCommitFreeBlockThreshold; support::ulittle64_t DeCommitTotalFreeThreshold; support::ulittle64_t LockPrefixTable; support::ulittle64_t MaximumAllocationSize; support::ulittle64_t VirtualMemoryThreshold; support::ulittle64_t ProcessAffinityMask; support::ulittle32_t ProcessHeapFlags; support::ulittle16_t CSDVersion; support::ulittle16_t DependentLoadFlags; support::ulittle64_t EditList; support::ulittle64_t SecurityCookie; support::ulittle64_t SEHandlerTable; support::ulittle64_t SEHandlerCount; // Added in MSVC 2015 for /guard:cf. support::ulittle64_t GuardCFCheckFunction; support::ulittle64_t GuardCFCheckDispatch; support::ulittle64_t GuardCFFunctionTable; support::ulittle64_t GuardCFFunctionCount; support::ulittle32_t GuardFlags; // Added in MSVC 2017 coff_load_config_code_integrity CodeIntegrity; support::ulittle64_t GuardAddressTakenIatEntryTable; support::ulittle64_t GuardAddressTakenIatEntryCount; support::ulittle64_t GuardLongJumpTargetTable; support::ulittle64_t GuardLongJumpTargetCount; support::ulittle64_t DynamicValueRelocTable; support::ulittle64_t CHPEMetadataPointer; support::ulittle64_t GuardRFFailureRoutine; support::ulittle64_t GuardRFFailureRoutineFunctionPointer; support::ulittle32_t DynamicValueRelocTableOffset; support::ulittle16_t DynamicValueRelocTableSection; support::ulittle16_t Reserved2; support::ulittle64_t GuardRFVerifyStackPointerFunctionPointer; support::ulittle32_t HotPatchTableOffset; }; struct coff_runtime_function_x64 { support::ulittle32_t BeginAddress; support::ulittle32_t EndAddress; support::ulittle32_t UnwindInformation; }; struct coff_base_reloc_block_header { support::ulittle32_t PageRVA; support::ulittle32_t BlockSize; }; struct coff_base_reloc_block_entry { support::ulittle16_t Data; int getType() const { return Data >> 12; } int getOffset() const { return Data & ((1 << 12) - 1); } }; struct coff_resource_dir_entry { union { support::ulittle32_t NameOffset; support::ulittle32_t ID; uint32_t getNameOffset() const { return maskTrailingOnes(31) & NameOffset; } // Even though the PE/COFF spec doesn't mention this, the high bit of a name // offset is set. void setNameOffset(uint32_t Offset) { NameOffset = Offset | (1 << 31); } } Identifier; union { support::ulittle32_t DataEntryOffset; support::ulittle32_t SubdirOffset; bool isSubDir() const { return SubdirOffset >> 31; } uint32_t value() const { return maskTrailingOnes(31) & SubdirOffset; } } Offset; }; struct coff_resource_data_entry { support::ulittle32_t DataRVA; support::ulittle32_t DataSize; support::ulittle32_t Codepage; support::ulittle32_t Reserved; }; struct coff_resource_dir_table { support::ulittle32_t Characteristics; support::ulittle32_t TimeDateStamp; support::ulittle16_t MajorVersion; support::ulittle16_t MinorVersion; support::ulittle16_t NumberOfNameEntries; support::ulittle16_t NumberOfIDEntries; }; struct debug_h_header { support::ulittle32_t Magic; support::ulittle16_t Version; support::ulittle16_t HashAlgorithm; }; class COFFObjectFile : public ObjectFile { private: friend class ImportDirectoryEntryRef; friend class ExportDirectoryEntryRef; const coff_file_header *COFFHeader; const coff_bigobj_file_header *COFFBigObjHeader; const pe32_header *PE32Header; const pe32plus_header *PE32PlusHeader; const data_directory *DataDirectory; const coff_section *SectionTable; const coff_symbol16 *SymbolTable16; const coff_symbol32 *SymbolTable32; const char *StringTable; uint32_t StringTableSize; const coff_import_directory_table_entry *ImportDirectory; const delay_import_directory_table_entry *DelayImportDirectory; uint32_t NumberOfDelayImportDirectory; const export_directory_table_entry *ExportDirectory; const coff_base_reloc_block_header *BaseRelocHeader; const coff_base_reloc_block_header *BaseRelocEnd; const debug_directory *DebugDirectoryBegin; const debug_directory *DebugDirectoryEnd; // Either coff_load_configuration32 or coff_load_configuration64. const void *LoadConfig = nullptr; std::error_code getString(uint32_t offset, StringRef &Res) const; template const coff_symbol_type *toSymb(DataRefImpl Symb) const; const coff_section *toSec(DataRefImpl Sec) const; const coff_relocation *toRel(DataRefImpl Rel) const; std::error_code initSymbolTablePtr(); std::error_code initImportTablePtr(); std::error_code initDelayImportTablePtr(); std::error_code initExportTablePtr(); std::error_code initBaseRelocPtr(); std::error_code initDebugDirectoryPtr(); std::error_code initLoadConfigPtr(); public: uintptr_t getSymbolTable() const { if (SymbolTable16) return reinterpret_cast(SymbolTable16); if (SymbolTable32) return reinterpret_cast(SymbolTable32); return uintptr_t(0); } uint16_t getMachine() const { if (COFFHeader) return COFFHeader->Machine; if (COFFBigObjHeader) return COFFBigObjHeader->Machine; llvm_unreachable("no COFF header!"); } uint16_t getSizeOfOptionalHeader() const { if (COFFHeader) return COFFHeader->isImportLibrary() ? 0 : COFFHeader->SizeOfOptionalHeader; // bigobj doesn't have this field. if (COFFBigObjHeader) return 0; llvm_unreachable("no COFF header!"); } uint16_t getCharacteristics() const { if (COFFHeader) return COFFHeader->isImportLibrary() ? 0 : COFFHeader->Characteristics; // bigobj doesn't have characteristics to speak of, // editbin will silently lie to you if you attempt to set any. if (COFFBigObjHeader) return 0; llvm_unreachable("no COFF header!"); } uint32_t getTimeDateStamp() const { if (COFFHeader) return COFFHeader->TimeDateStamp; if (COFFBigObjHeader) return COFFBigObjHeader->TimeDateStamp; llvm_unreachable("no COFF header!"); } uint32_t getNumberOfSections() const { if (COFFHeader) return COFFHeader->isImportLibrary() ? 0 : COFFHeader->NumberOfSections; if (COFFBigObjHeader) return COFFBigObjHeader->NumberOfSections; llvm_unreachable("no COFF header!"); } uint32_t getPointerToSymbolTable() const { if (COFFHeader) return COFFHeader->isImportLibrary() ? 0 : COFFHeader->PointerToSymbolTable; if (COFFBigObjHeader) return COFFBigObjHeader->PointerToSymbolTable; llvm_unreachable("no COFF header!"); } uint32_t getRawNumberOfSymbols() const { if (COFFHeader) return COFFHeader->isImportLibrary() ? 0 : COFFHeader->NumberOfSymbols; if (COFFBigObjHeader) return COFFBigObjHeader->NumberOfSymbols; llvm_unreachable("no COFF header!"); } uint32_t getNumberOfSymbols() const { if (!SymbolTable16 && !SymbolTable32) return 0; return getRawNumberOfSymbols(); } const coff_load_configuration32 *getLoadConfig32() const { assert(!is64()); return reinterpret_cast(LoadConfig); } const coff_load_configuration64 *getLoadConfig64() const { assert(is64()); return reinterpret_cast(LoadConfig); } StringRef getRelocationTypeName(uint16_t Type) const; protected: void moveSymbolNext(DataRefImpl &Symb) const override; Expected getSymbolName(DataRefImpl Symb) const override; Expected getSymbolAddress(DataRefImpl Symb) const override; uint32_t getSymbolAlignment(DataRefImpl Symb) const override; uint64_t getSymbolValueImpl(DataRefImpl Symb) const override; uint64_t getCommonSymbolSizeImpl(DataRefImpl Symb) const override; uint32_t getSymbolFlags(DataRefImpl Symb) const override; Expected getSymbolType(DataRefImpl Symb) const override; Expected getSymbolSection(DataRefImpl Symb) const override; void moveSectionNext(DataRefImpl &Sec) const override; std::error_code getSectionName(DataRefImpl Sec, StringRef &Res) const override; uint64_t getSectionAddress(DataRefImpl Sec) const override; uint64_t getSectionIndex(DataRefImpl Sec) const override; uint64_t getSectionSize(DataRefImpl Sec) const override; std::error_code getSectionContents(DataRefImpl Sec, StringRef &Res) const override; uint64_t getSectionAlignment(DataRefImpl Sec) const override; bool isSectionCompressed(DataRefImpl Sec) const override; bool isSectionText(DataRefImpl Sec) const override; bool isSectionData(DataRefImpl Sec) const override; bool isSectionBSS(DataRefImpl Sec) const override; bool isSectionVirtual(DataRefImpl Sec) const override; relocation_iterator section_rel_begin(DataRefImpl Sec) const override; relocation_iterator section_rel_end(DataRefImpl Sec) const override; void moveRelocationNext(DataRefImpl &Rel) const override; uint64_t getRelocationOffset(DataRefImpl Rel) const override; symbol_iterator getRelocationSymbol(DataRefImpl Rel) const override; uint64_t getRelocationType(DataRefImpl Rel) const override; void getRelocationTypeName(DataRefImpl Rel, SmallVectorImpl &Result) const override; public: COFFObjectFile(MemoryBufferRef Object, std::error_code &EC); basic_symbol_iterator symbol_begin() const override; basic_symbol_iterator symbol_end() const override; section_iterator section_begin() const override; section_iterator section_end() const override; const coff_section *getCOFFSection(const SectionRef &Section) const; COFFSymbolRef getCOFFSymbol(const DataRefImpl &Ref) const; COFFSymbolRef getCOFFSymbol(const SymbolRef &Symbol) const; const coff_relocation *getCOFFRelocation(const RelocationRef &Reloc) const; unsigned getSectionID(SectionRef Sec) const; unsigned getSymbolSectionID(SymbolRef Sym) const; uint8_t getBytesInAddress() const override; StringRef getFileFormatName() const override; Triple::ArchType getArch() const override; Expected getStartAddress() const override; SubtargetFeatures getFeatures() const override { return SubtargetFeatures(); } import_directory_iterator import_directory_begin() const; import_directory_iterator import_directory_end() const; delay_import_directory_iterator delay_import_directory_begin() const; delay_import_directory_iterator delay_import_directory_end() const; export_directory_iterator export_directory_begin() const; export_directory_iterator export_directory_end() const; base_reloc_iterator base_reloc_begin() const; base_reloc_iterator base_reloc_end() const; const debug_directory *debug_directory_begin() const { return DebugDirectoryBegin; } const debug_directory *debug_directory_end() const { return DebugDirectoryEnd; } iterator_range import_directories() const; iterator_range delay_import_directories() const; iterator_range export_directories() const; iterator_range base_relocs() const; iterator_range debug_directories() const { return make_range(debug_directory_begin(), debug_directory_end()); } const dos_header *getDOSHeader() const { if (!PE32Header && !PE32PlusHeader) return nullptr; return reinterpret_cast(base()); } std::error_code getPE32Header(const pe32_header *&Res) const; std::error_code getPE32PlusHeader(const pe32plus_header *&Res) const; std::error_code getDataDirectory(uint32_t index, const data_directory *&Res) const; std::error_code getSection(int32_t index, const coff_section *&Res) const; std::error_code getSection(StringRef SectionName, const coff_section *&Res) const; template std::error_code getSymbol(uint32_t Index, const coff_symbol_type *&Res) const { if (Index >= getNumberOfSymbols()) return object_error::parse_failed; Res = reinterpret_cast(getSymbolTable()) + Index; return std::error_code(); } Expected getSymbol(uint32_t index) const { if (SymbolTable16) { const coff_symbol16 *Symb = nullptr; if (std::error_code EC = getSymbol(index, Symb)) return errorCodeToError(EC); return COFFSymbolRef(Symb); } if (SymbolTable32) { const coff_symbol32 *Symb = nullptr; if (std::error_code EC = getSymbol(index, Symb)) return errorCodeToError(EC); return COFFSymbolRef(Symb); } return errorCodeToError(object_error::parse_failed); } template std::error_code getAuxSymbol(uint32_t index, const T *&Res) const { Expected S = getSymbol(index); if (Error E = S.takeError()) return errorToErrorCode(std::move(E)); Res = reinterpret_cast(S->getRawPtr()); return std::error_code(); } std::error_code getSymbolName(COFFSymbolRef Symbol, StringRef &Res) const; std::error_code getSymbolName(const coff_symbol_generic *Symbol, StringRef &Res) const; ArrayRef getSymbolAuxData(COFFSymbolRef Symbol) const; size_t getSymbolTableEntrySize() const { if (COFFHeader) return sizeof(coff_symbol16); if (COFFBigObjHeader) return sizeof(coff_symbol32); llvm_unreachable("null symbol table pointer!"); } ArrayRef getRelocations(const coff_section *Sec) const; std::error_code getSectionName(const coff_section *Sec, StringRef &Res) const; uint64_t getSectionSize(const coff_section *Sec) const; std::error_code getSectionContents(const coff_section *Sec, ArrayRef &Res) const; uint64_t getImageBase() const; std::error_code getVaPtr(uint64_t VA, uintptr_t &Res) const; std::error_code getRvaPtr(uint32_t Rva, uintptr_t &Res) const; /// Given an RVA base and size, returns a valid array of bytes or an error /// code if the RVA and size is not contained completely within a valid /// section. std::error_code getRvaAndSizeAsBytes(uint32_t RVA, uint32_t Size, ArrayRef &Contents) const; std::error_code getHintName(uint32_t Rva, uint16_t &Hint, StringRef &Name) const; /// Get PDB information out of a codeview debug directory entry. std::error_code getDebugPDBInfo(const debug_directory *DebugDir, const codeview::DebugInfo *&Info, StringRef &PDBFileName) const; /// Get PDB information from an executable. If the information is not present, /// Info will be set to nullptr and PDBFileName will be empty. An error is /// returned only on corrupt object files. Convenience accessor that can be /// used if the debug directory is not already handy. std::error_code getDebugPDBInfo(const codeview::DebugInfo *&Info, StringRef &PDBFileName) const; bool isRelocatableObject() const override; bool is64() const { return PE32PlusHeader; } static bool classof(const Binary *v) { return v->isCOFF(); } }; // The iterator for the import directory table. class ImportDirectoryEntryRef { public: ImportDirectoryEntryRef() = default; ImportDirectoryEntryRef(const coff_import_directory_table_entry *Table, uint32_t I, const COFFObjectFile *Owner) : ImportTable(Table), Index(I), OwningObject(Owner) {} bool operator==(const ImportDirectoryEntryRef &Other) const; void moveNext(); imported_symbol_iterator imported_symbol_begin() const; imported_symbol_iterator imported_symbol_end() const; iterator_range imported_symbols() const; imported_symbol_iterator lookup_table_begin() const; imported_symbol_iterator lookup_table_end() const; iterator_range lookup_table_symbols() const; std::error_code getName(StringRef &Result) const; std::error_code getImportLookupTableRVA(uint32_t &Result) const; std::error_code getImportAddressTableRVA(uint32_t &Result) const; std::error_code getImportTableEntry(const coff_import_directory_table_entry *&Result) const; private: const coff_import_directory_table_entry *ImportTable; uint32_t Index; const COFFObjectFile *OwningObject = nullptr; }; class DelayImportDirectoryEntryRef { public: DelayImportDirectoryEntryRef() = default; DelayImportDirectoryEntryRef(const delay_import_directory_table_entry *T, uint32_t I, const COFFObjectFile *Owner) : Table(T), Index(I), OwningObject(Owner) {} bool operator==(const DelayImportDirectoryEntryRef &Other) const; void moveNext(); imported_symbol_iterator imported_symbol_begin() const; imported_symbol_iterator imported_symbol_end() const; iterator_range imported_symbols() const; std::error_code getName(StringRef &Result) const; std::error_code getDelayImportTable( const delay_import_directory_table_entry *&Result) const; std::error_code getImportAddress(int AddrIndex, uint64_t &Result) const; private: const delay_import_directory_table_entry *Table; uint32_t Index; const COFFObjectFile *OwningObject = nullptr; }; // The iterator for the export directory table entry. class ExportDirectoryEntryRef { public: ExportDirectoryEntryRef() = default; ExportDirectoryEntryRef(const export_directory_table_entry *Table, uint32_t I, const COFFObjectFile *Owner) : ExportTable(Table), Index(I), OwningObject(Owner) {} bool operator==(const ExportDirectoryEntryRef &Other) const; void moveNext(); std::error_code getDllName(StringRef &Result) const; std::error_code getOrdinalBase(uint32_t &Result) const; std::error_code getOrdinal(uint32_t &Result) const; std::error_code getExportRVA(uint32_t &Result) const; std::error_code getSymbolName(StringRef &Result) const; std::error_code isForwarder(bool &Result) const; std::error_code getForwardTo(StringRef &Result) const; private: const export_directory_table_entry *ExportTable; uint32_t Index; const COFFObjectFile *OwningObject = nullptr; }; class ImportedSymbolRef { public: ImportedSymbolRef() = default; ImportedSymbolRef(const import_lookup_table_entry32 *Entry, uint32_t I, const COFFObjectFile *Owner) : Entry32(Entry), Entry64(nullptr), Index(I), OwningObject(Owner) {} ImportedSymbolRef(const import_lookup_table_entry64 *Entry, uint32_t I, const COFFObjectFile *Owner) : Entry32(nullptr), Entry64(Entry), Index(I), OwningObject(Owner) {} bool operator==(const ImportedSymbolRef &Other) const; void moveNext(); std::error_code getSymbolName(StringRef &Result) const; std::error_code isOrdinal(bool &Result) const; std::error_code getOrdinal(uint16_t &Result) const; std::error_code getHintNameRVA(uint32_t &Result) const; private: const import_lookup_table_entry32 *Entry32; const import_lookup_table_entry64 *Entry64; uint32_t Index; const COFFObjectFile *OwningObject = nullptr; }; class BaseRelocRef { public: BaseRelocRef() = default; BaseRelocRef(const coff_base_reloc_block_header *Header, const COFFObjectFile *Owner) : Header(Header), Index(0) {} bool operator==(const BaseRelocRef &Other) const; void moveNext(); std::error_code getType(uint8_t &Type) const; std::error_code getRVA(uint32_t &Result) const; private: const coff_base_reloc_block_header *Header; uint32_t Index; }; class ResourceSectionRef { public: ResourceSectionRef() = default; explicit ResourceSectionRef(StringRef Ref) : BBS(Ref, support::little) {} Expected> getEntryNameString(const coff_resource_dir_entry &Entry); Expected getEntrySubDir(const coff_resource_dir_entry &Entry); Expected getBaseTable(); private: BinaryByteStream BBS; Expected getTableAtOffset(uint32_t Offset); Expected> getDirStringAtOffset(uint32_t Offset); }; // Corresponds to `_FPO_DATA` structure in the PE/COFF spec. struct FpoData { support::ulittle32_t Offset; // ulOffStart: Offset 1st byte of function code support::ulittle32_t Size; // cbProcSize: # bytes in function support::ulittle32_t NumLocals; // cdwLocals: # bytes in locals/4 support::ulittle16_t NumParams; // cdwParams: # bytes in params/4 support::ulittle16_t Attributes; // cbProlog: # bytes in prolog int getPrologSize() const { return Attributes & 0xF; } // cbRegs: # regs saved int getNumSavedRegs() const { return (Attributes >> 8) & 0x7; } // fHasSEH: true if seh is func bool hasSEH() const { return (Attributes >> 9) & 1; } // fUseBP: true if EBP has been allocated bool useBP() const { return (Attributes >> 10) & 1; } // cbFrame: frame pointer - int getFP() const { return Attributes >> 14; } + frame_type getFP() const { return static_cast(Attributes >> 14); } }; } // end namespace object } // end namespace llvm #endif // LLVM_OBJECT_COFF_H Index: llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.cpp =================================================================== --- llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.cpp (revision 342079) +++ llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.cpp (revision 342080) @@ -1,1792 +1,1845 @@ //===- DumpOutputStyle.cpp ------------------------------------ *- C++ --*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "DumpOutputStyle.h" #include "FormatUtil.h" #include "InputFile.h" #include "MinimalSymbolDumper.h" #include "MinimalTypeDumper.h" #include "StreamUtil.h" #include "llvm-pdbutil.h" #include "llvm/ADT/STLExtras.h" #include "llvm/DebugInfo/CodeView/CVSymbolVisitor.h" #include "llvm/DebugInfo/CodeView/CVTypeVisitor.h" #include "llvm/DebugInfo/CodeView/DebugChecksumsSubsection.h" #include "llvm/DebugInfo/CodeView/DebugCrossExSubsection.h" #include "llvm/DebugInfo/CodeView/DebugCrossImpSubsection.h" #include "llvm/DebugInfo/CodeView/DebugFrameDataSubsection.h" #include "llvm/DebugInfo/CodeView/DebugInlineeLinesSubsection.h" #include "llvm/DebugInfo/CodeView/DebugLinesSubsection.h" #include "llvm/DebugInfo/CodeView/DebugStringTableSubsection.h" #include "llvm/DebugInfo/CodeView/DebugSymbolsSubsection.h" #include "llvm/DebugInfo/CodeView/Formatters.h" #include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h" #include "llvm/DebugInfo/CodeView/Line.h" #include "llvm/DebugInfo/CodeView/SymbolDeserializer.h" #include "llvm/DebugInfo/CodeView/SymbolVisitorCallbackPipeline.h" #include "llvm/DebugInfo/CodeView/SymbolVisitorCallbacks.h" #include "llvm/DebugInfo/CodeView/TypeHashing.h" #include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h" #include "llvm/DebugInfo/MSF/MappedBlockStream.h" #include "llvm/DebugInfo/PDB/Native/DbiModuleDescriptor.h" #include "llvm/DebugInfo/PDB/Native/DbiStream.h" #include "llvm/DebugInfo/PDB/Native/GlobalsStream.h" #include "llvm/DebugInfo/PDB/Native/ISectionContribVisitor.h" #include "llvm/DebugInfo/PDB/Native/InfoStream.h" #include "llvm/DebugInfo/PDB/Native/ModuleDebugStream.h" #include "llvm/DebugInfo/PDB/Native/PDBFile.h" #include "llvm/DebugInfo/PDB/Native/PublicsStream.h" #include "llvm/DebugInfo/PDB/Native/RawError.h" #include "llvm/DebugInfo/PDB/Native/SymbolStream.h" #include "llvm/DebugInfo/PDB/Native/TpiHashing.h" #include "llvm/DebugInfo/PDB/Native/TpiStream.h" #include "llvm/Object/COFF.h" #include "llvm/Support/BinaryStreamReader.h" #include "llvm/Support/FormatAdapters.h" #include "llvm/Support/FormatVariadic.h" #include using namespace llvm; using namespace llvm::codeview; using namespace llvm::msf; using namespace llvm::pdb; DumpOutputStyle::DumpOutputStyle(InputFile &File) : File(File), P(2, false, outs()) {} PDBFile &DumpOutputStyle::getPdb() { return File.pdb(); } object::COFFObjectFile &DumpOutputStyle::getObj() { return File.obj(); } void DumpOutputStyle::printStreamNotValidForObj() { AutoIndent Indent(P, 4); P.formatLine("Dumping this stream is not valid for object files"); } void DumpOutputStyle::printStreamNotPresent(StringRef StreamName) { AutoIndent Indent(P, 4); P.formatLine("{0} stream not present", StreamName); } Error DumpOutputStyle::dump() { if (opts::dump::DumpSummary) { if (auto EC = dumpFileSummary()) return EC; P.NewLine(); } if (opts::dump::DumpStreams) { if (auto EC = dumpStreamSummary()) return EC; P.NewLine(); } if (opts::dump::DumpSymbolStats) { if (auto EC = dumpSymbolStats()) return EC; P.NewLine(); } if (opts::dump::DumpUdtStats) { if (auto EC = dumpUdtStats()) return EC; P.NewLine(); } if (opts::dump::DumpNamedStreams) { if (auto EC = dumpNamedStreams()) return EC; P.NewLine(); } if (opts::dump::DumpStringTable || opts::dump::DumpStringTableDetails) { if (auto EC = dumpStringTable()) return EC; P.NewLine(); } if (opts::dump::DumpModules) { if (auto EC = dumpModules()) return EC; } if (opts::dump::DumpModuleFiles) { if (auto EC = dumpModuleFiles()) return EC; } if (opts::dump::DumpLines) { if (auto EC = dumpLines()) return EC; } if (opts::dump::DumpInlineeLines) { if (auto EC = dumpInlineeLines()) return EC; } if (opts::dump::DumpXmi) { if (auto EC = dumpXmi()) return EC; } if (opts::dump::DumpXme) { if (auto EC = dumpXme()) return EC; } if (opts::dump::DumpFpo) { if (auto EC = dumpFpo()) return EC; } if (File.isObj()) { if (opts::dump::DumpTypes || !opts::dump::DumpTypeIndex.empty() || opts::dump::DumpTypeExtras) if (auto EC = dumpTypesFromObjectFile()) return EC; } else { if (opts::dump::DumpTypes || !opts::dump::DumpTypeIndex.empty() || opts::dump::DumpTypeExtras) { if (auto EC = dumpTpiStream(StreamTPI)) return EC; } if (opts::dump::DumpIds || !opts::dump::DumpIdIndex.empty() || opts::dump::DumpIdExtras) { if (auto EC = dumpTpiStream(StreamIPI)) return EC; } } if (opts::dump::DumpGSIRecords) { if (auto EC = dumpGSIRecords()) return EC; } if (opts::dump::DumpGlobals) { if (auto EC = dumpGlobals()) return EC; } if (opts::dump::DumpPublics) { if (auto EC = dumpPublics()) return EC; } if (opts::dump::DumpSymbols) { auto EC = File.isPdb() ? dumpModuleSymsForPdb() : dumpModuleSymsForObj(); if (EC) return EC; } if (opts::dump::DumpSectionHeaders) { if (auto EC = dumpSectionHeaders()) return EC; } if (opts::dump::DumpSectionContribs) { if (auto EC = dumpSectionContribs()) return EC; } if (opts::dump::DumpSectionMap) { if (auto EC = dumpSectionMap()) return EC; } return Error::success(); } static void printHeader(LinePrinter &P, const Twine &S) { P.NewLine(); P.formatLine("{0,=60}", S); P.formatLine("{0}", fmt_repeat('=', 60)); } Error DumpOutputStyle::dumpFileSummary() { printHeader(P, "Summary"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Invalid PDB Format: "); 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(PS.getFeatures())); 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()); } return Error::success(); } static StatCollection getSymbolStats(const SymbolGroup &SG, StatCollection &CumulativeStats) { StatCollection Stats; 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(const SymbolGroup &SG, StatCollection &CumulativeStats) { StatCollection Stats; for (const auto &Chunk : SG.getDebugSubsections()) { Stats.update(uint32_t(Chunk.kind()), Chunk.getRecordLength()); CumulativeStats.update(uint32_t(Chunk.kind()), Chunk.getRecordLength()); } return Stats; } static inline std::string formatModuleDetailKind(DebugSubsectionKind K) { return formatChunkKind(K, false); } static inline std::string formatModuleDetailKind(SymbolKind K) { return formatSymbolKind(K); } template static void printModuleDetailStats(LinePrinter &P, StringRef Label, const StatCollection &Stats) { P.NewLine(); P.formatLine(" {0}", Label); AutoIndent Indent(P); P.formatLine("{0,40}: {1,7} entries ({2,8} bytes)", "Total", Stats.Totals.Count, Stats.Totals.Size); P.formatLine("{0}", fmt_repeat('-', 74)); for (const auto &K : Stats.Individual) { std::string KindName = formatModuleDetailKind(Kind(K.first)); P.formatLine("{0,40}: {1,7} entries ({2,8} bytes)", KindName, K.second.Count, K.second.Size); } } 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")) return false; if (Name.equals_lower("* linker *")) return false; if (Name.startswith_lower("f:\\binaries\\Intermediate\\vctools")) return false; if (Name.startswith_lower("f:\\dd\\vctools\\crt")) return false; return true; } 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. if (opts::dump::DumpModi.getNumOccurrences() == 0) return true; // Otherwise, only dump if this is the same module specified. return (opts::dump::DumpModi == Idx); } Error DumpOutputStyle::dumpStreamSummary() { printHeader(P, "Streams"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } AutoIndent Indent(P); 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(getPdb().getStreamByteSize(StreamIdx), AlignStyle::Right, NumDigits(MaxStreamSize)), StreamPurposes[StreamIdx].getLongName()); if (opts::dump::DumpStreamBlocks) { auto Blocks = getPdb().getStreamBlockList(StreamIdx); std::vector BV(Blocks.begin(), Blocks.end()); P.formatLine(" {0} Blocks: [{1}]", fmt_repeat(' ', NumDigits(StreamCount)), make_range(BV.begin(), BV.end())); } } return Error::success(); } static Expected getModuleDebugStream(PDBFile &File, uint32_t Index) { ExitOnError Err("Unexpected error: "); auto &Dbi = Err(File.getPDBDbiStream()); const auto &Modules = Dbi.modules(); auto Modi = Modules.getModuleDescriptor(Index); uint16_t ModiStream = Modi.getModuleStreamIndex(); if (ModiStream == kInvalidStreamIndex) return make_error(raw_error_code::no_stream, "Module stream not present"); auto ModStreamData = File.createIndexedStream(ModiStream); ModuleDebugStreamRef ModS(Modi, std::move(ModStreamData)); if (auto EC = ModS.reload()) return make_error(raw_error_code::corrupt_file, "Invalid module stream"); return std::move(ModS); } template static void iterateOneModule(InputFile &File, const Optional &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()); } AutoIndent Indent(HeaderScope); Callback(Modi, SG); } template static void iterateSymbolGroups(InputFile &Input, const Optional &HeaderScope, CallbackT Callback) { AutoIndent Indent(HeaderScope); ExitOnError Err("Unexpected error processing modules: "); if (opts::dump::DumpModi.getNumOccurrences() > 0) { assert(opts::dump::DumpModi.getNumOccurrences() == 1); uint32_t Modi = opts::dump::DumpModi; SymbolGroup SG(&Input, Modi); iterateOneModule(Input, withLabelWidth(HeaderScope, NumDigits(Modi)), SG, Modi, Callback); return; } 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 static void iterateModuleSubsections( InputFile &File, const Optional &HeaderScope, llvm::function_ref Callback) { iterateSymbolGroups(File, HeaderScope, [&](uint32_t Modi, const SymbolGroup &SG) { for (const auto &SS : SG.getDebugSubsections()) { SubsectionT Subsection; if (SS.kind() != Subsection.kind()) continue; BinaryStreamReader Reader(SS.getRecordData()); if (auto EC = Subsection.initialize(Reader)) continue; Callback(Modi, SG, Subsection); } }); } static Expected, ArrayRef>> loadSectionHeaders(PDBFile &File, DbgHeaderType Type) { if (!File.hasPDBDbiStream()) return make_error( "Section headers require a DBI Stream, which could not be loaded", inconvertibleErrorCode()); auto &Dbi = cantFail(File.getPDBDbiStream()); uint32_t SI = Dbi.getDebugStreamIndex(Type); if (SI == kInvalidStreamIndex) return make_error( "PDB does not contain the requested image section header type", inconvertibleErrorCode()); auto Stream = File.createIndexedStream(SI); if (!Stream) return make_error("Could not load the required stream data", inconvertibleErrorCode()); ArrayRef Headers; if (Stream->getLength() % sizeof(object::coff_section) != 0) return make_error( "Section header array size is not a multiple of section header size", inconvertibleErrorCode()); uint32_t NumHeaders = Stream->getLength() / sizeof(object::coff_section); BinaryStreamReader Reader(*Stream); cantFail(Reader.readArray(Headers, NumHeaders)); return std::make_pair(std::move(Stream), Headers); } static std::vector getSectionNames(PDBFile &File) { auto ExpectedHeaders = loadSectionHeaders(File, DbgHeaderType::SectionHdr); if (!ExpectedHeaders) return {}; std::unique_ptr Stream; ArrayRef Headers; std::tie(Stream, Headers) = std::move(*ExpectedHeaders); std::vector Names; for (const auto &H : Headers) Names.push_back(H.Name); return Names; } static void dumpSectionContrib(LinePrinter &P, const SectionContrib &SC, ArrayRef SectionNames, uint32_t FieldWidth) { std::string NameInsert; if (SC.ISect > 0 && SC.ISect <= SectionNames.size()) { StringRef SectionName = SectionNames[SC.ISect - 1]; NameInsert = formatv("[{0}]", SectionName).str(); } else NameInsert = "[???]"; P.formatLine("SC{5} | mod = {2}, {0}, size = {1}, data crc = {3}, reloc " "crc = {4}", formatSegmentOffset(SC.ISect, SC.Off), fmtle(SC.Size), fmtle(SC.Imod), fmtle(SC.DataCrc), fmtle(SC.RelocCrc), fmt_align(NameInsert, AlignStyle::Left, FieldWidth + 2)); AutoIndent Indent(P, FieldWidth + 2); P.formatLine(" {0}", formatSectionCharacteristics(P.getIndentLevel() + 6, SC.Characteristics, 3, " | ")); } static void dumpSectionContrib(LinePrinter &P, const SectionContrib2 &SC, ArrayRef SectionNames, uint32_t FieldWidth) { P.formatLine("SC2[{6}] | mod = {2}, {0}, size = {1}, data crc = {3}, reloc " "crc = {4}, coff section = {5}", formatSegmentOffset(SC.Base.ISect, SC.Base.Off), fmtle(SC.Base.Size), fmtle(SC.Base.Imod), fmtle(SC.Base.DataCrc), fmtle(SC.Base.RelocCrc), fmtle(SC.ISectCoff)); P.formatLine(" {0}", formatSectionCharacteristics(P.getIndentLevel() + 6, SC.Base.Characteristics, 3, " | ")); } Error DumpOutputStyle::dumpModules() { printHeader(P, "Modules"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Unexpected error processing modules: "); auto &Stream = Err(getPdb().getPDBDbiStream()); const DbiModuleList &Modules = Stream.modules(); iterateSymbolGroups( File, PrintScope{P, 11}, [&](uint32_t Modi, const SymbolGroup &Strings) { auto Desc = Modules.getModuleDescriptor(Modi); if (opts::dump::DumpSectionContribs) { std::vector Sections = getSectionNames(getPdb()); dumpSectionContrib(P, Desc.getSectionContrib(), Sections, 0); } P.formatLine("Obj: `{0}`: ", Desc.getObjFileName()); P.formatLine("debug stream: {0}, # files: {1}, has ec info: {2}", Desc.getModuleStreamIndex(), Desc.getNumberOfFiles(), Desc.hasECInfo()); StringRef PdbFilePath = Err(Stream.getECName(Desc.getPdbFilePathNameIndex())); StringRef SrcFilePath = Err(Stream.getECName(Desc.getSourceFileNameIndex())); P.formatLine("pdb file ni: {0} `{1}`, src file ni: {2} `{3}`", Desc.getPdbFilePathNameIndex(), PdbFilePath, Desc.getSourceFileNameIndex(), SrcFilePath); }); return Error::success(); } Error DumpOutputStyle::dumpModuleFiles() { printHeader(P, "Files"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } ExitOnError Err("Unexpected error processing modules: "); 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); } }); return Error::success(); } Error DumpOutputStyle::dumpSymbolStats() { printHeader(P, "Module Stats"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } ExitOnError Err("Unexpected error processing modules: "); StatCollection SymStats; StatCollection ChunkStats; Optional Scope; if (File.isPdb()) Scope.emplace(P, 2); iterateSymbolGroups(File, Scope, [&](uint32_t Modi, const SymbolGroup &SG) { StatCollection SS = getSymbolStats(SG, SymStats); StatCollection CS = getChunkStats(SG, ChunkStats); if (SG.getFile().isPdb()) { AutoIndent Indent(P); 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; } P.formatLine("Stream {0}, {1} bytes", StreamIdx, getPdb().getStreamByteSize(StreamIdx)); printModuleDetailStats(P, "Symbols", SS); printModuleDetailStats(P, "Chunks", CS); } }); if (SymStats.Totals.Count > 0) { P.printLine(" Summary |"); AutoIndent Indent(P, 4); printModuleDetailStats(P, "Symbols", SymStats); printModuleDetailStats(P, "Chunks", ChunkStats); } return Error::success(); } static bool isValidNamespaceIdentifier(StringRef S) { if (S.empty()) return false; if (std::isdigit(S[0])) return false; return llvm::all_of(S, [](char C) { return std::isalnum(C); }); } namespace { constexpr uint32_t kNoneUdtKind = 0; constexpr uint32_t kSimpleUdtKind = 1; constexpr uint32_t kUnknownUdtKind = 2; const StringRef NoneLabel(""); const StringRef SimpleLabel(""); const StringRef UnknownLabel(""); } // namespace static StringRef getUdtStatLabel(uint32_t Kind) { if (Kind == kNoneUdtKind) return NoneLabel; if (Kind == kSimpleUdtKind) return SimpleLabel; if (Kind == kUnknownUdtKind) return UnknownLabel; return formatTypeLeafKind(static_cast(Kind)); } static uint32_t getLongestTypeLeafName(const StatCollection &Stats) { size_t L = 0; for (const auto &Stat : Stats.Individual) { StringRef Label = getUdtStatLabel(Stat.first); L = std::max(L, Label.size()); } return static_cast(L); } Error DumpOutputStyle::dumpUdtStats() { printHeader(P, "S_UDT Record Stats"); if (File.isPdb() && !getPdb().hasPDBGlobalsStream()) { printStreamNotPresent("Globals"); return Error::success(); } StatCollection UdtStats; StatCollection UdtTargetStats; AutoIndent Indent(P, 4); auto &TpiTypes = File.types(); StringMap NamespacedStats; size_t LongestNamespace = 0; auto HandleOneSymbol = [&](const CVSymbol &Sym) { if (Sym.kind() != SymbolKind::S_UDT) return; UdtStats.update(SymbolKind::S_UDT, Sym.length()); UDTSym UDT = cantFail(SymbolDeserializer::deserializeAs(Sym)); uint32_t Kind = 0; uint32_t RecordSize = 0; if (UDT.Type.isNoneType()) Kind = kNoneUdtKind; else if (UDT.Type.isSimple()) Kind = kSimpleUdtKind; else if (Optional 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) return; StringRef Scope = UDT.Name.take_front(Pos); if (Scope.empty() || !isValidNamespaceIdentifier(Scope)) return; LongestNamespace = std::max(LongestNamespace, Scope.size()); NamespacedStats[Scope].update(RecordSize); }; P.NewLine(); if (File.isPdb()) { auto &SymbolRecords = cantFail(getPdb().getPDBSymbolStream()); auto ExpGlobals = getPdb().getPDBGlobalsStream(); if (!ExpGlobals) return ExpGlobals.takeError(); for (uint32_t PubSymOff : ExpGlobals->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(); size_t LongestTypeLeafKind = getLongestTypeLeafName(UdtTargetStats); size_t FieldWidth = std::max(LongestNamespace, LongestTypeLeafKind); // Compute the max number of digits for count and size fields, including comma // separators. StringRef CountHeader("Count"); StringRef SizeHeader("Size"); size_t CD = NumDigits(UdtStats.Totals.Count); CD += (CD - 1) / 3; CD = std::max(CD, CountHeader.size()); size_t SD = NumDigits(UdtStats.Totals.Size); SD += (SD - 1) / 3; SD = std::max(SD, SizeHeader.size()); uint32_t TableWidth = FieldWidth + 3 + CD + 2 + SD + 1; P.formatLine("{0} | {1} {2}", fmt_align("Record Kind", AlignStyle::Right, FieldWidth), fmt_align(CountHeader, AlignStyle::Right, CD), fmt_align(SizeHeader, AlignStyle::Right, SD)); P.formatLine("{0}", fmt_repeat('-', TableWidth)); for (const auto &Stat : UdtTargetStats.Individual) { StringRef Label = getUdtStatLabel(Stat.first); P.formatLine("{0} | {1:N} {2:N}", fmt_align(Label, AlignStyle::Right, FieldWidth), fmt_align(Stat.second.Count, AlignStyle::Right, CD), fmt_align(Stat.second.Size, AlignStyle::Right, SD)); } P.formatLine("{0}", fmt_repeat('-', TableWidth)); P.formatLine("{0} | {1:N} {2:N}", fmt_align("Total (S_UDT)", AlignStyle::Right, FieldWidth), fmt_align(UdtStats.Totals.Count, AlignStyle::Right, CD), fmt_align(UdtStats.Totals.Size, AlignStyle::Right, SD)); P.formatLine("{0}", fmt_repeat('-', TableWidth)); for (const auto &Stat : NamespacedStats) { std::string Label = formatv("namespace '{0}'", Stat.getKey()); P.formatLine("{0} | {1:N} {2:N}", fmt_align(Label, AlignStyle::Right, FieldWidth), fmt_align(Stat.second.Count, AlignStyle::Right, CD), fmt_align(Stat.second.Size, AlignStyle::Right, SD)); } return Error::success(); } 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; // Let's try to keep it under 100 characters constexpr uint32_t kMaxRowLength = 100; // At least 3 spaces between columns. uint32_t ColumnsPerRow = kMaxRowLength / (MinColumnWidth + 3); uint32_t ItemsLeft = E.LineNumbers.size(); auto LineIter = E.LineNumbers.begin(); while (ItemsLeft != 0) { uint32_t RowColumns = std::min(ItemsLeft, ColumnsPerRow); for (uint32_t I = 0; I < RowColumns; ++I) { LineInfo Line(LineIter->Flags); std::string LineStr; if (Line.isAlwaysStepInto()) LineStr = "ASI"; else if (Line.isNeverStepInto()) LineStr = "NSI"; else LineStr = utostr(Line.getStartLine()); char Statement = Line.isStatement() ? ' ' : '!'; P.format("{0} {1:X-} {2} ", fmt_align(LineStr, AlignStyle::Right, kMaxCharsPerLineNumber), fmt_align(Start + LineIter->Offset, AlignStyle::Right, 8, '0'), Statement); ++LineIter; --ItemsLeft; } P.NewLine(); } } Error DumpOutputStyle::dumpLines() { printHeader(P, "Lines"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } uint32_t LastModi = UINT32_MAX; uint32_t LastNameIndex = UINT32_MAX; iterateModuleSubsections( File, PrintScope{P, 4}, [this, &LastModi, &LastNameIndex](uint32_t Modi, const SymbolGroup &Strings, DebugLinesSubsectionRef &Lines) { uint16_t Segment = Lines.header()->RelocSegment; uint32_t Begin = Lines.header()->RelocOffset; uint32_t End = Begin + Lines.header()->CodeSize; for (const auto &Block : Lines) { if (LastModi != Modi || LastNameIndex != Block.NameIndex) { LastModi = Modi; LastNameIndex = Block.NameIndex; Strings.formatFromChecksumsOffset(P, Block.NameIndex); } AutoIndent Indent(P, 2); P.formatLine("{0:X-4}:{1:X-8}-{2:X-8}, ", Segment, Begin, End); uint32_t Count = Block.LineNumbers.size(); if (Lines.hasColumnInfo()) P.format("line/column/addr entries = {0}", Count); else P.format("line/addr entries = {0}", Count); P.NewLine(); typesetLinesAndColumns(P, Begin, Block); } }); return Error::success(); } Error DumpOutputStyle::dumpInlineeLines() { printHeader(P, "Inlinee Lines"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } iterateModuleSubsections( 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) { P.formatLine("{0,+8} | {1,+5} | ", Entry.Header->Inlinee, fmtle(Entry.Header->SourceLineNum)); Strings.formatFromChecksumsOffset(P, Entry.Header->FileID, true); } P.NewLine(); }); return Error::success(); } Error DumpOutputStyle::dumpXmi() { printHeader(P, "Cross Module Imports"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } iterateModuleSubsections( File, PrintScope{P, 2}, [this](uint32_t Modi, const SymbolGroup &Strings, DebugCrossModuleImportsSubsectionRef &Imports) { P.formatLine("{0,=32} | {1}", "Imported Module", "Type IDs"); for (const auto &Xmi : Imports) { auto ExpectedModule = Strings.getNameFromStringTable(Xmi.Header->ModuleNameOffset); StringRef Module; SmallString<32> ModuleStorage; if (!ExpectedModule) { Module = "(unknown module)"; consumeError(ExpectedModule.takeError()); } else Module = *ExpectedModule; if (Module.size() > 32) { ModuleStorage = "..."; ModuleStorage += Module.take_back(32 - 3); Module = ModuleStorage; } std::vector TIs; for (const auto I : Xmi.Imports) TIs.push_back(formatv("{0,+10:X+}", fmtle(I))); std::string Result = typesetItemList(TIs, P.getIndentLevel() + 35, 12, " "); P.formatLine("{0,+32} | {1}", Module, Result); } }); return Error::success(); } Error DumpOutputStyle::dumpXme() { printHeader(P, "Cross Module Exports"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } iterateModuleSubsections( 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) { P.formatLine("{0,+10:X+} | {1}", TypeIndex(Export.Local), TypeIndex(Export.Global)); } }); return Error::success(); } -Error DumpOutputStyle::dumpFpo() { - printHeader(P, "New FPO Data"); +std::string formatFrameType(object::frame_type FT) { + switch (FT) { + case object::frame_type::Fpo: + return "FPO"; + case object::frame_type::NonFpo: + return "Non-FPO"; + case object::frame_type::Trap: + return "Trap"; + case object::frame_type::Tss: + return "TSS"; + } + return ""; +} - if (!File.isPdb()) { - printStreamNotValidForObj(); +Error DumpOutputStyle::dumpOldFpo(PDBFile &File) { + printHeader(P, "Old FPO Data"); + + ExitOnError Err("Error dumping old fpo data:"); + auto &Dbi = Err(File.getPDBDbiStream()); + + uint32_t Index = Dbi.getDebugStreamIndex(DbgHeaderType::FPO); + if (Index == kInvalidStreamIndex) { + printStreamNotPresent("FPO"); return Error::success(); } - PDBFile &File = getPdb(); - if (!File.hasPDBDbiStream()) { - printStreamNotPresent("DBI"); - return Error::success(); + std::unique_ptr OldFpo = File.createIndexedStream(Index); + BinaryStreamReader Reader(*OldFpo); + FixedStreamArray Records; + Err(Reader.readArray(Records, + Reader.bytesRemaining() / sizeof(object::FpoData))); + + P.printLine(" RVA | Code | Locals | Params | Prolog | Saved Regs | Use " + "BP | Has SEH | Frame Type"); + + for (const object::FpoData &FD : Records) { + P.formatLine("{0:X-8} | {1,4} | {2,6} | {3,6} | {4,6} | {5,10} | {6,6} | " + "{7,7} | {8,9}", + uint32_t(FD.Offset), uint32_t(FD.Size), uint32_t(FD.NumLocals), + uint32_t(FD.NumParams), FD.getPrologSize(), + FD.getNumSavedRegs(), FD.useBP(), FD.hasSEH(), + formatFrameType(FD.getFP())); } + return Error::success(); +} - ExitOnError Err("Error dumping fpo data:"); +Error DumpOutputStyle::dumpNewFpo(PDBFile &File) { + printHeader(P, "New FPO Data"); + ExitOnError Err("Error dumping new fpo data:"); auto &Dbi = Err(File.getPDBDbiStream()); uint32_t Index = Dbi.getDebugStreamIndex(DbgHeaderType::NewFPO); if (Index == kInvalidStreamIndex) { printStreamNotPresent("New FPO"); return Error::success(); } std::unique_ptr NewFpo = File.createIndexedStream(Index); DebugFrameDataSubsectionRef FDS; if (auto EC = FDS.initialize(*NewFpo)) return make_error(raw_error_code::corrupt_file, "Invalid new fpo stream"); P.printLine(" RVA | Code | Locals | Params | Stack | Prolog | Saved Regs " "| Has SEH | Has C++EH | Start | Program"); for (const FrameData &FD : FDS) { bool IsFuncStart = FD.Flags & FrameData::IsFunctionStart; bool HasEH = FD.Flags & FrameData::HasEH; bool HasSEH = FD.Flags & FrameData::HasSEH; auto &StringTable = Err(File.getStringTable()); auto Program = Err(StringTable.getStringForID(FD.FrameFunc)); P.formatLine("{0:X-8} | {1,4} | {2,6} | {3,6} | {4,5} | {5,6} | {6,10} | " "{7,7} | {8,9} | {9,5} | {10}", uint32_t(FD.RvaStart), uint32_t(FD.CodeSize), uint32_t(FD.LocalSize), uint32_t(FD.ParamsSize), uint32_t(FD.MaxStackSize), uint16_t(FD.PrologSize), uint16_t(FD.SavedRegsSize), HasSEH, HasEH, IsFuncStart, Program); } return Error::success(); } +Error DumpOutputStyle::dumpFpo() { + if (!File.isPdb()) { + printStreamNotValidForObj(); + return Error::success(); + } + + PDBFile &File = getPdb(); + if (!File.hasPDBDbiStream()) { + printStreamNotPresent("DBI"); + return Error::success(); + } + + if (auto EC = dumpOldFpo(File)) + return EC; + if (auto EC = dumpNewFpo(File)) + return EC; + return Error::success(); +} + Error DumpOutputStyle::dumpStringTableFromPdb() { AutoIndent Indent(P); auto IS = getPdb().getStringTable(); if (!IS) { P.formatLine("Not present in file"); consumeError(IS.takeError()); return Error::success(); } if (opts::dump::DumpStringTable) { if (IS->name_ids().empty()) P.formatLine("Empty"); else { auto MaxID = std::max_element(IS->name_ids().begin(), IS->name_ids().end()); uint32_t Digits = NumDigits(*MaxID); P.formatLine("{0} | {1}", fmt_align("ID", AlignStyle::Right, Digits), "String"); std::vector SortedIDs(IS->name_ids().begin(), IS->name_ids().end()); llvm::sort(SortedIDs.begin(), SortedIDs.end()); for (uint32_t I : SortedIDs) { auto ES = IS->getStringForID(I); llvm::SmallString<32> Str; if (!ES) { consumeError(ES.takeError()); Str = "Error reading string"; } else if (!ES->empty()) { Str.append("'"); Str.append(*ES); Str.append("'"); } if (!Str.empty()) P.formatLine("{0} | {1}", fmt_align(I, AlignStyle::Right, Digits), Str); } } } if (opts::dump::DumpStringTableDetails) { P.NewLine(); { P.printLine("String Table Header:"); AutoIndent Indent(P); P.formatLine("Signature: {0}", IS->getSignature()); P.formatLine("Hash Version: {0}", IS->getHashVersion()); P.formatLine("Name Buffer Size: {0}", IS->getByteSize()); P.NewLine(); } BinaryStreamRef NameBuffer = IS->getStringTable().getBuffer(); ArrayRef Contents; cantFail(NameBuffer.readBytes(0, NameBuffer.getLength(), Contents)); P.formatBinary("Name Buffer", Contents, 0); P.NewLine(); { P.printLine("Hash Table:"); AutoIndent Indent(P); P.formatLine("Bucket Count: {0}", IS->name_ids().size()); for (const auto &Entry : enumerate(IS->name_ids())) P.formatLine("Bucket[{0}] : {1}", Entry.index(), uint32_t(Entry.value())); P.formatLine("Name Count: {0}", IS->getNameCount()); } } return Error::success(); } Error DumpOutputStyle::dumpStringTableFromObj() { iterateModuleSubsections( File, PrintScope{P, 4}, [&](uint32_t Modi, const SymbolGroup &Strings, DebugStringTableSubsectionRef &Strings2) { BinaryStreamRef StringTableBuffer = Strings2.getBuffer(); BinaryStreamReader Reader(StringTableBuffer); while (Reader.bytesRemaining() > 0) { StringRef Str; uint32_t Offset = Reader.getOffset(); cantFail(Reader.readCString(Str)); if (Str.empty()) continue; P.formatLine("{0} | {1}", fmt_align(Offset, AlignStyle::Right, 4), Str); } }); return Error::success(); } Error DumpOutputStyle::dumpNamedStreams() { printHeader(P, "Named Streams"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Invalid PDB File: "); auto &IS = Err(File.pdb().getPDBInfoStream()); const NamedStreamMap &NS = IS.getNamedStreams(); for (const auto &Entry : NS.entries()) { P.printLine(Entry.getKey()); AutoIndent Indent2(P, 2); P.formatLine("Index: {0}", Entry.getValue()); P.formatLine("Size in bytes: {0}", File.pdb().getStreamByteSize(Entry.getValue())); } return Error::success(); } Error DumpOutputStyle::dumpStringTable() { printHeader(P, "String Table"); if (File.isPdb()) return dumpStringTableFromPdb(); return dumpStringTableFromObj(); } static void buildDepSet(LazyRandomTypeCollection &Types, ArrayRef Indices, std::map &DepSet) { SmallVector DepList; for (const auto &I : Indices) { TypeIndex TI(I); if (DepSet.find(TI) != DepSet.end() || TI.isSimple() || TI.isNoneType()) continue; CVType Type = Types.getType(TI); DepSet[TI] = Type; codeview::discoverTypeIndices(Type, DepList); buildDepSet(Types, DepList, DepSet); } } static void dumpFullTypeStream(LinePrinter &Printer, LazyRandomTypeCollection &Types, uint32_t NumTypeRecords, uint32_t NumHashBuckets, FixedStreamArray HashValues, bool Bytes, bool Extras) { Printer.formatLine("Showing {0:N} records", NumTypeRecords); uint32_t Width = NumDigits(TypeIndex::FirstNonSimpleIndex + NumTypeRecords); MinimalTypeDumpVisitor V(Printer, Width + 2, Bytes, Extras, Types, NumHashBuckets, HashValues); if (auto EC = codeview::visitTypeStream(Types, V)) { Printer.formatLine("An error occurred dumping type records: {0}", toString(std::move(EC))); } } static void dumpPartialTypeStream(LinePrinter &Printer, LazyRandomTypeCollection &Types, TpiStream &Stream, ArrayRef TiList, bool Bytes, bool Extras, bool Deps) { uint32_t Width = NumDigits(TypeIndex::FirstNonSimpleIndex + Stream.getNumTypeRecords()); MinimalTypeDumpVisitor V(Printer, Width + 2, Bytes, Extras, Types, Stream.getNumHashBuckets(), Stream.getHashValues()); if (opts::dump::DumpTypeDependents) { // If we need to dump all dependents, then iterate each index and find // all dependents, adding them to a map ordered by TypeIndex. std::map DepSet; buildDepSet(Types, TiList, DepSet); Printer.formatLine( "Showing {0:N} records and their dependents ({1:N} records total)", TiList.size(), DepSet.size()); for (auto &Dep : DepSet) { if (auto EC = codeview::visitTypeRecord(Dep.second, Dep.first, V)) Printer.formatLine("An error occurred dumping type record {0}: {1}", Dep.first, toString(std::move(EC))); } } else { Printer.formatLine("Showing {0:N} records.", TiList.size()); for (const auto &I : TiList) { TypeIndex TI(I); CVType Type = Types.getType(TI); if (auto EC = codeview::visitTypeRecord(Type, TI, V)) Printer.formatLine("An error occurred dumping type record {0}: {1}", TI, toString(std::move(EC))); } } } Error DumpOutputStyle::dumpTypesFromObjectFile() { LazyRandomTypeCollection Types(100); for (const auto &S : getObj().sections()) { StringRef SectionName; if (auto EC = S.getName(SectionName)) return errorCodeToError(EC); // .debug$T is a standard CodeView type section, while .debug$P is the same // format but used for MSVC precompiled header object files. if (SectionName == ".debug$T") printHeader(P, "Types (.debug$T)"); else if (SectionName == ".debug$P") printHeader(P, "Precompiled Types (.debug$P)"); else continue; StringRef Contents; if (auto EC = S.getContents(Contents)) return errorCodeToError(EC); uint32_t Magic; BinaryStreamReader Reader(Contents, llvm::support::little); if (auto EC = Reader.readInteger(Magic)) return EC; if (Magic != COFF::DEBUG_SECTION_MAGIC) return make_error("Invalid CodeView debug section.", inconvertibleErrorCode()); Types.reset(Reader, 100); if (opts::dump::DumpTypes) { dumpFullTypeStream(P, Types, 0, 0, {}, opts::dump::DumpTypeData, false); } else if (opts::dump::DumpTypeExtras) { auto LocalHashes = LocallyHashedType::hashTypeCollection(Types); auto GlobalHashes = GloballyHashedType::hashTypeCollection(Types); assert(LocalHashes.size() == GlobalHashes.size()); P.formatLine("Local / Global hashes:"); TypeIndex TI(TypeIndex::FirstNonSimpleIndex); for (const auto &H : zip(LocalHashes, GlobalHashes)) { AutoIndent Indent2(P); LocallyHashedType &L = std::get<0>(H); GloballyHashedType &G = std::get<1>(H); P.formatLine("TI: {0}, LocalHash: {1:X}, GlobalHash: {2}", TI, L, G); ++TI; } P.NewLine(); } } return Error::success(); } 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)"); } assert(!File.isObj()); bool Present = false; bool DumpTypes = false; bool DumpBytes = false; bool DumpExtras = false; std::vector Indices; if (StreamIdx == StreamTPI) { 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) { Present = getPdb().hasPDBIpiStream(); DumpTypes = opts::dump::DumpIds; DumpBytes = opts::dump::DumpIdData; DumpExtras = opts::dump::DumpIdExtras; Indices.assign(opts::dump::DumpIdIndex.begin(), opts::dump::DumpIdIndex.end()); } if (!Present) { printStreamNotPresent(StreamIdx == StreamTPI ? "TPI" : "IPI"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Unexpected error processing types: "); auto &Stream = Err((StreamIdx == StreamTPI) ? getPdb().getPDBTpiStream() : getPdb().getPDBIpiStream()); auto &Types = (StreamIdx == StreamTPI) ? File.types() : File.ids(); if (DumpTypes || !Indices.empty()) { if (Indices.empty()) dumpFullTypeStream(P, Types, Stream.getNumTypeRecords(), Stream.getNumHashBuckets(), Stream.getHashValues(), DumpBytes, DumpExtras); else { std::vector TiList(Indices.begin(), Indices.end()); dumpPartialTypeStream(P, Types, Stream, TiList, DumpBytes, DumpExtras, opts::dump::DumpTypeDependents); } } if (DumpExtras) { P.NewLine(); auto IndexOffsets = Stream.getTypeIndexOffsets(); P.formatLine("Type Index Offsets:"); for (const auto &IO : IndexOffsets) { AutoIndent Indent2(P); P.formatLine("TI: {0}, Offset: {1}", IO.Type, fmtle(IO.Offset)); } P.NewLine(); P.formatLine("Hash Adjusters:"); auto &Adjusters = Stream.getHashAdjusters(); auto &Strings = Err(getPdb().getStringTable()); for (const auto &A : Adjusters) { AutoIndent Indent2(P); auto ExpectedStr = Strings.getStringForID(A.first); TypeIndex TI(A.second); if (ExpectedStr) P.formatLine("`{0}` -> {1}", *ExpectedStr, TI); else { P.formatLine("unknown str id ({0}) -> {1}", A.first, TI); consumeError(ExpectedStr.takeError()); } } } return Error::success(); } Error DumpOutputStyle::dumpModuleSymsForObj() { printHeader(P, "Symbols"); AutoIndent Indent(P); 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 SymbolError; iterateModuleSubsections( File, PrintScope{P, 2}, [&](uint32_t Modi, const SymbolGroup &Strings, DebugSymbolsSubsectionRef &Symbols) { Dumper.setSymbolGroup(&Strings); for (auto Symbol : Symbols) { if (auto EC = Visitor.visitSymbolRecord(Symbol)) { SymbolError = llvm::make_unique(std::move(EC)); return; } } }); if (SymbolError) return std::move(*SymbolError); return Error::success(); } Error DumpOutputStyle::dumpModuleSymsForPdb() { printHeader(P, "Symbols"); if (File.isPdb() && !getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Unexpected error processing symbols: "); auto &Ids = File.ids(); auto &Types = File.types(); 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())); return; } ModuleDebugStreamRef &ModS = *ExpectedModS; SymbolVisitorCallbackPipeline Pipeline; SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb); MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Strings, Ids, Types); Pipeline.addCallbackToPipeline(Deserializer); Pipeline.addCallbackToPipeline(Dumper); CVSymbolVisitor Visitor(Pipeline); auto SS = ModS.getSymbolsSubstream(); if (auto EC = Visitor.visitSymbolStream(ModS.getSymbolArray(), SS.Offset)) { P.formatLine("Error while processing symbol records. {0}", toString(std::move(EC))); return; } }); return Error::success(); } Error DumpOutputStyle::dumpGSIRecords() { printHeader(P, "GSI Records"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBSymbolStream()) { printStreamNotPresent("GSI Common Symbol"); return Error::success(); } AutoIndent Indent(P); auto &Records = cantFail(getPdb().getPDBSymbolStream()); auto &Types = File.types(); auto &Ids = File.ids(); P.printLine("Records"); SymbolVisitorCallbackPipeline Pipeline; SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb); MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Ids, Types); Pipeline.addCallbackToPipeline(Deserializer); Pipeline.addCallbackToPipeline(Dumper); CVSymbolVisitor Visitor(Pipeline); BinaryStreamRef SymStream = Records.getSymbolArray().getUnderlyingStream(); if (auto E = Visitor.visitSymbolStream(Records.getSymbolArray(), 0)) return E; return Error::success(); } Error DumpOutputStyle::dumpGlobals() { printHeader(P, "Global Symbols"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBGlobalsStream()) { printStreamNotPresent("Globals"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Error dumping globals stream: "); auto &Globals = Err(getPdb().getPDBGlobalsStream()); const GSIHashTable &Table = Globals.getGlobalsTable(); Err(dumpSymbolsFromGSI(Table, opts::dump::DumpGlobalExtras)); return Error::success(); } Error DumpOutputStyle::dumpPublics() { printHeader(P, "Public Symbols"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBPublicsStream()) { printStreamNotPresent("Publics"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Error dumping publics stream: "); auto &Publics = Err(getPdb().getPDBPublicsStream()); const GSIHashTable &PublicsTable = Publics.getPublicsTable(); if (opts::dump::DumpPublicExtras) { P.printLine("Publics Header"); AutoIndent Indent(P); P.formatLine("sym hash = {0}, thunk table addr = {1}", Publics.getSymHash(), formatSegmentOffset(Publics.getThunkTableSection(), Publics.getThunkTableOffset())); } Err(dumpSymbolsFromGSI(PublicsTable, opts::dump::DumpPublicExtras)); // Skip the rest if we aren't dumping extras. if (!opts::dump::DumpPublicExtras) return Error::success(); P.formatLine("Address Map"); { // These are offsets into the publics stream sorted by secidx:secrel. AutoIndent Indent2(P); for (uint32_t Addr : Publics.getAddressMap()) P.formatLine("off = {0}", Addr); } // The thunk map is optional debug info used for ILT thunks. if (!Publics.getThunkMap().empty()) { P.formatLine("Thunk Map"); AutoIndent Indent2(P); for (uint32_t Addr : Publics.getThunkMap()) P.formatLine("{0:x8}", Addr); } // The section offsets table appears to be empty when incremental linking // isn't in use. if (!Publics.getSectionOffsets().empty()) { P.formatLine("Section Offsets"); AutoIndent Indent2(P); for (const SectionOffset &SO : Publics.getSectionOffsets()) P.formatLine("{0:x4}:{1:x8}", uint16_t(SO.Isect), uint32_t(SO.Off)); } return Error::success(); } Error DumpOutputStyle::dumpSymbolsFromGSI(const GSIHashTable &Table, bool HashExtras) { auto ExpectedSyms = getPdb().getPDBSymbolStream(); if (!ExpectedSyms) return ExpectedSyms.takeError(); auto &Types = File.types(); auto &Ids = File.ids(); if (HashExtras) { P.printLine("GSI Header"); AutoIndent Indent(P); P.formatLine("sig = {0:X}, hdr = {1:X}, hr size = {2}, num buckets = {3}", Table.getVerSignature(), Table.getVerHeader(), Table.getHashRecordSize(), Table.getNumBuckets()); } { P.printLine("Records"); SymbolVisitorCallbackPipeline Pipeline; SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb); MinimalSymbolDumper Dumper(P, opts::dump::DumpSymRecordBytes, Ids, Types); Pipeline.addCallbackToPipeline(Deserializer); Pipeline.addCallbackToPipeline(Dumper); CVSymbolVisitor Visitor(Pipeline); BinaryStreamRef SymStream = ExpectedSyms->getSymbolArray().getUnderlyingStream(); for (uint32_t PubSymOff : Table) { Expected Sym = readSymbolFromStream(SymStream, PubSymOff); if (!Sym) return Sym.takeError(); if (auto E = Visitor.visitSymbolRecord(*Sym, PubSymOff)) return E; } } // Return early if we aren't dumping public hash table and address map info. if (HashExtras) { P.formatBinary("Hash Bitmap", Table.HashBitmap, 0); P.formatLine("Hash Entries"); { AutoIndent Indent2(P); for (const PSHashRecord &HR : Table.HashRecords) P.formatLine("off = {0}, refcnt = {1}", uint32_t(HR.Off), uint32_t(HR.CRef)); } P.formatLine("Hash Buckets"); { AutoIndent Indent2(P); for (uint32_t Hash : Table.HashBuckets) P.formatLine("{0:x8}", Hash); } } return Error::success(); } static std::string formatSegMapDescriptorFlag(uint32_t IndentLevel, OMFSegDescFlags Flags) { std::vector Opts; if (Flags == OMFSegDescFlags::None) return "none"; PUSH_FLAG(OMFSegDescFlags, Read, Flags, "read"); PUSH_FLAG(OMFSegDescFlags, Write, Flags, "write"); PUSH_FLAG(OMFSegDescFlags, Execute, Flags, "execute"); PUSH_FLAG(OMFSegDescFlags, AddressIs32Bit, Flags, "32 bit addr"); PUSH_FLAG(OMFSegDescFlags, IsSelector, Flags, "selector"); PUSH_FLAG(OMFSegDescFlags, IsAbsoluteAddress, Flags, "absolute addr"); PUSH_FLAG(OMFSegDescFlags, IsGroup, Flags, "group"); return typesetItemList(Opts, IndentLevel, 4, " | "); } Error DumpOutputStyle::dumpSectionHeaders() { dumpSectionHeaders("Section Headers", DbgHeaderType::SectionHdr); dumpSectionHeaders("Original Section Headers", DbgHeaderType::SectionHdrOrig); return Error::success(); } void DumpOutputStyle::dumpSectionHeaders(StringRef Label, DbgHeaderType Type) { printHeader(P, Label); if (File.isObj()) { printStreamNotValidForObj(); return; } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return; } AutoIndent Indent(P); ExitOnError Err("Error dumping section headers: "); std::unique_ptr Stream; ArrayRef Headers; auto ExpectedHeaders = loadSectionHeaders(getPdb(), Type); if (!ExpectedHeaders) { P.printLine(toString(ExpectedHeaders.takeError())); return; } std::tie(Stream, Headers) = std::move(*ExpectedHeaders); uint32_t I = 1; for (const auto &Header : Headers) { P.NewLine(); P.formatLine("SECTION HEADER #{0}", I); P.formatLine("{0,8} name", Header.Name); P.formatLine("{0,8:X-} virtual size", uint32_t(Header.VirtualSize)); P.formatLine("{0,8:X-} virtual address", uint32_t(Header.VirtualAddress)); P.formatLine("{0,8:X-} size of raw data", uint32_t(Header.SizeOfRawData)); P.formatLine("{0,8:X-} file pointer to raw data", uint32_t(Header.PointerToRawData)); P.formatLine("{0,8:X-} file pointer to relocation table", uint32_t(Header.PointerToRelocations)); P.formatLine("{0,8:X-} file pointer to line numbers", uint32_t(Header.PointerToLinenumbers)); P.formatLine("{0,8:X-} number of relocations", uint32_t(Header.NumberOfRelocations)); P.formatLine("{0,8:X-} number of line numbers", uint32_t(Header.NumberOfLinenumbers)); P.formatLine("{0,8:X-} flags", uint32_t(Header.Characteristics)); AutoIndent IndentMore(P, 9); P.formatLine("{0}", formatSectionCharacteristics( P.getIndentLevel(), Header.Characteristics, 1, "")); ++I; } return; } Error DumpOutputStyle::dumpSectionContribs() { printHeader(P, "Section Contributions"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Error dumping section contributions: "); auto &Dbi = Err(getPdb().getPDBDbiStream()); class Visitor : public ISectionContribVisitor { public: Visitor(LinePrinter &P, ArrayRef Names) : P(P), Names(Names) { auto Max = std::max_element( Names.begin(), Names.end(), [](StringRef S1, StringRef S2) { return S1.size() < S2.size(); }); MaxNameLen = (Max == Names.end() ? 0 : Max->size()); } void visit(const SectionContrib &SC) override { dumpSectionContrib(P, SC, Names, MaxNameLen); } void visit(const SectionContrib2 &SC) override { dumpSectionContrib(P, SC, Names, MaxNameLen); } private: LinePrinter &P; uint32_t MaxNameLen; ArrayRef Names; }; std::vector Names = getSectionNames(getPdb()); Visitor V(P, makeArrayRef(Names)); Dbi.visitSectionContributions(V); return Error::success(); } Error DumpOutputStyle::dumpSectionMap() { printHeader(P, "Section Map"); if (File.isObj()) { printStreamNotValidForObj(); return Error::success(); } if (!getPdb().hasPDBDbiStream()) { printStreamNotPresent("DBI"); return Error::success(); } AutoIndent Indent(P); ExitOnError Err("Error dumping section map: "); auto &Dbi = Err(getPdb().getPDBDbiStream()); uint32_t I = 0; for (auto &M : Dbi.getSectionMap()) { P.formatLine( "Section {0:4} | ovl = {1}, group = {2}, frame = {3}, name = {4}", I, fmtle(M.Ovl), fmtle(M.Group), fmtle(M.Frame), fmtle(M.SecName)); P.formatLine(" class = {0}, offset = {1}, size = {2}", fmtle(M.ClassName), fmtle(M.Offset), fmtle(M.SecByteLength)); P.formatLine(" flags = {0}", formatSegMapDescriptorFlag( P.getIndentLevel() + 13, static_cast(uint16_t(M.Flags)))); ++I; } return Error::success(); } Index: llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.h =================================================================== --- llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.h (revision 342079) +++ llvm/trunk/tools/llvm-pdbutil/DumpOutputStyle.h (revision 342080) @@ -1,112 +1,114 @@ //===- DumpOutputStyle.h -------------------------------------- *- C++ --*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #ifndef LLVM_TOOLS_LLVMPDBDUMP_DUMPOUTPUTSTYLE_H #define LLVM_TOOLS_LLVMPDBDUMP_DUMPOUTPUTSTYLE_H #include "LinePrinter.h" #include "OutputStyle.h" #include "StreamUtil.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/DebugInfo/PDB/Native/RawConstants.h" #include namespace llvm { class BitVector; namespace codeview { class LazyRandomTypeCollection; } namespace object { class COFFObjectFile; } namespace pdb { class GSIHashTable; class InputFile; struct StatCollection { struct Stat { Stat() {} Stat(uint32_t Count, uint32_t Size) : Count(Count), Size(Size) {} uint32_t Count = 0; uint32_t Size = 0; void update(uint32_t RecordSize) { ++Count; Size += RecordSize; } }; void update(uint32_t Kind, uint32_t RecordSize) { Totals.update(RecordSize); auto Iter = Individual.try_emplace(Kind, 1, RecordSize); if (!Iter.second) Iter.first->second.update(RecordSize); } Stat Totals; DenseMap Individual; }; class DumpOutputStyle : public OutputStyle { public: DumpOutputStyle(InputFile &File); Error dump() override; private: PDBFile &getPdb(); object::COFFObjectFile &getObj(); void printStreamNotValidForObj(); void printStreamNotPresent(StringRef StreamName); Error dumpFileSummary(); Error dumpStreamSummary(); Error dumpSymbolStats(); Error dumpUdtStats(); Error dumpNamedStreams(); Error dumpStringTable(); Error dumpStringTableFromPdb(); Error dumpStringTableFromObj(); Error dumpLines(); Error dumpInlineeLines(); Error dumpXmi(); Error dumpXme(); Error dumpFpo(); + Error dumpOldFpo(PDBFile &File); + Error dumpNewFpo(PDBFile &File); Error dumpTpiStream(uint32_t StreamIdx); Error dumpTypesFromObjectFile(); Error dumpModules(); Error dumpModuleFiles(); Error dumpModuleSymsForPdb(); Error dumpModuleSymsForObj(); Error dumpGSIRecords(); Error dumpGlobals(); Error dumpPublics(); Error dumpSymbolsFromGSI(const GSIHashTable &Table, bool HashExtras); Error dumpSectionHeaders(); Error dumpSectionContribs(); Error dumpSectionMap(); void dumpSectionHeaders(StringRef Label, DbgHeaderType Type); InputFile &File; LinePrinter P; SmallVector StreamPurposes; }; } // namespace pdb } // namespace llvm #endif Index: llvm/trunk/lib/DebugInfo/PDB/Native/DbiStreamBuilder.cpp =================================================================== --- llvm/trunk/lib/DebugInfo/PDB/Native/DbiStreamBuilder.cpp (revision 342079) +++ llvm/trunk/lib/DebugInfo/PDB/Native/DbiStreamBuilder.cpp (revision 342080) @@ -1,442 +1,456 @@ //===- DbiStreamBuilder.cpp - PDB Dbi Stream Creation -----------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/BinaryFormat/COFF.h" #include "llvm/DebugInfo/CodeView/DebugFrameDataSubsection.h" #include "llvm/DebugInfo/MSF/MSFBuilder.h" #include "llvm/DebugInfo/MSF/MappedBlockStream.h" #include "llvm/DebugInfo/PDB/Native/DbiModuleDescriptorBuilder.h" #include "llvm/DebugInfo/PDB/Native/DbiStream.h" #include "llvm/DebugInfo/PDB/Native/RawError.h" #include "llvm/Object/COFF.h" #include "llvm/Support/BinaryStreamWriter.h" using namespace llvm; using namespace llvm::codeview; using namespace llvm::msf; using namespace llvm::pdb; DbiStreamBuilder::DbiStreamBuilder(msf::MSFBuilder &Msf) : Msf(Msf), Allocator(Msf.getAllocator()), Age(1), BuildNumber(0), PdbDllVersion(0), PdbDllRbld(0), Flags(0), MachineType(PDB_Machine::x86), Header(nullptr) {} DbiStreamBuilder::~DbiStreamBuilder() {} void DbiStreamBuilder::setVersionHeader(PdbRaw_DbiVer V) { VerHeader = V; } void DbiStreamBuilder::setAge(uint32_t A) { Age = A; } void DbiStreamBuilder::setBuildNumber(uint16_t B) { BuildNumber = B; } void DbiStreamBuilder::setBuildNumber(uint8_t Major, uint8_t Minor) { BuildNumber = (uint16_t(Major) << DbiBuildNo::BuildMajorShift) & DbiBuildNo::BuildMajorMask; BuildNumber |= (uint16_t(Minor) << DbiBuildNo::BuildMinorShift) & DbiBuildNo::BuildMinorMask; BuildNumber |= DbiBuildNo::NewVersionFormatMask; } void DbiStreamBuilder::setPdbDllVersion(uint16_t V) { PdbDllVersion = V; } void DbiStreamBuilder::setPdbDllRbld(uint16_t R) { PdbDllRbld = R; } void DbiStreamBuilder::setFlags(uint16_t F) { Flags = F; } void DbiStreamBuilder::setMachineType(PDB_Machine M) { MachineType = M; } void DbiStreamBuilder::setMachineType(COFF::MachineTypes M) { // These enums are mirrors of each other, so we can just cast the value. MachineType = static_cast(static_cast(M)); } void DbiStreamBuilder::setSectionMap(ArrayRef SecMap) { SectionMap = SecMap; } void DbiStreamBuilder::setGlobalsStreamIndex(uint32_t Index) { GlobalsStreamIndex = Index; } void DbiStreamBuilder::setSymbolRecordStreamIndex(uint32_t Index) { SymRecordStreamIndex = Index; } void DbiStreamBuilder::setPublicsStreamIndex(uint32_t Index) { PublicsStreamIndex = Index; } -void DbiStreamBuilder::addFrameData(const codeview::FrameData &FD) { - if (!FrameData.hasValue()) - FrameData.emplace(false); +void DbiStreamBuilder::addNewFpoData(const codeview::FrameData &FD) { + if (!NewFpoData.hasValue()) + NewFpoData.emplace(false); - FrameData->addFrameData(FD); + NewFpoData->addFrameData(FD); +} + +void DbiStreamBuilder::addOldFpoData(const object::FpoData &FD) { + OldFpoData.push_back(FD); } Error DbiStreamBuilder::addDbgStream(pdb::DbgHeaderType Type, ArrayRef Data) { assert(Type != DbgHeaderType::NewFPO && "NewFPO data should be written via addFrameData()!"); DbgStreams[(int)Type].emplace(); DbgStreams[(int)Type]->Size = Data.size(); DbgStreams[(int)Type]->WriteFn = [Data](BinaryStreamWriter &Writer) { return Writer.writeArray(Data); }; return Error::success(); } uint32_t DbiStreamBuilder::addECName(StringRef Name) { return ECNamesBuilder.insert(Name); } uint32_t DbiStreamBuilder::calculateSerializedLength() const { // For now we only support serializing the header. return sizeof(DbiStreamHeader) + calculateFileInfoSubstreamSize() + calculateModiSubstreamSize() + calculateSectionContribsStreamSize() + calculateSectionMapStreamSize() + calculateDbgStreamsSize() + ECNamesBuilder.calculateSerializedSize(); } Expected DbiStreamBuilder::addModuleInfo(StringRef ModuleName) { uint32_t Index = ModiList.size(); ModiList.push_back( llvm::make_unique(ModuleName, Index, Msf)); return *ModiList.back(); } Error DbiStreamBuilder::addModuleSourceFile(DbiModuleDescriptorBuilder &Module, StringRef File) { uint32_t Index = SourceFileNames.size(); SourceFileNames.insert(std::make_pair(File, Index)); Module.addSourceFile(File); return Error::success(); } Expected DbiStreamBuilder::getSourceFileNameIndex(StringRef File) { auto NameIter = SourceFileNames.find(File); if (NameIter == SourceFileNames.end()) return make_error(raw_error_code::no_entry, "The specified source file was not found"); return NameIter->getValue(); } uint32_t DbiStreamBuilder::calculateModiSubstreamSize() const { uint32_t Size = 0; for (const auto &M : ModiList) Size += M->calculateSerializedLength(); return Size; } uint32_t DbiStreamBuilder::calculateSectionContribsStreamSize() const { if (SectionContribs.empty()) return 0; return sizeof(enum PdbRaw_DbiSecContribVer) + sizeof(SectionContribs[0]) * SectionContribs.size(); } uint32_t DbiStreamBuilder::calculateSectionMapStreamSize() const { if (SectionMap.empty()) return 0; return sizeof(SecMapHeader) + sizeof(SecMapEntry) * SectionMap.size(); } uint32_t DbiStreamBuilder::calculateNamesOffset() const { uint32_t Offset = 0; Offset += sizeof(ulittle16_t); // NumModules Offset += sizeof(ulittle16_t); // NumSourceFiles Offset += ModiList.size() * sizeof(ulittle16_t); // ModIndices Offset += ModiList.size() * sizeof(ulittle16_t); // ModFileCounts uint32_t NumFileInfos = 0; for (const auto &M : ModiList) NumFileInfos += M->source_files().size(); Offset += NumFileInfos * sizeof(ulittle32_t); // FileNameOffsets return Offset; } uint32_t DbiStreamBuilder::calculateFileInfoSubstreamSize() const { uint32_t Size = calculateNamesOffset(); Size += calculateNamesBufferSize(); return alignTo(Size, sizeof(uint32_t)); } uint32_t DbiStreamBuilder::calculateNamesBufferSize() const { uint32_t Size = 0; for (const auto &F : SourceFileNames) { Size += F.getKeyLength() + 1; // Names[I]; } return Size; } uint32_t DbiStreamBuilder::calculateDbgStreamsSize() const { return DbgStreams.size() * sizeof(uint16_t); } Error DbiStreamBuilder::generateFileInfoSubstream() { uint32_t Size = calculateFileInfoSubstreamSize(); auto Data = Allocator.Allocate(Size); uint32_t NamesOffset = calculateNamesOffset(); FileInfoBuffer = MutableBinaryByteStream(MutableArrayRef(Data, Size), llvm::support::little); WritableBinaryStreamRef MetadataBuffer = WritableBinaryStreamRef(FileInfoBuffer).keep_front(NamesOffset); BinaryStreamWriter MetadataWriter(MetadataBuffer); uint16_t ModiCount = std::min(UINT16_MAX, ModiList.size()); uint16_t FileCount = std::min(UINT16_MAX, SourceFileNames.size()); if (auto EC = MetadataWriter.writeInteger(ModiCount)) // NumModules return EC; if (auto EC = MetadataWriter.writeInteger(FileCount)) // NumSourceFiles return EC; for (uint16_t I = 0; I < ModiCount; ++I) { if (auto EC = MetadataWriter.writeInteger(I)) // Mod Indices return EC; } for (const auto &MI : ModiList) { FileCount = static_cast(MI->source_files().size()); if (auto EC = MetadataWriter.writeInteger(FileCount)) // Mod File Counts return EC; } // Before writing the FileNameOffsets array, write the NamesBuffer array. // A side effect of this is that this will actually compute the various // file name offsets, so we can then go back and write the FileNameOffsets // array to the other substream. NamesBuffer = WritableBinaryStreamRef(FileInfoBuffer).drop_front(NamesOffset); BinaryStreamWriter NameBufferWriter(NamesBuffer); for (auto &Name : SourceFileNames) { Name.second = NameBufferWriter.getOffset(); if (auto EC = NameBufferWriter.writeCString(Name.getKey())) return EC; } for (const auto &MI : ModiList) { for (StringRef Name : MI->source_files()) { auto Result = SourceFileNames.find(Name); if (Result == SourceFileNames.end()) return make_error(raw_error_code::no_entry, "The source file was not found."); if (auto EC = MetadataWriter.writeInteger(Result->second)) return EC; } } if (auto EC = NameBufferWriter.padToAlignment(sizeof(uint32_t))) return EC; if (NameBufferWriter.bytesRemaining() > 0) return make_error(raw_error_code::invalid_format, "The names buffer contained unexpected data."); if (MetadataWriter.bytesRemaining() > sizeof(uint32_t)) return make_error( raw_error_code::invalid_format, "The metadata buffer contained unexpected data."); return Error::success(); } Error DbiStreamBuilder::finalize() { if (Header) return Error::success(); for (auto &MI : ModiList) MI->finalize(); if (auto EC = generateFileInfoSubstream()) return EC; DbiStreamHeader *H = Allocator.Allocate(); ::memset(H, 0, sizeof(DbiStreamHeader)); H->VersionHeader = *VerHeader; H->VersionSignature = -1; H->Age = Age; H->BuildNumber = BuildNumber; H->Flags = Flags; H->PdbDllRbld = PdbDllRbld; H->PdbDllVersion = PdbDllVersion; H->MachineType = static_cast(MachineType); H->ECSubstreamSize = ECNamesBuilder.calculateSerializedSize(); H->FileInfoSize = FileInfoBuffer.getLength(); H->ModiSubstreamSize = calculateModiSubstreamSize(); H->OptionalDbgHdrSize = DbgStreams.size() * sizeof(uint16_t); H->SecContrSubstreamSize = calculateSectionContribsStreamSize(); H->SectionMapSize = calculateSectionMapStreamSize(); H->TypeServerSize = 0; H->SymRecordStreamIndex = SymRecordStreamIndex; H->PublicSymbolStreamIndex = PublicsStreamIndex; H->MFCTypeServerIndex = 0; // Not sure what this is, but link.exe writes 0. H->GlobalSymbolStreamIndex = GlobalsStreamIndex; Header = H; return Error::success(); } Error DbiStreamBuilder::finalizeMsfLayout() { - if (FrameData.hasValue()) { + if (NewFpoData.hasValue()) { DbgStreams[(int)DbgHeaderType::NewFPO].emplace(); DbgStreams[(int)DbgHeaderType::NewFPO]->Size = - FrameData->calculateSerializedSize(); + NewFpoData->calculateSerializedSize(); DbgStreams[(int)DbgHeaderType::NewFPO]->WriteFn = [this](BinaryStreamWriter &Writer) { - return FrameData->commit(Writer); + return NewFpoData->commit(Writer); + }; + } + + if (!OldFpoData.empty()) { + DbgStreams[(int)DbgHeaderType::FPO].emplace(); + DbgStreams[(int)DbgHeaderType::FPO]->Size = + sizeof(object::FpoData) * OldFpoData.size(); + DbgStreams[(int)DbgHeaderType::FPO]->WriteFn = + [this](BinaryStreamWriter &Writer) { + return Writer.writeArray(makeArrayRef(OldFpoData)); }; } for (auto &S : DbgStreams) { if (!S.hasValue()) continue; auto ExpectedIndex = Msf.addStream(S->Size); if (!ExpectedIndex) return ExpectedIndex.takeError(); S->StreamNumber = *ExpectedIndex; } for (auto &MI : ModiList) { if (auto EC = MI->finalizeMsfLayout()) return EC; } uint32_t Length = calculateSerializedLength(); if (auto EC = Msf.setStreamSize(StreamDBI, Length)) return EC; return Error::success(); } static uint16_t toSecMapFlags(uint32_t Flags) { uint16_t Ret = 0; if (Flags & COFF::IMAGE_SCN_MEM_READ) Ret |= static_cast(OMFSegDescFlags::Read); if (Flags & COFF::IMAGE_SCN_MEM_WRITE) Ret |= static_cast(OMFSegDescFlags::Write); if (Flags & COFF::IMAGE_SCN_MEM_EXECUTE) Ret |= static_cast(OMFSegDescFlags::Execute); if (Flags & COFF::IMAGE_SCN_MEM_EXECUTE) Ret |= static_cast(OMFSegDescFlags::Execute); if (!(Flags & COFF::IMAGE_SCN_MEM_16BIT)) Ret |= static_cast(OMFSegDescFlags::AddressIs32Bit); // This seems always 1. Ret |= static_cast(OMFSegDescFlags::IsSelector); return Ret; } // A utility function to create a Section Map for a given list of COFF sections. // // A Section Map seem to be a copy of a COFF section list in other format. // I don't know why a PDB file contains both a COFF section header and // a Section Map, but it seems it must be present in a PDB. std::vector DbiStreamBuilder::createSectionMap( ArrayRef SecHdrs) { std::vector Ret; int Idx = 0; auto Add = [&]() -> SecMapEntry & { Ret.emplace_back(); auto &Entry = Ret.back(); memset(&Entry, 0, sizeof(Entry)); Entry.Frame = Idx + 1; // We don't know the meaning of these fields yet. Entry.SecName = UINT16_MAX; Entry.ClassName = UINT16_MAX; return Entry; }; for (auto &Hdr : SecHdrs) { auto &Entry = Add(); Entry.Flags = toSecMapFlags(Hdr.Characteristics); Entry.SecByteLength = Hdr.VirtualSize; ++Idx; } // The last entry is for absolute symbols. auto &Entry = Add(); Entry.Flags = static_cast(OMFSegDescFlags::AddressIs32Bit) | static_cast(OMFSegDescFlags::IsAbsoluteAddress); Entry.SecByteLength = UINT32_MAX; return Ret; } Error DbiStreamBuilder::commit(const msf::MSFLayout &Layout, WritableBinaryStreamRef MsfBuffer) { if (auto EC = finalize()) return EC; auto DbiS = WritableMappedBlockStream::createIndexedStream( Layout, MsfBuffer, StreamDBI, Allocator); BinaryStreamWriter Writer(*DbiS); if (auto EC = Writer.writeObject(*Header)) return EC; for (auto &M : ModiList) { if (auto EC = M->commit(Writer, Layout, MsfBuffer)) return EC; } if (!SectionContribs.empty()) { if (auto EC = Writer.writeEnum(DbiSecContribVer60)) return EC; if (auto EC = Writer.writeArray(makeArrayRef(SectionContribs))) return EC; } if (!SectionMap.empty()) { ulittle16_t Size = static_cast(SectionMap.size()); SecMapHeader SMHeader = {Size, Size}; if (auto EC = Writer.writeObject(SMHeader)) return EC; if (auto EC = Writer.writeArray(SectionMap)) return EC; } if (auto EC = Writer.writeStreamRef(FileInfoBuffer)) return EC; if (auto EC = ECNamesBuilder.commit(Writer)) return EC; for (auto &Stream : DbgStreams) { uint16_t StreamNumber = kInvalidStreamIndex; if (Stream.hasValue()) StreamNumber = Stream->StreamNumber; if (auto EC = Writer.writeInteger(StreamNumber)) return EC; } for (auto &Stream : DbgStreams) { if (!Stream.hasValue()) continue; assert(Stream->StreamNumber != kInvalidStreamIndex); auto WritableStream = WritableMappedBlockStream::createIndexedStream( Layout, MsfBuffer, Stream->StreamNumber, Allocator); BinaryStreamWriter DbgStreamWriter(*WritableStream); if (auto EC = Stream->WriteFn(DbgStreamWriter)) return EC; } if (Writer.bytesRemaining() > 0) return make_error(raw_error_code::invalid_format, "Unexpected bytes found in DBI Stream"); return Error::success(); } Index: lld/trunk/COFF/PDB.cpp =================================================================== --- lld/trunk/COFF/PDB.cpp (revision 342079) +++ lld/trunk/COFF/PDB.cpp (revision 342080) @@ -1,1382 +1,1453 @@ //===- PDB.cpp ------------------------------------------------------------===// // // The LLVM Linker // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "PDB.h" #include "Chunks.h" #include "Config.h" #include "Driver.h" #include "SymbolTable.h" #include "Symbols.h" #include "Writer.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Timer.h" #include "llvm/DebugInfo/CodeView/DebugFrameDataSubsection.h" #include "llvm/DebugInfo/CodeView/DebugSubsectionRecord.h" #include "llvm/DebugInfo/CodeView/GlobalTypeTableBuilder.h" #include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h" #include "llvm/DebugInfo/CodeView/MergingTypeTableBuilder.h" #include "llvm/DebugInfo/CodeView/RecordName.h" #include "llvm/DebugInfo/CodeView/SymbolDeserializer.h" #include "llvm/DebugInfo/CodeView/SymbolSerializer.h" #include "llvm/DebugInfo/CodeView/TypeDeserializer.h" #include "llvm/DebugInfo/CodeView/TypeDumpVisitor.h" #include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h" #include "llvm/DebugInfo/CodeView/TypeStreamMerger.h" #include "llvm/DebugInfo/MSF/MSFBuilder.h" #include "llvm/DebugInfo/MSF/MSFCommon.h" #include "llvm/DebugInfo/PDB/GenericError.h" #include "llvm/DebugInfo/PDB/Native/DbiModuleDescriptorBuilder.h" #include "llvm/DebugInfo/PDB/Native/DbiStream.h" #include "llvm/DebugInfo/PDB/Native/DbiStreamBuilder.h" #include "llvm/DebugInfo/PDB/Native/GSIStreamBuilder.h" #include "llvm/DebugInfo/PDB/Native/InfoStream.h" #include "llvm/DebugInfo/PDB/Native/InfoStreamBuilder.h" #include "llvm/DebugInfo/PDB/Native/NativeSession.h" #include "llvm/DebugInfo/PDB/Native/PDBFile.h" #include "llvm/DebugInfo/PDB/Native/PDBFileBuilder.h" #include "llvm/DebugInfo/PDB/Native/PDBStringTableBuilder.h" #include "llvm/DebugInfo/PDB/Native/TpiHashing.h" #include "llvm/DebugInfo/PDB/Native/TpiStream.h" #include "llvm/DebugInfo/PDB/Native/TpiStreamBuilder.h" #include "llvm/DebugInfo/PDB/PDB.h" #include "llvm/Object/COFF.h" #include "llvm/Object/CVDebugRecord.h" #include "llvm/Support/BinaryByteStream.h" #include "llvm/Support/Endian.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/JamCRC.h" #include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" #include using namespace lld; using namespace lld::coff; using namespace llvm; using namespace llvm::codeview; using llvm::object::coff_section; static ExitOnError ExitOnErr; static Timer TotalPdbLinkTimer("PDB Emission (Cumulative)", Timer::root()); static Timer AddObjectsTimer("Add Objects", TotalPdbLinkTimer); static Timer TypeMergingTimer("Type Merging", AddObjectsTimer); static Timer SymbolMergingTimer("Symbol Merging", AddObjectsTimer); static Timer GlobalsLayoutTimer("Globals Stream Layout", TotalPdbLinkTimer); static Timer TpiStreamLayoutTimer("TPI Stream Layout", TotalPdbLinkTimer); static Timer DiskCommitTimer("Commit to Disk", TotalPdbLinkTimer); namespace { /// Map from type index and item index in a type server PDB to the /// corresponding index in the destination PDB. struct CVIndexMap { SmallVector TPIMap; SmallVector IPIMap; bool IsTypeServerMap = false; }; +class DebugSHandler; + class PDBLinker { + friend DebugSHandler; + public: PDBLinker(SymbolTable *Symtab) : Alloc(), Symtab(Symtab), Builder(Alloc), TypeTable(Alloc), IDTable(Alloc), GlobalTypeTable(Alloc), GlobalIDTable(Alloc) { // This isn't strictly necessary, but link.exe usually puts an empty string // as the first "valid" string in the string table, so we do the same in // order to maintain as much byte-for-byte compatibility as possible. PDBStrTab.insert(""); } /// Emit the basic PDB structure: initial streams, headers, etc. void initialize(const llvm::codeview::DebugInfo &BuildId); /// Add natvis files specified on the command line. void addNatvisFiles(); /// Link CodeView from each object file in the symbol table into the PDB. void addObjectsToPDB(); /// Link CodeView from a single object file into the PDB. void addObjFile(ObjFile *File); /// Produce a mapping from the type and item indices used in the object /// file to those in the destination PDB. /// /// If the object file uses a type server PDB (compiled with /Zi), merge TPI /// and IPI from the type server PDB and return a map for it. Each unique type /// server PDB is merged at most once, so this may return an existing index /// mapping. /// /// If the object does not use a type server PDB (compiled with /Z7), we merge /// all the type and item records from the .debug$S stream and fill in the /// caller-provided ObjectIndexMap. Expected mergeDebugT(ObjFile *File, CVIndexMap &ObjectIndexMap); Expected maybeMergeTypeServerPDB(ObjFile *File, TypeServer2Record &TS); /// Add the section map and section contributions to the PDB. void addSections(ArrayRef OutputSections, ArrayRef SectionTable); /// Write the PDB to disk. void commit(); private: BumpPtrAllocator Alloc; SymbolTable *Symtab; pdb::PDBFileBuilder Builder; /// Type records that will go into the PDB TPI stream. MergingTypeTableBuilder TypeTable; /// Item records that will go into the PDB IPI stream. MergingTypeTableBuilder IDTable; /// Type records that will go into the PDB TPI stream (for /DEBUG:GHASH) GlobalTypeTableBuilder GlobalTypeTable; /// Item records that will go into the PDB IPI stream (for /DEBUG:GHASH) GlobalTypeTableBuilder GlobalIDTable; /// PDBs use a single global string table for filenames in the file checksum /// table. DebugStringTableSubsection PDBStrTab; llvm::SmallString<128> NativePath; /// A list of other PDBs which are loaded during the linking process and which /// we need to keep around since the linking operation may reference pointers /// inside of these PDBs. llvm::SmallVector, 2> LoadedPDBs; std::vector SectionMap; /// Type index mappings of type server PDBs that we've loaded so far. std::map TypeServerIndexMappings; /// List of TypeServer PDBs which cannot be loaded. /// Cached to prevent repeated load attempts. std::map MissingTypeServerPDBs; }; + +class DebugSHandler { + PDBLinker &Linker; + + /// The object file whose .debug$S sections we're processing. + ObjFile &File; + + /// The result of merging type indices. + const CVIndexMap &IndexMap; + + /// The DEBUG_S_STRINGTABLE subsection. These strings are referred to by + /// index from other records in the .debug$S section. All of these strings + /// need to be added to the global PDB string table, and all references to + /// these strings need to have their indices re-written to refer to the + /// global PDB string table. + DebugStringTableSubsectionRef CVStrTab; + + /// The DEBUG_S_FILECHKSMS subsection. As above, these are referred to + /// by other records in the .debug$S section and need to be merged into the + /// PDB. + DebugChecksumsSubsectionRef Checksums; + + /// The DEBUG_S_FRAMEDATA subsection(s). There can be more than one of + /// these and they need not appear in any specific order. However, they + /// contain string table references which need to be re-written, so we + /// collect them all here and re-write them after all subsections have been + /// discovered and processed. + std::vector NewFpoFrames; + + /// Pointers to raw memory that we determine have string table references + /// that need to be re-written. We first process all .debug$S subsections + /// to ensure that we can handle subsections written in any order, building + /// up this list as we go. At the end, we use the string table (which must + /// have been discovered by now else it is an error) to re-write these + /// references. + std::vector StringTableReferences; + +public: + DebugSHandler(PDBLinker &Linker, ObjFile &File, const CVIndexMap &IndexMap) + : Linker(Linker), File(File), IndexMap(IndexMap) {} + + void handleDebugS(lld::coff::SectionChunk &DebugS); + void finish(); +}; } static SectionChunk *findByName(ArrayRef Sections, StringRef Name) { for (SectionChunk *C : Sections) if (C->getSectionName() == Name) return C; return nullptr; } static ArrayRef consumeDebugMagic(ArrayRef Data, StringRef SecName) { // First 4 bytes are section magic. if (Data.size() < 4) fatal(SecName + " too short"); if (support::endian::read32le(Data.data()) != COFF::DEBUG_SECTION_MAGIC) fatal(SecName + " has an invalid magic"); return Data.slice(4); } static ArrayRef getDebugSection(ObjFile *File, StringRef SecName) { if (SectionChunk *Sec = findByName(File->getDebugChunks(), SecName)) return consumeDebugMagic(Sec->getContents(), SecName); return {}; } // A COFF .debug$H section is currently a clang extension. This function checks // if a .debug$H section is in a format that we expect / understand, so that we // can ignore any sections which are coincidentally also named .debug$H but do // not contain a format we recognize. static bool canUseDebugH(ArrayRef DebugH) { if (DebugH.size() < sizeof(object::debug_h_header)) return false; auto *Header = reinterpret_cast(DebugH.data()); DebugH = DebugH.drop_front(sizeof(object::debug_h_header)); return Header->Magic == COFF::DEBUG_HASHES_SECTION_MAGIC && Header->Version == 0 && Header->HashAlgorithm == uint16_t(GlobalTypeHashAlg::SHA1_8) && (DebugH.size() % 8 == 0); } static Optional> getDebugH(ObjFile *File) { SectionChunk *Sec = findByName(File->getDebugChunks(), ".debug$H"); if (!Sec) return llvm::None; ArrayRef Contents = Sec->getContents(); if (!canUseDebugH(Contents)) return None; return Contents; } static ArrayRef getHashesFromDebugH(ArrayRef DebugH) { assert(canUseDebugH(DebugH)); DebugH = DebugH.drop_front(sizeof(object::debug_h_header)); uint32_t Count = DebugH.size() / sizeof(GloballyHashedType); return {reinterpret_cast(DebugH.data()), Count}; } static void addTypeInfo(pdb::TpiStreamBuilder &TpiBuilder, TypeCollection &TypeTable) { // Start the TPI or IPI stream header. TpiBuilder.setVersionHeader(pdb::PdbTpiV80); // Flatten the in memory type table and hash each type. TypeTable.ForEachRecord([&](TypeIndex TI, const CVType &Type) { auto Hash = pdb::hashTypeRecord(Type); if (auto E = Hash.takeError()) fatal("type hashing error"); TpiBuilder.addTypeRecord(Type.RecordData, *Hash); }); } static Optional maybeReadTypeServerRecord(CVTypeArray &Types) { auto I = Types.begin(); if (I == Types.end()) return None; const CVType &Type = *I; if (Type.kind() != LF_TYPESERVER2) return None; TypeServer2Record TS; if (auto EC = TypeDeserializer::deserializeAs(const_cast(Type), TS)) fatal("error reading type server record: " + toString(std::move(EC))); return std::move(TS); } Expected PDBLinker::mergeDebugT(ObjFile *File, CVIndexMap &ObjectIndexMap) { ScopedTimer T(TypeMergingTimer); ArrayRef Data = getDebugSection(File, ".debug$T"); if (Data.empty()) return ObjectIndexMap; BinaryByteStream Stream(Data, support::little); CVTypeArray Types; BinaryStreamReader Reader(Stream); if (auto EC = Reader.readArray(Types, Reader.getLength())) fatal("Reader::readArray failed: " + toString(std::move(EC))); // Look through type servers. If we've already seen this type server, don't // merge any type information. if (Optional TS = maybeReadTypeServerRecord(Types)) return maybeMergeTypeServerPDB(File, *TS); // This is a /Z7 object. Fill in the temporary, caller-provided // ObjectIndexMap. if (Config->DebugGHashes) { ArrayRef Hashes; std::vector OwnedHashes; if (Optional> DebugH = getDebugH(File)) Hashes = getHashesFromDebugH(*DebugH); else { OwnedHashes = GloballyHashedType::hashTypes(Types); Hashes = OwnedHashes; } if (auto Err = mergeTypeAndIdRecords(GlobalIDTable, GlobalTypeTable, ObjectIndexMap.TPIMap, Types, Hashes)) fatal("codeview::mergeTypeAndIdRecords failed: " + toString(std::move(Err))); } else { if (auto Err = mergeTypeAndIdRecords(IDTable, TypeTable, ObjectIndexMap.TPIMap, Types)) fatal("codeview::mergeTypeAndIdRecords failed: " + toString(std::move(Err))); } return ObjectIndexMap; } static Expected> tryToLoadPDB(const GUID &GuidFromObj, StringRef TSPath) { ErrorOr> MBOrErr = MemoryBuffer::getFile( TSPath, /*FileSize=*/-1, /*RequiresNullTerminator=*/false); if (!MBOrErr) return errorCodeToError(MBOrErr.getError()); std::unique_ptr ThisSession; if (auto EC = pdb::NativeSession::createFromPdb( MemoryBuffer::getMemBuffer(Driver->takeBuffer(std::move(*MBOrErr)), /*RequiresNullTerminator=*/false), ThisSession)) return std::move(EC); std::unique_ptr NS( static_cast(ThisSession.release())); pdb::PDBFile &File = NS->getPDBFile(); auto ExpectedInfo = File.getPDBInfoStream(); // All PDB Files should have an Info stream. if (!ExpectedInfo) return ExpectedInfo.takeError(); // Just because a file with a matching name was found and it was an actual // PDB file doesn't mean it matches. For it to match the InfoStream's GUID // must match the GUID specified in the TypeServer2 record. if (ExpectedInfo->getGuid() != GuidFromObj) return make_error(pdb::pdb_error_code::signature_out_of_date); return std::move(NS); } Expected PDBLinker::maybeMergeTypeServerPDB(ObjFile *File, TypeServer2Record &TS) { const GUID &TSId = TS.getGuid(); StringRef TSPath = TS.getName(); // First, check if the PDB has previously failed to load. auto PrevErr = MissingTypeServerPDBs.find(TSId); if (PrevErr != MissingTypeServerPDBs.end()) return createFileError( TSPath, make_error(PrevErr->second, inconvertibleErrorCode())); // Second, check if we already loaded a PDB with this GUID. Return the type // index mapping if we have it. auto Insertion = TypeServerIndexMappings.insert({TSId, CVIndexMap()}); CVIndexMap &IndexMap = Insertion.first->second; if (!Insertion.second) return IndexMap; // Mark this map as a type server map. IndexMap.IsTypeServerMap = true; // Check for a PDB at: // 1. The given file path // 2. Next to the object file or archive file auto ExpectedSession = handleExpected( tryToLoadPDB(TSId, TSPath), [&]() { StringRef LocalPath = !File->ParentName.empty() ? File->ParentName : File->getName(); SmallString<128> Path = sys::path::parent_path(LocalPath); sys::path::append( Path, sys::path::filename(TSPath, sys::path::Style::windows)); return tryToLoadPDB(TSId, Path); }, [&](std::unique_ptr EC) -> Error { auto SysErr = EC->convertToErrorCode(); // Only re-try loading if the previous error was "No such file or // directory" if (SysErr.category() == std::generic_category() && SysErr.value() == ENOENT) return Error::success(); return Error(std::move(EC)); }); if (auto E = ExpectedSession.takeError()) { TypeServerIndexMappings.erase(TSId); // Flatten the error to a string, for later display, if the error occurs // again on the same PDB. std::string ErrMsg; raw_string_ostream S(ErrMsg); S << E; MissingTypeServerPDBs.emplace(TSId, S.str()); return createFileError(TSPath, std::move(E)); } pdb::NativeSession *Session = ExpectedSession->get(); // Keep a strong reference to this PDB, so that it's safe to hold pointers // into the file. LoadedPDBs.push_back(std::move(*ExpectedSession)); auto ExpectedTpi = Session->getPDBFile().getPDBTpiStream(); if (auto E = ExpectedTpi.takeError()) fatal("Type server does not have TPI stream: " + toString(std::move(E))); auto ExpectedIpi = Session->getPDBFile().getPDBIpiStream(); if (auto E = ExpectedIpi.takeError()) fatal("Type server does not have TPI stream: " + toString(std::move(E))); if (Config->DebugGHashes) { // PDBs do not actually store global hashes, so when merging a type server // PDB we have to synthesize global hashes. To do this, we first synthesize // global hashes for the TPI stream, since it is independent, then we // synthesize hashes for the IPI stream, using the hashes for the TPI stream // as inputs. auto TpiHashes = GloballyHashedType::hashTypes(ExpectedTpi->typeArray()); auto IpiHashes = GloballyHashedType::hashIds(ExpectedIpi->typeArray(), TpiHashes); // Merge TPI first, because the IPI stream will reference type indices. if (auto Err = mergeTypeRecords(GlobalTypeTable, IndexMap.TPIMap, ExpectedTpi->typeArray(), TpiHashes)) fatal("codeview::mergeTypeRecords failed: " + toString(std::move(Err))); // Merge IPI. if (auto Err = mergeIdRecords(GlobalIDTable, IndexMap.TPIMap, IndexMap.IPIMap, ExpectedIpi->typeArray(), IpiHashes)) fatal("codeview::mergeIdRecords failed: " + toString(std::move(Err))); } else { // Merge TPI first, because the IPI stream will reference type indices. if (auto Err = mergeTypeRecords(TypeTable, IndexMap.TPIMap, ExpectedTpi->typeArray())) fatal("codeview::mergeTypeRecords failed: " + toString(std::move(Err))); // Merge IPI. if (auto Err = mergeIdRecords(IDTable, IndexMap.TPIMap, IndexMap.IPIMap, ExpectedIpi->typeArray())) fatal("codeview::mergeIdRecords failed: " + toString(std::move(Err))); } return IndexMap; } static bool remapTypeIndex(TypeIndex &TI, ArrayRef TypeIndexMap) { if (TI.isSimple()) return true; if (TI.toArrayIndex() >= TypeIndexMap.size()) return false; TI = TypeIndexMap[TI.toArrayIndex()]; return true; } static void remapTypesInSymbolRecord(ObjFile *File, SymbolKind SymKind, MutableArrayRef Contents, const CVIndexMap &IndexMap, ArrayRef TypeRefs) { for (const TiReference &Ref : TypeRefs) { unsigned ByteSize = Ref.Count * sizeof(TypeIndex); if (Contents.size() < Ref.Offset + ByteSize) fatal("symbol record too short"); // This can be an item index or a type index. Choose the appropriate map. ArrayRef TypeOrItemMap = IndexMap.TPIMap; bool IsItemIndex = Ref.Kind == TiRefKind::IndexRef; if (IsItemIndex && IndexMap.IsTypeServerMap) TypeOrItemMap = IndexMap.IPIMap; MutableArrayRef TIs( reinterpret_cast(Contents.data() + Ref.Offset), Ref.Count); for (TypeIndex &TI : TIs) { if (!remapTypeIndex(TI, TypeOrItemMap)) { log("ignoring symbol record of kind 0x" + utohexstr(SymKind) + " in " + File->getName() + " with bad " + (IsItemIndex ? "item" : "type") + " index 0x" + utohexstr(TI.getIndex())); TI = TypeIndex(SimpleTypeKind::NotTranslated); continue; } } } } static void recordStringTableReferenceAtOffset(MutableArrayRef Contents, uint32_t Offset, std::vector &StrTableRefs) { Contents = Contents.drop_front(Offset).take_front(sizeof(support::ulittle32_t)); ulittle32_t *Index = reinterpret_cast(Contents.data()); StrTableRefs.push_back(Index); } static void recordStringTableReferences(SymbolKind Kind, MutableArrayRef Contents, std::vector &StrTableRefs) { // For now we only handle S_FILESTATIC, but we may need the same logic for // S_DEFRANGE and S_DEFRANGE_SUBFIELD. However, I cannot seem to generate any // PDBs that contain these types of records, so because of the uncertainty // they are omitted here until we can prove that it's necessary. switch (Kind) { case SymbolKind::S_FILESTATIC: // FileStaticSym::ModFileOffset recordStringTableReferenceAtOffset(Contents, 4, StrTableRefs); break; case SymbolKind::S_DEFRANGE: case SymbolKind::S_DEFRANGE_SUBFIELD: log("Not fixing up string table reference in S_DEFRANGE / " "S_DEFRANGE_SUBFIELD record"); break; default: break; } } static SymbolKind symbolKind(ArrayRef RecordData) { const RecordPrefix *Prefix = reinterpret_cast(RecordData.data()); return static_cast(uint16_t(Prefix->RecordKind)); } /// MSVC translates S_PROC_ID_END to S_END, and S_[LG]PROC32_ID to S_[LG]PROC32 static void translateIdSymbols(MutableArrayRef &RecordData, TypeCollection &IDTable) { RecordPrefix *Prefix = reinterpret_cast(RecordData.data()); SymbolKind Kind = symbolKind(RecordData); if (Kind == SymbolKind::S_PROC_ID_END) { Prefix->RecordKind = SymbolKind::S_END; return; } // In an object file, GPROC32_ID has an embedded reference which refers to the // single object file type index namespace. This has already been translated // to the PDB file's ID stream index space, but we need to convert this to a // symbol that refers to the type stream index space. So we remap again from // ID index space to type index space. if (Kind == SymbolKind::S_GPROC32_ID || Kind == SymbolKind::S_LPROC32_ID) { SmallVector Refs; auto Content = RecordData.drop_front(sizeof(RecordPrefix)); CVSymbol Sym(Kind, RecordData); discoverTypeIndicesInSymbol(Sym, Refs); assert(Refs.size() == 1); assert(Refs.front().Count == 1); TypeIndex *TI = reinterpret_cast(Content.data() + Refs[0].Offset); // `TI` is the index of a FuncIdRecord or MemberFuncIdRecord which lives in // the IPI stream, whose `FunctionType` member refers to the TPI stream. // Note that LF_FUNC_ID and LF_MEMFUNC_ID have the same record layout, and // in both cases we just need the second type index. if (!TI->isSimple() && !TI->isNoneType()) { CVType FuncIdData = IDTable.getType(*TI); SmallVector Indices; discoverTypeIndices(FuncIdData, Indices); assert(Indices.size() == 2); *TI = Indices[1]; } Kind = (Kind == SymbolKind::S_GPROC32_ID) ? SymbolKind::S_GPROC32 : SymbolKind::S_LPROC32; Prefix->RecordKind = uint16_t(Kind); } } /// Copy the symbol record. In a PDB, symbol records must be 4 byte aligned. /// The object file may not be aligned. static MutableArrayRef copySymbolForPdb(const CVSymbol &Sym, BumpPtrAllocator &Alloc) { size_t Size = alignTo(Sym.length(), alignOf(CodeViewContainer::Pdb)); assert(Size >= 4 && "record too short"); assert(Size <= MaxRecordLength && "record too long"); void *Mem = Alloc.Allocate(Size, 4); // Copy the symbol record and zero out any padding bytes. MutableArrayRef NewData(reinterpret_cast(Mem), Size); memcpy(NewData.data(), Sym.data().data(), Sym.length()); memset(NewData.data() + Sym.length(), 0, Size - Sym.length()); // Update the record prefix length. It should point to the beginning of the // next record. auto *Prefix = reinterpret_cast(Mem); Prefix->RecordLen = Size - 2; return NewData; } /// Return true if this symbol opens a scope. This implies that the symbol has /// "parent" and "end" fields, which contain the offset of the S_END or /// S_INLINESITE_END record. static bool symbolOpensScope(SymbolKind Kind) { switch (Kind) { case SymbolKind::S_GPROC32: case SymbolKind::S_LPROC32: case SymbolKind::S_LPROC32_ID: case SymbolKind::S_GPROC32_ID: case SymbolKind::S_BLOCK32: case SymbolKind::S_SEPCODE: case SymbolKind::S_THUNK32: case SymbolKind::S_INLINESITE: case SymbolKind::S_INLINESITE2: return true; default: break; } return false; } static bool symbolEndsScope(SymbolKind Kind) { switch (Kind) { case SymbolKind::S_END: case SymbolKind::S_PROC_ID_END: case SymbolKind::S_INLINESITE_END: return true; default: break; } return false; } struct ScopeRecord { ulittle32_t PtrParent; ulittle32_t PtrEnd; }; struct SymbolScope { ScopeRecord *OpeningRecord; uint32_t ScopeOffset; }; static void scopeStackOpen(SmallVectorImpl &Stack, uint32_t CurOffset, CVSymbol &Sym) { assert(symbolOpensScope(Sym.kind())); SymbolScope S; S.ScopeOffset = CurOffset; S.OpeningRecord = const_cast( reinterpret_cast(Sym.content().data())); S.OpeningRecord->PtrParent = Stack.empty() ? 0 : Stack.back().ScopeOffset; Stack.push_back(S); } static void scopeStackClose(SmallVectorImpl &Stack, uint32_t CurOffset, ObjFile *File) { if (Stack.empty()) { warn("symbol scopes are not balanced in " + File->getName()); return; } SymbolScope S = Stack.pop_back_val(); S.OpeningRecord->PtrEnd = CurOffset; } static bool symbolGoesInModuleStream(const CVSymbol &Sym) { switch (Sym.kind()) { case SymbolKind::S_GDATA32: case SymbolKind::S_CONSTANT: case SymbolKind::S_UDT: // We really should not be seeing S_PROCREF and S_LPROCREF in the first place // since they are synthesized by the linker in response to S_GPROC32 and // S_LPROC32, but if we do see them, don't put them in the module stream I // guess. case SymbolKind::S_PROCREF: case SymbolKind::S_LPROCREF: return false; // S_GDATA32 does not go in the module stream, but S_LDATA32 does. case SymbolKind::S_LDATA32: default: return true; } } static bool symbolGoesInGlobalsStream(const CVSymbol &Sym) { switch (Sym.kind()) { case SymbolKind::S_CONSTANT: case SymbolKind::S_GDATA32: // S_LDATA32 goes in both the module stream and the globals stream. case SymbolKind::S_LDATA32: case SymbolKind::S_GPROC32: case SymbolKind::S_LPROC32: // We really should not be seeing S_PROCREF and S_LPROCREF in the first place // since they are synthesized by the linker in response to S_GPROC32 and // S_LPROC32, but if we do see them, copy them straight through. case SymbolKind::S_PROCREF: case SymbolKind::S_LPROCREF: return true; // FIXME: For now, we drop all S_UDT symbols (i.e. they don't go in the // globals stream or the modules stream). These have special handling which // needs more investigation before we can get right, but by putting them all // into the globals stream WinDbg fails to display local variables of class // types saying that it cannot find the type Foo *. So as a stopgap just to // keep things working, we drop them. case SymbolKind::S_UDT: default: return false; } } static void addGlobalSymbol(pdb::GSIStreamBuilder &Builder, ObjFile &File, const CVSymbol &Sym) { switch (Sym.kind()) { case SymbolKind::S_CONSTANT: case SymbolKind::S_UDT: case SymbolKind::S_GDATA32: case SymbolKind::S_LDATA32: case SymbolKind::S_PROCREF: case SymbolKind::S_LPROCREF: Builder.addGlobalSymbol(Sym); break; case SymbolKind::S_GPROC32: case SymbolKind::S_LPROC32: { SymbolRecordKind K = SymbolRecordKind::ProcRefSym; if (Sym.kind() == SymbolKind::S_LPROC32) K = SymbolRecordKind::LocalProcRef; ProcRefSym PS(K); PS.Module = static_cast(File.ModuleDBI->getModuleIndex()); // For some reason, MSVC seems to add one to this value. ++PS.Module; PS.Name = getSymbolName(Sym); PS.SumName = 0; PS.SymOffset = File.ModuleDBI->getNextSymbolOffset(); Builder.addGlobalSymbol(PS); break; } default: llvm_unreachable("Invalid symbol kind!"); } } static void mergeSymbolRecords(BumpPtrAllocator &Alloc, ObjFile *File, pdb::GSIStreamBuilder &GsiBuilder, const CVIndexMap &IndexMap, TypeCollection &IDTable, std::vector &StringTableRefs, BinaryStreamRef SymData) { // FIXME: Improve error recovery by warning and skipping records when // possible. ArrayRef SymsBuffer; cantFail(SymData.readBytes(0, SymData.getLength(), SymsBuffer)); SmallVector Scopes; auto EC = forEachCodeViewRecord( SymsBuffer, [&](const CVSymbol &Sym) -> llvm::Error { // Discover type index references in the record. Skip it if we don't // know where they are. SmallVector TypeRefs; if (!discoverTypeIndicesInSymbol(Sym, TypeRefs)) { log("ignoring unknown symbol record with kind 0x" + utohexstr(Sym.kind())); return Error::success(); } // Copy the symbol record so we can mutate it. MutableArrayRef NewData = copySymbolForPdb(Sym, Alloc); // Re-map all the type index references. MutableArrayRef Contents = NewData.drop_front(sizeof(RecordPrefix)); remapTypesInSymbolRecord(File, Sym.kind(), Contents, IndexMap, TypeRefs); // An object file may have S_xxx_ID symbols, but these get converted to // "real" symbols in a PDB. translateIdSymbols(NewData, IDTable); // If this record refers to an offset in the object file's string table, // add that item to the global PDB string table and re-write the index. recordStringTableReferences(Sym.kind(), Contents, StringTableRefs); SymbolKind NewKind = symbolKind(NewData); // Fill in "Parent" and "End" fields by maintaining a stack of scopes. CVSymbol NewSym(NewKind, NewData); if (symbolOpensScope(NewKind)) scopeStackOpen(Scopes, File->ModuleDBI->getNextSymbolOffset(), NewSym); else if (symbolEndsScope(NewKind)) scopeStackClose(Scopes, File->ModuleDBI->getNextSymbolOffset(), File); // Add the symbol to the globals stream if necessary. Do this before // adding the symbol to the module since we may need to get the next // symbol offset, and writing to the module's symbol stream will update // that offset. if (symbolGoesInGlobalsStream(NewSym)) addGlobalSymbol(GsiBuilder, *File, NewSym); // Add the symbol to the module. if (symbolGoesInModuleStream(NewSym)) File->ModuleDBI->addSymbol(NewSym); return Error::success(); }); cantFail(std::move(EC)); } -// Allocate memory for a .debug$S section and relocate it. +// Allocate memory for a .debug$S / .debug$F section and relocate it. static ArrayRef relocateDebugChunk(BumpPtrAllocator &Alloc, - SectionChunk *DebugChunk) { - uint8_t *Buffer = Alloc.Allocate(DebugChunk->getSize()); - assert(DebugChunk->OutputSectionOff == 0 && + SectionChunk &DebugChunk) { + uint8_t *Buffer = Alloc.Allocate(DebugChunk.getSize()); + assert(DebugChunk.OutputSectionOff == 0 && "debug sections should not be in output sections"); - DebugChunk->writeTo(Buffer); - return consumeDebugMagic(makeArrayRef(Buffer, DebugChunk->getSize()), - ".debug$S"); + DebugChunk.writeTo(Buffer); + return makeArrayRef(Buffer, DebugChunk.getSize()); } static pdb::SectionContrib createSectionContrib(const Chunk *C, uint32_t Modi) { OutputSection *OS = C->getOutputSection(); pdb::SectionContrib SC; memset(&SC, 0, sizeof(SC)); SC.ISect = OS->SectionIndex; SC.Off = C->getRVA() - OS->getRVA(); SC.Size = C->getSize(); if (auto *SecChunk = dyn_cast(C)) { SC.Characteristics = SecChunk->Header->Characteristics; SC.Imod = SecChunk->File->ModuleDBI->getModuleIndex(); ArrayRef Contents = SecChunk->getContents(); JamCRC CRC(0); ArrayRef CharContents = makeArrayRef( reinterpret_cast(Contents.data()), Contents.size()); CRC.update(CharContents); SC.DataCrc = CRC.getCRC(); } else { SC.Characteristics = OS->Header.Characteristics; // FIXME: When we start creating DBI for import libraries, use those here. SC.Imod = Modi; } SC.RelocCrc = 0; // FIXME return SC; } static uint32_t translateStringTableIndex(uint32_t ObjIndex, const DebugStringTableSubsectionRef &ObjStrTable, DebugStringTableSubsection &PdbStrTable) { auto ExpectedString = ObjStrTable.getString(ObjIndex); if (!ExpectedString) { warn("Invalid string table reference"); consumeError(ExpectedString.takeError()); return 0; } return PdbStrTable.insert(*ExpectedString); } +void DebugSHandler::handleDebugS(lld::coff::SectionChunk &DebugS) { + DebugSubsectionArray Subsections; + + ArrayRef RelocatedDebugContents = consumeDebugMagic( + relocateDebugChunk(Linker.Alloc, DebugS), DebugS.getSectionName()); + + BinaryStreamReader Reader(RelocatedDebugContents, support::little); + ExitOnErr(Reader.readArray(Subsections, RelocatedDebugContents.size())); + + for (const DebugSubsectionRecord &SS : Subsections) { + switch (SS.kind()) { + case DebugSubsectionKind::StringTable: { + assert(!CVStrTab.valid() && + "Encountered multiple string table subsections!"); + ExitOnErr(CVStrTab.initialize(SS.getRecordData())); + break; + } + case DebugSubsectionKind::FileChecksums: + assert(!Checksums.valid() && + "Encountered multiple checksum subsections!"); + ExitOnErr(Checksums.initialize(SS.getRecordData())); + break; + case DebugSubsectionKind::Lines: + // We can add the relocated line table directly to the PDB without + // modification because the file checksum offsets will stay the same. + File.ModuleDBI->addDebugSubsection(SS); + break; + case DebugSubsectionKind::FrameData: { + // We need to re-write string table indices here, so save off all + // frame data subsections until we've processed the entire list of + // subsections so that we can be sure we have the string table. + DebugFrameDataSubsectionRef FDS; + ExitOnErr(FDS.initialize(SS.getRecordData())); + NewFpoFrames.push_back(std::move(FDS)); + break; + } + case DebugSubsectionKind::Symbols: + if (Config->DebugGHashes) { + mergeSymbolRecords(Linker.Alloc, &File, Linker.Builder.getGsiBuilder(), + IndexMap, Linker.GlobalIDTable, + StringTableReferences, SS.getRecordData()); + } else { + mergeSymbolRecords(Linker.Alloc, &File, Linker.Builder.getGsiBuilder(), + IndexMap, Linker.IDTable, StringTableReferences, + SS.getRecordData()); + } + break; + default: + // FIXME: Process the rest of the subsections. + break; + } + } +} + +void DebugSHandler::finish() { + pdb::DbiStreamBuilder &DbiBuilder = Linker.Builder.getDbiBuilder(); + + // We should have seen all debug subsections across the entire object file now + // which means that if a StringTable subsection and Checksums subsection were + // present, now is the time to handle them. + if (!CVStrTab.valid()) { + if (Checksums.valid()) + fatal(".debug$S sections with a checksums subsection must also contain a " + "string table subsection"); + + if (!StringTableReferences.empty()) + warn("No StringTable subsection was encountered, but there are string " + "table references"); + return; + } + + // Rewrite string table indices in the Fpo Data and symbol records to refer to + // the global PDB string table instead of the object file string table. + for (DebugFrameDataSubsectionRef &FDS : NewFpoFrames) { + const uint32_t *Reloc = FDS.getRelocPtr(); + for (codeview::FrameData FD : FDS) { + FD.RvaStart += *Reloc; + FD.FrameFunc = + translateStringTableIndex(FD.FrameFunc, CVStrTab, Linker.PDBStrTab); + DbiBuilder.addNewFpoData(FD); + } + } + + for (ulittle32_t *Ref : StringTableReferences) + *Ref = translateStringTableIndex(*Ref, CVStrTab, Linker.PDBStrTab); + + // Make a new file checksum table that refers to offsets in the PDB-wide + // string table. Generally the string table subsection appears after the + // checksum table, so we have to do this after looping over all the + // subsections. + auto NewChecksums = make_unique(Linker.PDBStrTab); + for (FileChecksumEntry &FC : Checksums) { + SmallString<128> FileName = + ExitOnErr(CVStrTab.getString(FC.FileNameOffset)); + if (!sys::path::is_absolute(FileName) && !Config->PDBSourcePath.empty()) { + SmallString<128> AbsoluteFileName = Config->PDBSourcePath; + sys::path::append(AbsoluteFileName, FileName); + sys::path::native(AbsoluteFileName); + sys::path::remove_dots(AbsoluteFileName, /*remove_dot_dots=*/true); + FileName = std::move(AbsoluteFileName); + } + ExitOnErr(Linker.Builder.getDbiBuilder().addModuleSourceFile( + *File.ModuleDBI, FileName)); + NewChecksums->addChecksum(FileName, FC.Kind, FC.Checksum); + } + File.ModuleDBI->addDebugSubsection(std::move(NewChecksums)); +} + void PDBLinker::addObjFile(ObjFile *File) { // Add a module descriptor for every object file. We need to put an absolute // path to the object into the PDB. If this is a plain object, we make its // path absolute. If it's an object in an archive, we make the archive path // absolute. bool InArchive = !File->ParentName.empty(); SmallString<128> Path = InArchive ? File->ParentName : File->getName(); sys::fs::make_absolute(Path); sys::path::native(Path, sys::path::Style::windows); StringRef Name = InArchive ? File->getName() : StringRef(Path); pdb::DbiStreamBuilder &DbiBuilder = Builder.getDbiBuilder(); File->ModuleDBI = &ExitOnErr(DbiBuilder.addModuleInfo(Name)); File->ModuleDBI->setObjFileName(Path); auto Chunks = File->getChunks(); uint32_t Modi = File->ModuleDBI->getModuleIndex(); for (Chunk *C : Chunks) { auto *SecChunk = dyn_cast(C); if (!SecChunk || !SecChunk->Live) continue; pdb::SectionContrib SC = createSectionContrib(SecChunk, Modi); File->ModuleDBI->setFirstSectionContrib(SC); break; } // Before we can process symbol substreams from .debug$S, we need to process // type information, file checksums, and the string table. Add type info to // the PDB first, so that we can get the map from object file type and item // indices to PDB type and item indices. CVIndexMap ObjectIndexMap; auto IndexMapResult = mergeDebugT(File, ObjectIndexMap); // If the .debug$T sections fail to merge, assume there is no debug info. if (!IndexMapResult) { auto FileName = sys::path::filename(Path); warn("Cannot use debug info for '" + FileName + "'\n" + ">>> failed to load reference " + StringRef(toString(IndexMapResult.takeError()))); return; } - const CVIndexMap &IndexMap = *IndexMapResult; - ScopedTimer T(SymbolMergingTimer); - // Now do all live .debug$S sections. - DebugStringTableSubsectionRef CVStrTab; - DebugChecksumsSubsectionRef Checksums; - std::vector StringTableReferences; - std::vector FpoFrames; + DebugSHandler DSH(*this, *File, *IndexMapResult); + // Now do all live .debug$S and .debug$F sections. for (SectionChunk *DebugChunk : File->getDebugChunks()) { - if (!DebugChunk->Live || DebugChunk->getSectionName() != ".debug$S") + if (!DebugChunk->Live || DebugChunk->getSize() == 0) continue; - ArrayRef RelocatedDebugContents = - relocateDebugChunk(Alloc, DebugChunk); - if (RelocatedDebugContents.empty()) + if (DebugChunk->getSectionName() == ".debug$S") { + DSH.handleDebugS(*DebugChunk); continue; - - DebugSubsectionArray Subsections; - BinaryStreamReader Reader(RelocatedDebugContents, support::little); - ExitOnErr(Reader.readArray(Subsections, RelocatedDebugContents.size())); - - for (const DebugSubsectionRecord &SS : Subsections) { - switch (SS.kind()) { - case DebugSubsectionKind::StringTable: { - assert(!CVStrTab.valid() && - "Encountered multiple string table subsections!"); - ExitOnErr(CVStrTab.initialize(SS.getRecordData())); - break; - } - case DebugSubsectionKind::FileChecksums: - assert(!Checksums.valid() && - "Encountered multiple checksum subsections!"); - ExitOnErr(Checksums.initialize(SS.getRecordData())); - break; - case DebugSubsectionKind::Lines: - // We can add the relocated line table directly to the PDB without - // modification because the file checksum offsets will stay the same. - File->ModuleDBI->addDebugSubsection(SS); - break; - case DebugSubsectionKind::FrameData: { - // We need to re-write string table indices here, so save off all - // frame data subsections until we've processed the entire list of - // subsections so that we can be sure we have the string table. - DebugFrameDataSubsectionRef FDS; - ExitOnErr(FDS.initialize(SS.getRecordData())); - FpoFrames.push_back(std::move(FDS)); - break; - } - case DebugSubsectionKind::Symbols: - if (Config->DebugGHashes) { - mergeSymbolRecords(Alloc, File, Builder.getGsiBuilder(), IndexMap, - GlobalIDTable, StringTableReferences, - SS.getRecordData()); - } else { - mergeSymbolRecords(Alloc, File, Builder.getGsiBuilder(), IndexMap, - IDTable, StringTableReferences, - SS.getRecordData()); - } - break; - default: - // FIXME: Process the rest of the subsections. - break; - } } - } - // We should have seen all debug subsections across the entire object file now - // which means that if a StringTable subsection and Checksums subsection were - // present, now is the time to handle them. - if (!CVStrTab.valid()) { - if (Checksums.valid()) - fatal(".debug$S sections with a checksums subsection must also contain a " - "string table subsection"); - - if (!StringTableReferences.empty()) - warn("No StringTable subsection was encountered, but there are string " - "table references"); - return; - } - - // Rewrite string table indices in the Fpo Data and symbol records to refer to - // the global PDB string table instead of the object file string table. - for (DebugFrameDataSubsectionRef &FDS : FpoFrames) { - const uint32_t *Reloc = FDS.getRelocPtr(); - for (codeview::FrameData FD : FDS) { - FD.RvaStart += *Reloc; - FD.FrameFunc = - translateStringTableIndex(FD.FrameFunc, CVStrTab, PDBStrTab); - DbiBuilder.addFrameData(FD); + if (DebugChunk->getSectionName() == ".debug$F") { + ArrayRef RelocatedDebugContents = + relocateDebugChunk(Alloc, *DebugChunk); + + FixedStreamArray FpoRecords; + BinaryStreamReader Reader(RelocatedDebugContents, support::little); + uint32_t Count = RelocatedDebugContents.size() / sizeof(object::FpoData); + ExitOnErr(Reader.readArray(FpoRecords, Count)); + + // These are already relocated and don't refer to the string table, so we + // can just copy it. + for (const object::FpoData &FD : FpoRecords) + DbiBuilder.addOldFpoData(FD); + continue; } } - for (ulittle32_t *Ref : StringTableReferences) - *Ref = translateStringTableIndex(*Ref, CVStrTab, PDBStrTab); - - // Make a new file checksum table that refers to offsets in the PDB-wide - // string table. Generally the string table subsection appears after the - // checksum table, so we have to do this after looping over all the - // subsections. - auto NewChecksums = make_unique(PDBStrTab); - for (FileChecksumEntry &FC : Checksums) { - SmallString<128> FileName = ExitOnErr(CVStrTab.getString(FC.FileNameOffset)); - if (!sys::path::is_absolute(FileName) && - !Config->PDBSourcePath.empty()) { - SmallString<128> AbsoluteFileName = Config->PDBSourcePath; - sys::path::append(AbsoluteFileName, FileName); - sys::path::native(AbsoluteFileName); - sys::path::remove_dots(AbsoluteFileName, /*remove_dot_dots=*/true); - FileName = std::move(AbsoluteFileName); - } - ExitOnErr(Builder.getDbiBuilder().addModuleSourceFile(*File->ModuleDBI, - FileName)); - NewChecksums->addChecksum(FileName, FC.Kind, FC.Checksum); - } - File->ModuleDBI->addDebugSubsection(std::move(NewChecksums)); + // Do any post-processing now that all .debug$S sections have been processed. + DSH.finish(); } static PublicSym32 createPublic(Defined *Def) { PublicSym32 Pub(SymbolKind::S_PUB32); Pub.Name = Def->getName(); if (auto *D = dyn_cast(Def)) { if (D->getCOFFSymbol().isFunctionDefinition()) Pub.Flags = PublicSymFlags::Function; } else if (isa(Def)) { Pub.Flags = PublicSymFlags::Function; } OutputSection *OS = Def->getChunk()->getOutputSection(); assert(OS && "all publics should be in final image"); Pub.Offset = Def->getRVA() - OS->getRVA(); Pub.Segment = OS->SectionIndex; return Pub; } // Add all object files to the PDB. Merge .debug$T sections into IpiData and // TpiData. void PDBLinker::addObjectsToPDB() { ScopedTimer T1(AddObjectsTimer); for (ObjFile *File : ObjFile::Instances) addObjFile(File); Builder.getStringTableBuilder().setStrings(PDBStrTab); T1.stop(); // Construct TPI and IPI stream contents. ScopedTimer T2(TpiStreamLayoutTimer); if (Config->DebugGHashes) { addTypeInfo(Builder.getTpiBuilder(), GlobalTypeTable); addTypeInfo(Builder.getIpiBuilder(), GlobalIDTable); } else { addTypeInfo(Builder.getTpiBuilder(), TypeTable); addTypeInfo(Builder.getIpiBuilder(), IDTable); } T2.stop(); ScopedTimer T3(GlobalsLayoutTimer); // Compute the public and global symbols. auto &GsiBuilder = Builder.getGsiBuilder(); std::vector Publics; Symtab->forEachSymbol([&Publics](Symbol *S) { // Only emit defined, live symbols that have a chunk. auto *Def = dyn_cast(S); if (Def && Def->isLive() && Def->getChunk()) Publics.push_back(createPublic(Def)); }); if (!Publics.empty()) { // Sort the public symbols and add them to the stream. std::sort(Publics.begin(), Publics.end(), [](const PublicSym32 &L, const PublicSym32 &R) { return L.Name < R.Name; }); for (const PublicSym32 &Pub : Publics) GsiBuilder.addPublicSymbol(Pub); } } void PDBLinker::addNatvisFiles() { for (StringRef File : Config->NatvisFiles) { ErrorOr> DataOrErr = MemoryBuffer::getFile(File); if (!DataOrErr) { warn("Cannot open input file: " + File); continue; } Builder.addInjectedSource(File, std::move(*DataOrErr)); } } static codeview::CPUType toCodeViewMachine(COFF::MachineTypes Machine) { switch (Machine) { case COFF::IMAGE_FILE_MACHINE_AMD64: return codeview::CPUType::X64; case COFF::IMAGE_FILE_MACHINE_ARM: return codeview::CPUType::ARM7; case COFF::IMAGE_FILE_MACHINE_ARM64: return codeview::CPUType::ARM64; case COFF::IMAGE_FILE_MACHINE_ARMNT: return codeview::CPUType::ARMNT; case COFF::IMAGE_FILE_MACHINE_I386: return codeview::CPUType::Intel80386; default: llvm_unreachable("Unsupported CPU Type"); } } static void addCommonLinkerModuleSymbols(StringRef Path, pdb::DbiModuleDescriptorBuilder &Mod, BumpPtrAllocator &Allocator) { ObjNameSym ONS(SymbolRecordKind::ObjNameSym); Compile3Sym CS(SymbolRecordKind::Compile3Sym); EnvBlockSym EBS(SymbolRecordKind::EnvBlockSym); ONS.Name = "* Linker *"; ONS.Signature = 0; CS.Machine = toCodeViewMachine(Config->Machine); // Interestingly, if we set the string to 0.0.0.0, then when trying to view // local variables WinDbg emits an error that private symbols are not present. // By setting this to a valid MSVC linker version string, local variables are // displayed properly. As such, even though it is not representative of // LLVM's version information, we need this for compatibility. CS.Flags = CompileSym3Flags::None; CS.VersionBackendBuild = 25019; CS.VersionBackendMajor = 14; CS.VersionBackendMinor = 10; CS.VersionBackendQFE = 0; // MSVC also sets the frontend to 0.0.0.0 since this is specifically for the // linker module (which is by definition a backend), so we don't need to do // anything here. Also, it seems we can use "LLVM Linker" for the linker name // without any problems. Only the backend version has to be hardcoded to a // magic number. CS.VersionFrontendBuild = 0; CS.VersionFrontendMajor = 0; CS.VersionFrontendMinor = 0; CS.VersionFrontendQFE = 0; CS.Version = "LLVM Linker"; CS.setLanguage(SourceLanguage::Link); ArrayRef Args = makeArrayRef(Config->Argv).drop_front(); std::string ArgStr = llvm::join(Args, " "); EBS.Fields.push_back("cwd"); SmallString<64> cwd; sys::fs::current_path(cwd); EBS.Fields.push_back(cwd); EBS.Fields.push_back("exe"); SmallString<64> exe = Config->Argv[0]; llvm::sys::fs::make_absolute(exe); EBS.Fields.push_back(exe); EBS.Fields.push_back("pdb"); EBS.Fields.push_back(Path); EBS.Fields.push_back("cmd"); EBS.Fields.push_back(ArgStr); Mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( ONS, Allocator, CodeViewContainer::Pdb)); Mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( CS, Allocator, CodeViewContainer::Pdb)); Mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( EBS, Allocator, CodeViewContainer::Pdb)); } static void addLinkerModuleSectionSymbol(pdb::DbiModuleDescriptorBuilder &Mod, OutputSection &OS, BumpPtrAllocator &Allocator) { SectionSym Sym(SymbolRecordKind::SectionSym); Sym.Alignment = 12; // 2^12 = 4KB Sym.Characteristics = OS.Header.Characteristics; Sym.Length = OS.getVirtualSize(); Sym.Name = OS.Name; Sym.Rva = OS.getRVA(); Sym.SectionNumber = OS.SectionIndex; Mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( Sym, Allocator, CodeViewContainer::Pdb)); } // Creates a PDB file. void coff::createPDB(SymbolTable *Symtab, ArrayRef OutputSections, ArrayRef SectionTable, const llvm::codeview::DebugInfo &BuildId) { ScopedTimer T1(TotalPdbLinkTimer); PDBLinker PDB(Symtab); PDB.initialize(BuildId); PDB.addObjectsToPDB(); PDB.addSections(OutputSections, SectionTable); PDB.addNatvisFiles(); ScopedTimer T2(DiskCommitTimer); PDB.commit(); } void PDBLinker::initialize(const llvm::codeview::DebugInfo &BuildId) { ExitOnErr(Builder.initialize(4096)); // 4096 is blocksize // Create streams in MSF for predefined streams, namely // PDB, TPI, DBI and IPI. for (int I = 0; I < (int)pdb::kSpecialStreamCount; ++I) ExitOnErr(Builder.getMsfBuilder().addStream(0)); // Add an Info stream. auto &InfoBuilder = Builder.getInfoBuilder(); GUID uuid; memcpy(&uuid, &BuildId.PDB70.Signature, sizeof(uuid)); InfoBuilder.setAge(BuildId.PDB70.Age); InfoBuilder.setGuid(uuid); InfoBuilder.setVersion(pdb::PdbRaw_ImplVer::PdbImplVC70); // Add an empty DBI stream. pdb::DbiStreamBuilder &DbiBuilder = Builder.getDbiBuilder(); DbiBuilder.setAge(BuildId.PDB70.Age); DbiBuilder.setVersionHeader(pdb::PdbDbiV70); DbiBuilder.setMachineType(Config->Machine); // Technically we are not link.exe 14.11, but there are known cases where // debugging tools on Windows expect Microsoft-specific version numbers or // they fail to work at all. Since we know we produce PDBs that are // compatible with LINK 14.11, we set that version number here. DbiBuilder.setBuildNumber(14, 11); } void PDBLinker::addSections(ArrayRef OutputSections, ArrayRef SectionTable) { // It's not entirely clear what this is, but the * Linker * module uses it. pdb::DbiStreamBuilder &DbiBuilder = Builder.getDbiBuilder(); NativePath = Config->PDBPath; sys::fs::make_absolute(NativePath); sys::path::native(NativePath, sys::path::Style::windows); uint32_t PdbFilePathNI = DbiBuilder.addECName(NativePath); auto &LinkerModule = ExitOnErr(DbiBuilder.addModuleInfo("* Linker *")); LinkerModule.setPdbFilePathNI(PdbFilePathNI); addCommonLinkerModuleSymbols(NativePath, LinkerModule, Alloc); // Add section contributions. They must be ordered by ascending RVA. for (OutputSection *OS : OutputSections) { addLinkerModuleSectionSymbol(LinkerModule, *OS, Alloc); for (Chunk *C : OS->getChunks()) { pdb::SectionContrib SC = createSectionContrib(C, LinkerModule.getModuleIndex()); Builder.getDbiBuilder().addSectionContrib(SC); } } // Add Section Map stream. ArrayRef Sections = { (const object::coff_section *)SectionTable.data(), SectionTable.size() / sizeof(object::coff_section)}; SectionMap = pdb::DbiStreamBuilder::createSectionMap(Sections); DbiBuilder.setSectionMap(SectionMap); // Add COFF section header stream. ExitOnErr( DbiBuilder.addDbgStream(pdb::DbgHeaderType::SectionHdr, SectionTable)); } void PDBLinker::commit() { // Write to a file. ExitOnErr(Builder.commit(Config->PDBPath)); } static Expected getFileName(const DebugStringTableSubsectionRef &Strings, const DebugChecksumsSubsectionRef &Checksums, uint32_t FileID) { auto Iter = Checksums.getArray().at(FileID); if (Iter == Checksums.getArray().end()) return make_error(cv_error_code::no_records); uint32_t Offset = Iter->FileNameOffset; return Strings.getString(Offset); } static uint32_t getSecrelReloc() { switch (Config->Machine) { case AMD64: return COFF::IMAGE_REL_AMD64_SECREL; case I386: return COFF::IMAGE_REL_I386_SECREL; case ARMNT: return COFF::IMAGE_REL_ARM_SECREL; case ARM64: return COFF::IMAGE_REL_ARM64_SECREL; default: llvm_unreachable("unknown machine type"); } } // Try to find a line table for the given offset Addr into the given chunk C. // If a line table was found, the line table, the string and checksum tables // that are used to interpret the line table, and the offset of Addr in the line // table are stored in the output arguments. Returns whether a line table was // found. static bool findLineTable(const SectionChunk *C, uint32_t Addr, DebugStringTableSubsectionRef &CVStrTab, DebugChecksumsSubsectionRef &Checksums, DebugLinesSubsectionRef &Lines, uint32_t &OffsetInLinetable) { ExitOnError ExitOnErr; uint32_t SecrelReloc = getSecrelReloc(); for (SectionChunk *DbgC : C->File->getDebugChunks()) { if (DbgC->getSectionName() != ".debug$S") continue; // Build a mapping of SECREL relocations in DbgC that refer to C. DenseMap Secrels; for (const coff_relocation &R : DbgC->Relocs) { if (R.Type != SecrelReloc) continue; if (auto *S = dyn_cast_or_null( C->File->getSymbols()[R.SymbolTableIndex])) if (S->getChunk() == C) Secrels[R.VirtualAddress] = S->getValue(); } ArrayRef Contents = consumeDebugMagic(DbgC->getContents(), ".debug$S"); DebugSubsectionArray Subsections; BinaryStreamReader Reader(Contents, support::little); ExitOnErr(Reader.readArray(Subsections, Contents.size())); for (const DebugSubsectionRecord &SS : Subsections) { switch (SS.kind()) { case DebugSubsectionKind::StringTable: { assert(!CVStrTab.valid() && "Encountered multiple string table subsections!"); ExitOnErr(CVStrTab.initialize(SS.getRecordData())); break; } case DebugSubsectionKind::FileChecksums: assert(!Checksums.valid() && "Encountered multiple checksum subsections!"); ExitOnErr(Checksums.initialize(SS.getRecordData())); break; case DebugSubsectionKind::Lines: { ArrayRef Bytes; auto Ref = SS.getRecordData(); ExitOnErr(Ref.readLongestContiguousChunk(0, Bytes)); size_t OffsetInDbgC = Bytes.data() - DbgC->getContents().data(); // Check whether this line table refers to C. auto I = Secrels.find(OffsetInDbgC); if (I == Secrels.end()) break; // Check whether this line table covers Addr in C. DebugLinesSubsectionRef LinesTmp; ExitOnErr(LinesTmp.initialize(BinaryStreamReader(Ref))); uint32_t OffsetInC = I->second + LinesTmp.header()->RelocOffset; if (Addr < OffsetInC || Addr >= OffsetInC + LinesTmp.header()->CodeSize) break; assert(!Lines.header() && "Encountered multiple line tables for function!"); ExitOnErr(Lines.initialize(BinaryStreamReader(Ref))); OffsetInLinetable = Addr - OffsetInC; break; } default: break; } if (CVStrTab.valid() && Checksums.valid() && Lines.header()) return true; } } return false; } // Use CodeView line tables to resolve a file and line number for the given // offset into the given chunk and return them, or {"", 0} if a line table was // not found. std::pair coff::getFileLine(const SectionChunk *C, uint32_t Addr) { ExitOnError ExitOnErr; DebugStringTableSubsectionRef CVStrTab; DebugChecksumsSubsectionRef Checksums; DebugLinesSubsectionRef Lines; uint32_t OffsetInLinetable; if (!findLineTable(C, Addr, CVStrTab, Checksums, Lines, OffsetInLinetable)) return {"", 0}; uint32_t NameIndex; uint32_t LineNumber; for (LineColumnEntry &Entry : Lines) { for (const LineNumberEntry &LN : Entry.LineNumbers) { if (LN.Offset > OffsetInLinetable) { StringRef Filename = ExitOnErr(getFileName(CVStrTab, Checksums, NameIndex)); return {Filename, LineNumber}; } LineInfo LI(LN.Flags); NameIndex = Entry.NameIndex; LineNumber = LI.getStartLine(); } } StringRef Filename = ExitOnErr(getFileName(CVStrTab, Checksums, NameIndex)); return {Filename, LineNumber}; } Index: lld/trunk/test/COFF/pdb-debug-f.s =================================================================== --- lld/trunk/test/COFF/pdb-debug-f.s (revision 0) +++ lld/trunk/test/COFF/pdb-debug-f.s (revision 342080) @@ -0,0 +1,27 @@ +# RUN: llvm-mc -triple=i386-pc-win32 -filetype=obj -o %t.obj %s +# RUN: lld-link /subsystem:console /debug /nodefaultlib /entry:foo /out:%t.exe /pdb:%t.pdb %t.obj +# RUN: llvm-pdbutil dump -fpo %t.pdb | FileCheck %s + +# CHECK: Old FPO Data +# CHECK-NEXT: ============================================================ +# CHECK-NEXT: RVA | Code | Locals | Params | Prolog | Saved Regs | Use BP | Has SEH | Frame Type +# CHECK-NEXT: 00001002 | 1 | 2 | 3 | 4 | 0 | false | false | FPO + +.text +_foo: +ret + +.global _foo + +.section .debug$F,"dr" + .long _foo@IMGREL+2 + .long 1 # cbProc + .long 2 # cdwLocals; + .short 3 # cdwParams; + .short 4 # flags + # cbProlog : 8; + # cbRegs : 3; + # fHasSEH : 1; + # fUseBP : 1; + # reserved : 1; + # cbFrame : 2; \ No newline at end of file