//===----------------------------------------------------------------------===//
#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::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:
- TypeServerSource(ObjFile *F) : TpiSource(PDB, F) {}
+ 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(ObjFile *F, TypeServer2Record *TS)
+ UseTypeServerSource(const ObjFile *F, const TypeServer2Record *TS)
: TpiSource(UsingPDB, F), TypeServerDependency(*TS) {}
// Information about the PDB type server dependency, that needs to be loaded
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(ObjFile *F) : TpiSource(PCH, F) {}
+ 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(ObjFile *F, PrecompRecord *Precomp)
+ UsePrecompSource(const ObjFile *F, const PrecompRecord *Precomp)
: TpiSource(UsingPCH, F), PrecompDependency(*Precomp) {}
// Information about the Precomp OBJ dependency, that needs to be loaded in
static std::vector<std::unique_ptr<TpiSource>> GC;
-TpiSource::TpiSource(TpiKind K, ObjFile *F) : Kind(K), File(F) {
+TpiSource::TpiSource(TpiKind K, const ObjFile *F) : Kind(K), File(F) {
GC.push_back(std::unique_ptr<TpiSource>(this));
}
-TpiSource *coff::makeTpiSource(ObjFile *F) {
+TpiSource *lld::coff::makeTpiSource(const ObjFile *F) {
return new TpiSource(TpiSource::Regular, F);
}
-TpiSource *coff::makeTypeServerSource(ObjFile *F) {
- return new TypeServerSource(F);
-}
-
-TpiSource *coff::makeUseTypeServerSource(ObjFile *F, TypeServer2Record *TS) {
+TpiSource *lld::coff::makeUseTypeServerSource(const ObjFile *F,
+ const TypeServer2Record *TS) {
+ TypeServerSource::enqueue(F, *TS);
return new UseTypeServerSource(F, TS);
}
-TpiSource *coff::makePrecompSource(ObjFile *F) { return new PrecompSource(F); }
+TpiSource *lld::coff::makePrecompSource(const ObjFile *F) {
+ return new PrecompSource(F);
+}
-TpiSource *coff::makeUsePrecompSource(ObjFile *F, PrecompRecord *Precomp) {
+TpiSource *lld::coff::makeUsePrecompSource(const ObjFile *F,
+ const PrecompRecord *Precomp) {
return new UsePrecompSource(F, Precomp);
}
namespace lld {
namespace coff {
template <>
-const PrecompRecord &retrieveDependencyInfo(TpiSource *Source) {
+const PrecompRecord &retrieveDependencyInfo(const TpiSource *Source) {
assert(Source->Kind == TpiSource::UsingPCH);
- return ((UsePrecompSource *)Source)->PrecompDependency;
+ return ((const UsePrecompSource *)Source)->PrecompDependency;
}
template <>
-const TypeServer2Record &retrieveDependencyInfo(TpiSource *Source) {
+const TypeServer2Record &retrieveDependencyInfo(const TpiSource *Source) {
assert(Source->Kind == TpiSource::UsingPDB);
- return ((UseTypeServerSource *)Source)->TypeServerDependency;
+ 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();
+ std::string 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.
+ return Path + sys::path::filename(TSPath, sys::path::Style::windows).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 and Age must match between the PDB header and the OBJ
+ // TypeServer2 record. The 'Age' is used by MSVC incremental compilation.
+ if (Info.getGuid() != TS.getGuid() || Info.getAge() != TS.getAge())
+ 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);
+}
+
+// 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());
+}
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<std::unique_ptr<pdb::NativeSession>, 2> LoadedPDBs;
-
std::vector<pdb::SecMapEntry> SectionMap;
/// Type index mappings of type server PDBs that we've loaded so far.
/// far.
std::map<uint32_t, CVIndexMap> PrecompTypeIndexMappings;
- /// List of TypeServer PDBs which cannot be loaded.
- /// Cached to prevent repeated load attempts.
- std::map<codeview::GUID, std::string> MissingTypeServerPDBs;
-
// For statistics
uint64_t GlobalSymbols = 0;
uint64_t ModuleSymbols = 0;
return *ObjectIndexMap;
}
-static Expected<std::unique_ptr<pdb::NativeSession>>
-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<std::unique_ptr<MemoryBuffer>> MBOrErr = MemoryBuffer::getFile(
- TSPath, /*FileSize=*/-1, /*RequiresNullTerminator=*/false);
- if (!MBOrErr)
- return errorCodeToError(MBOrErr.getError());
-
- std::unique_ptr<pdb::IPDBSession> ThisSession;
- if (auto EC = pdb::NativeSession::createFromPdb(
- MemoryBuffer::getMemBuffer(Driver->takeBuffer(std::move(*MBOrErr)),
- /*RequiresNullTerminator=*/false),
- ThisSession))
- return std::move(EC);
-
- std::unique_ptr<pdb::NativeSession> NS(
- static_cast<pdb::NativeSession *>(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::PDBError>(pdb::pdb_error_code::signature_out_of_date);
-
- return std::move(NS);
-}
-
Expected<const CVIndexMap &> PDBLinker::maybeMergeTypeServerPDB(ObjFile *File) {
- const TypeServer2Record &TS =
- retrieveDependencyInfo<TypeServer2Record>(File->DebugTypesObj);
+ Expected<llvm::pdb::NativeSession *> PDBSession = findTypeServerSource(File);
+ if (!PDBSession)
+ return PDBSession.takeError();
- const codeview::GUID &TSId = TS.getGuid();
- StringRef TSPath = TS.getName();
+ pdb::PDBFile &PDBFile = PDBSession.get()->getPDBFile();
+ pdb::InfoStream &Info = cantFail(PDBFile.getPDBInfoStream());
- // First, check if the PDB has previously failed to load.
- auto PrevErr = MissingTypeServerPDBs.find(TSId);
- if (PrevErr != MissingTypeServerPDBs.end())
- return createFileError(
- TSPath,
- make_error<StringError>(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;
+ 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<ECError> 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<pdb::TpiStream &> 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<pdb::TpiStream &> ExpectedIpi = PDBFile.getPDBIpiStream();
if (auto E = ExpectedIpi.takeError())
fatal("Type server does not have TPI stream: " + toString(std::move(E)));