diff --git a/lld/COFF/PDB.cpp b/lld/COFF/PDB.cpp index 091a887e0ccf..f2d41308c1b7 100644 --- a/lld/COFF/PDB.cpp +++ b/lld/COFF/PDB.cpp @@ -29,12 +29,14 @@ #include "llvm/DebugInfo/PDB/Native/InfoStreamBuilder.h" #include "llvm/DebugInfo/PDB/Native/PDBFile.h" #include "llvm/DebugInfo/PDB/Native/PDBFileBuilder.h" +#include "llvm/DebugInfo/PDB/Native/PDBTypeServerHandler.h" #include "llvm/DebugInfo/PDB/Native/StringTableBuilder.h" #include "llvm/DebugInfo/PDB/Native/TpiStream.h" #include "llvm/DebugInfo/PDB/Native/TpiStreamBuilder.h" #include "llvm/Object/COFF.h" #include "llvm/Support/Endian.h" #include "llvm/Support/FileOutputBuffer.h" +#include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" #include @@ -94,9 +96,15 @@ static std::vector mergeDebugT(SymbolTable *Symtab) { msf::ByteStream Stream(Data); codeview::CVTypeArray Types; msf::StreamReader Reader(Stream); + // Follow type servers. If the same type server is encountered more than + // once for this instance of `PDBTypeServerHandler` (for example if many + // object files reference the same TypeServer), the types from the + // TypeServer will only be visited once. + pdb::PDBTypeServerHandler Handler; + Handler.addSearchPath(llvm::sys::path::parent_path(File->getName())); if (auto EC = Reader.readArray(Types, Reader.getLength())) fatal(EC, "Reader::readArray failed"); - if (auto Err = codeview::mergeTypeStreams(Builder, Types)) + if (auto Err = codeview::mergeTypeStreams(Builder, &Handler, Types)) fatal(Err, "codeview::mergeTypeStreams failed"); } @@ -116,6 +124,8 @@ static void dumpDebugT(ScopedPrinter &W, ObjectFile *File) { TypeDatabase TDB; TypeDumpVisitor TDV(TDB, &W, false); + // Use a default implementation that does not follow type servers and instead + // just dumps the contents of the TypeServer2 record. CVTypeDumper TypeDumper(TDB); if (auto EC = TypeDumper.dump(Data, TDV)) fatal(EC, "CVTypeDumper::dump failed"); diff --git a/llvm/include/llvm/DebugInfo/CodeView/CVTypeDumper.h b/llvm/include/llvm/DebugInfo/CodeView/CVTypeDumper.h index e1dd6a10b5a1..02f14ea2107b 100644 --- a/llvm/include/llvm/DebugInfo/CodeView/CVTypeDumper.h +++ b/llvm/include/llvm/DebugInfo/CodeView/CVTypeDumper.h @@ -22,10 +22,14 @@ namespace llvm { namespace codeview { +class TypeServerHandler; + /// Dumper for CodeView type streams found in COFF object files and PDB files. class CVTypeDumper { public: - explicit CVTypeDumper(TypeDatabase &TypeDB) : TypeDB(TypeDB) {} + explicit CVTypeDumper(TypeDatabase &TypeDB, + TypeServerHandler *Handler = nullptr) + : TypeDB(TypeDB), Handler(Handler) {} /// Dumps one type record. Returns false if there was a type parsing error, /// and true otherwise. This should be called in order, since the dumper @@ -48,6 +52,7 @@ public: private: TypeDatabase &TypeDB; + TypeServerHandler *Handler; }; } // end namespace codeview diff --git a/llvm/include/llvm/DebugInfo/CodeView/CVTypeVisitor.h b/llvm/include/llvm/DebugInfo/CodeView/CVTypeVisitor.h index d1b0363a4133..ab4b0cbaf969 100644 --- a/llvm/include/llvm/DebugInfo/CodeView/CVTypeVisitor.h +++ b/llvm/include/llvm/DebugInfo/CodeView/CVTypeVisitor.h @@ -10,9 +10,10 @@ #ifndef LLVM_DEBUGINFO_CODEVIEW_CVTYPEVISITOR_H #define LLVM_DEBUGINFO_CODEVIEW_CVTYPEVISITOR_H -#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/TinyPtrVector.h" #include "llvm/DebugInfo/CodeView/CVRecord.h" #include "llvm/DebugInfo/CodeView/TypeRecord.h" +#include "llvm/DebugInfo/CodeView/TypeServerHandler.h" #include "llvm/DebugInfo/CodeView/TypeVisitorCallbacks.h" #include "llvm/Support/Error.h" @@ -23,11 +24,14 @@ class CVTypeVisitor { public: explicit CVTypeVisitor(TypeVisitorCallbacks &Callbacks); + void addTypeServerHandler(TypeServerHandler &Handler); + Error visitTypeRecord(CVType &Record); Error visitMemberRecord(CVMemberRecord &Record); /// Visits the type records in Data. Sets the error flag on parse failures. Error visitTypeStream(const CVTypeArray &Types); + Error visitTypeStream(CVTypeRange Types); Error visitFieldListMemberStream(ArrayRef FieldList); Error visitFieldListMemberStream(msf::StreamReader Reader); @@ -35,6 +39,8 @@ public: private: /// The interface to the class that gets notified of each visitation. TypeVisitorCallbacks &Callbacks; + + TinyPtrVector Handlers; }; } // end namespace codeview diff --git a/llvm/include/llvm/DebugInfo/CodeView/CodeViewError.h b/llvm/include/llvm/DebugInfo/CodeView/CodeViewError.h index 0556fd0e19f2..586a720ce6e4 100644 --- a/llvm/include/llvm/DebugInfo/CodeView/CodeViewError.h +++ b/llvm/include/llvm/DebugInfo/CodeView/CodeViewError.h @@ -21,6 +21,7 @@ enum class cv_error_code { insufficient_buffer, operation_unsupported, corrupt_record, + no_records, unknown_member_record, }; diff --git a/llvm/include/llvm/DebugInfo/CodeView/TypeRecord.h b/llvm/include/llvm/DebugInfo/CodeView/TypeRecord.h index 4f1c047815d2..9af70fd1bf20 100644 --- a/llvm/include/llvm/DebugInfo/CodeView/TypeRecord.h +++ b/llvm/include/llvm/DebugInfo/CodeView/TypeRecord.h @@ -43,6 +43,7 @@ struct CVMemberRecord { ArrayRef Data; }; typedef msf::VarStreamArray CVTypeArray; +typedef iterator_range CVTypeRange; /// Equvalent to CV_fldattr_t in cvinfo.h. struct MemberAttributes { diff --git a/llvm/include/llvm/DebugInfo/CodeView/TypeServerHandler.h b/llvm/include/llvm/DebugInfo/CodeView/TypeServerHandler.h new file mode 100644 index 000000000000..35f06eaf6eb4 --- /dev/null +++ b/llvm/include/llvm/DebugInfo/CodeView/TypeServerHandler.h @@ -0,0 +1,36 @@ +//===- TypeServerHandler.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_DEBUGINFO_CODEVIEW_TYPESERVERHANDLER_H +#define LLVM_DEBUGINFO_CODEVIEW_TYPESERVERHANDLER_H + +#include "llvm/DebugInfo/CodeView/TypeRecord.h" +#include "llvm/Support/Error.h" + +namespace llvm { +namespace codeview { +class TypeVisitorCallbacks; + +class TypeServerHandler { +public: + virtual ~TypeServerHandler() {} + + /// Handle a TypeServer record. If the implementation returns true + /// the record will not be processed by the top-level visitor. If + /// it returns false, it will be processed. If it returns an Error, + /// then the top-level visitor will fail. + virtual Expected handle(TypeServer2Record &TS, + TypeVisitorCallbacks &Callbacks) { + return false; + } +}; +} +} + +#endif diff --git a/llvm/include/llvm/DebugInfo/CodeView/TypeStreamMerger.h b/llvm/include/llvm/DebugInfo/CodeView/TypeStreamMerger.h index 3c915af16572..a06b5b7ceed5 100644 --- a/llvm/include/llvm/DebugInfo/CodeView/TypeStreamMerger.h +++ b/llvm/include/llvm/DebugInfo/CodeView/TypeStreamMerger.h @@ -18,8 +18,11 @@ namespace llvm { namespace codeview { +class TypeServerHandler; + /// Merges one type stream into another. Returns true on success. -Error mergeTypeStreams(TypeTableBuilder &DestStream, const CVTypeArray &Types); +Error mergeTypeStreams(TypeTableBuilder &DestStream, TypeServerHandler *Handler, + const CVTypeArray &Types); } // end namespace codeview } // end namespace llvm diff --git a/llvm/include/llvm/DebugInfo/PDB/Native/PDBFile.h b/llvm/include/llvm/DebugInfo/PDB/Native/PDBFile.h index 9fb0ccc87f1a..ba73424c4859 100644 --- a/llvm/include/llvm/DebugInfo/PDB/Native/PDBFile.h +++ b/llvm/include/llvm/DebugInfo/PDB/Native/PDBFile.h @@ -42,10 +42,13 @@ class PDBFile : public msf::IMSFFile { friend PDBFileBuilder; public: - PDBFile(std::unique_ptr PdbFileBuffer, + PDBFile(StringRef Path, std::unique_ptr PdbFileBuffer, BumpPtrAllocator &Allocator); ~PDBFile() override; + StringRef getFileDirectory() const; + StringRef getFilePath() const; + uint32_t getFreeBlockMapBlock() const; uint32_t getUnknown1() const; @@ -110,6 +113,7 @@ private: const msf::ReadableStream &MsfData, uint32_t StreamIndex) const; + std::string FilePath; BumpPtrAllocator &Allocator; std::unique_ptr Buffer; diff --git a/llvm/include/llvm/DebugInfo/PDB/Native/PDBTypeServerHandler.h b/llvm/include/llvm/DebugInfo/PDB/Native/PDBTypeServerHandler.h new file mode 100644 index 000000000000..d965e1008e95 --- /dev/null +++ b/llvm/include/llvm/DebugInfo/PDB/Native/PDBTypeServerHandler.h @@ -0,0 +1,48 @@ +//===- PDBTypeServerHandler.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_DEBUGINFO_PDB_PDBTYPESERVERHANDLER_H +#define LLVM_DEBUGINFO_PDB_PDBTYPESERVERHANDLER_H + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h" +#include "llvm/DebugInfo/CodeView/TypeRecord.h" +#include "llvm/DebugInfo/CodeView/TypeServerHandler.h" +#include "llvm/DebugInfo/PDB/Native/NativeSession.h" +#include "llvm/DebugInfo/PDB/PDBTypes.h" + +#include +#include + +namespace llvm { +namespace pdb { +class NativeSession; + +class PDBTypeServerHandler : public codeview::TypeServerHandler { +public: + PDBTypeServerHandler(bool RevisitAlways = false); + + void addSearchPath(StringRef Path); + Expected handle(codeview::TypeServer2Record &TS, + codeview::TypeVisitorCallbacks &Callbacks) override; + +private: + Expected handleInternal(PDBFile &File, + codeview::TypeVisitorCallbacks &Callbacks); + + bool RevisitAlways; + std::unique_ptr Session; + SmallVector, 4> SearchPaths; +}; +} +} + +#endif diff --git a/llvm/include/llvm/DebugInfo/PDB/Native/TpiStream.h b/llvm/include/llvm/DebugInfo/PDB/Native/TpiStream.h index b9368a7ef25a..ca6780150729 100644 --- a/llvm/include/llvm/DebugInfo/PDB/Native/TpiStream.h +++ b/llvm/include/llvm/DebugInfo/PDB/Native/TpiStream.h @@ -50,7 +50,7 @@ public: msf::FixedStreamArray getTypeIndexOffsets() const; HashTable &getHashAdjusters(); - iterator_range types(bool *HadError) const; + codeview::CVTypeRange types(bool *HadError) const; Error commit(); diff --git a/llvm/lib/DebugInfo/CodeView/CVTypeDumper.cpp b/llvm/lib/DebugInfo/CodeView/CVTypeDumper.cpp index fcd239cce0dd..c7cd58a10e61 100644 --- a/llvm/lib/DebugInfo/CodeView/CVTypeDumper.cpp +++ b/llvm/lib/DebugInfo/CodeView/CVTypeDumper.cpp @@ -28,6 +28,8 @@ Error CVTypeDumper::dump(const CVType &Record, TypeVisitorCallbacks &Dumper) { Pipeline.addCallbackToPipeline(Dumper); CVTypeVisitor Visitor(Pipeline); + if (Handler) + Visitor.addTypeServerHandler(*Handler); CVType RecordCopy = Record; if (auto EC = Visitor.visitTypeRecord(RecordCopy)) @@ -45,6 +47,8 @@ Error CVTypeDumper::dump(const CVTypeArray &Types, Pipeline.addCallbackToPipeline(Dumper); CVTypeVisitor Visitor(Pipeline); + if (Handler) + Visitor.addTypeServerHandler(*Handler); if (auto EC = Visitor.visitTypeStream(Types)) return EC; diff --git a/llvm/lib/DebugInfo/CodeView/CVTypeVisitor.cpp b/llvm/lib/DebugInfo/CodeView/CVTypeVisitor.cpp index 5171e24f3aac..48160252c6db 100644 --- a/llvm/lib/DebugInfo/CodeView/CVTypeVisitor.cpp +++ b/llvm/lib/DebugInfo/CodeView/CVTypeVisitor.cpp @@ -10,9 +10,14 @@ #include "llvm/DebugInfo/CodeView/CVTypeVisitor.h" #include "llvm/DebugInfo/CodeView/CodeViewError.h" +#include "llvm/DebugInfo/CodeView/TypeDatabase.h" +#include "llvm/DebugInfo/CodeView/TypeDatabaseVisitor.h" #include "llvm/DebugInfo/CodeView/TypeDeserializer.h" +#include "llvm/DebugInfo/CodeView/TypeRecordMapping.h" +#include "llvm/DebugInfo/CodeView/TypeServerHandler.h" #include "llvm/DebugInfo/CodeView/TypeVisitorCallbackPipeline.h" #include "llvm/DebugInfo/MSF/ByteStream.h" +#include "llvm/DebugInfo/MSF/StreamReader.h" using namespace llvm; using namespace llvm::codeview; @@ -21,7 +26,8 @@ CVTypeVisitor::CVTypeVisitor(TypeVisitorCallbacks &Callbacks) : Callbacks(Callbacks) {} template -static Error visitKnownRecord(CVType &Record, TypeVisitorCallbacks &Callbacks) { +static Error visitKnownRecord(CVTypeVisitor &Visitor, CVType &Record, + TypeVisitorCallbacks &Callbacks) { TypeRecordKind RK = static_cast(Record.Type); T KnownRecord(RK); if (auto EC = Callbacks.visitKnownRecord(Record, KnownRecord)) @@ -39,7 +45,58 @@ static Error visitKnownMember(CVMemberRecord &Record, return Error::success(); } +static Expected deserializeTypeServerRecord(CVType &Record) { + class StealTypeServerVisitor : public TypeVisitorCallbacks { + public: + explicit StealTypeServerVisitor(TypeServer2Record &TR) : TR(TR) {} + + Error visitKnownRecord(CVType &CVR, TypeServer2Record &Record) override { + TR = Record; + return Error::success(); + } + + private: + TypeServer2Record &TR; + }; + + TypeServer2Record R(TypeRecordKind::TypeServer2); + TypeDeserializer Deserializer; + StealTypeServerVisitor Thief(R); + TypeVisitorCallbackPipeline Pipeline; + Pipeline.addCallbackToPipeline(Deserializer); + Pipeline.addCallbackToPipeline(Thief); + CVTypeVisitor Visitor(Pipeline); + if (auto EC = Visitor.visitTypeRecord(Record)) + return std::move(EC); + + return R; +} + +void CVTypeVisitor::addTypeServerHandler(TypeServerHandler &Handler) { + Handlers.push_back(&Handler); +} + Error CVTypeVisitor::visitTypeRecord(CVType &Record) { + if (Record.Type == TypeLeafKind::LF_TYPESERVER2 && !Handlers.empty()) { + auto TS = deserializeTypeServerRecord(Record); + if (!TS) + return TS.takeError(); + + for (auto Handler : Handlers) { + auto ExpectedResult = Handler->handle(*TS, Callbacks); + // If there was an error, return the error. + if (!ExpectedResult) + return ExpectedResult.takeError(); + + // If the handler processed the record, return success. + if (*ExpectedResult) + return Error::success(); + + // Otherwise keep searching for a handler, eventually falling out and + // using the default record handler. + } + } + if (auto EC = Callbacks.visitTypeBegin(Record)) return EC; @@ -50,7 +107,7 @@ Error CVTypeVisitor::visitTypeRecord(CVType &Record) { break; #define TYPE_RECORD(EnumName, EnumVal, Name) \ case EnumName: { \ - if (auto EC = visitKnownRecord(Record, Callbacks)) \ + if (auto EC = visitKnownRecord(*this, Record, Callbacks)) \ return EC; \ break; \ } @@ -109,6 +166,14 @@ Error CVTypeVisitor::visitTypeStream(const CVTypeArray &Types) { return Error::success(); } +Error CVTypeVisitor::visitTypeStream(CVTypeRange Types) { + for (auto I : Types) { + if (auto EC = visitTypeRecord(I)) + return EC; + } + return Error::success(); +} + Error CVTypeVisitor::visitFieldListMemberStream(msf::StreamReader Reader) { FieldListDeserializer Deserializer(Reader); TypeVisitorCallbackPipeline Pipeline; diff --git a/llvm/lib/DebugInfo/CodeView/CodeViewError.cpp b/llvm/lib/DebugInfo/CodeView/CodeViewError.cpp index 55c10c076eef..8de266b836b4 100644 --- a/llvm/lib/DebugInfo/CodeView/CodeViewError.cpp +++ b/llvm/lib/DebugInfo/CodeView/CodeViewError.cpp @@ -31,6 +31,8 @@ public: "bytes."; case cv_error_code::corrupt_record: return "The CodeView record is corrupted."; + case cv_error_code::no_records: + return "There are no records"; case cv_error_code::operation_unsupported: return "The requested operation is not supported."; case cv_error_code::unknown_member_record: diff --git a/llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp b/llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp index 07984ad2fe3e..85104d25cb4b 100644 --- a/llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp +++ b/llvm/lib/DebugInfo/CodeView/TypeStreamMerger.cpp @@ -54,8 +54,9 @@ namespace { /// existing destination type index. class TypeStreamMerger : public TypeVisitorCallbacks { public: - TypeStreamMerger(TypeTableBuilder &DestStream) - : DestStream(DestStream), FieldListBuilder(DestStream) {} + TypeStreamMerger(TypeTableBuilder &DestStream, TypeServerHandler *Handler) + : DestStream(DestStream), FieldListBuilder(DestStream), Handler(Handler) { + } /// TypeVisitorCallbacks overrides. #define TYPE_RECORD(EnumName, EnumVal, Name) \ @@ -109,6 +110,7 @@ private: TypeTableBuilder &DestStream; FieldListRecordBuilder FieldListBuilder; + TypeServerHandler *Handler; bool IsInFieldList{false}; size_t BeginIndexMapSize = 0; @@ -175,6 +177,8 @@ Error TypeStreamMerger::mergeStream(const CVTypeArray &Types) { Pipeline.addCallbackToPipeline(*this); CVTypeVisitor Visitor(Pipeline); + if (Handler) + Visitor.addTypeServerHandler(*Handler); if (auto EC = Visitor.visitTypeStream(Types)) return EC; @@ -186,6 +190,7 @@ Error TypeStreamMerger::mergeStream(const CVTypeArray &Types) { } Error llvm::codeview::mergeTypeStreams(TypeTableBuilder &DestStream, + TypeServerHandler *Handler, const CVTypeArray &Types) { - return TypeStreamMerger(DestStream).mergeStream(Types); + return TypeStreamMerger(DestStream, Handler).mergeStream(Types); } diff --git a/llvm/lib/DebugInfo/PDB/CMakeLists.txt b/llvm/lib/DebugInfo/PDB/CMakeLists.txt index 774edcd4011a..4d4eb6ceaf13 100644 --- a/llvm/lib/DebugInfo/PDB/CMakeLists.txt +++ b/llvm/lib/DebugInfo/PDB/CMakeLists.txt @@ -44,6 +44,7 @@ add_pdb_impl_folder(Native Native/NativeSession.cpp Native/PDBFile.cpp Native/PDBFileBuilder.cpp + Native/PDBTypeServerHandler.cpp Native/PublicsStream.cpp Native/RawError.cpp Native/StringTable.cpp diff --git a/llvm/lib/DebugInfo/PDB/Native/NativeSession.cpp b/llvm/lib/DebugInfo/PDB/Native/NativeSession.cpp index e2c233175115..cafaa9bffa33 100644 --- a/llvm/lib/DebugInfo/PDB/Native/NativeSession.cpp +++ b/llvm/lib/DebugInfo/PDB/Native/NativeSession.cpp @@ -47,7 +47,7 @@ Error NativeSession::createFromPdb(StringRef Path, auto Stream = llvm::make_unique(std::move(Buffer)); auto Allocator = llvm::make_unique(); - auto File = llvm::make_unique(std::move(Stream), *Allocator); + auto File = llvm::make_unique(Path, std::move(Stream), *Allocator); if (auto EC = File->parseFileHeaders()) return EC; if (auto EC = File->parseStreamData()) diff --git a/llvm/lib/DebugInfo/PDB/Native/PDBFile.cpp b/llvm/lib/DebugInfo/PDB/Native/PDBFile.cpp index 02e883b84186..3a3692dcac5b 100644 --- a/llvm/lib/DebugInfo/PDB/Native/PDBFile.cpp +++ b/llvm/lib/DebugInfo/PDB/Native/PDBFile.cpp @@ -25,6 +25,7 @@ #include "llvm/DebugInfo/PDB/Native/TpiStream.h" #include "llvm/Support/Endian.h" #include "llvm/Support/Error.h" +#include "llvm/Support/Path.h" #include #include #include @@ -38,12 +39,18 @@ namespace { typedef FixedStreamArray ulittle_array; } // end anonymous namespace -PDBFile::PDBFile(std::unique_ptr PdbFileBuffer, +PDBFile::PDBFile(StringRef Path, std::unique_ptr PdbFileBuffer, BumpPtrAllocator &Allocator) - : Allocator(Allocator), Buffer(std::move(PdbFileBuffer)) {} + : FilePath(Path), Allocator(Allocator), Buffer(std::move(PdbFileBuffer)) {} PDBFile::~PDBFile() = default; +StringRef PDBFile::getFilePath() const { return FilePath; } + +StringRef PDBFile::getFileDirectory() const { + return sys::path::parent_path(FilePath); +} + uint32_t PDBFile::getBlockSize() const { return ContainerLayout.SB->BlockSize; } uint32_t PDBFile::getFreeBlockMapBlock() const { diff --git a/llvm/lib/DebugInfo/PDB/Native/PDBTypeServerHandler.cpp b/llvm/lib/DebugInfo/PDB/Native/PDBTypeServerHandler.cpp new file mode 100644 index 000000000000..629f3e80b0ed --- /dev/null +++ b/llvm/lib/DebugInfo/PDB/Native/PDBTypeServerHandler.cpp @@ -0,0 +1,119 @@ +//===- PDBTypeServerHandler.cpp ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Handles CodeView LF_TYPESERVER2 records by attempting to locate a matching +// PDB file, then loading the PDB file and visiting all types from the +// referenced PDB using the original supplied visitor. +// +// The net effect of this is that when visiting a PDB containing a TypeServer +// record, the TypeServer record is "replaced" with all of the records in +// the referenced PDB file. If a single instance of PDBTypeServerHandler +// encounters the same TypeServer multiple times (for example reusing one +// PDBTypeServerHandler across multiple visitations of distinct object files or +// PDB files), PDBTypeServerHandler will optionally revisit all the records +// again, or simply consume the record and do nothing. +//===----------------------------------------------------------------------===// + +#include "llvm/DebugInfo/PDB/Native/PDBTypeServerHandler.h" + +#include "llvm/DebugInfo/CodeView/CodeViewError.h" +#include "llvm/DebugInfo/PDB/GenericError.h" +#include "llvm/DebugInfo/PDB/Native/InfoStream.h" +#include "llvm/DebugInfo/PDB/Native/NativeSession.h" +#include "llvm/DebugInfo/PDB/Native/PDBFile.h" +#include "llvm/DebugInfo/PDB/Native/TpiStream.h" +#include "llvm/DebugInfo/PDB/PDB.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +using namespace llvm; +using namespace llvm::codeview; +using namespace llvm::pdb; + +static void ignoreErrors(Error EC) { + llvm::handleAllErrors(std::move(EC), [&](ErrorInfoBase &EIB) {}); +} + +PDBTypeServerHandler::PDBTypeServerHandler(bool RevisitAlways) + : RevisitAlways(RevisitAlways) {} + +void PDBTypeServerHandler::addSearchPath(StringRef Path) { + if (Path.empty() || !sys::fs::is_directory(Path)) + return; + + SearchPaths.push_back(Path); +} + +Expected +PDBTypeServerHandler::handleInternal(PDBFile &File, + TypeVisitorCallbacks &Callbacks) { + auto ExpectedTpi = File.getPDBTpiStream(); + if (!ExpectedTpi) + return ExpectedTpi.takeError(); + CVTypeVisitor Visitor(Callbacks); + + if (auto EC = Visitor.visitTypeStream(ExpectedTpi->types(nullptr))) + return std::move(EC); + + return true; +} + +Expected PDBTypeServerHandler::handle(TypeServer2Record &TS, + TypeVisitorCallbacks &Callbacks) { + if (Session) { + // If we've already handled this TypeServer and we only want to handle each + // TypeServer once, consume the record without doing anything. + if (!RevisitAlways) + return true; + + return handleInternal(Session->getPDBFile(), Callbacks); + } + + StringRef File = sys::path::filename(TS.Name); + if (File.empty()) + return make_error( + cv_error_code::corrupt_record, + "TypeServer2Record does not contain filename!"); + + for (auto Path : SearchPaths) { + sys::path::append(Path, File); + if (!sys::fs::exists(Path)) + continue; + + std::unique_ptr ThisSession; + if (auto EC = loadDataForPDB(PDB_ReaderType::Native, Path, ThisSession)) { + // It is not an error if this PDB fails to load, it just means that it + // doesn't match and we should continue searching. + ignoreErrors(std::move(EC)); + continue; + } + + std::unique_ptr NS( + static_cast(ThisSession.release())); + 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. + ArrayRef GuidBytes(ExpectedInfo->getGuid().Guid); + StringRef GuidStr(reinterpret_cast(GuidBytes.begin()), + GuidBytes.size()); + if (GuidStr != TS.Guid) + continue; + + Session = std::move(NS); + return handleInternal(File, Callbacks); + } + + // We couldn't find a matching PDB, so let it be handled by someone else. + return false; +} diff --git a/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp b/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp index 603a2c5e8339..a24291f5cd43 100644 --- a/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp +++ b/llvm/lib/DebugInfo/PDB/Native/TpiStream.cpp @@ -16,6 +16,7 @@ #include "llvm/DebugInfo/MSF/MappedBlockStream.h" #include "llvm/DebugInfo/MSF/StreamReader.h" #include "llvm/DebugInfo/PDB/Native/PDBFile.h" +#include "llvm/DebugInfo/PDB/Native/PDBTypeServerHandler.h" #include "llvm/DebugInfo/PDB/Native/RawConstants.h" #include "llvm/DebugInfo/PDB/Native/RawError.h" #include "llvm/DebugInfo/PDB/Native/RawTypes.h" @@ -164,7 +165,7 @@ FixedStreamArray TpiStream::getTypeIndexOffsets() const { HashTable &TpiStream::getHashAdjusters() { return HashAdjusters; } -iterator_range TpiStream::types(bool *HadError) const { +CVTypeRange TpiStream::types(bool *HadError) const { return make_range(TypeRecords.begin(HadError), TypeRecords.end()); } diff --git a/llvm/tools/llvm-readobj/CMakeLists.txt b/llvm/tools/llvm-readobj/CMakeLists.txt index 5fd45a8cff68..0ad149538f63 100644 --- a/llvm/tools/llvm-readobj/CMakeLists.txt +++ b/llvm/tools/llvm-readobj/CMakeLists.txt @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS Support DebugInfoCodeView DebugInfoMSF + DebugInfoPDB ) add_llvm_tool(llvm-readobj diff --git a/llvm/tools/llvm-readobj/COFFDumper.cpp b/llvm/tools/llvm-readobj/COFFDumper.cpp index 3d4ebc8a7355..a0274c0ad952 100644 --- a/llvm/tools/llvm-readobj/COFFDumper.cpp +++ b/llvm/tools/llvm-readobj/COFFDumper.cpp @@ -43,6 +43,7 @@ #include "llvm/Support/Compiler.h" #include "llvm/Support/DataExtractor.h" #include "llvm/Support/Format.h" +#include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/Win64EH.h" @@ -1086,7 +1087,7 @@ void COFFDumper::mergeCodeViewTypes(TypeTableBuilder &CVTypes) { error(object_error::parse_failed); } - if (auto EC = mergeTypeStreams(CVTypes, Types)) { + if (auto EC = mergeTypeStreams(CVTypes, nullptr, Types)) { consumeError(std::move(EC)); return error(object_error::parse_failed); } diff --git a/llvm/tools/llvm-readobj/LLVMBuild.txt b/llvm/tools/llvm-readobj/LLVMBuild.txt index 76dd436e21d6..c0ed38e18d0c 100644 --- a/llvm/tools/llvm-readobj/LLVMBuild.txt +++ b/llvm/tools/llvm-readobj/LLVMBuild.txt @@ -19,4 +19,4 @@ type = Tool name = llvm-readobj parent = Tools -required_libraries = all-targets BitReader Object DebugInfoCodeView DebugInfoMSF +required_libraries = all-targets BitReader Object DebugInfoCodeView DebugInfoPDB DebugInfoMSF diff --git a/llvm/unittests/DebugInfo/PDB/CMakeLists.txt b/llvm/unittests/DebugInfo/PDB/CMakeLists.txt index 3ba3196ed1c7..cbbbd8177483 100644 --- a/llvm/unittests/DebugInfo/PDB/CMakeLists.txt +++ b/llvm/unittests/DebugInfo/PDB/CMakeLists.txt @@ -10,6 +10,7 @@ set(DebugInfoPDBSources StringTableBuilderTest.cpp MSFBuilderTest.cpp PDBApiTest.cpp + TypeServerHandlerTest.cpp ) add_llvm_unittest(DebugInfoPDBTests diff --git a/llvm/unittests/DebugInfo/PDB/TypeServerHandlerTest.cpp b/llvm/unittests/DebugInfo/PDB/TypeServerHandlerTest.cpp new file mode 100644 index 000000000000..6995e8f9dded --- /dev/null +++ b/llvm/unittests/DebugInfo/PDB/TypeServerHandlerTest.cpp @@ -0,0 +1,175 @@ +//===- llvm/unittest/DebugInfo/PDB/TypeServerHandlerTest.cpp --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ErrorChecking.h" + +#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h" +#include "llvm/DebugInfo/CodeView/TypeRecord.h" +#include "llvm/DebugInfo/CodeView/TypeRecordMapping.h" +#include "llvm/DebugInfo/CodeView/TypeSerializer.h" +#include "llvm/DebugInfo/CodeView/TypeServerHandler.h" +#include "llvm/DebugInfo/CodeView/TypeTableBuilder.h" +#include "llvm/DebugInfo/CodeView/TypeVisitorCallbackPipeline.h" +#include "llvm/DebugInfo/CodeView/TypeVisitorCallbacks.h" +#include "llvm/DebugInfo/PDB/Native/RawTypes.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Error.h" + +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::codeview; +using namespace llvm::pdb; + +namespace { + +constexpr uint8_t Guid[] = {0x2a, 0x2c, 0x1c, 0x2a, 0xcb, 0x9e, 0x48, 0x18, + 0x82, 0x82, 0x7a, 0x87, 0xc3, 0xfe, 0x16, 0xe8}; +StringRef GuidStr(reinterpret_cast(Guid), + llvm::array_lengthof(Guid)); + +constexpr const char *Name = "Test Name"; +constexpr int Age = 1; + +class MockTypeServerHandler : public TypeServerHandler { +public: + explicit MockTypeServerHandler(bool HandleAlways) + : HandleAlways(HandleAlways) {} + + Expected handle(TypeServer2Record &TS, + TypeVisitorCallbacks &Callbacks) override { + if (TS.Age != Age || TS.Guid != GuidStr || TS.Name != Name) + return make_error(cv_error_code::corrupt_record, + "Invalid TypeServer record!"); + + if (Handled && !HandleAlways) + return false; + + Handled = true; + return true; + } + + bool Handled = false; + bool HandleAlways; +}; + +class MockTypeVisitorCallbacks : public TypeVisitorCallbacks { +public: + enum class State { + Ready, + VisitTypeBegin, + VisitKnownRecord, + VisitTypeEnd, + }; + Error visitTypeBegin(CVType &CVT) override { + if (S != State::Ready) + return make_error(cv_error_code::unspecified, + "Invalid visitor state!"); + + S = State::VisitTypeBegin; + return Error::success(); + } + + Error visitKnownRecord(CVType &CVT, TypeServer2Record &TS) override { + if (S != State::VisitTypeBegin) + return make_error(cv_error_code::unspecified, + "Invalid visitor state!"); + + S = State::VisitKnownRecord; + return Error::success(); + } + + Error visitTypeEnd(CVType &CVT) override { + if (S != State::VisitKnownRecord) + return make_error(cv_error_code::unspecified, + "Invalid visitor state!"); + + S = State::VisitTypeEnd; + return Error::success(); + } + + State S = State::Ready; +}; + +class TypeServerHandlerTest : public testing::Test { +public: + void SetUp() override { + TypeServer2Record R(TypeRecordKind::TypeServer2); + R.Age = Age; + R.Guid = GuidStr; + R.Name = Name; + + TypeTableBuilder Builder(Allocator); + Builder.writeKnownType(R); + TypeServerRecord.RecordData = Builder.records().front(); + TypeServerRecord.Type = TypeLeafKind::LF_TYPESERVER2; + } + +protected: + BumpPtrAllocator Allocator; + CVType TypeServerRecord; +}; + +// Test that when no type server handler is registered, it gets handled by the +// normal +// visitor callbacks. +TEST_F(TypeServerHandlerTest, VisitRecordNoTypeServer) { + MockTypeVisitorCallbacks C2; + MockTypeVisitorCallbacks C1; + TypeVisitorCallbackPipeline Pipeline; + + Pipeline.addCallbackToPipeline(C1); + Pipeline.addCallbackToPipeline(C2); + CVTypeVisitor Visitor(Pipeline); + EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord)); + + EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C1.S); + EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C2.S); +} + +// Test that when a TypeServerHandler is registered, it gets consumed by the +// handler if and only if the handler returns true. +TEST_F(TypeServerHandlerTest, VisitRecordWithTypeServerOnce) { + MockTypeServerHandler Handler(false); + + MockTypeVisitorCallbacks C1; + CVTypeVisitor Visitor(C1); + Visitor.addTypeServerHandler(Handler); + + // Our mock server returns true the first time. + EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord)); + EXPECT_TRUE(Handler.Handled); + EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S); + + // And false the second time. + EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord)); + EXPECT_TRUE(Handler.Handled); + EXPECT_EQ(MockTypeVisitorCallbacks::State::VisitTypeEnd, C1.S); +} + +// Test that when a type server handler is registered, if the handler keeps +// returning true, it will keep getting consumed by the handler and not go +// to the default processor. +TEST_F(TypeServerHandlerTest, VisitRecordWithTypeServerAlways) { + MockTypeServerHandler Handler(true); + + MockTypeVisitorCallbacks C1; + CVTypeVisitor Visitor(C1); + Visitor.addTypeServerHandler(Handler); + + EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord)); + EXPECT_TRUE(Handler.Handled); + EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S); + + EXPECT_NO_ERROR(Visitor.visitTypeRecord(TypeServerRecord)); + EXPECT_TRUE(Handler.Handled); + EXPECT_EQ(MockTypeVisitorCallbacks::State::Ready, C1.S); +} + +} // end anonymous namespace