diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2020-07-26 19:36:28 +0000 |
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2020-07-26 19:36:28 +0000 |
| commit | cfca06d7963fa0909f90483b42a6d7d194d01e08 (patch) | |
| tree | 209fb2a2d68f8f277793fc8df46c753d31bc853b /lld/COFF | |
| parent | 706b4fc47bbc608932d3b491ae19a3b9cde9497b (diff) | |
Notes
Diffstat (limited to 'lld/COFF')
| -rw-r--r-- | lld/COFF/Chunks.cpp | 2 | ||||
| -rw-r--r-- | lld/COFF/Chunks.h | 13 | ||||
| -rw-r--r-- | lld/COFF/Config.h | 9 | ||||
| -rw-r--r-- | lld/COFF/DLL.cpp | 16 | ||||
| -rw-r--r-- | lld/COFF/DebugTypes.cpp | 538 | ||||
| -rw-r--r-- | lld/COFF/DebugTypes.h | 56 | ||||
| -rw-r--r-- | lld/COFF/Driver.cpp | 118 | ||||
| -rw-r--r-- | lld/COFF/Driver.h | 22 | ||||
| -rw-r--r-- | lld/COFF/DriverUtils.cpp | 67 | ||||
| -rw-r--r-- | lld/COFF/ICF.cpp | 21 | ||||
| -rw-r--r-- | lld/COFF/InputFiles.cpp | 189 | ||||
| -rw-r--r-- | lld/COFF/InputFiles.h | 46 | ||||
| -rw-r--r-- | lld/COFF/LLDMapFile.cpp | 123 | ||||
| -rw-r--r-- | lld/COFF/LLDMapFile.h | 21 | ||||
| -rw-r--r-- | lld/COFF/LTO.cpp | 61 | ||||
| -rw-r--r-- | lld/COFF/LTO.h | 2 | ||||
| -rw-r--r-- | lld/COFF/MapFile.cpp | 323 | ||||
| -rw-r--r-- | lld/COFF/MarkLive.cpp | 6 | ||||
| -rw-r--r-- | lld/COFF/MinGW.cpp | 10 | ||||
| -rw-r--r-- | lld/COFF/Options.td | 23 | ||||
| -rw-r--r-- | lld/COFF/PDB.cpp | 785 | ||||
| -rw-r--r-- | lld/COFF/SymbolTable.cpp | 18 | ||||
| -rw-r--r-- | lld/COFF/SymbolTable.h | 1 | ||||
| -rw-r--r-- | lld/COFF/Symbols.cpp | 32 | ||||
| -rw-r--r-- | lld/COFF/Symbols.h | 16 | ||||
| -rw-r--r-- | lld/COFF/TypeMerger.h | 13 | ||||
| -rw-r--r-- | lld/COFF/Writer.cpp | 81 |
27 files changed, 1573 insertions, 1039 deletions
diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp index 0e43d2b478b4..e04ceed505c2 100644 --- a/lld/COFF/Chunks.cpp +++ b/lld/COFF/Chunks.cpp @@ -333,7 +333,7 @@ static void maybeReportRelocationToDiscarded(const SectionChunk *fromChunk, } else { COFFSymbolRef coffSym = check(file->getCOFFObj()->getSymbol(rel.SymbolTableIndex)); - file->getCOFFObj()->getSymbolName(coffSym, name); + name = check(file->getCOFFObj()->getSymbolName(coffSym)); } std::vector<std::string> symbolLocations = diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h index 7ae4ee735f4a..0528143383c5 100644 --- a/lld/COFF/Chunks.h +++ b/lld/COFF/Chunks.h @@ -269,7 +269,8 @@ public: AssociatedIterator() = default; AssociatedIterator(SectionChunk *head) : cur(head) {} bool operator==(const AssociatedIterator &r) const { return cur == r.cur; } - const SectionChunk &operator*() const { return *cur; } + // FIXME: Wrong const-ness, but it makes filter ranges work. + SectionChunk &operator*() const { return *cur; } SectionChunk &operator*() { return *cur; } AssociatedIterator &operator++() { cur = cur->assocChildren; @@ -486,7 +487,9 @@ public: class ImportThunkChunkARM : public ImportThunkChunk { public: - explicit ImportThunkChunkARM(Defined *s) : ImportThunkChunk(s) {} + explicit ImportThunkChunkARM(Defined *s) : ImportThunkChunk(s) { + setAlignment(2); + } size_t getSize() const override { return sizeof(importThunkARM); } void getBaserels(std::vector<Baserel> *res) override; void writeTo(uint8_t *buf) const override; @@ -494,14 +497,16 @@ public: class ImportThunkChunkARM64 : public ImportThunkChunk { public: - explicit ImportThunkChunkARM64(Defined *s) : ImportThunkChunk(s) {} + explicit ImportThunkChunkARM64(Defined *s) : ImportThunkChunk(s) { + setAlignment(4); + } size_t getSize() const override { return sizeof(importThunkARM64); } void writeTo(uint8_t *buf) const override; }; class RangeExtensionThunkARM : public NonSectionChunk { public: - explicit RangeExtensionThunkARM(Defined *t) : target(t) {} + explicit RangeExtensionThunkARM(Defined *t) : target(t) { setAlignment(2); } size_t getSize() const override; void writeTo(uint8_t *buf) const override; diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h index 2690ea5c4082..72d826b8bd17 100644 --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -110,6 +110,7 @@ struct Configuration { bool showSummary = false; unsigned debugTypes = static_cast<unsigned>(DebugType::None); std::vector<std::string> natvisFiles; + llvm::StringMap<std::string> namedStreams; llvm::SmallString<128> pdbAltPath; llvm::SmallString<128> pdbPath; llvm::SmallString<128> pdbSourcePath; @@ -144,7 +145,7 @@ struct Configuration { unsigned ltoo = 2; // Used for /opt:lldltojobs=N - unsigned thinLTOJobs = 0; + std::string thinLTOJobs; // Used for /opt:lldltopartitions=N unsigned ltoPartitions = 1; @@ -182,6 +183,9 @@ struct Configuration { llvm::StringMap<int> order; // Used for /lldmap. + std::string lldmapFile; + + // Used for /map. std::string mapFile; // Used for /thinlto-index-only: @@ -211,6 +215,7 @@ struct Configuration { uint32_t functionPadMin = 0; bool dynamicBase = true; bool allowBind = true; + bool cetCompat = false; bool nxCompat = true; bool allowIsolation = true; bool terminalServerAware = true; @@ -230,6 +235,8 @@ struct Configuration { bool swaprunNet = false; bool thinLTOEmitImportsFiles; bool thinLTOIndexOnly; + bool autoImport = false; + bool pseudoRelocs = false; }; extern Configuration *config; diff --git a/lld/COFF/DLL.cpp b/lld/COFF/DLL.cpp index 39d9fbab63d5..50301ad91b1d 100644 --- a/lld/COFF/DLL.cpp +++ b/lld/COFF/DLL.cpp @@ -365,7 +365,9 @@ public: class ThunkChunkARM : public NonSectionChunk { public: - ThunkChunkARM(Defined *i, Chunk *tm) : imp(i), tailMerge(tm) {} + ThunkChunkARM(Defined *i, Chunk *tm) : imp(i), tailMerge(tm) { + setAlignment(2); + } size_t getSize() const override { return sizeof(thunkARM); } @@ -385,7 +387,9 @@ public: class TailMergeChunkARM : public NonSectionChunk { public: - TailMergeChunkARM(Chunk *d, Defined *h) : desc(d), helper(h) {} + TailMergeChunkARM(Chunk *d, Defined *h) : desc(d), helper(h) { + setAlignment(2); + } size_t getSize() const override { return sizeof(tailMergeARM); } @@ -405,7 +409,9 @@ public: class ThunkChunkARM64 : public NonSectionChunk { public: - ThunkChunkARM64(Defined *i, Chunk *tm) : imp(i), tailMerge(tm) {} + ThunkChunkARM64(Defined *i, Chunk *tm) : imp(i), tailMerge(tm) { + setAlignment(4); + } size_t getSize() const override { return sizeof(thunkARM64); } @@ -422,7 +428,9 @@ public: class TailMergeChunkARM64 : public NonSectionChunk { public: - TailMergeChunkARM64(Chunk *d, Defined *h) : desc(d), helper(h) {} + TailMergeChunkARM64(Chunk *d, Defined *h) : desc(d), helper(h) { + setAlignment(4); + } size_t getSize() const override { return sizeof(tailMergeARM64); } diff --git a/lld/COFF/DebugTypes.cpp b/lld/COFF/DebugTypes.cpp index 0960f16b01e6..4790b0166799 100644 --- a/lld/COFF/DebugTypes.cpp +++ b/lld/COFF/DebugTypes.cpp @@ -7,22 +7,26 @@ //===----------------------------------------------------------------------===// #include "DebugTypes.h" +#include "Chunks.h" #include "Driver.h" #include "InputFiles.h" +#include "TypeMerger.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Memory.h" #include "llvm/DebugInfo/CodeView/TypeRecord.h" +#include "llvm/DebugInfo/CodeView/TypeRecordHelpers.h" +#include "llvm/DebugInfo/CodeView/TypeStreamMerger.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/DebugInfo/PDB/Native/TpiStream.h" #include "llvm/Support/Path.h" using namespace llvm; using namespace llvm::codeview; - -namespace lld { -namespace coff { +using namespace lld; +using namespace lld::coff; namespace { // The TypeServerSource class represents a PDB type server, a file referenced by @@ -34,36 +38,40 @@ namespace { // 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; + explicit TypeServerSource(PDBInputFile *f) + : TpiSource(PDB, nullptr), pdbInputFile(f) { + if (f->loadErr && *f->loadErr) + return; + pdb::PDBFile &file = f->session->getPDBFile(); + auto expectedInfo = file.getPDBInfoStream(); + if (!expectedInfo) + return; + auto it = mappings.emplace(expectedInfo->getGuid(), this); + assert(it.second); + (void)it; + tsIndexMap.isTypeServerMap = true; + } + + Expected<const CVIndexMap *> mergeDebugT(TypeMerger *m, + CVIndexMap *indexMap) override; + bool isDependency() const override { return true; } + + PDBInputFile *pdbInputFile = nullptr; + + CVIndexMap tsIndexMap; + + static std::map<codeview::GUID, TypeServerSource *> mappings; }; // 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) {} + UseTypeServerSource(ObjFile *f, TypeServer2Record ts) + : TpiSource(UsingPDB, f), typeServerDependency(ts) {} + + Expected<const CVIndexMap *> mergeDebugT(TypeMerger *m, + CVIndexMap *indexMap) override; // Information about the PDB type server dependency, that needs to be loaded // in before merging this OBJ. @@ -76,15 +84,35 @@ public: // such files, clang does not. class PrecompSource : public TpiSource { public: - PrecompSource(const ObjFile *f) : TpiSource(PCH, f) {} + PrecompSource(ObjFile *f) : TpiSource(PCH, f) { + if (!f->pchSignature || !*f->pchSignature) + fatal(toString(f) + + " claims to be a PCH object, but does not have a valid signature"); + auto it = mappings.emplace(*f->pchSignature, this); + if (!it.second) + fatal("a PCH object with the same signature has already been provided (" + + toString(it.first->second->file) + " and " + toString(file) + ")"); + precompIndexMap.isPrecompiledTypeMap = true; + } + + Expected<const CVIndexMap *> mergeDebugT(TypeMerger *m, + CVIndexMap *indexMap) override; + bool isDependency() const override { return true; } + + CVIndexMap precompIndexMap; + + static std::map<uint32_t, PrecompSource *> mappings; }; // 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) {} + UsePrecompSource(ObjFile *f, PrecompRecord precomp) + : TpiSource(UsingPCH, f), precompDependency(precomp) {} + + Expected<const CVIndexMap *> mergeDebugT(TypeMerger *m, + CVIndexMap *indexMap) override; // Information about the Precomp OBJ dependency, that needs to be loaded in // before merging this OBJ. @@ -92,173 +120,363 @@ public: }; } // namespace -TpiSource::TpiSource(TpiKind k, const ObjFile *f) : kind(k), file(f) {} +static std::vector<TpiSource *> gc; -TpiSource *makeTpiSource(const ObjFile *f) { - return make<TpiSource>(TpiSource::Regular, f); +TpiSource::TpiSource(TpiKind k, ObjFile *f) : kind(k), file(f) { + gc.push_back(this); } -TpiSource *makeUseTypeServerSource(const ObjFile *f, - const TypeServer2Record *ts) { - TypeServerSource::enqueue(f, *ts); - return make<UseTypeServerSource>(f, ts); +// Vtable key method. +TpiSource::~TpiSource() = default; + +TpiSource *lld::coff::makeTpiSource(ObjFile *file) { + return make<TpiSource>(TpiSource::Regular, file); } -TpiSource *makePrecompSource(const ObjFile *f) { - return make<PrecompSource>(f); +TpiSource *lld::coff::makeTypeServerSource(PDBInputFile *pdbInputFile) { + return make<TypeServerSource>(pdbInputFile); } -TpiSource *makeUsePrecompSource(const ObjFile *f, - const PrecompRecord *precomp) { - return make<UsePrecompSource>(f, precomp); +TpiSource *lld::coff::makeUseTypeServerSource(ObjFile *file, + TypeServer2Record ts) { + return make<UseTypeServerSource>(file, ts); } -template <> -const PrecompRecord &retrieveDependencyInfo(const TpiSource *source) { - assert(source->kind == TpiSource::UsingPCH); - return ((const UsePrecompSource *)source)->precompDependency; +TpiSource *lld::coff::makePrecompSource(ObjFile *file) { + return make<PrecompSource>(file); } -template <> -const TypeServer2Record &retrieveDependencyInfo(const TpiSource *source) { - assert(source->kind == TpiSource::UsingPDB); - return ((const UseTypeServerSource *)source)->typeServerDependency; +TpiSource *lld::coff::makeUsePrecompSource(ObjFile *file, + PrecompRecord precomp) { + return make<UsePrecompSource>(file, precomp); } -std::map<std::string, std::pair<std::string, TypeServerSource *>> - TypeServerSource::instances; +void TpiSource::forEachSource(llvm::function_ref<void(TpiSource *)> fn) { + for_each(gc, fn); +} -// 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); +std::map<codeview::GUID, TypeServerSource *> TypeServerSource::mappings; + +std::map<uint32_t, PrecompSource *> PrecompSource::mappings; + +// A COFF .debug$H section is currently a clang extension. This function checks +// if a .debug$H section is in a format that we expect / understand, so that we +// can ignore any sections which are coincidentally also named .debug$H but do +// not contain a format we recognize. +static bool canUseDebugH(ArrayRef<uint8_t> debugH) { + if (debugH.size() < sizeof(object::debug_h_header)) + return false; + auto *header = + reinterpret_cast<const object::debug_h_header *>(debugH.data()); + debugH = debugH.drop_front(sizeof(object::debug_h_header)); + return header->Magic == COFF::DEBUG_HASHES_SECTION_MAGIC && + header->Version == 0 && + header->HashAlgorithm == uint16_t(GlobalTypeHashAlg::SHA1_8) && + (debugH.size() % 8 == 0); +} - // 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(); +static Optional<ArrayRef<uint8_t>> getDebugH(ObjFile *file) { + SectionChunk *sec = + SectionChunk::findByName(file->getDebugChunks(), ".debug$H"); + if (!sec) + return llvm::None; + ArrayRef<uint8_t> contents = sec->getContents(); + if (!canUseDebugH(contents)) + return None; + return contents; } -// 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 +static ArrayRef<GloballyHashedType> +getHashesFromDebugH(ArrayRef<uint8_t> debugH) { + assert(canUseDebugH(debugH)); + + debugH = debugH.drop_front(sizeof(object::debug_h_header)); + uint32_t count = debugH.size() / sizeof(GloballyHashedType); + return {reinterpret_cast<const GloballyHashedType *>(debugH.data()), count}; } -// 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; +// Merge .debug$T for a generic object file. +Expected<const CVIndexMap *> TpiSource::mergeDebugT(TypeMerger *m, + CVIndexMap *indexMap) { + CVTypeArray types; + BinaryStreamReader reader(file->debugTypes, support::little); + cantFail(reader.readArray(types, reader.getLength())); + + if (config->debugGHashes) { + ArrayRef<GloballyHashedType> hashes; + std::vector<GloballyHashedType> ownedHashes; + if (Optional<ArrayRef<uint8_t>> debugH = getDebugH(file)) + hashes = getHashesFromDebugH(*debugH); + else { + ownedHashes = GloballyHashedType::hashTypes(types); + hashes = ownedHashes; + } + + if (auto err = mergeTypeAndIdRecords(m->globalIDTable, m->globalTypeTable, + indexMap->tpiMap, types, hashes, + file->pchSignature)) + fatal("codeview::mergeTypeAndIdRecords failed: " + + toString(std::move(err))); + } else { + if (auto err = + mergeTypeAndIdRecords(m->idTable, m->typeTable, indexMap->tpiMap, + types, file->pchSignature)) + fatal("codeview::mergeTypeAndIdRecords failed: " + + toString(std::move(err))); + } + + if (config->showSummary) { + // Count how many times we saw each type record in our input. This + // calculation requires a second pass over the type records to classify each + // record as a type or index. This is slow, but this code executes when + // collecting statistics. + m->tpiCounts.resize(m->getTypeTable().size()); + m->ipiCounts.resize(m->getIDTable().size()); + uint32_t srcIdx = 0; + for (CVType &ty : types) { + TypeIndex dstIdx = indexMap->tpiMap[srcIdx++]; + // Type merging may fail, so a complex source type may become the simple + // NotTranslated type, which cannot be used as an array index. + if (dstIdx.isSimple()) + continue; + SmallVectorImpl<uint32_t> &counts = + isIdRecord(ty.kind()) ? m->ipiCounts : m->tpiCounts; + ++counts[dstIdx.toArrayIndex()]; + } + } + + return indexMap; } -// 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); +// Merge types from a type server PDB. +Expected<const CVIndexMap *> TypeServerSource::mergeDebugT(TypeMerger *m, + CVIndexMap *) { + pdb::PDBFile &pdbFile = pdbInputFile->session->getPDBFile(); + Expected<pdb::TpiStream &> expectedTpi = pdbFile.getPDBTpiStream(); + if (auto e = expectedTpi.takeError()) + fatal("Type server does not have TPI stream: " + toString(std::move(e))); + pdb::TpiStream *maybeIpi = nullptr; + if (pdbFile.hasPDBIpiStream()) { + Expected<pdb::TpiStream &> expectedIpi = pdbFile.getPDBIpiStream(); + if (auto e = expectedIpi.takeError()) + fatal("Error getting type server IPI stream: " + toString(std::move(e))); + maybeIpi = &*expectedIpi; + } + + if (config->debugGHashes) { + // PDBs do not actually store global hashes, so when merging a type server + // PDB we have to synthesize global hashes. To do this, we first synthesize + // global hashes for the TPI stream, since it is independent, then we + // synthesize hashes for the IPI stream, using the hashes for the TPI stream + // as inputs. + auto tpiHashes = GloballyHashedType::hashTypes(expectedTpi->typeArray()); + Optional<uint32_t> endPrecomp; + // Merge TPI first, because the IPI stream will reference type indices. + if (auto err = + mergeTypeRecords(m->globalTypeTable, tsIndexMap.tpiMap, + expectedTpi->typeArray(), tpiHashes, endPrecomp)) + fatal("codeview::mergeTypeRecords failed: " + toString(std::move(err))); + + // Merge IPI. + if (maybeIpi) { + auto ipiHashes = + GloballyHashedType::hashIds(maybeIpi->typeArray(), tpiHashes); + if (auto err = mergeIdRecords(m->globalIDTable, tsIndexMap.tpiMap, + tsIndexMap.ipiMap, maybeIpi->typeArray(), + ipiHashes)) + fatal("codeview::mergeIdRecords failed: " + toString(std::move(err))); + } + } else { + // Merge TPI first, because the IPI stream will reference type indices. + if (auto err = mergeTypeRecords(m->typeTable, tsIndexMap.tpiMap, + expectedTpi->typeArray())) + fatal("codeview::mergeTypeRecords failed: " + toString(std::move(err))); + + // Merge IPI. + if (maybeIpi) { + if (auto err = mergeIdRecords(m->idTable, tsIndexMap.tpiMap, + tsIndexMap.ipiMap, maybeIpi->typeArray())) + fatal("codeview::mergeIdRecords failed: " + toString(std::move(err))); + } + } + + if (config->showSummary) { + // Count how many times we saw each type record in our input. If a + // destination type index is present in the source to destination type index + // map, that means we saw it once in the input. Add it to our histogram. + m->tpiCounts.resize(m->getTypeTable().size()); + m->ipiCounts.resize(m->getIDTable().size()); + for (TypeIndex ti : tsIndexMap.tpiMap) + if (!ti.isSimple()) + ++m->tpiCounts[ti.toArrayIndex()]; + for (TypeIndex ti : tsIndexMap.ipiMap) + if (!ti.isSimple()) + ++m->ipiCounts[ti.toArrayIndex()]; + } + + return &tsIndexMap; +} - Optional<std::string> p = findPdbPath(ts.Name, dependentFile); - if (!p) - return createFileError(ts.Name, errorCodeToError(std::error_code( - ENOENT, std::generic_category()))); +Expected<const CVIndexMap *> +UseTypeServerSource::mergeDebugT(TypeMerger *m, CVIndexMap *indexMap) { + const codeview::GUID &tsId = typeServerDependency.getGuid(); + StringRef tsPath = typeServerDependency.getName(); + + TypeServerSource *tsSrc; + auto it = TypeServerSource::mappings.find(tsId); + if (it != TypeServerSource::mappings.end()) { + tsSrc = it->second; + } else { + // The file failed to load, lookup by name + PDBInputFile *pdb = PDBInputFile::findFromRecordPath(tsPath, file); + if (!pdb) + return createFileError(tsPath, errorCodeToError(std::error_code( + ENOENT, std::generic_category()))); + // If an error occurred during loading, throw it now + if (pdb->loadErr && *pdb->loadErr) + return createFileError(tsPath, std::move(*pdb->loadErr)); + + tsSrc = (TypeServerSource *)pdb->debugTypesObj; + } + + pdb::PDBFile &pdbSession = tsSrc->pdbInputFile->session->getPDBFile(); + auto expectedInfo = pdbSession.getPDBInfoStream(); + if (!expectedInfo) + return &tsSrc->tsIndexMap; + + // 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() != typeServerDependency.getGuid()) + return createFileError( + tsPath, + make_error<pdb::PDBError>(pdb::pdb_error_code::signature_out_of_date)); - 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()); + return &tsSrc->tsIndexMap; +} + +static bool equalsPath(StringRef path1, StringRef path2) { +#if defined(_WIN32) + return path1.equals_lower(path2); +#else + return path1.equals(path2); +#endif +} - std::pair<std::string, TypeServerSource *> &pdb = it->second; +// Find by name an OBJ provided on the command line +static PrecompSource *findObjByName(StringRef fileNameOnly) { + SmallString<128> currentPath; + for (auto kv : PrecompSource::mappings) { + StringRef currentFileName = sys::path::filename(kv.second->file->getName(), + sys::path::Style::windows); + + // Compare based solely on the file name (link.exe behavior) + if (equalsPath(currentFileName, fileNameOnly)) + return kv.second; + } + return nullptr; +} - if (!pdb.second) +Expected<const CVIndexMap *> findPrecompMap(ObjFile *file, PrecompRecord &pr) { + // Cross-compile warning: given that Clang doesn't generate LF_PRECOMP + // records, we assume the OBJ comes from a Windows build of cl.exe. Thusly, + // the paths embedded in the OBJs are in the Windows format. + SmallString<128> prFileName = + sys::path::filename(pr.getPrecompFilePath(), sys::path::Style::windows); + + PrecompSource *precomp; + auto it = PrecompSource::mappings.find(pr.getSignature()); + if (it != PrecompSource::mappings.end()) { + precomp = it->second; + } else { + // Lookup by name + precomp = findObjByName(prFileName); + } + + if (!precomp) return createFileError( - *p, createStringError(inconvertibleErrorCode(), pdb.first.c_str())); + prFileName, + make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch)); - pdb::PDBFile &pdbFile = (pdb.second)->session->getPDBFile(); - pdb::InfoStream &info = cantFail(pdbFile.getPDBInfoStream()); + if (pr.getSignature() != file->pchSignature) + return createFileError( + toString(file), + make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch)); - // 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()) + if (pr.getSignature() != *precomp->file->pchSignature) return createFileError( - ts.Name, - make_error<pdb::PDBError>(pdb::pdb_error_code::signature_out_of_date)); + toString(precomp->file), + make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch)); - return pdb.second; + return &precomp->precompIndexMap; } -// 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(); +/// Merges a precompiled headers TPI map into the current TPI map. The +/// precompiled headers object will also be loaded and remapped in the +/// process. +static Expected<const CVIndexMap *> +mergeInPrecompHeaderObj(ObjFile *file, CVIndexMap *indexMap, + PrecompRecord &precomp) { + auto e = findPrecompMap(file, precomp); + if (!e) + return e.takeError(); + + const CVIndexMap *precompIndexMap = *e; + assert(precompIndexMap->isPrecompiledTypeMap); + + if (precompIndexMap->tpiMap.empty()) + return precompIndexMap; + + assert(precomp.getStartTypeIndex() == TypeIndex::FirstNonSimpleIndex); + assert(precomp.getTypesCount() <= precompIndexMap->tpiMap.size()); + // Use the previously remapped index map from the precompiled headers. + indexMap->tpiMap.append(precompIndexMap->tpiMap.begin(), + precompIndexMap->tpiMap.begin() + + precomp.getTypesCount()); + return indexMap; } -// 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); +Expected<const CVIndexMap *> +UsePrecompSource::mergeDebugT(TypeMerger *m, CVIndexMap *indexMap) { + // This object was compiled with /Yu, so process the corresponding + // precompiled headers object (/Yc) first. Some type indices in the current + // object are referencing data in the precompiled headers object, so we need + // both to be loaded. + auto e = mergeInPrecompHeaderObj(file, indexMap, precompDependency); + if (!e) + return e.takeError(); + + // Drop LF_PRECOMP record from the input stream, as it has been replaced + // with the precompiled headers Type stream in the mergeInPrecompHeaderObj() + // call above. Note that we can't just call Types.drop_front(), as we + // explicitly want to rebase the stream. + CVTypeArray types; + BinaryStreamReader reader(file->debugTypes, support::little); + cantFail(reader.readArray(types, reader.getLength())); + auto firstType = types.begin(); + file->debugTypes = file->debugTypes.drop_front(firstType->RecordData.size()); + + return TpiSource::mergeDebugT(m, indexMap); } -// 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<const CVIndexMap *> PrecompSource::mergeDebugT(TypeMerger *m, + CVIndexMap *) { + // Note that we're not using the provided CVIndexMap. Instead, we use our + // local one. Precompiled headers objects need to save the index map for + // further reference by other objects which use the precompiled headers. + return TpiSource::mergeDebugT(m, &precompIndexMap); } -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 make<TypeServerSource>(m, session.release()); +uint32_t TpiSource::countTypeServerPDBs() { + return TypeServerSource::mappings.size(); } -} // namespace coff -} // namespace lld +uint32_t TpiSource::countPrecompObjs() { + return PrecompSource::mappings.size(); +} + +void TpiSource::clear() { + gc.clear(); + TypeServerSource::mappings.clear(); + PrecompSource::mappings.clear(); +} diff --git a/lld/COFF/DebugTypes.h b/lld/COFF/DebugTypes.h index e37c727232d0..24d79d83e4c6 100644 --- a/lld/COFF/DebugTypes.h +++ b/lld/COFF/DebugTypes.h @@ -26,35 +26,55 @@ namespace lld { namespace coff { class ObjFile; +class PDBInputFile; +struct CVIndexMap; +class TypeMerger; class TpiSource { public: enum TpiKind { Regular, PCH, UsingPCH, PDB, UsingPDB }; - TpiSource(TpiKind k, const ObjFile *f); - virtual ~TpiSource() {} + TpiSource(TpiKind k, ObjFile *f); + virtual ~TpiSource(); - const TpiKind kind; - const ObjFile *file; -}; + /// Produce a mapping from the type and item indices used in the object + /// file to those in the destination PDB. + /// + /// If the object file uses a type server PDB (compiled with /Zi), merge TPI + /// and IPI from the type server PDB and return a map for it. Each unique type + /// server PDB is merged at most once, so this may return an existing index + /// mapping. + /// + /// If the object does not use a type server PDB (compiled with /Z7), we merge + /// all the type and item records from the .debug$S stream and fill in the + /// caller-provided ObjectIndexMap. + virtual llvm::Expected<const CVIndexMap *> mergeDebugT(TypeMerger *m, + CVIndexMap *indexMap); + /// Is this a dependent file that needs to be processed first, before other + /// OBJs? + virtual bool isDependency() const { return false; } + + static void forEachSource(llvm::function_ref<void(TpiSource *)> fn); -TpiSource *makeTpiSource(const ObjFile *f); -TpiSource *makeUseTypeServerSource(const ObjFile *f, - const llvm::codeview::TypeServer2Record *ts); -TpiSource *makePrecompSource(const ObjFile *f); -TpiSource *makeUsePrecompSource(const ObjFile *f, - const llvm::codeview::PrecompRecord *precomp); + static uint32_t countTypeServerPDBs(); + static uint32_t countPrecompObjs(); -void loadTypeServerSource(llvm::MemoryBufferRef m); + /// Clear global data structures for TpiSources. + static void clear(); -// Temporary interface to get the dependency -template <typename T> const T &retrieveDependencyInfo(const TpiSource *source); + const TpiKind kind; + ObjFile *file; +}; -// Temporary interface until we move PDBLinker::maybeMergeTypeServerPDB here -llvm::Expected<llvm::pdb::NativeSession *> -findTypeServerSource(const ObjFile *f); +TpiSource *makeTpiSource(ObjFile *file); +TpiSource *makeTypeServerSource(PDBInputFile *pdbInputFile); +TpiSource *makeUseTypeServerSource(ObjFile *file, + llvm::codeview::TypeServer2Record ts); +TpiSource *makePrecompSource(ObjFile *file); +TpiSource *makeUsePrecompSource(ObjFile *file, + llvm::codeview::PrecompRecord ts); } // namespace coff } // namespace lld -#endif
\ No newline at end of file +#endif diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index f770fff80bcb..7372505bb616 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -21,7 +21,6 @@ #include "lld/Common/ErrorHandler.h" #include "lld/Common/Filesystem.h" #include "lld/Common/Memory.h" -#include "lld/Common/Threads.h" #include "lld/Common/Timer.h" #include "lld/Common/Version.h" #include "llvm/ADT/Optional.h" @@ -39,6 +38,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/LEB128.h" #include "llvm/Support/MathExtras.h" +#include "llvm/Support/Parallel.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/TarWriter.h" @@ -89,6 +89,8 @@ bool link(ArrayRef<const char *> args, bool canExitEarly, raw_ostream &stdoutOS, ImportFile::instances.clear(); BitcodeFile::instances.clear(); memset(MergeChunk::instances, 0, sizeof(MergeChunk::instances)); + TpiSource::clear(); + return !errorCount(); } @@ -218,7 +220,7 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb, symtab->addFile(make<ObjFile>(mbref)); break; case file_magic::pdb: - loadTypeServerSource(mbref); + symtab->addFile(make<PDBInputFile>(mbref)); break; case file_magic::coff_cl_gl_object: error(filename + ": is not a native COFF file. Recompile without /GL"); @@ -237,9 +239,9 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb, } void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) { - auto future = - std::make_shared<std::future<MBErrPair>>(createFutureForFile(path)); - std::string pathStr = path; + auto future = std::make_shared<std::future<MBErrPair>>( + createFutureForFile(std::string(path))); + std::string pathStr = std::string(path); enqueueTask([=]() { auto mbOrErr = future->get(); if (mbOrErr.second) { @@ -251,7 +253,7 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) { // the option `/nodefaultlib` than a reference to a file in the root // directory. std::string nearest; - if (COFFOptTable().findNearest(pathStr, nearest) > 1) + if (optTable.findNearest(pathStr, nearest) > 1) error(msg); else error(msg + "; did you mean '" + nearest + "'"); @@ -343,11 +345,9 @@ void LinkerDriver::parseDirectives(InputFile *file) { ArgParser parser; // .drectve is always tokenized using Windows shell rules. // /EXPORT: option can appear too many times, processing in fastpath. - opt::InputArgList args; - std::vector<StringRef> exports; - std::tie(args, exports) = parser.parseDirectives(s); + ParsedDirectives directives = parser.parseDirectives(s); - for (StringRef e : exports) { + for (StringRef e : directives.exports) { // If a common header file contains dllexported function // declarations, many object files may end up with having the // same /EXPORT options. In order to save cost of parsing them, @@ -366,7 +366,11 @@ void LinkerDriver::parseDirectives(InputFile *file) { config->exports.push_back(exp); } - for (auto *arg : args) { + // Handle /include: in bulk. + for (StringRef inc : directives.includes) + addUndefined(inc); + + for (auto *arg : directives.args) { switch (arg->getOption().getID()) { case OPT_aligncomm: parseAligncomm(arg->getValue()); @@ -452,7 +456,7 @@ Optional<StringRef> LinkerDriver::findFile(StringRef filename) { } if (path.endswith_lower(".lib")) - visitedLibs.insert(sys::path::filename(path)); + visitedLibs.insert(std::string(sys::path::filename(path))); return path; } @@ -624,6 +628,7 @@ static std::string createResponseFile(const opt::InputArgList &args, break; case OPT_implib: case OPT_pdb: + case OPT_pdbstripped: case OPT_out: os << arg->getSpelling() << sys::path::filename(arg->getValue()) << "\n"; break; @@ -640,7 +645,7 @@ static std::string createResponseFile(const opt::InputArgList &args, for (StringRef path : filePaths) os << quote(relativeToRoot(path)) << "\n"; - return data.str(); + return std::string(data.str()); } enum class DebugKind { Unknown, None, Full, FastLink, GHash, Dwarf, Symtab }; @@ -706,24 +711,25 @@ static unsigned parseDebugTypes(const opt::InputArgList &args) { return debugTypes; } -static std::string getMapFile(const opt::InputArgList &args) { - auto *arg = args.getLastArg(OPT_lldmap, OPT_lldmap_file); +static std::string getMapFile(const opt::InputArgList &args, + opt::OptSpecifier os, opt::OptSpecifier osFile) { + auto *arg = args.getLastArg(os, osFile); if (!arg) return ""; - if (arg->getOption().getID() == OPT_lldmap_file) + if (arg->getOption().getID() == osFile.getID()) return arg->getValue(); - assert(arg->getOption().getID() == OPT_lldmap); + assert(arg->getOption().getID() == os.getID()); StringRef outFile = config->outputFile; return (outFile.substr(0, outFile.rfind('.')) + ".map").str(); } static std::string getImplibPath() { if (!config->implib.empty()) - return config->implib; + return std::string(config->implib); SmallString<128> out = StringRef(config->outputFile); sys::path::replace_extension(out, ".lib"); - return out.str(); + return std::string(out.str()); } // The import name is calculated as follows: @@ -747,16 +753,16 @@ static std::string getImportName(bool asLib) { (config->dll || asLib) ? ".dll" : ".exe"); } - return out.str(); + return std::string(out.str()); } static void createImportLibrary(bool asLib) { std::vector<COFFShortExport> exports; for (Export &e1 : config->exports) { COFFShortExport e2; - e2.Name = e1.name; - e2.SymbolName = e1.symbolName; - e2.ExtName = e1.extName; + e2.Name = std::string(e1.name); + e2.SymbolName = std::string(e1.symbolName); + e2.ExtName = std::string(e1.extName); e2.Ordinal = e1.ordinal; e2.Noname = e1.noname; e2.Data = e1.data; @@ -817,8 +823,8 @@ static void parseModuleDefs(StringRef path) { mb->getMemBufferRef(), config->machine, config->mingw)); if (config->outputFile.empty()) - config->outputFile = saver.save(m.OutputFile); - config->importName = saver.save(m.ImportName); + config->outputFile = std::string(saver.save(m.OutputFile)); + config->importName = std::string(saver.save(m.ImportName)); if (m.ImageBase) config->imageBase = m.ImageBase; if (m.StackReserve) @@ -844,7 +850,8 @@ static void parseModuleDefs(StringRef path) { // and set as "ExtName = Name". If Name has the form "OtherDll.Func", // it shouldn't be a normal exported function but a forward to another // DLL instead. This is supported by both MS and GNU linkers. - if (e1.ExtName != e1.Name && StringRef(e1.Name).contains('.')) { + if (!e1.ExtName.empty() && e1.ExtName != e1.Name && + StringRef(e1.Name).contains('.')) { e2.name = saver.save(e1.ExtName); e2.forwardTo = saver.save(e1.Name); config->exports.push_back(e2); @@ -903,7 +910,8 @@ static void parseOrderFile(StringRef arg) { // All symbols that were not present in a given order file are // considered to have the lowest priority 0 and are placed at // end of an output section. - for (std::string s : args::getLines(mb->getMemBufferRef())) { + for (StringRef arg : args::getLines(mb->getMemBufferRef())) { + std::string s(arg); if (config->machine == I386 && !isDecorated(s)) s = "_" + s; @@ -1093,13 +1101,15 @@ Optional<std::string> getReproduceFile(const opt::InputArgList &args) { if (auto *arg = args.getLastArg(OPT_linkrepro)) { SmallString<64> path = StringRef(arg->getValue()); sys::path::append(path, "repro.tar"); - return path.str().str(); + return std::string(path); } return None; } void LinkerDriver::link(ArrayRef<const char *> argsArr) { + ScopedTimer rootTimer(Timer::root()); + // Needed for LTO. InitializeAllTargetInfos(); InitializeAllTargets(); @@ -1141,14 +1151,23 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) { return; } - lld::threadsEnabled = args.hasFlag(OPT_threads, OPT_threads_no, true); + // /threads: takes a positive integer and provides the default value for + // /opt:lldltojobs=. + if (auto *arg = args.getLastArg(OPT_threads)) { + StringRef v(arg->getValue()); + unsigned threads = 0; + if (!llvm::to_integer(v, threads, 0) || threads == 0) + error(arg->getSpelling() + ": expected a positive integer, but got '" + + arg->getValue() + "'"); + parallel::strategy = hardware_concurrency(threads); + config->thinLTOJobs = v.str(); + } if (args.hasArg(OPT_show_timing)) config->showTiming = true; config->showSummary = args.hasArg(OPT_summary); - ScopedTimer t(Timer::root()); // Handle --version, which is an lld extension. This option is a bit odd // because it doesn't start with "/", but we deliberately chose "--" to // avoid conflict with /version and for compatibility with clang-cl. @@ -1260,11 +1279,23 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) { config->pdbAltPath = arg->getValue(); if (args.hasArg(OPT_natvis)) config->natvisFiles = args.getAllArgValues(OPT_natvis); + if (args.hasArg(OPT_pdbstream)) { + for (const StringRef value : args.getAllArgValues(OPT_pdbstream)) { + const std::pair<StringRef, StringRef> nameFile = value.split("="); + const StringRef name = nameFile.first; + const std::string file = nameFile.second.str(); + config->namedStreams[name] = file; + } + } if (auto *arg = args.getLastArg(OPT_pdb_source_path)) config->pdbSourcePath = arg->getValue(); } + // Handle /pdbstripped + if (args.hasArg(OPT_pdbstripped)) + warn("ignoring /pdbstripped flag, it is not yet supported"); + // Handle /noentry if (args.hasArg(OPT_noentry)) { if (args.hasArg(OPT_dll)) @@ -1410,9 +1441,9 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) { error("/opt:lldlto: invalid optimization level: " + optLevel); } else if (s.startswith("lldltojobs=")) { StringRef jobs = s.substr(11); - if (jobs.getAsInteger(10, config->thinLTOJobs) || - config->thinLTOJobs == 0) + if (!get_threadpool_strategy(jobs)) error("/opt:lldltojobs: invalid job count: " + jobs); + config->thinLTOJobs = jobs.str(); } else if (s.startswith("lldltopartitions=")) { StringRef n = s.substr(17); if (n.getAsInteger(10, config->ltoPartitions) || @@ -1543,6 +1574,7 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) { !args.hasArg(OPT_profile)); config->integrityCheck = args.hasFlag(OPT_integritycheck, OPT_integritycheck_no, false); + config->cetCompat = args.hasFlag(OPT_cetcompat, OPT_cetcompat_no, false); config->nxCompat = args.hasFlag(OPT_nxcompat, OPT_nxcompat_no, true); for (auto *arg : args.filtered(OPT_swaprun)) parseSwaprun(arg->getValue()); @@ -1551,13 +1583,24 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) { config->debugDwarf = debug == DebugKind::Dwarf; config->debugGHashes = debug == DebugKind::GHash; config->debugSymtab = debug == DebugKind::Symtab; + config->autoImport = + args.hasFlag(OPT_auto_import, OPT_auto_import_no, config->mingw); + config->pseudoRelocs = args.hasFlag( + OPT_runtime_pseudo_reloc, OPT_runtime_pseudo_reloc_no, config->mingw); // Don't warn about long section names, such as .debug_info, for mingw or when // -debug:dwarf is requested. if (config->mingw || config->debugDwarf) config->warnLongSectionNames = false; - config->mapFile = getMapFile(args); + config->lldmapFile = getMapFile(args, OPT_lldmap, OPT_lldmap_file); + config->mapFile = getMapFile(args, OPT_map, OPT_map_file); + + if (config->lldmapFile != "" && config->lldmapFile == config->mapFile) { + warn("/lldmap and /map have the same output file '" + config->mapFile + + "'.\n>>> ignoring /lldmap"); + config->lldmapFile.clear(); + } if (config->incremental && args.hasArg(OPT_profile)) { warn("ignoring '/incremental' due to '/profile' specification"); @@ -1805,9 +1848,11 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) { // Needed for MSVC 2017 15.5 CRT. symtab->addAbsolute(mangle("__enclave_config"), 0); - if (config->mingw) { + if (config->pseudoRelocs) { symtab->addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST__"), 0); symtab->addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST_END__"), 0); + } + if (config->mingw) { symtab->addAbsolute(mangle("__CTOR_LIST__"), 0); symtab->addAbsolute(mangle("__DTOR_LIST__"), 0); } @@ -1865,7 +1910,8 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) { while (run()); } - if (config->mingw) { + if (config->autoImport) { + // MinGW specific. // Load any further object files that might be needed for doing automatic // imports. // @@ -1997,7 +2043,7 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) { writeResult(); // Stop early so we can print the results. - Timer::root().stop(); + rootTimer.stop(); if (config->showTiming) Timer::root().print(); } diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index cc2f25a6f95e..3fee9b1fe50e 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -41,6 +41,21 @@ public: COFFOptTable(); }; +// Constructing the option table is expensive. Use a global table to avoid doing +// it more than once. +extern COFFOptTable optTable; + +// The result of parsing the .drective section. The /export: and /include: +// options are handled separately because they reference symbols, and the number +// of symbols can be quite large. The LLVM Option library will perform at least +// one memory allocation per argument, and that is prohibitively slow for +// parsing directives. +struct ParsedDirectives { + std::vector<StringRef> exports; + std::vector<StringRef> includes; + llvm::opt::InputArgList args; +}; + class ArgParser { public: // Parses command line options. @@ -52,16 +67,13 @@ public: // Tokenizes a given string and then parses as command line options in // .drectve section. /EXPORT options are returned in second element // to be processed in fastpath. - std::pair<llvm::opt::InputArgList, std::vector<StringRef>> - parseDirectives(StringRef s); + ParsedDirectives parseDirectives(StringRef s); private: // Concatenate LINK environment variable. void addLINK(SmallVector<const char *, 256> &argv); std::vector<const char *> tokenize(StringRef s); - - COFFOptTable table; }; class LinkerDriver { @@ -75,6 +87,8 @@ public: void enqueueArchiveMember(const Archive::Child &c, const Archive::Symbol &sym, StringRef parentName); + void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false); } + MemoryBufferRef takeBuffer(std::unique_ptr<MemoryBuffer> mb); void enqueuePath(StringRef path, bool wholeArchive, bool lazy); diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp index 301a5c5efa8a..6cb761abea4e 100644 --- a/lld/COFF/DriverUtils.cpp +++ b/lld/COFF/DriverUtils.cpp @@ -220,7 +220,8 @@ void parseAligncomm(StringRef s) { error("/aligncomm: invalid argument: " + s); return; } - config->alignComm[name] = std::max(config->alignComm[name], 1 << v); + config->alignComm[std::string(name)] = + std::max(config->alignComm[std::string(name)], 1 << v); } // Parses /functionpadmin option argument. @@ -318,7 +319,7 @@ public: SmallString<128> s; if (auto ec = sys::fs::createTemporaryFile("lld-" + prefix, extn, s)) fatal("cannot create a temporary file: " + ec.message()); - path = s.str(); + path = std::string(s.str()); if (!contents.empty()) { std::error_code ec; @@ -403,7 +404,7 @@ static std::string createManifestXmlWithInternalMt(StringRef defaultXml) { toString(std::move(e))); } - return merger.getMergedManifest().get()->getBuffer(); + return std::string(merger.getMergedManifest().get()->getBuffer()); } static std::string createManifestXmlWithExternalMt(StringRef defaultXml) { @@ -431,9 +432,10 @@ static std::string createManifestXmlWithExternalMt(StringRef defaultXml) { e.add("/out:" + StringRef(user.path)); e.run(); - return CHECK(MemoryBuffer::getFile(user.path), "could not open " + user.path) - .get() - ->getBuffer(); + return std::string( + CHECK(MemoryBuffer::getFile(user.path), "could not open " + user.path) + .get() + ->getBuffer()); } static std::string createManifestXml() { @@ -507,7 +509,7 @@ std::unique_ptr<MemoryBuffer> createManifestRes() { } void createSideBySideManifest() { - std::string path = config->manifestFile; + std::string path = std::string(config->manifestFile); if (path == "") path = config->outputFile + ".manifest"; std::error_code ec; @@ -765,6 +767,8 @@ static const llvm::opt::OptTable::Info infoTable[] = { COFFOptTable::COFFOptTable() : OptTable(infoTable, true) {} +COFFOptTable optTable; + // Set color diagnostics according to --color-diagnostics={auto,always,never} // or --no-color-diagnostics flags. static void handleColorDiagnostics(opt::InputArgList &args) { @@ -810,8 +814,7 @@ opt::InputArgList ArgParser::parse(ArrayRef<const char *> argv) { // options so we parse here before and ignore all the options but // --rsp-quoting and /lldignoreenv. // (This means --rsp-quoting can't be added through %LINK%.) - opt::InputArgList args = table.ParseArgs(argv, missingIndex, missingCount); - + opt::InputArgList args = optTable.ParseArgs(argv, missingIndex, missingCount); // Expand response files (arguments in the form of @<filename>) and insert // flags from %LINK% and %_LINK_%, and then parse the argument again. @@ -820,8 +823,8 @@ opt::InputArgList ArgParser::parse(ArrayRef<const char *> argv) { if (!args.hasArg(OPT_lldignoreenv)) addLINK(expandedArgv); cl::ExpandResponseFiles(saver, getQuotingStyle(args), expandedArgv); - args = table.ParseArgs(makeArrayRef(expandedArgv).drop_front(), missingIndex, - missingCount); + args = optTable.ParseArgs(makeArrayRef(expandedArgv).drop_front(), + missingIndex, missingCount); // Print the real command line if response files are expanded. if (args.hasArg(OPT_verbose) && argv.size() != expandedArgv.size()) { @@ -845,7 +848,7 @@ opt::InputArgList ArgParser::parse(ArrayRef<const char *> argv) { for (auto *arg : args.filtered(OPT_UNKNOWN)) { std::string nearest; - if (table.findNearest(arg->getAsString(args), nearest) > 1) + if (optTable.findNearest(arg->getAsString(args), nearest) > 1) warn("ignoring unknown argument '" + arg->getAsString(args) + "'"); else warn("ignoring unknown argument '" + arg->getAsString(args) + @@ -859,30 +862,38 @@ opt::InputArgList ArgParser::parse(ArrayRef<const char *> argv) { } // Tokenizes and parses a given string as command line in .drective section. -// /EXPORT options are processed in fastpath. -std::pair<opt::InputArgList, std::vector<StringRef>> -ArgParser::parseDirectives(StringRef s) { - std::vector<StringRef> exports; +ParsedDirectives ArgParser::parseDirectives(StringRef s) { + ParsedDirectives result; SmallVector<const char *, 16> rest; - for (StringRef tok : tokenize(s)) { + // Handle /EXPORT and /INCLUDE in a fast path. These directives can appear for + // potentially every symbol in the object, so they must be handled quickly. + SmallVector<StringRef, 16> tokens; + cl::TokenizeWindowsCommandLineNoCopy(s, saver, tokens); + for (StringRef tok : tokens) { if (tok.startswith_lower("/export:") || tok.startswith_lower("-export:")) - exports.push_back(tok.substr(strlen("/export:"))); - else - rest.push_back(tok.data()); + result.exports.push_back(tok.substr(strlen("/export:"))); + else if (tok.startswith_lower("/include:") || + tok.startswith_lower("-include:")) + result.includes.push_back(tok.substr(strlen("/include:"))); + else { + // Save non-null-terminated strings to make proper C strings. + bool HasNul = tok.data()[tok.size()] == '\0'; + rest.push_back(HasNul ? tok.data() : saver.save(tok).data()); + } } // Make InputArgList from unparsed string vectors. unsigned missingIndex; unsigned missingCount; - opt::InputArgList args = table.ParseArgs(rest, missingIndex, missingCount); + result.args = optTable.ParseArgs(rest, missingIndex, missingCount); if (missingCount) - fatal(Twine(args.getArgString(missingIndex)) + ": missing argument"); - for (auto *arg : args.filtered(OPT_UNKNOWN)) - warn("ignoring unknown argument: " + arg->getAsString(args)); - return {std::move(args), std::move(exports)}; + fatal(Twine(result.args.getArgString(missingIndex)) + ": missing argument"); + for (auto *arg : result.args.filtered(OPT_UNKNOWN)) + warn("ignoring unknown argument: " + arg->getAsString(result.args)); + return result; } // link.exe has an interesting feature. If LINK or _LINK_ environment @@ -907,9 +918,9 @@ std::vector<const char *> ArgParser::tokenize(StringRef s) { } void printHelp(const char *argv0) { - COFFOptTable().PrintHelp(lld::outs(), - (std::string(argv0) + " [options] file...").c_str(), - "LLVM Linker", false); + optTable.PrintHelp(lld::outs(), + (std::string(argv0) + " [options] file...").c_str(), + "LLVM Linker", false); } } // namespace coff diff --git a/lld/COFF/ICF.cpp b/lld/COFF/ICF.cpp index c821569e3457..1b33634b63d6 100644 --- a/lld/COFF/ICF.cpp +++ b/lld/COFF/ICF.cpp @@ -21,7 +21,6 @@ #include "Chunks.h" #include "Symbols.h" #include "lld/Common/ErrorHandler.h" -#include "lld/Common/Threads.h" #include "lld/Common/Timer.h" #include "llvm/ADT/Hashing.h" #include "llvm/Support/Debug.h" @@ -127,15 +126,19 @@ void ICF::segregate(size_t begin, size_t end, bool constant) { // Returns true if two sections' associative children are equal. bool ICF::assocEquals(const SectionChunk *a, const SectionChunk *b) { - auto childClasses = [&](const SectionChunk *sc) { - std::vector<uint32_t> classes; - for (const SectionChunk &c : sc->children()) - if (!c.getSectionName().startswith(".debug") && - c.getSectionName() != ".gfids$y" && c.getSectionName() != ".gljmp$y") - classes.push_back(c.eqClass[cnt % 2]); - return classes; + // Ignore associated metadata sections that don't participate in ICF, such as + // debug info and CFGuard metadata. + auto considerForICF = [](const SectionChunk &assoc) { + StringRef Name = assoc.getSectionName(); + return !(Name.startswith(".debug") || Name == ".gfids$y" || + Name == ".gljmp$y"); }; - return childClasses(a) == childClasses(b); + auto ra = make_filter_range(a->children(), considerForICF); + auto rb = make_filter_range(b->children(), considerForICF); + return std::equal(ra.begin(), ra.end(), rb.begin(), rb.end(), + [&](const SectionChunk &ia, const SectionChunk &ib) { + return ia.eqClass[cnt % 2] == ib.eqClass[cnt % 2]; + }); } // Compare "non-moving" part of two sections, namely everything diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp index d884201ba31b..0adc2b91bd99 100644 --- a/lld/COFF/InputFiles.cpp +++ b/lld/COFF/InputFiles.cpp @@ -25,6 +25,8 @@ #include "llvm/DebugInfo/CodeView/SymbolDeserializer.h" #include "llvm/DebugInfo/CodeView/SymbolRecord.h" #include "llvm/DebugInfo/CodeView/TypeDeserializer.h" +#include "llvm/DebugInfo/PDB/Native/NativeSession.h" +#include "llvm/DebugInfo/PDB/Native/PDBFile.h" #include "llvm/LTO/LTO.h" #include "llvm/Object/Binary.h" #include "llvm/Object/COFF.h" @@ -44,32 +46,31 @@ using namespace llvm::COFF; using namespace llvm::codeview; using namespace llvm::object; using namespace llvm::support::endian; +using namespace lld; +using namespace lld::coff; using llvm::Triple; using llvm::support::ulittle32_t; -namespace lld { - // Returns the last element of a path, which is supposed to be a filename. static StringRef getBasename(StringRef path) { return sys::path::filename(path, sys::path::Style::windows); } // Returns a string in the format of "foo.obj" or "foo.obj(bar.lib)". -std::string toString(const coff::InputFile *file) { +std::string lld::toString(const coff::InputFile *file) { if (!file) return "<internal>"; if (file->parentName.empty() || file->kind() == coff::InputFile::ImportKind) - return file->getName(); + return std::string(file->getName()); return (getBasename(file->parentName) + "(" + getBasename(file->getName()) + ")") .str(); } -namespace coff { - std::vector<ObjFile *> ObjFile::instances; +std::map<std::string, PDBInputFile *> PDBInputFile::instances; std::vector<ImportFile *> ImportFile::instances; std::vector<BitcodeFile *> BitcodeFile::instances; @@ -121,7 +122,7 @@ void ArchiveFile::addMember(const Archive::Symbol &sym) { driver->enqueueArchiveMember(c, sym, getName()); } -std::vector<MemoryBufferRef> getArchiveMembers(Archive *file) { +std::vector<MemoryBufferRef> lld::coff::getArchiveMembers(Archive *file) { std::vector<MemoryBufferRef> v; Error err = Error::success(); for (const Archive::Child &c : file->children(err)) { @@ -171,8 +172,7 @@ void LazyObjFile::parse() { if (coffSym.isUndefined() || !coffSym.isExternal() || coffSym.isWeakExternal()) continue; - StringRef name; - coffObj->getSymbolName(coffSym, name); + StringRef name = check(coffObj->getSymbolName(coffSym)); if (coffSym.isAbsolute() && ignoredSymbolName(name)) continue; symtab->addLazyObject(this, name); @@ -198,11 +198,11 @@ void ObjFile::parse() { initializeDependencies(); } -const coff_section* ObjFile::getSection(uint32_t i) { - const coff_section *sec; - if (auto ec = coffObj->getSection(i, sec)) - fatal("getSection failed: #" + Twine(i) + ": " + ec.message()); - return sec; +const coff_section *ObjFile::getSection(uint32_t i) { + auto sec = coffObj->getSection(i); + if (!sec) + fatal("getSection failed: #" + Twine(i) + ": " + toString(sec.takeError())); + return *sec; } // We set SectionChunk pointers in the SparseChunks vector to this value @@ -215,7 +215,6 @@ static SectionChunk *const pendingComdat = reinterpret_cast<SectionChunk *>(1); void ObjFile::initializeChunks() { uint32_t numSections = coffObj->getNumberOfSections(); - chunks.reserve(numSections); sparseChunks.resize(numSections + 1); for (uint32_t i = 1; i < numSections + 1; ++i) { const coff_section *sec = getSection(i); @@ -279,7 +278,7 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber, else if (name == ".gljmp$y") guardLJmpChunks.push_back(c); else if (name == ".sxdata") - sXDataChunks.push_back(c); + sxDataChunks.push_back(c); else if (config->tailMerge && sec->NumberOfRelocations == 0 && name == ".rdata" && leaderName.startswith("??_C@")) // COFF sections that look like string literal sections (i.e. no @@ -310,9 +309,9 @@ void ObjFile::readAssociativeDefinition(COFFSymbolRef sym, int32_t sectionNumber = sym.getSectionNumber(); auto diag = [&]() { - StringRef name, parentName; - coffObj->getSymbolName(sym, name); + StringRef name = check(coffObj->getSymbolName(sym)); + StringRef parentName; const coff_section *parentSec = getSection(parentIndex); if (Expected<StringRef> e = coffObj->getSectionName(parentSec)) parentName = *e; @@ -353,7 +352,7 @@ void ObjFile::recordPrevailingSymbolForMingw( SectionChunk *sc = sparseChunks[sectionNumber]; if (sc && sc->getOutputCharacteristics() & IMAGE_SCN_MEM_EXECUTE) { StringRef name; - coffObj->getSymbolName(sym, name); + name = check(coffObj->getSymbolName(sym)); if (getMachineType() == I386) name.consume_front("_"); prevailingSectionMap[name] = sectionNumber; @@ -363,8 +362,7 @@ void ObjFile::recordPrevailingSymbolForMingw( void ObjFile::maybeAssociateSEHForMingw( COFFSymbolRef sym, const coff_aux_section_definition *def, const DenseMap<StringRef, uint32_t> &prevailingSectionMap) { - StringRef name; - coffObj->getSymbolName(sym, name); + StringRef name = check(coffObj->getSymbolName(sym)); if (name.consume_front(".pdata$") || name.consume_front(".xdata$") || name.consume_front(".eh_frame$")) { // For MinGW, treat .[px]data$<func> and .eh_frame$<func> as implicitly @@ -378,8 +376,7 @@ void ObjFile::maybeAssociateSEHForMingw( Symbol *ObjFile::createRegular(COFFSymbolRef sym) { SectionChunk *sc = sparseChunks[sym.getSectionNumber()]; if (sym.isExternal()) { - StringRef name; - coffObj->getSymbolName(sym, name); + StringRef name = check(coffObj->getSymbolName(sym)); if (sc) return symtab->addRegular(this, name, sym.getGeneric(), sc, sym.getValue()); @@ -447,8 +444,7 @@ void ObjFile::initializeSymbols() { maybeAssociateSEHForMingw(sym, def, prevailingSectionMap); } if (sparseChunks[sym.getSectionNumber()] == pendingComdat) { - StringRef name; - coffObj->getSymbolName(sym, name); + StringRef name = check(coffObj->getSymbolName(sym)); log("comdat section " + name + " without leader and unassociated, discarding"); continue; @@ -461,11 +457,13 @@ void ObjFile::initializeSymbols() { uint32_t idx = kv.second; checkAndSetWeakAlias(symtab, this, sym, symbols[idx]); } + + // Free the memory used by sparseChunks now that symbol loading is finished. + decltype(sparseChunks)().swap(sparseChunks); } Symbol *ObjFile::createUndefined(COFFSymbolRef sym) { - StringRef name; - coffObj->getSymbolName(sym, name); + StringRef name = check(coffObj->getSymbolName(sym)); return symtab->addUndefined(name, this, sym.isWeakExternal()); } @@ -500,6 +498,17 @@ void ObjFile::handleComdatSelection(COFFSymbolRef sym, COMDATType &selection, leaderSelection = selection = IMAGE_COMDAT_SELECT_LARGEST; } + // GCCs __declspec(selectany) doesn't actually pick "any" but "same size as". + // Clang on the other hand picks "any". To be able to link two object files + // with a __declspec(selectany) declaration, one compiled with gcc and the + // other with clang, we merge them as proper "same size as" + if (config->mingw && ((selection == IMAGE_COMDAT_SELECT_ANY && + leaderSelection == IMAGE_COMDAT_SELECT_SAME_SIZE) || + (selection == IMAGE_COMDAT_SELECT_SAME_SIZE && + leaderSelection == IMAGE_COMDAT_SELECT_ANY))) { + leaderSelection = selection = IMAGE_COMDAT_SELECT_SAME_SIZE; + } + // Other than that, comdat selections must match. This is a bit more // strict than link.exe which allows merging "any" and "largest" if "any" // is the first symbol the linker sees, and it allows merging "largest" @@ -550,8 +559,7 @@ void ObjFile::handleComdatSelection(COFFSymbolRef sym, COMDATType &selection, case IMAGE_COMDAT_SELECT_LARGEST: if (leaderChunk->getSize() < getSection(sym)->SizeOfRawData) { // Replace the existing comdat symbol with the new one. - StringRef name; - coffObj->getSymbolName(sym, name); + StringRef name = check(coffObj->getSymbolName(sym)); // FIXME: This is incorrect: With /opt:noref, the previous sections // make it into the final executable as well. Correct handling would // be to undo reading of the whole old section that's being replaced, @@ -575,11 +583,7 @@ Optional<Symbol *> ObjFile::createDefined( std::vector<const coff_aux_section_definition *> &comdatDefs, bool &prevailing) { prevailing = false; - auto getName = [&]() { - StringRef s; - coffObj->getSymbolName(sym, s); - return s; - }; + auto getName = [&]() { return check(coffObj->getSymbolName(sym)); }; if (sym.isCommon()) { auto *c = make<CommonChunk>(sym); @@ -756,10 +760,11 @@ void ObjFile::initializeDependencies() { if (data.empty()) return; + // Get the first type record. It will indicate if this object uses a type + // server (/Zi) or a PCH file (/Yu). CVTypeArray types; BinaryStreamReader reader(data, support::little); cantFail(reader.readArray(types, reader.getLength())); - CVTypeArray::Iterator firstType = types.begin(); if (firstType == types.end()) return; @@ -767,28 +772,120 @@ void ObjFile::initializeDependencies() { // Remember the .debug$T or .debug$P section. debugTypes = data; + // This object file is a PCH file that others will depend on. if (isPCH) { debugTypesObj = makePrecompSource(this); return; } + // This object file was compiled with /Zi. Enqueue the PDB dependency. if (firstType->kind() == LF_TYPESERVER2) { TypeServer2Record ts = cantFail( TypeDeserializer::deserializeAs<TypeServer2Record>(firstType->data())); - debugTypesObj = makeUseTypeServerSource(this, &ts); + debugTypesObj = makeUseTypeServerSource(this, ts); + PDBInputFile::enqueue(ts.getName(), this); return; } + // This object was compiled with /Yu. It uses types from another object file + // with a matching signature. if (firstType->kind() == LF_PRECOMP) { PrecompRecord precomp = cantFail( TypeDeserializer::deserializeAs<PrecompRecord>(firstType->data())); - debugTypesObj = makeUsePrecompSource(this, &precomp); + debugTypesObj = makeUsePrecompSource(this, precomp); return; } + // This is a plain old object file. 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(); + 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 std::string(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 std::string(path); +#endif +} + +// If existing, return the actual PDB path on disk. +static Optional<std::string> findPdbPath(StringRef pdbPath, + 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; +} + +PDBInputFile::PDBInputFile(MemoryBufferRef m) : InputFile(PDBKind, m) {} + +PDBInputFile::~PDBInputFile() = default; + +PDBInputFile *PDBInputFile::findFromRecordPath(StringRef path, + ObjFile *fromFile) { + auto p = findPdbPath(path.str(), fromFile); + if (!p) + return nullptr; + auto it = PDBInputFile::instances.find(*p); + if (it != PDBInputFile::instances.end()) + return it->second; + return nullptr; +} + +void PDBInputFile::enqueue(StringRef path, ObjFile *fromFile) { + auto p = findPdbPath(path.str(), fromFile); + if (!p) + return; + auto it = PDBInputFile::instances.emplace(*p, nullptr); + if (!it.second) + return; // already scheduled for load + driver->enqueuePDB(*p); +} + +void PDBInputFile::parse() { + PDBInputFile::instances[mb.getBufferIdentifier().str()] = this; + + std::unique_ptr<pdb::IPDBSession> thisSession; + loadErr.emplace(pdb::NativeSession::createFromPdb( + MemoryBuffer::getMemBuffer(mb, false), thisSession)); + if (*loadErr) + return; // fail silently at this point - the error will be handled later, + // when merging the debug type stream + + session.reset(static_cast<pdb::NativeSession *>(thisSession.release())); + + pdb::PDBFile &pdbFile = session->getPDBFile(); + auto expectedInfo = pdbFile.getPDBInfoStream(); + // All PDB Files should have an Info stream. + if (!expectedInfo) { + loadErr.emplace(expectedInfo.takeError()); + return; + } + debugTypesObj = makeTypeServerSource(this); +} + // Used only for DWARF debug info, which is not common (except in MinGW // environments). This returns an optional pair of file name and line // number for where the variable was defined. @@ -820,7 +917,7 @@ Optional<DILineInfo> ObjFile::getDILineInfo(uint32_t offset, return dwarf->getDILineInfo(offset, sectionIndex); } -StringRef ltrim1(StringRef s, const char *chars) { +static StringRef ltrim1(StringRef s, const char *chars) { if (!s.empty() && strchr(chars, s[0])) return s.substr(1); return s; @@ -838,7 +935,7 @@ void ImportFile::parse() { StringRef name = saver.save(StringRef(buf + sizeof(*hdr))); StringRef impName = saver.save("__imp_" + name); const char *nameStart = buf + sizeof(coff_import_header) + name.size() + 1; - dllName = StringRef(nameStart); + dllName = std::string(StringRef(nameStart)); StringRef extName; switch (hdr->getNameType()) { case IMPORT_ORDINAL: @@ -896,8 +993,9 @@ BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName, // filename unique. MemoryBufferRef mbref( mb.getBuffer(), - saver.save(archiveName + path + - (archiveName.empty() ? "" : utostr(offsetInArchive)))); + saver.save(archiveName.empty() ? path + : archiveName + sys::path::filename(path) + + utostr(offsetInArchive))); obj = check(lto::InputFile::create(mbref)); } @@ -921,7 +1019,7 @@ void BitcodeFile::parse() { } else if (objSym.isWeak() && objSym.isIndirect()) { // Weak external. sym = symtab->addUndefined(symName, this, true); - std::string fallback = objSym.getCOFFWeakExternalFallback(); + std::string fallback = std::string(objSym.getCOFFWeakExternalFallback()); Symbol *alias = symtab->addUndefined(saver.save(fallback)); checkAndSetWeakAlias(symtab, this, sym, alias); } else if (comdatIndex != -1) { @@ -956,14 +1054,11 @@ MachineTypes BitcodeFile::getMachineType() { } } -std::string replaceThinLTOSuffix(StringRef path) { +std::string lld::coff::replaceThinLTOSuffix(StringRef path) { StringRef suffix = config->thinLTOObjectSuffixReplace.first; StringRef repl = config->thinLTOObjectSuffixReplace.second; if (path.consume_back(suffix)) return (path + repl).str(); - return path; + return std::string(path); } - -} // namespace coff -} // namespace lld diff --git a/lld/COFF/InputFiles.h b/lld/COFF/InputFiles.h index 805d9121d8bc..50323f596e2c 100644 --- a/lld/COFF/InputFiles.h +++ b/lld/COFF/InputFiles.h @@ -26,6 +26,7 @@ namespace llvm { struct DILineInfo; namespace pdb { class DbiModuleDescriptorBuilder; +class NativeSession; } namespace lto { class InputFile; @@ -64,6 +65,7 @@ public: ArchiveKind, ObjectKind, LazyObjectKind, + PDBKind, ImportKind, BitcodeKind }; @@ -140,7 +142,7 @@ public: MachineTypes getMachineType() override; ArrayRef<Chunk *> getChunks() { return chunks; } ArrayRef<SectionChunk *> getDebugChunks() { return debugChunks; } - ArrayRef<SectionChunk *> getSXDataChunks() { return sXDataChunks; } + ArrayRef<SectionChunk *> getSXDataChunks() { return sxDataChunks; } ArrayRef<SectionChunk *> getGuardFidChunks() { return guardFidChunks; } ArrayRef<SectionChunk *> getGuardLJmpChunks() { return guardLJmpChunks; } ArrayRef<Symbol *> getSymbols() { return symbols; } @@ -276,29 +278,55 @@ private: // Chunks containing symbol table indices of exception handlers. Only used for // 32-bit x86. - std::vector<SectionChunk *> sXDataChunks; + std::vector<SectionChunk *> sxDataChunks; // Chunks containing symbol table indices of address taken symbols and longjmp // targets. These are not linked into the final binary when /guard:cf is set. std::vector<SectionChunk *> guardFidChunks; std::vector<SectionChunk *> guardLJmpChunks; - // This vector contains the same chunks as Chunks, but they are - // indexed such that you can get a SectionChunk by section index. - // Nonexistent section indices are filled with null pointers. - // (Because section number is 1-based, the first slot is always a - // null pointer.) - std::vector<SectionChunk *> sparseChunks; - // This vector contains a list of all symbols defined or referenced by this // file. They are indexed such that you can get a Symbol by symbol // index. Nonexistent indices (which are occupied by auxiliary // symbols in the real symbol table) are filled with null pointers. std::vector<Symbol *> symbols; + // This vector contains the same chunks as Chunks, but they are + // indexed such that you can get a SectionChunk by section index. + // Nonexistent section indices are filled with null pointers. + // (Because section number is 1-based, the first slot is always a + // null pointer.) This vector is only valid during initialization. + std::vector<SectionChunk *> sparseChunks; + DWARFCache *dwarf = nullptr; }; +// This is a PDB type server dependency, that is not a input file per se, but +// needs to be treated like one. Such files are discovered from the debug type +// stream. +class PDBInputFile : public InputFile { +public: + explicit PDBInputFile(MemoryBufferRef m); + ~PDBInputFile(); + static bool classof(const InputFile *f) { return f->kind() == PDBKind; } + void parse() override; + + static void enqueue(StringRef path, ObjFile *fromFile); + + static PDBInputFile *findFromRecordPath(StringRef path, ObjFile *fromFile); + + static std::map<std::string, PDBInputFile *> instances; + + // Record possible errors while opening the PDB file + llvm::Optional<Error> loadErr; + + // This is the actual interface to the PDB (if it was opened successfully) + std::unique_ptr<llvm::pdb::NativeSession> session; + + // If the PDB has a .debug$T stream, this tells how it will be handled. + TpiSource *debugTypesObj = nullptr; +}; + // 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. diff --git a/lld/COFF/LLDMapFile.cpp b/lld/COFF/LLDMapFile.cpp new file mode 100644 index 000000000000..79df33a3535f --- /dev/null +++ b/lld/COFF/LLDMapFile.cpp @@ -0,0 +1,123 @@ +//===- LLDMapFile.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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the /lldmap option. It shows lists in order and +// hierarchically the output sections, input sections, input files and +// symbol: +// +// Address Size Align Out File Symbol +// 00201000 00000015 4 .text +// 00201000 0000000e 4 test.o:(.text) +// 0020100e 00000000 0 local +// 00201005 00000000 0 f(int) +// +//===----------------------------------------------------------------------===// + +#include "LLDMapFile.h" +#include "SymbolTable.h" +#include "Symbols.h" +#include "Writer.h" +#include "lld/Common/ErrorHandler.h" +#include "llvm/Support/Parallel.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace llvm::object; +using namespace lld; +using namespace lld::coff; + +using SymbolMapTy = + DenseMap<const SectionChunk *, SmallVector<DefinedRegular *, 4>>; + +static constexpr char indent8[] = " "; // 8 spaces +static constexpr char indent16[] = " "; // 16 spaces + +// Print out the first three columns of a line. +static void writeHeader(raw_ostream &os, uint64_t addr, uint64_t size, + uint64_t align) { + os << format("%08llx %08llx %5lld ", addr, size, align); +} + +// Returns a list of all symbols that we want to print out. +static std::vector<DefinedRegular *> getSymbols() { + std::vector<DefinedRegular *> v; + for (ObjFile *file : ObjFile::instances) + for (Symbol *b : file->getSymbols()) + if (auto *sym = dyn_cast_or_null<DefinedRegular>(b)) + if (sym && !sym->getCOFFSymbol().isSectionDefinition()) + v.push_back(sym); + return v; +} + +// Returns a map from sections to their symbols. +static SymbolMapTy getSectionSyms(ArrayRef<DefinedRegular *> syms) { + SymbolMapTy ret; + for (DefinedRegular *s : syms) + ret[s->getChunk()].push_back(s); + + // Sort symbols by address. + for (auto &it : ret) { + SmallVectorImpl<DefinedRegular *> &v = it.second; + std::stable_sort(v.begin(), v.end(), [](DefinedRegular *a, DefinedRegular *b) { + return a->getRVA() < b->getRVA(); + }); + } + return ret; +} + +// Construct a map from symbols to their stringified representations. +static DenseMap<DefinedRegular *, std::string> +getSymbolStrings(ArrayRef<DefinedRegular *> syms) { + std::vector<std::string> str(syms.size()); + parallelForEachN((size_t)0, syms.size(), [&](size_t i) { + raw_string_ostream os(str[i]); + writeHeader(os, syms[i]->getRVA(), 0, 0); + os << indent16 << toString(*syms[i]); + }); + + DenseMap<DefinedRegular *, std::string> ret; + for (size_t i = 0, e = syms.size(); i < e; ++i) + ret[syms[i]] = std::move(str[i]); + return ret; +} + +void lld::coff::writeLLDMapFile(ArrayRef<OutputSection *> outputSections) { + if (config->lldmapFile.empty()) + return; + + std::error_code ec; + raw_fd_ostream os(config->lldmapFile, ec, sys::fs::OF_None); + if (ec) + fatal("cannot open " + config->lldmapFile + ": " + ec.message()); + + // Collect symbol info that we want to print out. + std::vector<DefinedRegular *> syms = getSymbols(); + SymbolMapTy sectionSyms = getSectionSyms(syms); + DenseMap<DefinedRegular *, std::string> symStr = getSymbolStrings(syms); + + // Print out the header line. + os << "Address Size Align Out In Symbol\n"; + + // Print out file contents. + for (OutputSection *sec : outputSections) { + writeHeader(os, sec->getRVA(), sec->getVirtualSize(), /*align=*/pageSize); + os << sec->name << '\n'; + + for (Chunk *c : sec->chunks) { + auto *sc = dyn_cast<SectionChunk>(c); + if (!sc) + continue; + + writeHeader(os, sc->getRVA(), sc->getSize(), sc->getAlignment()); + os << indent8 << sc->file->getName() << ":(" << sc->getSectionName() + << ")\n"; + for (DefinedRegular *sym : sectionSyms[sc]) + os << symStr[sym] << '\n'; + } + } +} diff --git a/lld/COFF/LLDMapFile.h b/lld/COFF/LLDMapFile.h new file mode 100644 index 000000000000..b731293a8625 --- /dev/null +++ b/lld/COFF/LLDMapFile.h @@ -0,0 +1,21 @@ +//===- LLDMapFile.h ---------------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLD_COFF_LLDMAPFILE_H +#define LLD_COFF_LLDMAPFILE_H + +#include "llvm/ADT/ArrayRef.h" + +namespace lld { +namespace coff { +class OutputSection; +void writeLLDMapFile(llvm::ArrayRef<OutputSection *> outputSections); +} +} + +#endif diff --git a/lld/COFF/LTO.cpp b/lld/COFF/LTO.cpp index 1c21236dce2b..bb44819e60f8 100644 --- a/lld/COFF/LTO.cpp +++ b/lld/COFF/LTO.cpp @@ -38,9 +38,8 @@ using namespace llvm; using namespace llvm::object; - -namespace lld { -namespace coff { +using namespace lld; +using namespace lld::coff; // Creates an empty file to and returns a raw_fd_ostream to write to it. static std::unique_ptr<raw_fd_ostream> openFile(StringRef file) { @@ -55,9 +54,9 @@ static std::unique_ptr<raw_fd_ostream> openFile(StringRef file) { } static std::string getThinLTOOutputFile(StringRef path) { - return lto::getThinLTOOutputFile(path, - config->thinLTOPrefixReplace.first, - config->thinLTOPrefixReplace.second); + return lto::getThinLTOOutputFile( + std::string(path), std::string(config->thinLTOPrefixReplace.first), + std::string(config->thinLTOPrefixReplace.second)); } static lto::Config createConfig() { @@ -82,6 +81,7 @@ static lto::Config createConfig() { c.CPU = getCPUStr(); c.MAttrs = getMAttrs(); c.CGOptLevel = args::getCGOptLevel(config->ltoo); + c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty(); if (config->saveTemps) checkError(c.addSaveTemps(std::string(config->outputFile) + ".", @@ -99,10 +99,12 @@ BitcodeCompiler::BitcodeCompiler() { if (config->thinLTOIndexOnly) { auto OnIndexWrite = [&](StringRef S) { thinIndices.erase(S); }; backend = lto::createWriteIndexesThinBackend( - config->thinLTOPrefixReplace.first, config->thinLTOPrefixReplace.second, + std::string(config->thinLTOPrefixReplace.first), + std::string(config->thinLTOPrefixReplace.second), config->thinLTOEmitImportsFiles, indexFile.get(), OnIndexWrite); - } else if (config->thinLTOJobs != 0) { - backend = lto::createInProcessThinBackend(config->thinLTOJobs); + } else { + backend = lto::createInProcessThinBackend( + llvm::heavyweight_hardware_concurrency(config->thinLTOJobs)); } ltoObj = std::make_unique<lto::LTO>(createConfig(), backend, @@ -143,7 +145,7 @@ void BitcodeCompiler::add(BitcodeFile &f) { // Merge all the bitcode files we have seen, codegen the result // and return the resulting objects. -std::vector<StringRef> BitcodeCompiler::compile() { +std::vector<InputFile *> BitcodeCompiler::compile() { unsigned maxTasks = ltoObj->getMaxTasks(); buf.resize(maxTasks); files.resize(maxTasks); @@ -187,25 +189,32 @@ std::vector<StringRef> BitcodeCompiler::compile() { if (!config->ltoCache.empty()) pruneCache(config->ltoCache, config->ltoCachePolicy); - std::vector<StringRef> ret; + std::vector<InputFile *> ret; for (unsigned i = 0; i != maxTasks; ++i) { - if (buf[i].empty()) + // Assign unique names to LTO objects. This ensures they have unique names + // in the PDB if one is produced. The names should look like: + // - foo.exe.lto.obj + // - foo.exe.lto.1.obj + // - ... + StringRef ltoObjName = + saver.save(Twine(config->outputFile) + ".lto" + + (i == 0 ? Twine("") : Twine('.') + Twine(i)) + ".obj"); + + // Get the native object contents either from the cache or from memory. Do + // not use the cached MemoryBuffer directly, or the PDB will not be + // deterministic. + StringRef objBuf; + if (files[i]) + objBuf = files[i]->getBuffer(); + else + objBuf = buf[i]; + if (objBuf.empty()) continue; - if (config->saveTemps) { - if (i == 0) - saveBuffer(buf[i], config->outputFile + ".lto.obj"); - else - saveBuffer(buf[i], config->outputFile + Twine(i) + ".lto.obj"); - } - ret.emplace_back(buf[i].data(), buf[i].size()); - } - for (std::unique_ptr<MemoryBuffer> &file : files) - if (file) - ret.push_back(file->getBuffer()); + if (config->saveTemps) + saveBuffer(buf[i], ltoObjName); + ret.push_back(make<ObjFile>(MemoryBufferRef(objBuf, ltoObjName))); + } return ret; } - -} // namespace coff -} // namespace lld diff --git a/lld/COFF/LTO.h b/lld/COFF/LTO.h index 2a0cfa061c95..a2b321df7901 100644 --- a/lld/COFF/LTO.h +++ b/lld/COFF/LTO.h @@ -45,7 +45,7 @@ public: ~BitcodeCompiler(); void add(BitcodeFile &f); - std::vector<StringRef> compile(); + std::vector<InputFile *> compile(); private: std::unique_ptr<llvm::lto::LTO> ltoObj; diff --git a/lld/COFF/MapFile.cpp b/lld/COFF/MapFile.cpp index 0fea60aab99b..41e169ef56e2 100644 --- a/lld/COFF/MapFile.cpp +++ b/lld/COFF/MapFile.cpp @@ -6,16 +6,25 @@ // //===----------------------------------------------------------------------===// // -// This file implements the /lldmap option. It shows lists in order and -// hierarchically the output sections, input sections, input files and -// symbol: +// This file implements the /map option in the same format as link.exe +// (based on observations) // -// Address Size Align Out File Symbol -// 00201000 00000015 4 .text -// 00201000 0000000e 4 test.o:(.text) -// 0020100e 00000000 0 local -// 00201005 00000000 0 f(int) +// Header (program name, timestamp info, preferred load address) // +// Section list (Start = Section index:Base address): +// Start Length Name Class +// 0001:00001000 00000015H .text CODE +// +// Symbols list: +// Address Publics by Value Rva + Base Lib:Object +// 0001:00001000 main 0000000140001000 main.obj +// 0001:00001300 ?__scrt_common_main@@YAHXZ 0000000140001300 libcmt:exe_main.obj +// +// entry point at 0001:00000360 +// +// Static symbols +// +// 0000:00000000 __guard_fids__ 0000000140000000 libcmt : exe_main.obj //===----------------------------------------------------------------------===// #include "MapFile.h" @@ -23,71 +32,176 @@ #include "Symbols.h" #include "Writer.h" #include "lld/Common/ErrorHandler.h" -#include "lld/Common/Threads.h" +#include "lld/Common/Timer.h" +#include "llvm/Support/Parallel.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; using namespace llvm::object; +using namespace lld; +using namespace lld::coff; -namespace lld { -namespace coff { +static Timer totalMapTimer("MAP emission (Cumulative)", Timer::root()); +static Timer symbolGatherTimer("Gather symbols", totalMapTimer); +static Timer symbolStringsTimer("Build symbol strings", totalMapTimer); +static Timer writeTimer("Write to file", totalMapTimer); -using SymbolMapTy = - DenseMap<const SectionChunk *, SmallVector<DefinedRegular *, 4>>; +// Print out the first two columns of a line. +static void writeHeader(raw_ostream &os, uint32_t sec, uint64_t addr) { + os << format(" %04x:%08llx", sec, addr); +} -static constexpr char indent8[] = " "; // 8 spaces -static constexpr char indent16[] = " "; // 16 spaces +// Write the time stamp with the format used by link.exe +// It seems identical to strftime with "%c" on msvc build, but we need a +// locale-agnostic version. +static void writeFormattedTimestamp(raw_ostream &os, time_t tds) { + constexpr const char *const days[7] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + constexpr const char *const months[12] = {"Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec"}; + tm *time = localtime(&tds); + os << format("%s %s %2d %02d:%02d:%02d %d", days[time->tm_wday], + months[time->tm_mon], time->tm_mday, time->tm_hour, time->tm_min, + time->tm_sec, time->tm_year + 1900); +} -// Print out the first three columns of a line. -static void writeHeader(raw_ostream &os, uint64_t addr, uint64_t size, - uint64_t align) { - os << format("%08llx %08llx %5lld ", addr, size, align); +static void sortUniqueSymbols(std::vector<Defined *> &syms) { + // Build helper vector + using SortEntry = std::pair<Defined *, size_t>; + std::vector<SortEntry> v; + v.resize(syms.size()); + for (size_t i = 0, e = syms.size(); i < e; ++i) + v[i] = SortEntry(syms[i], i); + + // Remove duplicate symbol pointers + parallelSort(v, std::less<SortEntry>()); + auto end = std::unique(v.begin(), v.end(), + [](const SortEntry &a, const SortEntry &b) { + return a.first == b.first; + }); + v.erase(end, v.end()); + + // Sort by RVA then original order + parallelSort(v, [](const SortEntry &a, const SortEntry &b) { + // Add config->imageBase to avoid comparing "negative" RVAs. + // This can happen with symbols of Absolute kind + uint64_t rvaa = config->imageBase + a.first->getRVA(); + uint64_t rvab = config->imageBase + b.first->getRVA(); + return rvaa < rvab || (rvaa == rvab && a.second < b.second); + }); + + syms.resize(v.size()); + for (size_t i = 0, e = v.size(); i < e; ++i) + syms[i] = v[i].first; } -// Returns a list of all symbols that we want to print out. -static std::vector<DefinedRegular *> getSymbols() { - std::vector<DefinedRegular *> v; +// Returns the lists of all symbols that we want to print out. +static void getSymbols(std::vector<Defined *> &syms, + std::vector<Defined *> &staticSyms) { + for (ObjFile *file : ObjFile::instances) - for (Symbol *b : file->getSymbols()) - if (auto *sym = dyn_cast_or_null<DefinedRegular>(b)) - if (sym && !sym->getCOFFSymbol().isSectionDefinition()) - v.push_back(sym); - return v; -} + for (Symbol *b : file->getSymbols()) { + if (!b || !b->isLive()) + continue; + if (auto *sym = dyn_cast<DefinedCOFF>(b)) { + COFFSymbolRef symRef = sym->getCOFFSymbol(); + if (!symRef.isSectionDefinition() && + symRef.getStorageClass() != COFF::IMAGE_SYM_CLASS_LABEL) { + if (symRef.getStorageClass() == COFF::IMAGE_SYM_CLASS_STATIC) + staticSyms.push_back(sym); + else + syms.push_back(sym); + } + } else if (auto *sym = dyn_cast<Defined>(b)) { + syms.push_back(sym); + } + } + + for (ImportFile *file : ImportFile::instances) { + if (!file->live) + continue; + + if (!file->thunkSym) + continue; + + if (!file->thunkLive) + continue; + + if (auto *thunkSym = dyn_cast<Defined>(file->thunkSym)) + syms.push_back(thunkSym); -// Returns a map from sections to their symbols. -static SymbolMapTy getSectionSyms(ArrayRef<DefinedRegular *> syms) { - SymbolMapTy ret; - for (DefinedRegular *s : syms) - ret[s->getChunk()].push_back(s); - - // Sort symbols by address. - for (auto &it : ret) { - SmallVectorImpl<DefinedRegular *> &v = it.second; - std::sort(v.begin(), v.end(), [](DefinedRegular *a, DefinedRegular *b) { - return a->getRVA() < b->getRVA(); - }); + if (auto *impSym = dyn_cast_or_null<Defined>(file->impSym)) + syms.push_back(impSym); } - return ret; + + sortUniqueSymbols(syms); + sortUniqueSymbols(staticSyms); } // Construct a map from symbols to their stringified representations. -static DenseMap<DefinedRegular *, std::string> -getSymbolStrings(ArrayRef<DefinedRegular *> syms) { +static DenseMap<Defined *, std::string> +getSymbolStrings(ArrayRef<Defined *> syms) { std::vector<std::string> str(syms.size()); parallelForEachN((size_t)0, syms.size(), [&](size_t i) { raw_string_ostream os(str[i]); - writeHeader(os, syms[i]->getRVA(), 0, 0); - os << indent16 << toString(*syms[i]); + Defined *sym = syms[i]; + + uint16_t sectionIdx = 0; + uint64_t address = 0; + SmallString<128> fileDescr; + + if (auto *absSym = dyn_cast<DefinedAbsolute>(sym)) { + address = absSym->getVA(); + fileDescr = "<absolute>"; + } else if (isa<DefinedSynthetic>(sym)) { + fileDescr = "<linker-defined>"; + } else if (isa<DefinedCommon>(sym)) { + fileDescr = "<common>"; + } else if (Chunk *chunk = sym->getChunk()) { + address = sym->getRVA(); + if (OutputSection *sec = chunk->getOutputSection()) + address -= sec->header.VirtualAddress; + + sectionIdx = chunk->getOutputSectionIdx(); + + InputFile *file; + if (auto *impSym = dyn_cast<DefinedImportData>(sym)) + file = impSym->file; + else if (auto *thunkSym = dyn_cast<DefinedImportThunk>(sym)) + file = thunkSym->wrappedSym->file; + else + file = sym->getFile(); + + if (file) { + if (!file->parentName.empty()) { + fileDescr = sys::path::filename(file->parentName); + sys::path::replace_extension(fileDescr, ""); + fileDescr += ":"; + } + fileDescr += sys::path::filename(file->getName()); + } + } + writeHeader(os, sectionIdx, address); + os << " "; + os << left_justify(sym->getName(), 26); + os << " "; + os << format_hex_no_prefix((config->imageBase + sym->getRVA()), 16); + if (!fileDescr.empty()) { + os << " "; // FIXME : Handle "f" and "i" flags sometimes generated + // by link.exe in those spaces + os << fileDescr; + } }); - DenseMap<DefinedRegular *, std::string> ret; + DenseMap<Defined *, std::string> ret; for (size_t i = 0, e = syms.size(); i < e; ++i) ret[syms[i]] = std::move(str[i]); return ret; } -void writeMapFile(ArrayRef<OutputSection *> outputSections) { +void lld::coff::writeMapFile(ArrayRef<OutputSection *> outputSections) { if (config->mapFile.empty()) return; @@ -96,32 +210,113 @@ void writeMapFile(ArrayRef<OutputSection *> outputSections) { if (ec) fatal("cannot open " + config->mapFile + ": " + ec.message()); + ScopedTimer t1(totalMapTimer); + // Collect symbol info that we want to print out. - std::vector<DefinedRegular *> syms = getSymbols(); - SymbolMapTy sectionSyms = getSectionSyms(syms); - DenseMap<DefinedRegular *, std::string> symStr = getSymbolStrings(syms); + ScopedTimer t2(symbolGatherTimer); + std::vector<Defined *> syms; + std::vector<Defined *> staticSyms; + getSymbols(syms, staticSyms); + t2.stop(); - // Print out the header line. - os << "Address Size Align Out In Symbol\n"; + ScopedTimer t3(symbolStringsTimer); + DenseMap<Defined *, std::string> symStr = getSymbolStrings(syms); + DenseMap<Defined *, std::string> staticSymStr = getSymbolStrings(staticSyms); + t3.stop(); - // Print out file contents. - for (OutputSection *sec : outputSections) { - writeHeader(os, sec->getRVA(), sec->getVirtualSize(), /*align=*/pageSize); - os << sec->name << '\n'; + ScopedTimer t4(writeTimer); + SmallString<128> AppName = sys::path::filename(config->outputFile); + sys::path::replace_extension(AppName, ""); + + // Print out the file header + os << " " << AppName << "\n"; + os << "\n"; + os << " Timestamp is " << format_hex_no_prefix(config->timestamp, 8) << " ("; + if (config->repro) { + os << "Repro mode"; + } else { + writeFormattedTimestamp(os, config->timestamp); + } + os << ")\n"; + + os << "\n"; + os << " Preferred load address is " + << format_hex_no_prefix(config->imageBase, 16) << "\n"; + os << "\n"; + + // Print out section table. + os << " Start Length Name Class\n"; + + for (OutputSection *sec : outputSections) { + // Merge display of chunks with same sectionName + std::vector<std::pair<SectionChunk *, SectionChunk *>> ChunkRanges; for (Chunk *c : sec->chunks) { auto *sc = dyn_cast<SectionChunk>(c); if (!sc) continue; - writeHeader(os, sc->getRVA(), sc->getSize(), sc->getAlignment()); - os << indent8 << sc->file->getName() << ":(" << sc->getSectionName() - << ")\n"; - for (DefinedRegular *sym : sectionSyms[sc]) - os << symStr[sym] << '\n'; + if (ChunkRanges.empty() || + c->getSectionName() != ChunkRanges.back().first->getSectionName()) { + ChunkRanges.emplace_back(sc, sc); + } else { + ChunkRanges.back().second = sc; + } + } + + const bool isCodeSection = + (sec->header.Characteristics & COFF::IMAGE_SCN_CNT_CODE) && + (sec->header.Characteristics & COFF::IMAGE_SCN_MEM_READ) && + (sec->header.Characteristics & COFF::IMAGE_SCN_MEM_EXECUTE); + StringRef SectionClass = (isCodeSection ? "CODE" : "DATA"); + + for (auto &cr : ChunkRanges) { + size_t size = + cr.second->getRVA() + cr.second->getSize() - cr.first->getRVA(); + + auto address = cr.first->getRVA() - sec->header.VirtualAddress; + writeHeader(os, sec->sectionIndex, address); + os << " " << format_hex_no_prefix(size, 8) << "H"; + os << " " << left_justify(cr.first->getSectionName(), 23); + os << " " << SectionClass; + os << '\n'; } } -} -} // namespace coff -} // namespace lld + // Print out the symbols table (without static symbols) + os << "\n"; + os << " Address Publics by Value Rva+Base" + " Lib:Object\n"; + os << "\n"; + for (Defined *sym : syms) + os << symStr[sym] << '\n'; + + // Print out the entry point. + os << "\n"; + + uint16_t entrySecIndex = 0; + uint64_t entryAddress = 0; + + if (!config->noEntry) { + Defined *entry = dyn_cast_or_null<Defined>(config->entry); + if (entry) { + Chunk *chunk = entry->getChunk(); + entrySecIndex = chunk->getOutputSectionIdx(); + entryAddress = + entry->getRVA() - chunk->getOutputSection()->header.VirtualAddress; + } + } + os << " entry point at "; + os << format("%04x:%08llx", entrySecIndex, entryAddress); + os << "\n"; + + // Print out the static symbols + os << "\n"; + os << " Static symbols\n"; + os << "\n"; + for (Defined *sym : staticSyms) + os << staticSymStr[sym] << '\n'; + + t4.stop(); + t1.stop(); +} diff --git a/lld/COFF/MarkLive.cpp b/lld/COFF/MarkLive.cpp index 6d34cb864e3c..0afa615a1933 100644 --- a/lld/COFF/MarkLive.cpp +++ b/lld/COFF/MarkLive.cpp @@ -28,10 +28,12 @@ void markLive(ArrayRef<Chunk *> chunks) { // as we push, so sections never appear twice in the list. SmallVector<SectionChunk *, 256> worklist; - // COMDAT section chunks are dead by default. Add non-COMDAT chunks. + // COMDAT section chunks are dead by default. Add non-COMDAT chunks. Do not + // traverse DWARF sections. They are live, but they should not keep other + // sections alive. for (Chunk *c : chunks) if (auto *sc = dyn_cast<SectionChunk>(c)) - if (sc->live) + if (sc->live && !sc->isDWARF()) worklist.push_back(sc); auto enqueue = [&](SectionChunk *c) { diff --git a/lld/COFF/MinGW.cpp b/lld/COFF/MinGW.cpp index 270cdaab4d9c..bded985f04d0 100644 --- a/lld/COFF/MinGW.cpp +++ b/lld/COFF/MinGW.cpp @@ -15,9 +15,8 @@ using namespace llvm; using namespace llvm::COFF; - -namespace lld { -namespace coff { +using namespace lld; +using namespace lld::coff; AutoExporter::AutoExporter() { excludeLibs = { @@ -147,7 +146,7 @@ bool AutoExporter::shouldExport(Defined *sym) const { return !excludeObjects.count(fileName); } -void writeDefFile(StringRef name) { +void lld::coff::writeDefFile(StringRef name) { std::error_code ec; raw_fd_ostream os(name, ec, sys::fs::OF_None); if (ec) @@ -165,6 +164,3 @@ void writeDefFile(StringRef name) { os << "\n"; } } - -} // namespace coff -} // namespace lld diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td index 468e4da7d054..212879e1d60b 100644 --- a/lld/COFF/Options.td +++ b/lld/COFF/Options.td @@ -16,6 +16,13 @@ multiclass B<string name, string help_on, string help_off> { def _no : F<name#":no">, HelpText<help_off>; } +// Same as B<> above, but without help texts, for private undocumented +// options. +multiclass B_priv<string name> { + def "" : F<name>; + def _no : F<name#":no">; +} + def align : P<"align", "Section alignment">; def aligncomm : P<"aligncomm", "Set common symbol alignment">; def alternatename : P<"alternatename", "Define weak alias">; @@ -64,7 +71,11 @@ def natvis : P<"natvis", "Path to natvis file to embed in the PDB">; def no_color_diagnostics: F<"no-color-diagnostics">, HelpText<"Do not use colors in diagnostics">; def pdb : P<"pdb", "PDB file path">; +def pdbstripped : P<"pdbstripped", "Stripped PDB file path">; def pdbaltpath : P<"pdbaltpath", "PDB file path to embed in the image">; +def pdbstream : Joined<["/", "-", "/?", "-?"], "pdbstream:">, + MetaVarName<"<name>=<file>">, + HelpText<"Embed the contents of <file> in the PDB as named stream <name>">; def section : P<"section", "Specify section attributes">; def stack : P<"stack", "Size of the stack">; def stub : P<"stub", "Specify DOS stub file">; @@ -147,6 +158,8 @@ defm allowisolation : B<"allowisolation", "Enable DLL isolation (default)", defm appcontainer : B<"appcontainer", "Image can only be run in an app container", "Image can run outside an app container (default)">; +defm cetcompat : B<"cetcompat", "Mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack", + "Don't mark executable image as compatible with Control-flow Enforcement Technology (CET) Shadow Stack (default)">; defm dynamicbase : B<"dynamicbase", "Enable ASLR (default unless /fixed)", "Disable ASLR (default when /fixed)">; defm fixed : B<"fixed", "Disable base relocations", @@ -178,6 +191,8 @@ def help : F<"help">; def help_q : Flag<["/??", "-??", "/?", "-?"], "">, Alias<help>; // LLD extensions +defm auto_import : B_priv<"auto-import">; +defm runtime_pseudo_reloc : B_priv<"runtime-pseudo-reloc">; def end_lib : F<"end-lib">, HelpText<"Ends group of objects treated as if they were in a library">; def exclude_all_symbols : F<"exclude-all-symbols">; @@ -216,13 +231,15 @@ def lto_obj_path : P< "output native object for merged LTO unit to this path">; def dash_dash_version : Flag<["--"], "version">, HelpText<"Print version information">; -defm threads: B<"threads", - "Run the linker multi-threaded (default)", - "Do not run the linker multi-threaded">; +def threads + : P<"threads", "Number of threads. '1' disables multi-threading. By " + "default all available hardware threads are used">; // Flags for debugging def lldmap : F<"lldmap">; def lldmap_file : Joined<["/", "-", "/?", "-?"], "lldmap:">; +def map : F<"map">; +def map_file : Joined<["/", "-", "/?", "-?"], "map:">; def show_timing : F<"time">; def summary : F<"summary">; diff --git a/lld/COFF/PDB.cpp b/lld/COFF/PDB.cpp index f68c60a13254..49d04add5be0 100644 --- a/lld/COFF/PDB.cpp +++ b/lld/COFF/PDB.cpp @@ -16,7 +16,6 @@ #include "TypeMerger.h" #include "Writer.h" #include "lld/Common/ErrorHandler.h" -#include "lld/Common/Threads.h" #include "lld/Common/Timer.h" #include "llvm/DebugInfo/CodeView/DebugFrameDataSubsection.h" #include "llvm/DebugInfo/CodeView/DebugSubsectionRecord.h" @@ -27,11 +26,7 @@ #include "llvm/DebugInfo/CodeView/SymbolDeserializer.h" #include "llvm/DebugInfo/CodeView/SymbolRecordHelpers.h" #include "llvm/DebugInfo/CodeView/SymbolSerializer.h" -#include "llvm/DebugInfo/CodeView/TypeDeserializer.h" -#include "llvm/DebugInfo/CodeView/TypeDumpVisitor.h" #include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h" -#include "llvm/DebugInfo/CodeView/TypeRecordHelpers.h" -#include "llvm/DebugInfo/CodeView/TypeStreamMerger.h" #include "llvm/DebugInfo/MSF/MSFBuilder.h" #include "llvm/DebugInfo/MSF/MSFCommon.h" #include "llvm/DebugInfo/PDB/GenericError.h" @@ -63,12 +58,11 @@ using namespace llvm; using namespace llvm::codeview; +using namespace lld; +using namespace lld::coff; using llvm::object::coff_section; -namespace lld { -namespace coff { - static ExitOnError exitOnErr; static Timer totalPdbLinkTimer("PDB Emission (Cumulative)", Timer::root()); @@ -76,7 +70,7 @@ static Timer totalPdbLinkTimer("PDB Emission (Cumulative)", Timer::root()); static Timer addObjectsTimer("Add Objects", totalPdbLinkTimer); static Timer typeMergingTimer("Type Merging", addObjectsTimer); static Timer symbolMergingTimer("Symbol Merging", addObjectsTimer); -static Timer globalsLayoutTimer("Globals Stream Layout", totalPdbLinkTimer); +static Timer publicsLayoutTimer("Publics Stream Layout", totalPdbLinkTimer); static Timer tpiStreamLayoutTimer("TPI Stream Layout", totalPdbLinkTimer); static Timer diskCommitTimer("Commit to Disk", totalPdbLinkTimer); @@ -88,7 +82,7 @@ class PDBLinker { public: PDBLinker(SymbolTable *symtab) - : alloc(), symtab(symtab), builder(alloc), tMerger(alloc) { + : symtab(symtab), builder(bAlloc), tMerger(bAlloc) { // This isn't strictly necessary, but link.exe usually puts an empty string // as the first "valid" string in the string table, so we do the same in // order to maintain as much byte-for-byte compatibility as possible. @@ -101,53 +95,26 @@ public: /// Add natvis files specified on the command line. void addNatvisFiles(); + /// Add named streams specified on the command line. + void addNamedStreams(); + /// Link CodeView from each object file in the symbol table into the PDB. void addObjectsToPDB(); + /// Add every live, defined public symbol to the PDB. + void addPublicsToPDB(); + /// Link info for each import file in the symbol table into the PDB. void addImportFilesToPDB(ArrayRef<OutputSection *> outputSections); /// Link CodeView from a single object file into the target (output) PDB. /// When a precompiled headers object is linked, its TPI map might be provided /// externally. - void addObjFile(ObjFile *file, CVIndexMap *externIndexMap = nullptr); - - /// Produce a mapping from the type and item indices used in the object - /// file to those in the destination PDB. - /// - /// If the object file uses a type server PDB (compiled with /Zi), merge TPI - /// and IPI from the type server PDB and return a map for it. Each unique type - /// server PDB is merged at most once, so this may return an existing index - /// mapping. - /// - /// If the object does not use a type server PDB (compiled with /Z7), we merge - /// all the type and item records from the .debug$S stream and fill in the - /// caller-provided objectIndexMap. - Expected<const CVIndexMap &> mergeDebugT(ObjFile *file, - CVIndexMap *objectIndexMap); - - /// Reads and makes available a PDB. - Expected<const CVIndexMap &> maybeMergeTypeServerPDB(ObjFile *file); - - /// Merges a precompiled headers TPI map into the current TPI map. The - /// precompiled headers object will also be loaded and remapped in the - /// process. - Error mergeInPrecompHeaderObj(ObjFile *file, CVIndexMap *objectIndexMap); - - /// Reads and makes available a precompiled headers object. - /// - /// This is a requirement for objects compiled with cl.exe /Yu. In that - /// case, the referenced object (which was compiled with /Yc) has to be loaded - /// first. This is mainly because the current object's TPI stream has external - /// references to the precompiled headers object. - /// - /// If the precompiled headers object was already loaded, this function will - /// simply return its (remapped) TPI map. - Expected<const CVIndexMap &> aquirePrecompObj(ObjFile *file); - - /// Adds a precompiled headers object signature -> TPI mapping. - std::pair<CVIndexMap &, bool /*already there*/> - registerPrecompiledHeaders(uint32_t signature); + void addDebug(TpiSource *source); + + const CVIndexMap *mergeTypeRecords(TpiSource *source, CVIndexMap *localMap); + + void addDebugSymbols(ObjFile *file, const CVIndexMap *indexMap); void mergeSymbolRecords(ObjFile *file, const CVIndexMap &indexMap, std::vector<ulittle32_t *> &stringTableRefs, @@ -164,8 +131,6 @@ public: void printStats(); private: - BumpPtrAllocator alloc; - SymbolTable *symtab; pdb::PDBFileBuilder builder; @@ -178,24 +143,10 @@ private: llvm::SmallString<128> nativePath; - std::vector<pdb::SecMapEntry> sectionMap; - - /// Type index mappings of type server PDBs that we've loaded so far. - std::map<codeview::GUID, CVIndexMap> typeServerIndexMappings; - - /// Type index mappings of precompiled objects type map that we've loaded so - /// far. - std::map<uint32_t, CVIndexMap> precompTypeIndexMappings; - // For statistics uint64_t globalSymbols = 0; uint64_t moduleSymbols = 0; uint64_t publicSymbols = 0; - - // When showSummary is enabled, these are histograms of TPI and IPI records - // keyed by type index. - SmallVector<uint32_t, 0> tpiCounts; - SmallVector<uint32_t, 0> ipiCounts; }; class DebugSHandler { @@ -205,24 +156,20 @@ class DebugSHandler { ObjFile &file; /// The result of merging type indices. - const CVIndexMap &indexMap; + const CVIndexMap *indexMap; /// The DEBUG_S_STRINGTABLE subsection. These strings are referred to by /// index from other records in the .debug$S section. All of these strings /// need to be added to the global PDB string table, and all references to /// these strings need to have their indices re-written to refer to the /// global PDB string table. - DebugStringTableSubsectionRef cVStrTab; + DebugStringTableSubsectionRef cvStrTab; /// The DEBUG_S_FILECHKSMS subsection. As above, these are referred to /// by other records in the .debug$S section and need to be merged into the /// PDB. DebugChecksumsSubsectionRef checksums; - /// The DEBUG_S_INLINEELINES subsection. There can be only one of these per - /// object file. - DebugInlineeLinesSubsectionRef inlineeLines; - /// The DEBUG_S_FRAMEDATA subsection(s). There can be more than one of /// these and they need not appear in any specific order. However, they /// contain string table references which need to be re-written, so we @@ -238,14 +185,13 @@ class DebugSHandler { /// references. std::vector<ulittle32_t *> stringTableReferences; + void mergeInlineeLines(const DebugSubsectionRecord &inlineeLines); + public: - DebugSHandler(PDBLinker &linker, ObjFile &file, const CVIndexMap &indexMap) + DebugSHandler(PDBLinker &linker, ObjFile &file, const CVIndexMap *indexMap) : linker(linker), file(file), indexMap(indexMap) {} - void handleDebugS(lld::coff::SectionChunk &debugS); - - std::shared_ptr<DebugInlineeLinesSubsection> - mergeInlineeLines(DebugChecksumsSubsection *newChecksums); + void handleDebugS(ArrayRef<uint8_t> relocatedDebugContents); void finish(); }; @@ -290,42 +236,6 @@ static void pdbMakeAbsolute(SmallVectorImpl<char> &fileName) { fileName = std::move(absoluteFileName); } -// A COFF .debug$H section is currently a clang extension. This function checks -// if a .debug$H section is in a format that we expect / understand, so that we -// can ignore any sections which are coincidentally also named .debug$H but do -// not contain a format we recognize. -static bool canUseDebugH(ArrayRef<uint8_t> debugH) { - if (debugH.size() < sizeof(object::debug_h_header)) - return false; - auto *header = - reinterpret_cast<const object::debug_h_header *>(debugH.data()); - debugH = debugH.drop_front(sizeof(object::debug_h_header)); - return header->Magic == COFF::DEBUG_HASHES_SECTION_MAGIC && - header->Version == 0 && - header->HashAlgorithm == uint16_t(GlobalTypeHashAlg::SHA1_8) && - (debugH.size() % 8 == 0); -} - -static Optional<ArrayRef<uint8_t>> getDebugH(ObjFile *file) { - SectionChunk *sec = - SectionChunk::findByName(file->getDebugChunks(), ".debug$H"); - if (!sec) - return llvm::None; - ArrayRef<uint8_t> contents = sec->getContents(); - if (!canUseDebugH(contents)) - return None; - return contents; -} - -static ArrayRef<GloballyHashedType> -getHashesFromDebugH(ArrayRef<uint8_t> debugH) { - assert(canUseDebugH(debugH)); - - debugH = debugH.drop_front(sizeof(object::debug_h_header)); - uint32_t count = debugH.size() / sizeof(GloballyHashedType); - return {reinterpret_cast<const GloballyHashedType *>(debugH.data()), count}; -} - static void addTypeInfo(pdb::TpiStreamBuilder &tpiBuilder, TypeCollection &typeTable) { // Start the TPI or IPI stream header. @@ -340,281 +250,6 @@ static void addTypeInfo(pdb::TpiStreamBuilder &tpiBuilder, }); } -Expected<const CVIndexMap &> -PDBLinker::mergeDebugT(ObjFile *file, CVIndexMap *objectIndexMap) { - ScopedTimer t(typeMergingTimer); - - if (!file->debugTypesObj) - return *objectIndexMap; // no Types stream - - // Precompiled headers objects need to save the index map for further - // reference by other objects which use the precompiled headers. - if (file->debugTypesObj->kind == TpiSource::PCH) { - uint32_t pchSignature = file->pchSignature.getValueOr(0); - if (pchSignature == 0) - fatal("No signature found for the precompiled headers OBJ (" + - file->getName() + ")"); - - // When a precompiled headers object comes first on the command-line, we - // update the mapping here. Otherwise, if an object referencing the - // precompiled headers object comes first, the mapping is created in - // aquirePrecompObj(), thus we would skip this block. - if (!objectIndexMap->isPrecompiledTypeMap) { - auto r = registerPrecompiledHeaders(pchSignature); - if (r.second) - fatal( - "A precompiled headers OBJ with the same signature was already " - "provided! (" + - file->getName() + ")"); - - objectIndexMap = &r.first; - } - } - - if (file->debugTypesObj->kind == TpiSource::UsingPDB) { - // Look through type servers. If we've already seen this type server, - // don't merge any type information. - return maybeMergeTypeServerPDB(file); - } - - CVTypeArray types; - BinaryStreamReader reader(file->debugTypes, support::little); - cantFail(reader.readArray(types, reader.getLength())); - - if (file->debugTypesObj->kind == TpiSource::UsingPCH) { - // This object was compiled with /Yu, so process the corresponding - // precompiled headers object (/Yc) first. Some type indices in the current - // object are referencing data in the precompiled headers object, so we need - // both to be loaded. - Error e = mergeInPrecompHeaderObj(file, objectIndexMap); - if (e) - return std::move(e); - - // Drop LF_PRECOMP record from the input stream, as it has been replaced - // with the precompiled headers Type stream in the mergeInPrecompHeaderObj() - // call above. Note that we can't just call Types.drop_front(), as we - // explicitly want to rebase the stream. - CVTypeArray::Iterator firstType = types.begin(); - types.setUnderlyingStream( - types.getUnderlyingStream().drop_front(firstType->RecordData.size())); - } - - // Fill in the temporary, caller-provided ObjectIndexMap. - if (config->debugGHashes) { - ArrayRef<GloballyHashedType> hashes; - std::vector<GloballyHashedType> ownedHashes; - if (Optional<ArrayRef<uint8_t>> debugH = getDebugH(file)) - hashes = getHashesFromDebugH(*debugH); - else { - ownedHashes = GloballyHashedType::hashTypes(types); - hashes = ownedHashes; - } - - if (auto err = mergeTypeAndIdRecords( - tMerger.globalIDTable, tMerger.globalTypeTable, - objectIndexMap->tpiMap, types, hashes, file->pchSignature)) - fatal("codeview::mergeTypeAndIdRecords failed: " + - toString(std::move(err))); - } else { - if (auto err = mergeTypeAndIdRecords(tMerger.iDTable, tMerger.typeTable, - objectIndexMap->tpiMap, types, - file->pchSignature)) - fatal("codeview::mergeTypeAndIdRecords failed: " + - toString(std::move(err))); - } - - if (config->showSummary) { - // Count how many times we saw each type record in our input. This - // calculation requires a second pass over the type records to classify each - // record as a type or index. This is slow, but this code executes when - // collecting statistics. - tpiCounts.resize(tMerger.getTypeTable().size()); - ipiCounts.resize(tMerger.getIDTable().size()); - uint32_t srcIdx = 0; - for (CVType &ty : types) { - TypeIndex dstIdx = objectIndexMap->tpiMap[srcIdx++]; - // Type merging may fail, so a complex source type may become the simple - // NotTranslated type, which cannot be used as an array index. - if (dstIdx.isSimple()) - continue; - SmallVectorImpl<uint32_t> &counts = - isIdRecord(ty.kind()) ? ipiCounts : tpiCounts; - ++counts[dstIdx.toArrayIndex()]; - } - } - - return *objectIndexMap; -} - -Expected<const CVIndexMap &> PDBLinker::maybeMergeTypeServerPDB(ObjFile *file) { - Expected<llvm::pdb::NativeSession *> pdbSession = findTypeServerSource(file); - if (!pdbSession) - return pdbSession.takeError(); - - pdb::PDBFile &pdbFile = pdbSession.get()->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; - - Expected<pdb::TpiStream &> expectedTpi = pdbFile.getPDBTpiStream(); - if (auto e = expectedTpi.takeError()) - fatal("Type server does not have TPI stream: " + toString(std::move(e))); - pdb::TpiStream *maybeIpi = nullptr; - if (pdbFile.hasPDBIpiStream()) { - Expected<pdb::TpiStream &> expectedIpi = pdbFile.getPDBIpiStream(); - if (auto e = expectedIpi.takeError()) - fatal("Error getting type server IPI stream: " + toString(std::move(e))); - maybeIpi = &*expectedIpi; - } - - if (config->debugGHashes) { - // PDBs do not actually store global hashes, so when merging a type server - // PDB we have to synthesize global hashes. To do this, we first synthesize - // global hashes for the TPI stream, since it is independent, then we - // synthesize hashes for the IPI stream, using the hashes for the TPI stream - // as inputs. - auto tpiHashes = GloballyHashedType::hashTypes(expectedTpi->typeArray()); - Optional<uint32_t> endPrecomp; - // Merge TPI first, because the IPI stream will reference type indices. - if (auto err = - mergeTypeRecords(tMerger.globalTypeTable, indexMap.tpiMap, - expectedTpi->typeArray(), tpiHashes, endPrecomp)) - fatal("codeview::mergeTypeRecords failed: " + toString(std::move(err))); - - // Merge IPI. - if (maybeIpi) { - auto ipiHashes = - GloballyHashedType::hashIds(maybeIpi->typeArray(), tpiHashes); - if (auto err = - mergeIdRecords(tMerger.globalIDTable, indexMap.tpiMap, - indexMap.ipiMap, maybeIpi->typeArray(), ipiHashes)) - fatal("codeview::mergeIdRecords failed: " + toString(std::move(err))); - } - } else { - // Merge TPI first, because the IPI stream will reference type indices. - if (auto err = mergeTypeRecords(tMerger.typeTable, indexMap.tpiMap, - expectedTpi->typeArray())) - fatal("codeview::mergeTypeRecords failed: " + toString(std::move(err))); - - // Merge IPI. - if (maybeIpi) { - if (auto err = mergeIdRecords(tMerger.iDTable, indexMap.tpiMap, - indexMap.ipiMap, maybeIpi->typeArray())) - fatal("codeview::mergeIdRecords failed: " + toString(std::move(err))); - } - } - - if (config->showSummary) { - // Count how many times we saw each type record in our input. If a - // destination type index is present in the source to destination type index - // map, that means we saw it once in the input. Add it to our histogram. - tpiCounts.resize(tMerger.getTypeTable().size()); - ipiCounts.resize(tMerger.getIDTable().size()); - for (TypeIndex ti : indexMap.tpiMap) - if (!ti.isSimple()) - ++tpiCounts[ti.toArrayIndex()]; - for (TypeIndex ti : indexMap.ipiMap) - if (!ti.isSimple()) - ++ipiCounts[ti.toArrayIndex()]; - } - - return indexMap; -} - -Error PDBLinker::mergeInPrecompHeaderObj(ObjFile *file, - CVIndexMap *objectIndexMap) { - const PrecompRecord &precomp = - retrieveDependencyInfo<PrecompRecord>(file->debugTypesObj); - - Expected<const CVIndexMap &> e = aquirePrecompObj(file); - if (!e) - return e.takeError(); - - const CVIndexMap &precompIndexMap = *e; - assert(precompIndexMap.isPrecompiledTypeMap); - - if (precompIndexMap.tpiMap.empty()) - return Error::success(); - - assert(precomp.getStartTypeIndex() == TypeIndex::FirstNonSimpleIndex); - assert(precomp.getTypesCount() <= precompIndexMap.tpiMap.size()); - // Use the previously remapped index map from the precompiled headers. - objectIndexMap->tpiMap.append(precompIndexMap.tpiMap.begin(), - precompIndexMap.tpiMap.begin() + - precomp.getTypesCount()); - return Error::success(); -} - -static bool equals_path(StringRef path1, StringRef path2) { -#if defined(_WIN32) - return path1.equals_lower(path2); -#else - return path1.equals(path2); -#endif -} -// Find by name an OBJ provided on the command line -static ObjFile *findObjWithPrecompSignature(StringRef fileNameOnly, - uint32_t precompSignature) { - for (ObjFile *f : ObjFile::instances) { - StringRef currentFileName = sys::path::filename(f->getName()); - - if (f->pchSignature.hasValue() && - f->pchSignature.getValue() == precompSignature && - equals_path(fileNameOnly, currentFileName)) - return f; - } - return nullptr; -} - -std::pair<CVIndexMap &, bool /*already there*/> -PDBLinker::registerPrecompiledHeaders(uint32_t signature) { - auto insertion = precompTypeIndexMappings.insert({signature, CVIndexMap()}); - CVIndexMap &indexMap = insertion.first->second; - if (!insertion.second) - return {indexMap, true}; - // Mark this map as a precompiled types map. - indexMap.isPrecompiledTypeMap = true; - return {indexMap, false}; -} - -Expected<const CVIndexMap &> PDBLinker::aquirePrecompObj(ObjFile *file) { - const PrecompRecord &precomp = - retrieveDependencyInfo<PrecompRecord>(file->debugTypesObj); - - // First, check if we already loaded the precompiled headers object with this - // signature. Return the type index mapping if we've already seen it. - auto r = registerPrecompiledHeaders(precomp.getSignature()); - if (r.second) - return r.first; - - CVIndexMap &indexMap = r.first; - - // Cross-compile warning: given that Clang doesn't generate LF_PRECOMP - // records, we assume the OBJ comes from a Windows build of cl.exe. Thusly, - // the paths embedded in the OBJs are in the Windows format. - SmallString<128> precompFileName = sys::path::filename( - precomp.getPrecompFilePath(), sys::path::Style::windows); - - // link.exe requires that a precompiled headers object must always be provided - // on the command-line, even if that's not necessary. - auto precompFile = - findObjWithPrecompSignature(precompFileName, precomp.Signature); - if (!precompFile) - return createFileError( - precomp.getPrecompFilePath().str(), - make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch)); - - addObjFile(precompFile, &indexMap); - - return indexMap; -} - static bool remapTypeIndex(TypeIndex &ti, ArrayRef<TypeIndex> typeIndexMap) { if (ti.isSimple()) return true; @@ -695,7 +330,7 @@ static SymbolKind symbolKind(ArrayRef<uint8_t> recordData) { /// MSVC translates S_PROC_ID_END to S_END, and S_[LG]PROC32_ID to S_[LG]PROC32 static void translateIdSymbols(MutableArrayRef<uint8_t> &recordData, - TypeCollection &iDTable) { + TypeCollection &idTable) { RecordPrefix *prefix = reinterpret_cast<RecordPrefix *>(recordData.data()); SymbolKind kind = symbolKind(recordData); @@ -725,11 +360,10 @@ static void translateIdSymbols(MutableArrayRef<uint8_t> &recordData, // Note that LF_FUNC_ID and LF_MEMFUNC_ID have the same record layout, and // in both cases we just need the second type index. if (!ti->isSimple() && !ti->isNoneType()) { - CVType funcIdData = iDTable.getType(*ti); - SmallVector<TypeIndex, 2> indices; - discoverTypeIndices(funcIdData, indices); - assert(indices.size() == 2); - *ti = indices[1]; + CVType funcIdData = idTable.getType(*ti); + ArrayRef<uint8_t> tiBuf = funcIdData.data().slice(8, 4); + assert(tiBuf.size() == 4 && "corrupt LF_[MEM]FUNC_ID record"); + *ti = *reinterpret_cast<const TypeIndex *>(tiBuf.data()); } kind = (kind == SymbolKind::S_GPROC32_ID) ? SymbolKind::S_GPROC32 @@ -795,6 +429,7 @@ static bool symbolGoesInModuleStream(const CVSymbol &sym, bool isGlobalScope) { switch (sym.kind()) { case SymbolKind::S_GDATA32: case SymbolKind::S_CONSTANT: + case SymbolKind::S_GTHREAD32: // We really should not be seeing S_PROCREF and S_LPROCREF in the first place // since they are synthesized by the linker in response to S_GPROC32 and // S_LPROC32, but if we do see them, don't put them in the module stream I @@ -807,17 +442,18 @@ static bool symbolGoesInModuleStream(const CVSymbol &sym, bool isGlobalScope) { return !isGlobalScope; // S_GDATA32 does not go in the module stream, but S_LDATA32 does. case SymbolKind::S_LDATA32: + case SymbolKind::S_LTHREAD32: default: return true; } } -static bool symbolGoesInGlobalsStream(const CVSymbol &sym, bool isGlobalScope) { +static bool symbolGoesInGlobalsStream(const CVSymbol &sym, + bool isFunctionScope) { switch (sym.kind()) { case SymbolKind::S_CONSTANT: case SymbolKind::S_GDATA32: - // S_LDATA32 goes in both the module stream and the globals stream. - case SymbolKind::S_LDATA32: + case SymbolKind::S_GTHREAD32: case SymbolKind::S_GPROC32: case SymbolKind::S_LPROC32: // We really should not be seeing S_PROCREF and S_LPROCREF in the first place @@ -826,9 +462,11 @@ static bool symbolGoesInGlobalsStream(const CVSymbol &sym, bool isGlobalScope) { case SymbolKind::S_PROCREF: case SymbolKind::S_LPROCREF: return true; - // S_UDT records go in the globals stream if it is a global S_UDT. + // Records that go in the globals stream, unless they are function-local. case SymbolKind::S_UDT: - return isGlobalScope; + case SymbolKind::S_LDATA32: + case SymbolKind::S_LTHREAD32: + return !isFunctionScope; default: return false; } @@ -840,6 +478,8 @@ static void addGlobalSymbol(pdb::GSIStreamBuilder &builder, uint16_t modIndex, case SymbolKind::S_CONSTANT: case SymbolKind::S_UDT: case SymbolKind::S_GDATA32: + case SymbolKind::S_GTHREAD32: + case SymbolKind::S_LTHREAD32: case SymbolKind::S_LDATA32: case SymbolKind::S_PROCREF: case SymbolKind::S_LPROCREF: @@ -899,7 +539,7 @@ void PDBLinker::mergeSymbolRecords(ObjFile *file, const CVIndexMap &indexMap, MutableArrayRef<uint8_t> alignedSymbolMem; if (needsRealignment) { void *alignedData = - alloc.Allocate(totalRealignedSize, alignOf(CodeViewContainer::Pdb)); + bAlloc.Allocate(totalRealignedSize, alignOf(CodeViewContainer::Pdb)); alignedSymbolMem = makeMutableArrayRef( reinterpret_cast<uint8_t *>(alignedData), totalRealignedSize); } @@ -953,7 +593,7 @@ void PDBLinker::mergeSymbolRecords(ObjFile *file, const CVIndexMap &indexMap, // adding the symbol to the module since we may need to get the next // symbol offset, and writing to the module's symbol stream will update // that offset. - if (symbolGoesInGlobalsStream(sym, scopes.empty())) { + if (symbolGoesInGlobalsStream(sym, !scopes.empty())) { addGlobalSymbol(builder.getGsiBuilder(), file->moduleDBI->getModuleIndex(), curSymOffset, sym); ++globalSymbols; @@ -980,16 +620,6 @@ void PDBLinker::mergeSymbolRecords(ObjFile *file, const CVIndexMap &indexMap, file->moduleDBI->addSymbolsInBulk(bulkSymbols); } -// Allocate memory for a .debug$S / .debug$F section and relocate it. -static ArrayRef<uint8_t> relocateDebugChunk(BumpPtrAllocator &alloc, - SectionChunk &debugChunk) { - uint8_t *buffer = alloc.Allocate<uint8_t>(debugChunk.getSize()); - assert(debugChunk.getOutputSectionIdx() == 0 && - "debug sections should not be in output sections"); - debugChunk.writeTo(buffer); - return makeArrayRef(buffer, debugChunk.getSize()); -} - static pdb::SectionContrib createSectionContrib(const Chunk *c, uint32_t modi) { OutputSection *os = c ? c->getOutputSection() : nullptr; pdb::SectionContrib sc; @@ -1027,15 +657,19 @@ translateStringTableIndex(uint32_t objIndex, return pdbStrTable.insert(*expectedString); } -void DebugSHandler::handleDebugS(lld::coff::SectionChunk &debugS) { - DebugSubsectionArray subsections; - - ArrayRef<uint8_t> relocatedDebugContents = SectionChunk::consumeDebugMagic( - relocateDebugChunk(linker.alloc, debugS), debugS.getSectionName()); +void DebugSHandler::handleDebugS(ArrayRef<uint8_t> relocatedDebugContents) { + relocatedDebugContents = + SectionChunk::consumeDebugMagic(relocatedDebugContents, ".debug$S"); + DebugSubsectionArray subsections; BinaryStreamReader reader(relocatedDebugContents, support::little); exitOnErr(reader.readArray(subsections, relocatedDebugContents.size())); + // If there is no index map, use an empty one. + CVIndexMap tempIndexMap; + if (!indexMap) + indexMap = &tempIndexMap; + for (const DebugSubsectionRecord &ss : subsections) { // Ignore subsections with the 'ignore' bit. Some versions of the Visual C++ // runtime have subsections with this bit set. @@ -1044,9 +678,9 @@ void DebugSHandler::handleDebugS(lld::coff::SectionChunk &debugS) { switch (ss.kind()) { case DebugSubsectionKind::StringTable: { - assert(!cVStrTab.valid() && + assert(!cvStrTab.valid() && "Encountered multiple string table subsections!"); - exitOnErr(cVStrTab.initialize(ss.getRecordData())); + exitOnErr(cvStrTab.initialize(ss.getRecordData())); break; } case DebugSubsectionKind::FileChecksums: @@ -1060,9 +694,10 @@ void DebugSHandler::handleDebugS(lld::coff::SectionChunk &debugS) { file.moduleDBI->addDebugSubsection(ss); break; case DebugSubsectionKind::InlineeLines: - assert(!inlineeLines.valid() && - "Encountered multiple inlinee lines subsections!"); - exitOnErr(inlineeLines.initialize(ss.getRecordData())); + // The inlinee lines subsection also has file checksum table references + // that can be used directly, but it contains function id references that + // must be remapped. + mergeInlineeLines(ss); break; case DebugSubsectionKind::FrameData: { // We need to re-write string table indices here, so save off all @@ -1074,7 +709,7 @@ void DebugSHandler::handleDebugS(lld::coff::SectionChunk &debugS) { break; } case DebugSubsectionKind::Symbols: { - linker.mergeSymbolRecords(&file, indexMap, stringTableReferences, + linker.mergeSymbolRecords(&file, *indexMap, stringTableReferences, ss.getRecordData()); break; } @@ -1114,39 +749,24 @@ getFileName(const DebugStringTableSubsectionRef &strings, return strings.getString(offset); } -std::shared_ptr<DebugInlineeLinesSubsection> -DebugSHandler::mergeInlineeLines(DebugChecksumsSubsection *newChecksums) { - auto newInlineeLines = std::make_shared<DebugInlineeLinesSubsection>( - *newChecksums, inlineeLines.hasExtraFiles()); +void DebugSHandler::mergeInlineeLines( + const DebugSubsectionRecord &inlineeSubsection) { + DebugInlineeLinesSubsectionRef inlineeLines; + exitOnErr(inlineeLines.initialize(inlineeSubsection.getRecordData())); + // Remap type indices in inlinee line records in place. for (const InlineeSourceLine &line : inlineeLines) { - TypeIndex inlinee = line.Header->Inlinee; - uint32_t fileID = line.Header->FileID; - uint32_t sourceLine = line.Header->SourceLineNum; - + TypeIndex &inlinee = *const_cast<TypeIndex *>(&line.Header->Inlinee); ArrayRef<TypeIndex> typeOrItemMap = - indexMap.isTypeServerMap ? indexMap.ipiMap : indexMap.tpiMap; + indexMap->isTypeServerMap ? indexMap->ipiMap : indexMap->tpiMap; if (!remapTypeIndex(inlinee, typeOrItemMap)) { - log("ignoring inlinee line record in " + file.getName() + + log("bad inlinee line record in " + file.getName() + " with bad inlinee index 0x" + utohexstr(inlinee.getIndex())); - continue; - } - - SmallString<128> filename = - exitOnErr(getFileName(cVStrTab, checksums, fileID)); - pdbMakeAbsolute(filename); - newInlineeLines->addInlineSite(inlinee, filename, sourceLine); - - if (inlineeLines.hasExtraFiles()) { - for (uint32_t extraFileId : line.ExtraFiles) { - filename = exitOnErr(getFileName(cVStrTab, checksums, extraFileId)); - pdbMakeAbsolute(filename); - newInlineeLines->addExtraFile(filename); - } } } - return newInlineeLines; + // Add the modified inlinee line subsection directly. + file.moduleDBI->addDebugSubsection(inlineeSubsection); } void DebugSHandler::finish() { @@ -1155,7 +775,7 @@ void DebugSHandler::finish() { // We should have seen all debug subsections across the entire object file now // which means that if a StringTable subsection and Checksums subsection were // present, now is the time to handle them. - if (!cVStrTab.valid()) { + if (!cvStrTab.valid()) { if (checksums.valid()) fatal(".debug$S sections with a checksums subsection must also contain a " "string table subsection"); @@ -1173,77 +793,92 @@ void DebugSHandler::finish() { for (codeview::FrameData fd : fds) { fd.RvaStart += *reloc; fd.FrameFunc = - translateStringTableIndex(fd.FrameFunc, cVStrTab, linker.pdbStrTab); + translateStringTableIndex(fd.FrameFunc, cvStrTab, linker.pdbStrTab); dbiBuilder.addNewFpoData(fd); } } for (ulittle32_t *ref : stringTableReferences) - *ref = translateStringTableIndex(*ref, cVStrTab, linker.pdbStrTab); + *ref = translateStringTableIndex(*ref, cvStrTab, linker.pdbStrTab); // Make a new file checksum table that refers to offsets in the PDB-wide // string table. Generally the string table subsection appears after the // checksum table, so we have to do this after looping over all the - // subsections. + // subsections. The new checksum table must have the exact same layout and + // size as the original. Otherwise, the file references in the line and + // inlinee line tables will be incorrect. auto newChecksums = std::make_unique<DebugChecksumsSubsection>(linker.pdbStrTab); for (FileChecksumEntry &fc : checksums) { SmallString<128> filename = - exitOnErr(cVStrTab.getString(fc.FileNameOffset)); + exitOnErr(cvStrTab.getString(fc.FileNameOffset)); pdbMakeAbsolute(filename); exitOnErr(dbiBuilder.addModuleSourceFile(*file.moduleDBI, filename)); newChecksums->addChecksum(filename, fc.Kind, fc.Checksum); } - - // Rewrite inlinee item indices if present. - if (inlineeLines.valid()) - file.moduleDBI->addDebugSubsection(mergeInlineeLines(newChecksums.get())); + assert(checksums.getArray().getUnderlyingStream().getLength() == + newChecksums->calculateSerializedSize() && + "file checksum table must have same layout"); file.moduleDBI->addDebugSubsection(std::move(newChecksums)); } -void PDBLinker::addObjFile(ObjFile *file, CVIndexMap *externIndexMap) { - if (file->mergedIntoPDB) +static void warnUnusable(InputFile *f, Error e) { + if (!config->warnDebugInfoUnusable) { + consumeError(std::move(e)); return; - file->mergedIntoPDB = true; + } + auto msg = "Cannot use debug info for '" + toString(f) + "' [LNK4099]"; + if (e) + warn(msg + "\n>>> failed to load reference " + toString(std::move(e))); + else + warn(msg); +} +const CVIndexMap *PDBLinker::mergeTypeRecords(TpiSource *source, + CVIndexMap *localMap) { + ScopedTimer t(typeMergingTimer); // Before we can process symbol substreams from .debug$S, we need to process // type information, file checksums, and the string table. Add type info to // the PDB first, so that we can get the map from object file type and item // indices to PDB type and item indices. - CVIndexMap objectIndexMap; - auto indexMapResult = - mergeDebugT(file, externIndexMap ? externIndexMap : &objectIndexMap); + Expected<const CVIndexMap *> r = source->mergeDebugT(&tMerger, localMap); // If the .debug$T sections fail to merge, assume there is no debug info. - if (!indexMapResult) { - if (!config->warnDebugInfoUnusable) { - consumeError(indexMapResult.takeError()); - return; - } - warn("Cannot use debug info for '" + toString(file) + "' [LNK4099]\n" + - ">>> failed to load reference " + - StringRef(toString(indexMapResult.takeError()))); - return; + if (!r) { + warnUnusable(source->file, r.takeError()); + return nullptr; } + return *r; +} - ScopedTimer t(symbolMergingTimer); +// Allocate memory for a .debug$S / .debug$F section and relocate it. +static ArrayRef<uint8_t> relocateDebugChunk(SectionChunk &debugChunk) { + uint8_t *buffer = bAlloc.Allocate<uint8_t>(debugChunk.getSize()); + assert(debugChunk.getOutputSectionIdx() == 0 && + "debug sections should not be in output sections"); + debugChunk.writeTo(buffer); + return makeArrayRef(buffer, debugChunk.getSize()); +} +void PDBLinker::addDebugSymbols(ObjFile *file, const CVIndexMap *indexMap) { + ScopedTimer t(symbolMergingTimer); pdb::DbiStreamBuilder &dbiBuilder = builder.getDbiBuilder(); - DebugSHandler dsh(*this, *file, *indexMapResult); + DebugSHandler dsh(*this, *file, indexMap); // Now do all live .debug$S and .debug$F sections. for (SectionChunk *debugChunk : file->getDebugChunks()) { if (!debugChunk->live || debugChunk->getSize() == 0) continue; - if (debugChunk->getSectionName() == ".debug$S") { - dsh.handleDebugS(*debugChunk); + bool isDebugS = debugChunk->getSectionName() == ".debug$S"; + bool isDebugF = debugChunk->getSectionName() == ".debug$F"; + if (!isDebugS && !isDebugF) continue; - } - if (debugChunk->getSectionName() == ".debug$F") { - ArrayRef<uint8_t> relocatedDebugContents = - relocateDebugChunk(alloc, *debugChunk); + ArrayRef<uint8_t> relocatedDebugContents = relocateDebugChunk(*debugChunk); + if (isDebugS) { + dsh.handleDebugS(relocatedDebugContents); + } else if (isDebugF) { FixedStreamArray<object::FpoData> fpoRecords; BinaryStreamReader reader(relocatedDebugContents, support::little); uint32_t count = relocatedDebugContents.size() / sizeof(object::FpoData); @@ -1253,7 +888,6 @@ void PDBLinker::addObjFile(ObjFile *file, CVIndexMap *externIndexMap) { // can just copy it. for (const object::FpoData &fd : fpoRecords) dbiBuilder.addOldFpoData(fd); - continue; } } @@ -1265,43 +899,54 @@ void PDBLinker::addObjFile(ObjFile *file, CVIndexMap *externIndexMap) { // path to the object into the PDB. If this is a plain object, we make its // path absolute. If it's an object in an archive, we make the archive path // absolute. -static void createModuleDBI(pdb::PDBFileBuilder &builder) { +static void createModuleDBI(pdb::PDBFileBuilder &builder, ObjFile *file) { pdb::DbiStreamBuilder &dbiBuilder = builder.getDbiBuilder(); SmallString<128> objName; - for (ObjFile *file : ObjFile::instances) { + bool inArchive = !file->parentName.empty(); + objName = inArchive ? file->parentName : file->getName(); + pdbMakeAbsolute(objName); + StringRef modName = inArchive ? file->getName() : StringRef(objName); - bool inArchive = !file->parentName.empty(); - objName = inArchive ? file->parentName : file->getName(); - pdbMakeAbsolute(objName); - StringRef modName = inArchive ? file->getName() : StringRef(objName); + file->moduleDBI = &exitOnErr(dbiBuilder.addModuleInfo(modName)); + file->moduleDBI->setObjFileName(objName); - file->moduleDBI = &exitOnErr(dbiBuilder.addModuleInfo(modName)); - file->moduleDBI->setObjFileName(objName); + ArrayRef<Chunk *> chunks = file->getChunks(); + uint32_t modi = file->moduleDBI->getModuleIndex(); - ArrayRef<Chunk *> chunks = file->getChunks(); - uint32_t modi = file->moduleDBI->getModuleIndex(); - - for (Chunk *c : chunks) { - auto *secChunk = dyn_cast<SectionChunk>(c); - if (!secChunk || !secChunk->live) - continue; - pdb::SectionContrib sc = createSectionContrib(secChunk, modi); - file->moduleDBI->setFirstSectionContrib(sc); - break; - } + for (Chunk *c : chunks) { + auto *secChunk = dyn_cast<SectionChunk>(c); + if (!secChunk || !secChunk->live) + continue; + pdb::SectionContrib sc = createSectionContrib(secChunk, modi); + file->moduleDBI->setFirstSectionContrib(sc); + break; } } -static PublicSym32 createPublic(Defined *def) { - PublicSym32 pub(SymbolKind::S_PUB32); - pub.Name = def->getName(); +void PDBLinker::addDebug(TpiSource *source) { + CVIndexMap localMap; + const CVIndexMap *indexMap = mergeTypeRecords(source, &localMap); + + if (source->kind == TpiSource::PDB) + return; // No symbols in TypeServer PDBs + + addDebugSymbols(source->file, indexMap); +} + +static pdb::BulkPublic createPublic(Defined *def) { + pdb::BulkPublic pub; + pub.Name = def->getName().data(); + pub.NameLen = def->getName().size(); + + PublicSymFlags flags = PublicSymFlags::None; if (auto *d = dyn_cast<DefinedCOFF>(def)) { if (d->getCOFFSymbol().isFunctionDefinition()) - pub.Flags = PublicSymFlags::Function; + flags = PublicSymFlags::Function; } else if (isa<DefinedImportThunk>(def)) { - pub.Flags = PublicSymFlags::Function; + flags = PublicSymFlags::Function; } + pub.setFlags(flags); OutputSection *os = def->getChunk()->getOutputSection(); assert(os && "all publics should be in final image"); @@ -1315,10 +960,30 @@ static PublicSym32 createPublic(Defined *def) { void PDBLinker::addObjectsToPDB() { ScopedTimer t1(addObjectsTimer); - createModuleDBI(builder); + // Create module descriptors + for_each(ObjFile::instances, + [&](ObjFile *obj) { createModuleDBI(builder, obj); }); - for (ObjFile *file : ObjFile::instances) - addObjFile(file); + // Merge OBJs that do not have debug types + for_each(ObjFile::instances, [&](ObjFile *obj) { + if (obj->debugTypesObj) + return; + // Even if there're no types, still merge non-symbol .Debug$S and .Debug$F + // sections + addDebugSymbols(obj, nullptr); + }); + + // Merge dependencies + TpiSource::forEachSource([&](TpiSource *source) { + if (source->isDependency()) + addDebug(source); + }); + + // Merge regular and dependent OBJs + TpiSource::forEachSource([&](TpiSource *source) { + if (!source->isDependency()) + addDebug(source); + }); builder.getStringTableBuilder().setStrings(pdbStrTab); t1.stop(); @@ -1328,13 +993,16 @@ void PDBLinker::addObjectsToPDB() { addTypeInfo(builder.getTpiBuilder(), tMerger.getTypeTable()); addTypeInfo(builder.getIpiBuilder(), tMerger.getIDTable()); t2.stop(); +} - ScopedTimer t3(globalsLayoutTimer); - // Compute the public and global symbols. +void PDBLinker::addPublicsToPDB() { + ScopedTimer t3(publicsLayoutTimer); + // Compute the public symbols. auto &gsiBuilder = builder.getGsiBuilder(); - std::vector<PublicSym32> publics; + std::vector<pdb::BulkPublic> publics; symtab->forEachSymbol([&publics](Symbol *s) { - // Only emit defined, live symbols that have a chunk. + // Only emit external, defined, live symbols that have a chunk. Static, + // non-external symbols do not appear in the symbol table. auto *def = dyn_cast<Defined>(s); if (def && def->isLive() && def->getChunk()) publics.push_back(createPublic(def)); @@ -1342,12 +1010,7 @@ void PDBLinker::addObjectsToPDB() { if (!publics.empty()) { publicSymbols = publics.size(); - // Sort the public symbols and add them to the stream. - parallelSort(publics, [](const PublicSym32 &l, const PublicSym32 &r) { - return l.Name < r.Name; - }); - for (const PublicSym32 &pub : publics) - gsiBuilder.addPublicSymbol(pub); + gsiBuilder.addPublicSymbols(std::move(publics)); } } @@ -1367,8 +1030,8 @@ void PDBLinker::printStats() { print(ObjFile::instances.size(), "Input OBJ files (expanded from all cmd-line inputs)"); - print(typeServerIndexMappings.size(), "PDB type server dependencies"); - print(precompTypeIndexMappings.size(), "Precomp OBJ dependencies"); + print(TpiSource::countTypeServerPDBs(), "PDB type server dependencies"); + print(TpiSource::countPrecompObjs(), "Precomp OBJ dependencies"); print(tMerger.getTypeTable().size() + tMerger.getIDTable().size(), "Merged TPI records"); print(pdbStrTab.size(), "Output PDB strings"); @@ -1388,6 +1051,8 @@ void PDBLinker::printStats() { TypeIndex typeIndex; uint64_t totalInputSize() const { return uint64_t(dupCount) * typeSize; } bool operator<(const TypeSizeInfo &rhs) const { + if (totalInputSize() == rhs.totalInputSize()) + return typeIndex < rhs.typeIndex; return totalInputSize() < rhs.totalInputSize(); } }; @@ -1420,8 +1085,8 @@ void PDBLinker::printStats() { } }; - printLargeInputTypeRecs("TPI", tpiCounts, tMerger.getTypeTable()); - printLargeInputTypeRecs("IPI", ipiCounts, tMerger.getIDTable()); + printLargeInputTypeRecs("TPI", tMerger.tpiCounts, tMerger.getTypeTable()); + printLargeInputTypeRecs("IPI", tMerger.ipiCounts, tMerger.getIDTable()); message(buffer); } @@ -1438,6 +1103,19 @@ void PDBLinker::addNatvisFiles() { } } +void PDBLinker::addNamedStreams() { + for (const auto &streamFile : config->namedStreams) { + const StringRef stream = streamFile.getKey(), file = streamFile.getValue(); + ErrorOr<std::unique_ptr<MemoryBuffer>> dataOrErr = + MemoryBuffer::getFile(file); + if (!dataOrErr) { + warn("Cannot open input file: " + file); + continue; + } + exitOnErr(builder.addNamedStream(stream, (*dataOrErr)->getBuffer())); + } +} + static codeview::CPUType toCodeViewMachine(COFF::MachineTypes machine) { switch (machine) { case COFF::IMAGE_FILE_MACHINE_AMD64: @@ -1473,7 +1151,7 @@ static std::string quote(ArrayRef<StringRef> args) { a.split(s, '"'); r.append(join(s, "\"\"")); } else { - r.append(a); + r.append(std::string(a)); } if (hasWS || hasQ) r.push_back('"'); @@ -1508,8 +1186,7 @@ static void fillLinkerVerRecord(Compile3Sym &cs) { } static void addCommonLinkerModuleSymbols(StringRef path, - pdb::DbiModuleDescriptorBuilder &mod, - BumpPtrAllocator &allocator) { + pdb::DbiModuleDescriptorBuilder &mod) { ObjNameSym ons(SymbolRecordKind::ObjNameSym); EnvBlockSym ebs(SymbolRecordKind::EnvBlockSym); Compile3Sym cs(SymbolRecordKind::Compile3Sym); @@ -1536,17 +1213,16 @@ static void addCommonLinkerModuleSymbols(StringRef path, ebs.Fields.push_back("cmd"); ebs.Fields.push_back(argStr); mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( - ons, allocator, CodeViewContainer::Pdb)); + ons, bAlloc, CodeViewContainer::Pdb)); mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( - cs, allocator, CodeViewContainer::Pdb)); + cs, bAlloc, CodeViewContainer::Pdb)); mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( - ebs, allocator, CodeViewContainer::Pdb)); + ebs, bAlloc, CodeViewContainer::Pdb)); } static void addLinkerModuleCoffGroup(PartialSection *sec, pdb::DbiModuleDescriptorBuilder &mod, - OutputSection &os, - BumpPtrAllocator &allocator) { + OutputSection &os) { // If there's a section, there's at least one chunk assert(!sec->chunks.empty()); const Chunk *firstChunk = *sec->chunks.begin(); @@ -1567,12 +1243,11 @@ static void addLinkerModuleCoffGroup(PartialSection *sec, cgs.Characteristics |= llvm::COFF::IMAGE_SCN_MEM_WRITE; mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( - cgs, allocator, CodeViewContainer::Pdb)); + cgs, bAlloc, CodeViewContainer::Pdb)); } static void addLinkerModuleSectionSymbol(pdb::DbiModuleDescriptorBuilder &mod, - OutputSection &os, - BumpPtrAllocator &allocator) { + OutputSection &os) { SectionSym sym(SymbolRecordKind::SectionSym); sym.Alignment = 12; // 2^12 = 4KB sym.Characteristics = os.header.Characteristics; @@ -1581,7 +1256,7 @@ static void addLinkerModuleSectionSymbol(pdb::DbiModuleDescriptorBuilder &mod, sym.Rva = os.getRVA(); sym.SectionNumber = os.sectionIndex; mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol( - sym, allocator, CodeViewContainer::Pdb)); + sym, bAlloc, CodeViewContainer::Pdb)); // Skip COFF groups in MinGW because it adds a significant footprint to the // PDB, due to each function being in its own section @@ -1590,7 +1265,7 @@ static void addLinkerModuleSectionSymbol(pdb::DbiModuleDescriptorBuilder &mod, // Output COFF groups for individual chunks of this section. for (PartialSection *sec : os.contribSections) { - addLinkerModuleCoffGroup(sec, mod, os, allocator); + addLinkerModuleCoffGroup(sec, mod, os); } } @@ -1657,18 +1332,18 @@ void PDBLinker::addImportFilesToPDB(ArrayRef<OutputSection *> outputSections) { ts.Offset = thunkChunk->getRVA() - thunkOS->getRVA(); mod->addSymbol(codeview::SymbolSerializer::writeOneSymbol( - ons, alloc, CodeViewContainer::Pdb)); + ons, bAlloc, CodeViewContainer::Pdb)); mod->addSymbol(codeview::SymbolSerializer::writeOneSymbol( - cs, alloc, CodeViewContainer::Pdb)); + cs, bAlloc, CodeViewContainer::Pdb)); SmallVector<SymbolScope, 4> scopes; CVSymbol newSym = codeview::SymbolSerializer::writeOneSymbol( - ts, alloc, CodeViewContainer::Pdb); + ts, bAlloc, CodeViewContainer::Pdb); scopeStackOpen(scopes, mod->getNextSymbolOffset(), newSym); mod->addSymbol(newSym); - newSym = codeview::SymbolSerializer::writeOneSymbol(es, alloc, + newSym = codeview::SymbolSerializer::writeOneSymbol(es, bAlloc, CodeViewContainer::Pdb); scopeStackClose(scopes, mod->getNextSymbolOffset(), file); @@ -1681,10 +1356,10 @@ void PDBLinker::addImportFilesToPDB(ArrayRef<OutputSection *> outputSections) { } // Creates a PDB file. -void createPDB(SymbolTable *symtab, - ArrayRef<OutputSection *> outputSections, - ArrayRef<uint8_t> sectionTable, - llvm::codeview::DebugInfo *buildId) { +void lld::coff::createPDB(SymbolTable *symtab, + ArrayRef<OutputSection *> outputSections, + ArrayRef<uint8_t> sectionTable, + llvm::codeview::DebugInfo *buildId) { ScopedTimer t1(totalPdbLinkTimer); PDBLinker pdb(symtab); @@ -1693,6 +1368,8 @@ void createPDB(SymbolTable *symtab, pdb.addImportFilesToPDB(outputSections); pdb.addSections(outputSections, sectionTable); pdb.addNatvisFiles(); + pdb.addNamedStreams(); + pdb.addPublicsToPDB(); ScopedTimer t2(diskCommitTimer); codeview::GUID guid; @@ -1743,11 +1420,11 @@ void PDBLinker::addSections(ArrayRef<OutputSection *> outputSections, uint32_t pdbFilePathNI = dbiBuilder.addECName(nativePath); auto &linkerModule = exitOnErr(dbiBuilder.addModuleInfo("* Linker *")); linkerModule.setPdbFilePathNI(pdbFilePathNI); - addCommonLinkerModuleSymbols(nativePath, linkerModule, alloc); + addCommonLinkerModuleSymbols(nativePath, linkerModule); // Add section contributions. They must be ordered by ascending RVA. for (OutputSection *os : outputSections) { - addLinkerModuleSectionSymbol(linkerModule, *os, alloc); + addLinkerModuleSectionSymbol(linkerModule, *os); for (Chunk *c : os->chunks) { pdb::SectionContrib sc = createSectionContrib(c, linkerModule.getModuleIndex()); @@ -1766,8 +1443,7 @@ void PDBLinker::addSections(ArrayRef<OutputSection *> outputSections, ArrayRef<object::coff_section> sections = { (const object::coff_section *)sectionTable.data(), sectionTable.size() / sizeof(object::coff_section)}; - sectionMap = pdb::DbiStreamBuilder::createSectionMap(sections); - dbiBuilder.setSectionMap(sectionMap); + dbiBuilder.createSectionMap(sections); // Add COFF section header stream. exitOnErr( @@ -1801,7 +1477,7 @@ static uint32_t getSecrelReloc() { // table are stored in the output arguments. Returns whether a line table was // found. static bool findLineTable(const SectionChunk *c, uint32_t addr, - DebugStringTableSubsectionRef &cVStrTab, + DebugStringTableSubsectionRef &cvStrTab, DebugChecksumsSubsectionRef &checksums, DebugLinesSubsectionRef &lines, uint32_t &offsetInLinetable) { @@ -1833,9 +1509,9 @@ static bool findLineTable(const SectionChunk *c, uint32_t addr, for (const DebugSubsectionRecord &ss : subsections) { switch (ss.kind()) { case DebugSubsectionKind::StringTable: { - assert(!cVStrTab.valid() && + assert(!cvStrTab.valid() && "Encountered multiple string table subsections!"); - exitOnErr(cVStrTab.initialize(ss.getRecordData())); + exitOnErr(cvStrTab.initialize(ss.getRecordData())); break; } case DebugSubsectionKind::FileChecksums: @@ -1871,7 +1547,7 @@ static bool findLineTable(const SectionChunk *c, uint32_t addr, break; } - if (cVStrTab.valid() && checksums.valid() && lines.header()) + if (cvStrTab.valid() && checksums.valid() && lines.header()) return true; } } @@ -1883,15 +1559,15 @@ static bool findLineTable(const SectionChunk *c, uint32_t addr, // offset into the given chunk and return them, or None if a line table was // not found. Optional<std::pair<StringRef, uint32_t>> -getFileLineCodeView(const SectionChunk *c, uint32_t addr) { +lld::coff::getFileLineCodeView(const SectionChunk *c, uint32_t addr) { ExitOnError exitOnErr; - DebugStringTableSubsectionRef cVStrTab; + DebugStringTableSubsectionRef cvStrTab; DebugChecksumsSubsectionRef checksums; DebugLinesSubsectionRef lines; uint32_t offsetInLinetable; - if (!findLineTable(c, addr, cVStrTab, checksums, lines, offsetInLinetable)) + if (!findLineTable(c, addr, cvStrTab, checksums, lines, offsetInLinetable)) return None; Optional<uint32_t> nameIndex; @@ -1905,7 +1581,7 @@ getFileLineCodeView(const SectionChunk *c, uint32_t addr) { lineNumber = li.getStartLine(); } StringRef filename = - exitOnErr(getFileName(cVStrTab, checksums, *nameIndex)); + exitOnErr(getFileName(cvStrTab, checksums, *nameIndex)); return std::make_pair(filename, *lineNumber); } nameIndex = entry.NameIndex; @@ -1914,9 +1590,6 @@ getFileLineCodeView(const SectionChunk *c, uint32_t addr) { } if (!nameIndex) return None; - StringRef filename = exitOnErr(getFileName(cVStrTab, checksums, *nameIndex)); + StringRef filename = exitOnErr(getFileName(cvStrTab, checksums, *nameIndex)); return std::make_pair(filename, *lineNumber); } - -} // namespace coff -} // namespace lld diff --git a/lld/COFF/SymbolTable.cpp b/lld/COFF/SymbolTable.cpp index 7072f4d8d0e3..d4d2a159a639 100644 --- a/lld/COFF/SymbolTable.cpp +++ b/lld/COFF/SymbolTable.cpp @@ -204,7 +204,7 @@ static void reportUndefinedSymbol(const UndefinedDiag &undefDiag) { llvm::raw_string_ostream os(out); os << "undefined symbol: " << toString(*undefDiag.sym); - const size_t maxUndefReferences = 10; + const size_t maxUndefReferences = 3; size_t i = 0, numRefs = 0; for (const UndefinedDiag::File &ref : undefDiag.files) { std::vector<std::string> symbolLocations = @@ -438,7 +438,7 @@ void SymbolTable::resolveRemainingUndefines() { if (name.contains("_PchSym_")) continue; - if (config->mingw && handleMinGWAutomaticImport(sym, name)) + if (config->autoImport && handleMinGWAutomaticImport(sym, name)) continue; // Remaining undefined symbols are not fatal if /force is specified. @@ -789,20 +789,16 @@ Symbol *SymbolTable::addUndefined(StringRef name) { return addUndefined(name, nullptr, false); } -std::vector<StringRef> SymbolTable::compileBitcodeFiles() { - lto.reset(new BitcodeCompiler); - for (BitcodeFile *f : BitcodeFile::instances) - lto->add(*f); - return lto->compile(); -} - void SymbolTable::addCombinedLTOObjects() { if (BitcodeFile::instances.empty()) return; ScopedTimer t(ltoTimer); - for (StringRef object : compileBitcodeFiles()) { - auto *obj = make<ObjFile>(MemoryBufferRef(object, "lto.tmp")); + lto.reset(new BitcodeCompiler); + for (BitcodeFile *f : BitcodeFile::instances) + lto->add(*f); + for (InputFile *newObj : lto->compile()) { + ObjFile *obj = cast<ObjFile>(newObj); obj->parse(); ObjFile::instances.push_back(obj); } diff --git a/lld/COFF/SymbolTable.h b/lld/COFF/SymbolTable.h index cd8a53dcecdc..870a7151fa8e 100644 --- a/lld/COFF/SymbolTable.h +++ b/lld/COFF/SymbolTable.h @@ -77,7 +77,6 @@ public: // BitcodeFiles and add them to the symbol table. Called after all files are // added and before the writer writes results to a file. void addCombinedLTOObjects(); - std::vector<StringRef> compileBitcodeFiles(); // Creates an Undefined symbol for a given name. Symbol *addUndefined(StringRef name); diff --git a/lld/COFF/Symbols.cpp b/lld/COFF/Symbols.cpp index 938c9c527ffa..60ff72aeb522 100644 --- a/lld/COFF/Symbols.cpp +++ b/lld/COFF/Symbols.cpp @@ -36,12 +36,12 @@ static std::string maybeDemangleSymbol(StringRef symName) { StringRef demangleInput = prefixless; if (config->machine == I386) demangleInput.consume_front("_"); - std::string demangled = demangle(demangleInput); + std::string demangled = demangle(std::string(demangleInput)); if (demangled != demangleInput) - return prefix + demangle(demangleInput); + return prefix + demangle(std::string(demangleInput)); return (prefix + prefixless).str(); } - return symName; + return std::string(symName); } std::string toString(coff::Symbol &b) { return maybeDemangleSymbol(b.getName()); @@ -52,23 +52,15 @@ std::string toCOFFString(const Archive::Symbol &b) { namespace coff { -StringRef Symbol::getName() { - // COFF symbol names are read lazily for a performance reason. - // Non-external symbol names are never used by the linker except for logging - // or debugging. Their internal references are resolved not by name but by - // symbol index. And because they are not external, no one can refer them by - // name. Object files contain lots of non-external symbols, and creating - // StringRefs for them (which involves lots of strlen() on the string table) - // is a waste of time. - if (nameData == nullptr) { - auto *d = cast<DefinedCOFF>(this); - StringRef nameStr; - cast<ObjFile>(d->file)->getCOFFObj()->getSymbolName(d->sym, nameStr); - nameData = nameStr.data(); - nameSize = nameStr.size(); - assert(nameSize == nameStr.size() && "name length truncated"); - } - return StringRef(nameData, nameSize); +void Symbol::computeName() { + assert(nameData == nullptr && + "should only compute the name once for DefinedCOFF symbols"); + auto *d = cast<DefinedCOFF>(this); + StringRef nameStr = + check(cast<ObjFile>(d->file)->getCOFFObj()->getSymbolName(d->sym)); + nameData = nameStr.data(); + nameSize = nameStr.size(); + assert(nameSize == nameStr.size() && "name length truncated"); } InputFile *Symbol::getFile() { diff --git a/lld/COFF/Symbols.h b/lld/COFF/Symbols.h index a8e70320b995..1da4df366966 100644 --- a/lld/COFF/Symbols.h +++ b/lld/COFF/Symbols.h @@ -69,7 +69,18 @@ public: Kind kind() const { return static_cast<Kind>(symbolKind); } // Returns the symbol name. - StringRef getName(); + StringRef getName() { + // COFF symbol names are read lazily for a performance reason. + // Non-external symbol names are never used by the linker except for logging + // or debugging. Their internal references are resolved not by name but by + // symbol index. And because they are not external, no one can refer them by + // name. Object files contain lots of non-external symbols, and creating + // StringRefs for them (which involves lots of strlen() on the string table) + // is a waste of time. + if (nameData == nullptr) + computeName(); + return StringRef(nameData, nameSize); + } void replaceKeepingName(Symbol *other, size_t size); @@ -84,6 +95,9 @@ public: return symbolKind == LazyArchiveKind || symbolKind == LazyObjectKind; } +private: + void computeName(); + protected: friend SymbolTable; explicit Symbol(Kind k, StringRef n = "") diff --git a/lld/COFF/TypeMerger.h b/lld/COFF/TypeMerger.h index e2cfe668cfa5..858f55b6856d 100644 --- a/lld/COFF/TypeMerger.h +++ b/lld/COFF/TypeMerger.h @@ -20,7 +20,7 @@ namespace coff { class TypeMerger { public: TypeMerger(llvm::BumpPtrAllocator &alloc) - : typeTable(alloc), iDTable(alloc), globalTypeTable(alloc), + : typeTable(alloc), idTable(alloc), globalTypeTable(alloc), globalIDTable(alloc) {} /// Get the type table or the global type table if /DEBUG:GHASH is enabled. @@ -34,20 +34,25 @@ public: inline llvm::codeview::TypeCollection &getIDTable() { if (config->debugGHashes) return globalIDTable; - return iDTable; + return idTable; } /// Type records that will go into the PDB TPI stream. llvm::codeview::MergingTypeTableBuilder typeTable; /// Item records that will go into the PDB IPI stream. - llvm::codeview::MergingTypeTableBuilder iDTable; + llvm::codeview::MergingTypeTableBuilder idTable; /// Type records that will go into the PDB TPI stream (for /DEBUG:GHASH) llvm::codeview::GlobalTypeTableBuilder globalTypeTable; /// Item records that will go into the PDB IPI stream (for /DEBUG:GHASH) llvm::codeview::GlobalTypeTableBuilder globalIDTable; + + // When showSummary is enabled, these are histograms of TPI and IPI records + // keyed by type index. + SmallVector<uint32_t, 0> tpiCounts; + SmallVector<uint32_t, 0> ipiCounts; }; /// Map from type index and item index in a type server PDB to the @@ -62,4 +67,4 @@ struct CVIndexMap { } // namespace coff } // namespace lld -#endif
\ No newline at end of file +#endif diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp index fcad7739d300..3bcc1777f7ac 100644 --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -10,13 +10,13 @@ #include "Config.h" #include "DLL.h" #include "InputFiles.h" +#include "LLDMapFile.h" #include "MapFile.h" #include "PDB.h" #include "SymbolTable.h" #include "Symbols.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Memory.h" -#include "lld/Common/Threads.h" #include "lld/Common/Timer.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" @@ -41,9 +41,8 @@ using namespace llvm::COFF; using namespace llvm::object; using namespace llvm::support; using namespace llvm::support::endian; - -namespace lld { -namespace coff { +using namespace lld; +using namespace lld::coff; /* To re-generate DOSProgram: $ cat > /tmp/DOSProgram.asm @@ -92,7 +91,8 @@ namespace { class DebugDirectoryChunk : public NonSectionChunk { public: - DebugDirectoryChunk(const std::vector<Chunk *> &r, bool writeRepro) + DebugDirectoryChunk(const std::vector<std::pair<COFF::DebugType, Chunk *>> &r, + bool writeRepro) : records(r), writeRepro(writeRepro) {} size_t getSize() const override { @@ -102,11 +102,11 @@ public: void writeTo(uint8_t *b) const override { auto *d = reinterpret_cast<debug_directory *>(b); - for (const Chunk *record : records) { - OutputSection *os = record->getOutputSection(); - uint64_t offs = os->getFileOff() + (record->getRVA() - os->getRVA()); - fillEntry(d, COFF::IMAGE_DEBUG_TYPE_CODEVIEW, record->getSize(), - record->getRVA(), offs); + for (const std::pair<COFF::DebugType, Chunk *>& record : records) { + Chunk *c = record.second; + OutputSection *os = c->getOutputSection(); + uint64_t offs = os->getFileOff() + (c->getRVA() - os->getRVA()); + fillEntry(d, record.first, c->getSize(), c->getRVA(), offs); ++d; } @@ -141,7 +141,7 @@ private: } mutable std::vector<support::ulittle32_t *> timeDateStamps; - const std::vector<Chunk *> &records; + const std::vector<std::pair<COFF::DebugType, Chunk *>> &records; bool writeRepro; }; @@ -166,6 +166,17 @@ public: mutable codeview::DebugInfo *buildId = nullptr; }; +class ExtendedDllCharacteristicsChunk : public NonSectionChunk { +public: + ExtendedDllCharacteristicsChunk(uint32_t c) : characteristics(c) {} + + size_t getSize() const override { return 4; } + + void writeTo(uint8_t *buf) const override { write32le(buf, characteristics); } + + uint32_t characteristics = 0; +}; + // PartialSection represents a group of chunks that contribute to an // OutputSection. Collating a collection of PartialSections of same name and // characteristics constitutes the OutputSection. @@ -251,7 +262,7 @@ private: bool setNoSEHCharacteristic = false; DebugDirectoryChunk *debugDirectory = nullptr; - std::vector<Chunk *> debugRecords; + std::vector<std::pair<COFF::DebugType, Chunk *>> debugRecords; CVDebugRecordChunk *buildId = nullptr; ArrayRef<uint8_t> sectionTable; @@ -290,7 +301,7 @@ private: static Timer codeLayoutTimer("Code Layout", Timer::root()); static Timer diskCommitTimer("Commit Output File", Timer::root()); -void writeResult() { Writer().run(); } +void lld::coff::writeResult() { Writer().run(); } void OutputSection::addChunk(Chunk *c) { chunks.push_back(c); @@ -622,6 +633,7 @@ void Writer::run() { } writeBuildId(); + writeLLDMapFile(outputSections); writeMapFile(outputSections); if (errorCount()) @@ -921,8 +933,9 @@ void Writer::createMiscChunks() { // Create Debug Information Chunks OutputSection *debugInfoSec = config->mingw ? buildidSec : rdataSec; - if (config->debug || config->repro) { + if (config->debug || config->repro || config->cetCompat) { debugDirectory = make<DebugDirectoryChunk>(debugRecords, config->repro); + debugDirectory->setAlignment(4); debugInfoSec->addChunk(debugDirectory); } @@ -932,10 +945,20 @@ void Writer::createMiscChunks() { // allowing a debugger to match a PDB and an executable. So we need it even // if we're ultimately not going to write CodeView data to the PDB. buildId = make<CVDebugRecordChunk>(); - debugRecords.push_back(buildId); + debugRecords.push_back({COFF::IMAGE_DEBUG_TYPE_CODEVIEW, buildId}); + } - for (Chunk *c : debugRecords) - debugInfoSec->addChunk(c); + if (config->cetCompat) { + ExtendedDllCharacteristicsChunk *extendedDllChars = + make<ExtendedDllCharacteristicsChunk>( + IMAGE_DLL_CHARACTERISTICS_EX_CET_COMPAT); + debugRecords.push_back( + {COFF::IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, extendedDllChars}); + } + + if (debugRecords.size() > 0) { + for (std::pair<COFF::DebugType, Chunk *> r : debugRecords) + debugInfoSec->addChunk(r.second); } // Create SEH table. x86-only. @@ -946,11 +969,11 @@ void Writer::createMiscChunks() { if (config->guardCF != GuardCFLevel::Off) createGuardCFTables(); - if (config->mingw) { + if (config->autoImport) createRuntimePseudoRelocs(); + if (config->mingw) insertCtorDtorSymbols(); - } } // Create .idata section for the DLL-imported symbol table. @@ -1699,6 +1722,15 @@ void Writer::createRuntimePseudoRelocs() { sc->getRuntimePseudoRelocs(rels); } + if (!config->pseudoRelocs) { + // Not writing any pseudo relocs; if some were needed, error out and + // indicate what required them. + for (const RuntimePseudoReloc &rpr : rels) + error("automatic dllimport of " + rpr.sym->getName() + " in " + + toString(rpr.target->file) + " requires pseudo relocations"); + return; + } + if (!rels.empty()) log("Writing " + Twine(rels.size()) + " runtime pseudo relocations"); PseudoRelocTableChunk *table = make<PseudoRelocTableChunk>(rels); @@ -1832,6 +1864,10 @@ void Writer::sortExceptionTable() { uint8_t *end = bufAddr(lastPdata) + lastPdata->getSize(); if (config->machine == AMD64) { struct Entry { ulittle32_t begin, end, unwind; }; + if ((end - begin) % sizeof(Entry) != 0) { + fatal("unexpected .pdata size: " + Twine(end - begin) + + " is not a multiple of " + Twine(sizeof(Entry))); + } parallelSort( MutableArrayRef<Entry>((Entry *)begin, (Entry *)end), [](const Entry &a, const Entry &b) { return a.begin < b.begin; }); @@ -1839,6 +1875,10 @@ void Writer::sortExceptionTable() { } if (config->machine == ARMNT || config->machine == ARM64) { struct Entry { ulittle32_t begin, unwind; }; + if ((end - begin) % sizeof(Entry) != 0) { + fatal("unexpected .pdata size: " + Twine(end - begin) + + " is not a multiple of " + Twine(sizeof(Entry))); + } parallelSort( MutableArrayRef<Entry>((Entry *)begin, (Entry *)end), [](const Entry &a, const Entry &b) { return a.begin < b.begin; }); @@ -1950,6 +1990,3 @@ PartialSection *Writer::findPartialSection(StringRef name, uint32_t outChars) { return it->second; return nullptr; } - -} // namespace coff -} // namespace lld |
