Index: COFF/DebugTypes.h =================================================================== --- COFF/DebugTypes.h +++ COFF/DebugTypes.h @@ -22,6 +22,7 @@ namespace coff { class ObjFile; +class PDBInputFile; class TpiSource { public: @@ -35,7 +36,7 @@ }; TpiSource *makeTpiSource(ObjFile *F); -TpiSource *makeTypeServerSource(ObjFile *F); +TpiSource *makeTypeServerSource(PDBInputFile *F); TpiSource *makeUseTypeServerSource(ObjFile *F, llvm::codeview::TypeServer2Record *TS); TpiSource *makePrecompSource(ObjFile *F); Index: COFF/DebugTypes.cpp =================================================================== --- COFF/DebugTypes.cpp +++ COFF/DebugTypes.cpp @@ -9,6 +9,8 @@ #include "DebugTypes.h" #include "InputFiles.h" #include "llvm/DebugInfo/CodeView/TypeRecord.h" +#include "llvm/DebugInfo/PDB/Native/PDBFile.h" +#include "llvm/DebugInfo/PDB/Native/InfoStream.h" using namespace lld; using namespace lld::coff; @@ -18,7 +20,9 @@ namespace { class TypeServerSource : public TpiSource { public: - TypeServerSource(ObjFile *F) : TpiSource(PDB, F) {} + TypeServerSource(PDBInputFile *F) : TpiSource(PDB, nullptr), PDBFile(F) {} + + PDBInputFile *PDBFile; }; class UseTypeServerSource : public TpiSource { @@ -57,7 +61,7 @@ return new TpiSource(TpiSource::Regular, F); } -TpiSource *coff::makeTypeServerSource(ObjFile *F) { +TpiSource *coff::makeTypeServerSource(PDBInputFile *F) { return new TypeServerSource(F); } Index: COFF/Driver.h =================================================================== --- COFF/Driver.h +++ COFF/Driver.h @@ -77,6 +77,8 @@ MemoryBufferRef takeBuffer(std::unique_ptr MB); + void enqueuePath(StringRef Path, bool WholeArchive); + private: std::unique_ptr Tar; // for /linkrepro @@ -120,8 +122,6 @@ void addArchiveBuffer(MemoryBufferRef MBRef, StringRef SymName, StringRef ParentName); - void enqueuePath(StringRef Path, bool WholeArchive); - void enqueueTask(std::function Task); bool run(); Index: COFF/Driver.cpp =================================================================== --- COFF/Driver.cpp +++ COFF/Driver.cpp @@ -171,6 +171,9 @@ case file_magic::coff_import_library: Symtab->addFile(make(MBRef)); break; + case file_magic::pdb: + Symtab->addFile(make(MBRef)); + break; case file_magic::coff_cl_gl_object: error(Filename + ": is not a native COFF file. Recompile without /GL"); break; Index: COFF/InputFiles.h =================================================================== --- COFF/InputFiles.h +++ COFF/InputFiles.h @@ -26,6 +26,7 @@ namespace llvm { namespace pdb { class DbiModuleDescriptorBuilder; +class NativeSession; } } @@ -56,7 +57,7 @@ // The root class of input files. class InputFile { public: - enum Kind { ArchiveKind, ObjectKind, ImportKind, BitcodeKind }; + enum Kind { ArchiveKind, ObjectKind, PDBKind, ImportKind, BitcodeKind }; Kind kind() const { return FileKind; } virtual ~InputFile() {} @@ -257,6 +258,33 @@ std::vector Symbols; }; +// A PDB type server dependency that is not a cmd-line input file per se, but +// needs to be treated like one. A PDBInputFile is discovered from the debug +// type stream from dependent ObjFiles. +class PDBInputFile : public InputFile { +public: + explicit PDBInputFile(MemoryBufferRef M) : InputFile(PDBKind, M) {} + static bool classof(const InputFile *F) { return F->kind() == PDBKind; } + void parse() override; + + static void enqueue(ObjFile *DependentFile); + + // Fetch the PDB instance loaded for a corresponding dependent OBJ. + static Expected findFromFile(ObjFile *DependentFile); + + static std::map Instances; + + // The actual interface to the PDB (if it was opened successfully) + llvm::pdb::NativeSession *Session; + + // If the PDB has a .debug$T stream, this tells how it will be handled. + TpiSource *DebugTypesObj; + +private: + // Record possible errors while opening the PDB file + std::string ErrMsg; +}; + // This type represents import library members that contain DLL names // and symbols exported from the DLLs. See Microsoft PE/COFF spec. 7 // for details about the format. Index: COFF/InputFiles.cpp =================================================================== --- COFF/InputFiles.cpp +++ COFF/InputFiles.cpp @@ -24,6 +24,10 @@ #include "llvm/DebugInfo/CodeView/SymbolDeserializer.h" #include "llvm/DebugInfo/CodeView/SymbolRecord.h" #include "llvm/DebugInfo/CodeView/TypeDeserializer.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/Object/Binary.h" #include "llvm/Object/COFF.h" #include "llvm/Support/Casting.h" @@ -50,6 +54,7 @@ namespace coff { std::vector ObjFile::Instances; +std::map PDBInputFile::Instances; std::vector ImportFile::Instances; std::vector BitcodeFile::Instances; @@ -699,6 +704,7 @@ TypeServer2Record TS = cantFail( TypeDeserializer::deserializeAs(FirstType->data())); DebugTypesObj = makeUseTypeServerSource(this, &TS); + PDBInputFile::enqueue(this); return; } @@ -712,6 +718,116 @@ DebugTypesObj = makeTpiSource(this); } +// Make a PDB path assuming the PDB is in the same folder as the OBJ +static std::string getPdbBaseName(ObjFile *File, StringRef TSPath) { + StringRef LocalPath = + !File->ParentName.empty() ? File->ParentName : File->getName(); + std::string Path = sys::path::parent_path(LocalPath); + // Currently, type server PDBs are only created by cl, which only runs + // on Windows, so we can assume type server paths are Windows style. + Path += sys::path::filename(TSPath, sys::path::Style::windows); + return Path; +} + +// The casing of the PDB path stamped in the OBJ can differ from the actual path +// on disk. With this, we ensure to always use lowercase as a key for the +// PDBInputFile::Instances map, at least on Windows. +static std::string normalizePdbPath(StringRef path) { +#if defined(_WIN32) + return path.lower(); +#else // LINUX + return path; +#endif +} + +// If existing, return the actual PDB path on disk. +static Optional findPdbPath(StringRef PDBPath, + ObjFile *DependentFile) { + // Ensure the file exists before anything else. In some cases, if the path + // points to a removable device, enqueuePath() would fail with an error + // (EAGAIN, "resource unavailable try again") which we want to skip + // silently. + if (llvm::sys::fs::exists(PDBPath)) + return PDBPath.str(); + std::string Ret = getPdbBaseName(DependentFile, PDBPath); + if (llvm::sys::fs::exists(Ret)) + return Ret; + return None; +} + +Expected PDBInputFile::findFromFile(ObjFile *DependentFile) { + const TypeServer2Record &TS = + retrieveDependencyInfo(DependentFile->DebugTypesObj); + + Optional P = findPdbPath(TS.Name, DependentFile); + if (!P) + return createFileError(TS.Name, errorCodeToError(std::error_code( + ENOENT, std::generic_category()))); + + auto It = PDBInputFile::Instances.find(*P); + // The PDB file exists on disk, we expect it to have been inserted in the map + // in PDBInputFile::parse() + assert(It != PDBInputFile::Instances.end()); + + PDBInputFile *PDB = It->second; + + if (!PDB->ErrMsg.empty()) + return createFileError(TS.Name, make_error( + PDB->ErrMsg, inconvertibleErrorCode())); + + pdb::PDBFile &PDBFile = PDB->Session->getPDBFile(); + pdb::InfoStream &Info = cantFail(PDBFile.getPDBInfoStream()); + + // Just because a file with a matching name was found doesn't mean it can be + // used. The GUID and Age must match between the PDB header and the OBJ + // TypeServer2 record. + if (Info.getGuid() != TS.getGuid() || Info.getAge() != TS.getAge()) + return createFileError( + TS.Name, + make_error(pdb::pdb_error_code::signature_out_of_date)); + + return *PDB; +} + +void PDBInputFile::enqueue(ObjFile *DependentFile) { + const TypeServer2Record &TS = + retrieveDependencyInfo(DependentFile->DebugTypesObj); + + Optional P = findPdbPath(TS.Name, DependentFile); + if (!P) + return; + auto It = PDBInputFile::Instances.emplace(*P, nullptr); + if (!It.second) + return; // already scheduled for load + Driver->enqueuePath(*P, false); +} + +void PDBInputFile::parse() { + PDBInputFile::Instances[normalizePdbPath(MB.getBufferIdentifier())] = this; + + std::unique_ptr ISession; + Error Err = pdb::NativeSession::createFromPdb( + MemoryBuffer::getMemBuffer(MB, false), ISession); + if (Err) { + ErrMsg = toString(std::move(Err)); + return; // fail silently at this point - the error will be handled later, + // when merging the debug type stream + } + + static std::vector> SessionGC; + SessionGC.emplace_back(static_cast(ISession.release())); + Session = SessionGC.rbegin()->get(); + + pdb::PDBFile &PDBFile = Session->getPDBFile(); + Expected Info = PDBFile.getPDBInfoStream(); + if (!Info) { + // All PDB Files should have an Info stream. + ErrMsg = toString(Info.takeError()); + return; + } + DebugTypesObj = makeTypeServerSource(this); +} + StringRef ltrim1(StringRef S, const char *Chars) { if (!S.empty() && strchr(Chars, S[0])) return S.substr(1); Index: COFF/PDB.cpp =================================================================== --- COFF/PDB.cpp +++ COFF/PDB.cpp @@ -208,11 +208,6 @@ 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. @@ -222,10 +217,6 @@ /// far. std::map PrecompTypeIndexMappings; - /// List of TypeServer PDBs which cannot be loaded. - /// Cached to prevent repeated load attempts. - std::map MissingTypeServerPDBs; - // For statistics uint64_t GlobalSymbols = 0; uint64_t ModuleSymbols = 0; @@ -449,115 +440,26 @@ return *ObjectIndexMap; } -static Expected> -tryToLoadPDB(const codeview::GUID &GuidFromObj, StringRef TSPath) { - // Ensure the file exists before anything else. We want to return ENOENT, - // "file not found", even if the path points to a removable device (in which - // case the return message would be EAGAIN, "resource unavailable try again") - if (!llvm::sys::fs::exists(TSPath)) - return errorCodeToError(std::error_code(ENOENT, std::generic_category())); - - 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) { - const TypeServer2Record &TS = - retrieveDependencyInfo(File->DebugTypesObj); - - const codeview::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; + Expected PDB = PDBInputFile::findFromFile(File); + if (!PDB) + return PDB.takeError(); + + pdb::PDBFile &PDBFile = PDB->Session->getPDBFile(); + pdb::InfoStream &Info = cantFail(PDBFile.getPDBInfoStream()); + + auto It = TypeServerIndexMappings.emplace(Info.getGuid(), CVIndexMap()); + CVIndexMap &IndexMap = It.first->second; + if (!It.second) + return IndexMap; // already merged // 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); - // Currently, type server PDBs are only created by cl, which only runs - // on Windows, so we can assume type server paths are Windows style. - 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(); + Expected ExpectedTpi = PDBFile.getPDBTpiStream(); if (auto E = ExpectedTpi.takeError()) fatal("Type server does not have TPI stream: " + toString(std::move(E))); - auto ExpectedIpi = Session->getPDBFile().getPDBIpiStream(); + Expected ExpectedIpi = PDBFile.getPDBIpiStream(); if (auto E = ExpectedIpi.takeError()) fatal("Type server does not have TPI stream: " + toString(std::move(E))); Index: test/COFF/Inputs/pdb-type-server-valid-signature.yaml =================================================================== --- test/COFF/Inputs/pdb-type-server-valid-signature.yaml +++ test/COFF/Inputs/pdb-type-server-valid-signature.yaml @@ -69,7 +69,7 @@ - Kind: LF_TYPESERVER2 TypeServer2: Guid: '{8DABD2A0-28FF-CB43-9BAF-175B77B76414}' - Age: 18 + Age: 1 Name: 'pdb-diff-cl.pdb' - Name: '.text$mn' Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ] Index: test/COFF/pdb-type-server-invalid-signature.yaml =================================================================== --- test/COFF/pdb-type-server-invalid-signature.yaml +++ test/COFF/pdb-type-server-invalid-signature.yaml @@ -4,7 +4,7 @@ # RUN: lld-link %t.obj -out:%t.exe -debug -pdb:%t.pdb -nodefaultlib -entry:main 2>&1 | FileCheck %s # RUN: cd %S -# CHECK: warning: Cannot use debug info for {{.*}}.obj +# CHECK: warning: Cannot use debug info for '{{.*}}.obj' # CHECK-NEXT: The signature does not match; the file(s) might be out of date # Also test a valid match @@ -14,7 +14,7 @@ # RUN: lld-link %t2.obj -out:%t2.exe -debug -pdb:%t2.pdb -nodefaultlib -entry:main 2>&1 | FileCheck %s -check-prefix=VALID-SIGNATURE -allow-empty # RUN: cd %S -# VALID-SIGNATURE-NOT: warning: Cannot use debug info for {{.*}}.obj +# VALID-SIGNATURE-NOT: warning: Cannot use debug info for '{{.*}}.obj' # VALID-SIGNATURE-NOT: The signature does not match; the file(s) might be out of date --- !COFF