//===- DebugTypes.cpp -----------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "DebugTypes.h" #include "Driver.h" #include "InputFiles.h" #include "lld/Common/ErrorHandler.h" #include "llvm/DebugInfo/CodeView/TypeRecord.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/Support/Path.h" using namespace lld; using namespace lld::coff; using namespace llvm; using namespace llvm::codeview; namespace { // The TypeServerSource class represents a PDB type server, a file referenced by // OBJ files compiled with MSVC /Zi. A single PDB can be shared by several OBJ // files, therefore there must be only once instance per OBJ lot. The file path // is discovered from the dependent OBJ's debug type stream. The // TypeServerSource object is then queued and loaded by the COFF Driver. The // debug type stream for such PDB files will be merged first in the final PDB, // before any dependent OBJ. class TypeServerSource : public TpiSource { public: explicit TypeServerSource(MemoryBufferRef M, llvm::pdb::NativeSession *S) : TpiSource(PDB, nullptr), Session(S), MB(M) {} // Queue a PDB type server for loading in the COFF Driver static void enqueue(const ObjFile *DependentFile, const TypeServer2Record &TS); // Create an instance static Expected getInstance(MemoryBufferRef M); // Fetch the PDB instance loaded for a corresponding dependent OBJ. static Expected findFromFile(const ObjFile *DependentFile); static std::map> Instances; // The interface to the PDB (if it was opened successfully) std::unique_ptr Session; private: MemoryBufferRef MB; }; // This class represents the debug type stream of an OBJ file that depends on a // PDB type server (see TypeServerSource). class UseTypeServerSource : public TpiSource { public: UseTypeServerSource(const ObjFile *F, const TypeServer2Record *TS) : TpiSource(UsingPDB, F), TypeServerDependency(*TS) {} // Information about the PDB type server dependency, that needs to be loaded // in before merging this OBJ. TypeServer2Record TypeServerDependency; }; // This class represents the debug type stream of a Microsoft precompiled // headers OBJ (PCH OBJ). This OBJ kind needs to be merged first in the output // PDB, before any other OBJs that depend on this. Note that only MSVC generate // such files, clang does not. class PrecompSource : public TpiSource { public: PrecompSource(const ObjFile *F) : TpiSource(PCH, F) {} }; // This class represents the debug type stream of an OBJ file that depends on a // Microsoft precompiled headers OBJ (see PrecompSource). class UsePrecompSource : public TpiSource { public: UsePrecompSource(const ObjFile *F, const PrecompRecord *Precomp) : TpiSource(UsingPCH, F), PrecompDependency(*Precomp) {} // Information about the Precomp OBJ dependency, that needs to be loaded in // before merging this OBJ. PrecompRecord PrecompDependency; }; } // namespace static std::vector> GC; TpiSource::TpiSource(TpiKind K, const ObjFile *F) : Kind(K), File(F) { GC.push_back(std::unique_ptr(this)); } TpiSource *lld::coff::makeTpiSource(const ObjFile *F) { return new TpiSource(TpiSource::Regular, F); } TpiSource *lld::coff::makeUseTypeServerSource(const ObjFile *F, const TypeServer2Record *TS) { TypeServerSource::enqueue(F, *TS); return new UseTypeServerSource(F, TS); } TpiSource *lld::coff::makePrecompSource(const ObjFile *F) { return new PrecompSource(F); } TpiSource *lld::coff::makeUsePrecompSource(const ObjFile *F, const PrecompRecord *Precomp) { return new UsePrecompSource(F, Precomp); } namespace lld { namespace coff { template <> const PrecompRecord &retrieveDependencyInfo(const TpiSource *Source) { assert(Source->Kind == TpiSource::UsingPCH); return ((const UsePrecompSource *)Source)->PrecompDependency; } template <> const TypeServer2Record &retrieveDependencyInfo(const TpiSource *Source) { assert(Source->Kind == TpiSource::UsingPDB); return ((const UseTypeServerSource *)Source)->TypeServerDependency; } } // namespace coff } // namespace lld std::map> TypeServerSource::Instances; // Make a PDB path assuming the PDB is in the same folder as the OBJ static std::string getPdbBaseName(const ObjFile *File, StringRef 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 MSVC 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 Path.str(); } // 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, const ObjFile *DependentFile) { // Ensure the file exists before anything else. In some cases, if the path // points to a removable device, Driver::enqueuePath() would fail with an // error (EAGAIN, "resource unavailable try again") which we want to skip // silently. if (llvm::sys::fs::exists(PDBPath)) return normalizePdbPath(PDBPath); std::string Ret = getPdbBaseName(DependentFile, PDBPath); if (llvm::sys::fs::exists(Ret)) return normalizePdbPath(Ret); return None; } // Fetch the PDB instance that was already loaded by the COFF Driver. Expected TypeServerSource::findFromFile(const 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 = TypeServerSource::Instances.find(*P); // The PDB file exists on disk, at this point we expect it to have been // inserted in the map by TypeServerSource::loadPDB() assert(It != TypeServerSource::Instances.end()); std::pair &PDB = It->second; if (!PDB.second) return createFileError( *P, createStringError(inconvertibleErrorCode(), PDB.first.c_str())); pdb::PDBFile &PDBFile = (PDB.second)->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 must match between the PDB header and the OBJ // TypeServer2 record. The 'Age' is used by MSVC incremental compilation. if (Info.getGuid() != TS.getGuid()) return createFileError( TS.Name, make_error(pdb::pdb_error_code::signature_out_of_date)); return PDB.second; } // FIXME: Temporary interface until PDBLinker::maybeMergeTypeServerPDB() is // moved here. Expected lld::coff::findTypeServerSource(const ObjFile *F) { Expected TS = TypeServerSource::findFromFile(F); if (!TS) return TS.takeError(); return TS.get()->Session.get(); } // Queue a PDB type server for loading in the COFF Driver void TypeServerSource::enqueue(const ObjFile *DependentFile, const TypeServer2Record &TS) { // Start by finding where the PDB is located (either the record path or next // to the OBJ file) Optional P = findPdbPath(TS.Name, DependentFile); if (!P) return; auto It = TypeServerSource::Instances.emplace( *P, std::pair{}); if (!It.second) return; // another OBJ already scheduled this PDB for load Driver->enqueuePath(*P, false); } // Create an instance of TypeServerSource or an error string if the PDB couldn't // be loaded. The error message will be displayed later, when the referring OBJ // will be merged in. NOTE - a PDB load failure is not a link error: some // debug info will simply be missing from the final PDB - that is the default // accepted behavior. void lld::coff::loadTypeServerSource(llvm::MemoryBufferRef M) { std::string Path = normalizePdbPath(M.getBufferIdentifier()); Expected TS = TypeServerSource::getInstance(M); if (!TS) TypeServerSource::Instances[Path] = {toString(TS.takeError()), nullptr}; else TypeServerSource::Instances[Path] = {{}, *TS}; } Expected TypeServerSource::getInstance(MemoryBufferRef M) { std::unique_ptr ISession; Error Err = pdb::NativeSession::createFromPdb( MemoryBuffer::getMemBuffer(M, false), ISession); if (Err) return std::move(Err); std::unique_ptr Session( static_cast(ISession.release())); pdb::PDBFile &PDBFile = Session->getPDBFile(); Expected Info = PDBFile.getPDBInfoStream(); // All PDB Files should have an Info stream. if (!Info) return Info.takeError(); return new TypeServerSource(M, Session.release()); }