| //===- 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 llvm; | |
| using namespace llvm::codeview; | |
| namespace lld { | |
| namespace coff { | |
| 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<TypeServerSource *> getInstance(MemoryBufferRef m); | |
| // Fetch the PDB instance loaded for a corresponding dependent OBJ. | |
| static Expected<TypeServerSource *> | |
| findFromFile(const ObjFile *dependentFile); | |
| static std::map<std::string, std::pair<std::string, TypeServerSource *>> | |
| instances; | |
| // The interface to the PDB (if it was opened successfully) | |
| std::unique_ptr<llvm::pdb::NativeSession> 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<std::unique_ptr<TpiSource>> GC; | |
| TpiSource::TpiSource(TpiKind k, const ObjFile *f) : kind(k), file(f) { | |
| GC.push_back(std::unique_ptr<TpiSource>(this)); | |
| } | |
| TpiSource *makeTpiSource(const ObjFile *f) { | |
| return new TpiSource(TpiSource::Regular, f); | |
| } | |
| TpiSource *makeUseTypeServerSource(const ObjFile *f, | |
| const TypeServer2Record *ts) { | |
| TypeServerSource::enqueue(f, *ts); | |
| return new UseTypeServerSource(f, ts); | |
| } | |
| TpiSource *makePrecompSource(const ObjFile *f) { | |
| return new PrecompSource(f); | |
| } | |
| TpiSource *makeUsePrecompSource(const ObjFile *f, | |
| const PrecompRecord *precomp) { | |
| return new UsePrecompSource(f, precomp); | |
| } | |
| 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; | |
| } | |
| std::map<std::string, std::pair<std::string, TypeServerSource *>> | |
| 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<std::string> 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 *> | |
| TypeServerSource::findFromFile(const ObjFile *dependentFile) { | |
| const TypeServer2Record &ts = | |
| retrieveDependencyInfo<TypeServer2Record>(dependentFile->debugTypesObj); | |
| Optional<std::string> 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<std::string, TypeServerSource *> &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::PDBError>(pdb::pdb_error_code::signature_out_of_date)); | |
| return pdb.second; | |
| } | |
| // FIXME: Temporary interface until PDBLinker::maybeMergeTypeServerPDB() is | |
| // moved here. | |
| Expected<llvm::pdb::NativeSession *> findTypeServerSource(const ObjFile *f) { | |
| Expected<TypeServerSource *> 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<std::string> p = findPdbPath(ts.Name, dependentFile); | |
| if (!p) | |
| return; | |
| auto it = TypeServerSource::instances.emplace( | |
| *p, std::pair<std::string, TypeServerSource *>{}); | |
| if (!it.second) | |
| return; // another OBJ already scheduled this PDB for load | |
| driver->enqueuePath(*p, false, 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 loadTypeServerSource(llvm::MemoryBufferRef m) { | |
| std::string path = normalizePdbPath(m.getBufferIdentifier()); | |
| Expected<TypeServerSource *> ts = TypeServerSource::getInstance(m); | |
| if (!ts) | |
| TypeServerSource::instances[path] = {toString(ts.takeError()), nullptr}; | |
| else | |
| TypeServerSource::instances[path] = {{}, *ts}; | |
| } | |
| Expected<TypeServerSource *> TypeServerSource::getInstance(MemoryBufferRef m) { | |
| std::unique_ptr<llvm::pdb::IPDBSession> iSession; | |
| Error err = pdb::NativeSession::createFromPdb( | |
| MemoryBuffer::getMemBuffer(m, false), iSession); | |
| if (err) | |
| return std::move(err); | |
| std::unique_ptr<llvm::pdb::NativeSession> session( | |
| static_cast<pdb::NativeSession *>(iSession.release())); | |
| pdb::PDBFile &pdbFile = session->getPDBFile(); | |
| Expected<pdb::InfoStream &> info = pdbFile.getPDBInfoStream(); | |
| // All PDB Files should have an Info stream. | |
| if (!info) | |
| return info.takeError(); | |
| return new TypeServerSource(m, session.release()); | |
| } | |
| } // namespace coff | |
| } // namespace lld |