| //===- 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<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 *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<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 *> |
| lld::coff::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 lld::coff::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()); |
| } |