//===- Core/Resolver.cpp - Resolves Atom References -----------------------===// // // The LLVM Linker // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "lld/Core/Atom.h" #include "lld/Core/ArchiveLibraryFile.h" #include "lld/Core/File.h" #include "lld/Core/Instrumentation.h" #include "lld/Core/LLVM.h" #include "lld/Core/LinkingContext.h" #include "lld/Core/Resolver.h" #include "lld/Core/SharedLibraryFile.h" #include "lld/Core/SymbolTable.h" #include "lld/Core/UndefinedAtom.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Format.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include namespace lld { bool Resolver::handleFile(File &file) { bool undefAdded = false; for (const DefinedAtom *atom : file.defined()) doDefinedAtom(*atom); for (const UndefinedAtom *atom : file.undefined()) { if (doUndefinedAtom(*atom)) { undefAdded = true; maybePreloadArchiveMember(atom->name()); } } for (const SharedLibraryAtom *atom : file.sharedLibrary()) doSharedLibraryAtom(*atom); for (const AbsoluteAtom *atom : file.absolute()) doAbsoluteAtom(*atom); return undefAdded; } void Resolver::forEachUndefines(File &file, bool searchForOverrides, UndefCallback callback) { size_t i = _undefineIndex[&file]; do { for (; i < _undefines.size(); ++i) { StringRef undefName = _undefines[i]; if (undefName.empty()) continue; const Atom *atom = _symbolTable.findByName(undefName); if (!isa(atom) || _symbolTable.isCoalescedAway(atom)) { // The symbol was resolved by some other file. Cache the result. _undefines[i] = ""; continue; } callback(undefName, false); } if (!searchForOverrides) continue; for (StringRef tentDefName : _symbolTable.tentativeDefinitions()) { // Load for previous tentative may also have loaded // something that overrode this tentative, so always check. const Atom *curAtom = _symbolTable.findByName(tentDefName); assert(curAtom != nullptr); if (const DefinedAtom *curDefAtom = dyn_cast(curAtom)) if (curDefAtom->merge() == DefinedAtom::mergeAsTentative) callback(tentDefName, true); } } while (i < _undefines.size()); _undefineIndex[&file] = i; } bool Resolver::handleArchiveFile(File &file) { ArchiveLibraryFile *archiveFile = cast(&file); bool searchForOverrides = _ctx.searchArchivesToOverrideTentativeDefinitions(); bool undefAdded = false; forEachUndefines(file, searchForOverrides, [&](StringRef undefName, bool dataSymbolOnly) { if (File *member = archiveFile->find(undefName, dataSymbolOnly)) { member->setOrdinal(_ctx.getNextOrdinalAndIncrement()); member->beforeLink(); updatePreloadArchiveMap(); undefAdded = handleFile(*member) || undefAdded; } }); return undefAdded; } void Resolver::handleSharedLibrary(File &file) { // Add all the atoms from the shared library SharedLibraryFile *sharedLibrary = cast(&file); handleFile(*sharedLibrary); bool searchForOverrides = _ctx.searchSharedLibrariesToOverrideTentativeDefinitions(); forEachUndefines(file, searchForOverrides, [&](StringRef undefName, bool dataSymbolOnly) { if (const SharedLibraryAtom *atom = sharedLibrary->exports(undefName, dataSymbolOnly)) doSharedLibraryAtom(*atom); }); } bool Resolver::doUndefinedAtom(const UndefinedAtom &atom) { DEBUG_WITH_TYPE("resolver", llvm::dbgs() << " UndefinedAtom: " << llvm::format("0x%09lX", &atom) << ", name=" << atom.name() << "\n"); // add to list of known atoms _atoms.push_back(&atom); // tell symbol table bool newUndefAdded = _symbolTable.add(atom); if (newUndefAdded) _undefines.push_back(atom.name()); // If the undefined symbol has an alternative name, try to resolve the // symbol with the name to give it a second chance. This feature is used // for COFF "weak external" symbol. if (newUndefAdded || !_symbolTable.isDefined(atom.name())) { if (const UndefinedAtom *fallbackAtom = atom.fallback()) { doUndefinedAtom(*fallbackAtom); _symbolTable.addReplacement(&atom, fallbackAtom); } } return newUndefAdded; } /// \brief Add the section group and the group-child reference members. void Resolver::maybeAddSectionGroupOrGnuLinkOnce(const DefinedAtom &atom) { // First time adding a group? bool isFirstTime = _symbolTable.addGroup(atom); if (!isFirstTime) { // If duplicate symbols are allowed, select the first group. if (_ctx.getAllowDuplicates()) return; auto *prevGroup = dyn_cast(_symbolTable.findGroup(atom.name())); assert(prevGroup && "Internal Error: The group atom could only be a defined atom"); // The atoms should be of the same content type, reject invalid group // resolution behaviors. if (atom.contentType() == prevGroup->contentType()) return; llvm::errs() << "SymbolTable: error while merging " << atom.name() << "\n"; llvm::report_fatal_error("duplicate symbol error"); return; } for (const Reference *r : atom) { if (r->kindNamespace() == lld::Reference::KindNamespace::all && r->kindValue() == lld::Reference::kindGroupChild) { const DefinedAtom *target = dyn_cast(r->target()); assert(target && "Internal Error: kindGroupChild references need to " "be associated with Defined Atoms only"); _atoms.push_back(target); _symbolTable.add(*target); } } } // Called on each atom when a file is added. Returns true if a given // atom is added to the symbol table. void Resolver::doDefinedAtom(const DefinedAtom &atom) { DEBUG_WITH_TYPE("resolver", llvm::dbgs() << " DefinedAtom: " << llvm::format("0x%09lX", &atom) << ", file=#" << atom.file().ordinal() << ", atom=#" << atom.ordinal() << ", name=" << atom.name() << "\n"); // add to list of known atoms _atoms.push_back(&atom); if (atom.isGroupParent()) { maybeAddSectionGroupOrGnuLinkOnce(atom); } else { _symbolTable.add(atom); } // An atom that should never be dead-stripped is a dead-strip root. if (_ctx.deadStrip() && atom.deadStrip() == DefinedAtom::deadStripNever) { _deadStripRoots.insert(&atom); } } void Resolver::doSharedLibraryAtom(const SharedLibraryAtom &atom) { DEBUG_WITH_TYPE("resolver", llvm::dbgs() << " SharedLibraryAtom: " << llvm::format("0x%09lX", &atom) << ", name=" << atom.name() << "\n"); // add to list of known atoms _atoms.push_back(&atom); // tell symbol table _symbolTable.add(atom); } void Resolver::doAbsoluteAtom(const AbsoluteAtom &atom) { DEBUG_WITH_TYPE("resolver", llvm::dbgs() << " AbsoluteAtom: " << llvm::format("0x%09lX", &atom) << ", name=" << atom.name() << "\n"); // add to list of known atoms _atoms.push_back(&atom); // tell symbol table if (atom.scope() != Atom::scopeTranslationUnit) _symbolTable.add(atom); } // utility to add a vector of atoms void Resolver::addAtoms(const std::vector &newAtoms) { for (const DefinedAtom *newAtom : newAtoms) doDefinedAtom(*newAtom); } // Instantiate an archive file member if there's a file containing a // defined symbol for a given symbol name. Instantiation is done in a // different worker thread and has no visible side effect. void Resolver::maybePreloadArchiveMember(StringRef sym) { auto it = _archiveMap.find(sym); if (it == _archiveMap.end()) return; ArchiveLibraryFile *archive = it->second; archive->preload(_ctx.getTaskGroup(), sym); } // Returns true if at least one of N previous files has created an // undefined symbol. bool Resolver::undefinesAdded(int begin, int end) { std::vector> &inputs = _ctx.getNodes(); for (int i = begin; i < end; ++i) if (FileNode *node = dyn_cast(inputs[i].get())) if (_newUndefinesAdded[node->getFile()]) return true; return false; } File *Resolver::getFile(int &index) { std::vector> &inputs = _ctx.getNodes(); if ((size_t)index >= inputs.size()) return nullptr; if (GroupEnd *group = dyn_cast(inputs[index].get())) { // We are at the end of the current group. If one or more new // undefined atom has been added in the last groupSize files, we // reiterate over the files. int size = group->getSize(); if (undefinesAdded(index - size, index)) { index -= size; return getFile(index); } ++index; return getFile(index); } return cast(inputs[index++].get())->getFile(); } // Update a map of Symbol -> ArchiveFile. The map is used for speculative // file loading. void Resolver::updatePreloadArchiveMap() { std::vector> &nodes = _ctx.getNodes(); for (int i = nodes.size() - 1; i >= 0; --i) { auto *fnode = dyn_cast(nodes[i].get()); if (!fnode) continue; auto *archive = dyn_cast(fnode->getFile()); if (!archive || _archiveSeen.count(archive)) continue; _archiveSeen.insert(archive); for (StringRef sym : archive->getDefinedSymbols()) _archiveMap[sym] = archive; } } // Keep adding atoms until _ctx.getNextFile() returns an error. This // function is where undefined atoms are resolved. bool Resolver::resolveUndefines() { ScopedTask task(getDefaultDomain(), "resolveUndefines"); int index = 0; std::set seen; for (;;) { bool undefAdded = false; File *file = getFile(index); if (!file) return true; if (std::error_code ec = file->parse()) { llvm::errs() << "Cannot open " + file->path() << ": " << ec.message() << "\n"; return false; } file->beforeLink(); updatePreloadArchiveMap(); switch (file->kind()) { case File::kindObject: // The same file may be visited more than once if the file is // in --start-group and --end-group. Only library files should // be processed more than once. if (seen.count(file)) break; seen.insert(file); assert(!file->hasOrdinal()); file->setOrdinal(_ctx.getNextOrdinalAndIncrement()); undefAdded = handleFile(*file); break; case File::kindArchiveLibrary: if (!file->hasOrdinal()) file->setOrdinal(_ctx.getNextOrdinalAndIncrement()); undefAdded = handleArchiveFile(*file); break; case File::kindSharedLibrary: if (!file->hasOrdinal()) file->setOrdinal(_ctx.getNextOrdinalAndIncrement()); handleSharedLibrary(*file); break; } _newUndefinesAdded[file] = undefAdded; } } // switch all references to undefined or coalesced away atoms // to the new defined atom void Resolver::updateReferences() { ScopedTask task(getDefaultDomain(), "updateReferences"); for (const Atom *atom : _atoms) { if (const DefinedAtom *defAtom = dyn_cast(atom)) { for (const Reference *ref : *defAtom) { // A reference of type kindAssociate should't be updated. // Instead, an atom having such reference will be removed // if the target atom is coalesced away, so that they will // go away as a group. if (ref->kindNamespace() == lld::Reference::KindNamespace::all && ref->kindValue() == lld::Reference::kindAssociate) { if (_symbolTable.isCoalescedAway(atom)) _deadAtoms.insert(ref->target()); continue; } const Atom *newTarget = _symbolTable.replacement(ref->target()); const_cast(ref)->setTarget(newTarget); } } } } // For dead code stripping, recursively mark atoms "live" void Resolver::markLive(const Atom *atom) { // Mark the atom is live. If it's already marked live, then stop recursion. auto exists = _liveAtoms.insert(atom); if (!exists.second) return; // Mark all atoms it references as live if (const DefinedAtom *defAtom = dyn_cast(atom)) { for (const Reference *ref : *defAtom) markLive(ref->target()); for (auto &p : llvm::make_range(_reverseRef.equal_range(defAtom))) { const Atom *target = p.second; markLive(target); } } } static bool isBackref(const Reference *ref) { if (ref->kindNamespace() != lld::Reference::KindNamespace::all) return false; return (ref->kindValue() == lld::Reference::kindLayoutAfter || ref->kindValue() == lld::Reference::kindGroupChild); } // remove all atoms not actually used void Resolver::deadStripOptimize() { ScopedTask task(getDefaultDomain(), "deadStripOptimize"); // only do this optimization with -dead_strip if (!_ctx.deadStrip()) return; // Some type of references prevent referring atoms to be dead-striped. // Make a reverse map of such references before traversing the graph. // While traversing the list of atoms, mark AbsoluteAtoms as live // in order to avoid reclaim. for (const Atom *atom : _atoms) { if (const DefinedAtom *defAtom = dyn_cast(atom)) for (const Reference *ref : *defAtom) if (isBackref(ref)) _reverseRef.insert(std::make_pair(ref->target(), atom)); if (const AbsoluteAtom *absAtom = dyn_cast(atom)) markLive(absAtom); } // By default, shared libraries are built with all globals as dead strip roots if (_ctx.globalsAreDeadStripRoots()) for (const Atom *atom : _atoms) if (const DefinedAtom *defAtom = dyn_cast(atom)) if (defAtom->scope() == DefinedAtom::scopeGlobal) _deadStripRoots.insert(defAtom); // Or, use list of names that are dead strip roots. for (const StringRef &name : _ctx.deadStripRoots()) { const Atom *symAtom = _symbolTable.findByName(name); assert(symAtom); _deadStripRoots.insert(symAtom); } // mark all roots as live, and recursively all atoms they reference for (const Atom *dsrAtom : _deadStripRoots) markLive(dsrAtom); // now remove all non-live atoms from _atoms _atoms.erase(std::remove_if(_atoms.begin(), _atoms.end(), [&](const Atom *a) { return _liveAtoms.count(a) == 0; }), _atoms.end()); } // error out if some undefines remain bool Resolver::checkUndefines() { // build vector of remaining undefined symbols std::vector undefinedAtoms = _symbolTable.undefines(); if (_ctx.deadStrip()) { // When dead code stripping, we don't care if dead atoms are undefined. undefinedAtoms.erase( std::remove_if(undefinedAtoms.begin(), undefinedAtoms.end(), [&](const Atom *a) { return _liveAtoms.count(a) == 0; }), undefinedAtoms.end()); } if (undefinedAtoms.empty()) return false; // Warn about unresolved symbols. bool foundUndefines = false; for (const UndefinedAtom *undef : undefinedAtoms) { // Skip over a weak symbol. if (undef->canBeNull() != UndefinedAtom::canBeNullNever) continue; // If this is a library and undefined symbols are allowed on the // target platform, skip over it. if (isa(undef->file()) && _ctx.allowShlibUndefines()) continue; // If the undefine is coalesced away, skip over it. if (_symbolTable.isCoalescedAway(undef)) continue; // Seems like this symbol is undefined. Warn that. foundUndefines = true; if (_ctx.printRemainingUndefines()) { llvm::errs() << "Undefined symbol: " << undef->file().path() << ": " << _ctx.demangle(undef->name()) << "\n"; } } if (!foundUndefines) return false; if (_ctx.printRemainingUndefines()) llvm::errs() << "symbol(s) not found\n"; return true; } // remove from _atoms all coaleseced away atoms void Resolver::removeCoalescedAwayAtoms() { ScopedTask task(getDefaultDomain(), "removeCoalescedAwayAtoms"); _atoms.erase(std::remove_if(_atoms.begin(), _atoms.end(), [&](const Atom *a) { return _symbolTable.isCoalescedAway(a) || _deadAtoms.count(a); }), _atoms.end()); } bool Resolver::resolve() { updatePreloadArchiveMap(); if (!resolveUndefines()) return false; updateReferences(); deadStripOptimize(); if (checkUndefines()) if (!_ctx.allowRemainingUndefines()) return false; removeCoalescedAwayAtoms(); _result->addAtoms(_atoms); return true; } void Resolver::MergedFile::addAtoms(std::vector &all) { ScopedTask task(getDefaultDomain(), "addAtoms"); DEBUG_WITH_TYPE("resolver", llvm::dbgs() << "Resolver final atom list:\n"); for (const Atom *atom : all) { DEBUG_WITH_TYPE("resolver", llvm::dbgs() << llvm::format(" 0x%09lX", atom) << ", name=" << atom->name() << "\n"); addAtom(*atom); } } } // namespace lld