| //===- SymbolTable.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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Symbol table is a bag of all known symbols. We put all symbols of | |
| // all input files to the symbol table. The symbol table is basically | |
| // a hash table with the logic to resolve symbol name conflicts using | |
| // the symbol types. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "SymbolTable.h" | |
| #include "Config.h" | |
| #include "InputFiles.h" | |
| #include "Symbols.h" | |
| #include "lld/Common/Memory.h" | |
| #include "lld/Common/Strings.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/Demangle/Demangle.h" | |
| using namespace llvm; | |
| using namespace llvm::object; | |
| using namespace llvm::ELF; | |
| using namespace lld; | |
| using namespace lld::elf; | |
| void SymbolTable::wrap(Symbol *sym, Symbol *real, Symbol *wrap) { | |
| // Redirect __real_foo to the original foo and foo to the original __wrap_foo. | |
| int &idx1 = symMap[CachedHashStringRef(sym->getName())]; | |
| int &idx2 = symMap[CachedHashStringRef(real->getName())]; | |
| int &idx3 = symMap[CachedHashStringRef(wrap->getName())]; | |
| idx2 = idx1; | |
| idx1 = idx3; | |
| // Propagate symbol usage information to the redirected symbols. | |
| if (sym->isUsedInRegularObj) | |
| wrap->isUsedInRegularObj = true; | |
| if (real->isUsedInRegularObj) | |
| sym->isUsedInRegularObj = true; | |
| else if (!sym->isDefined()) | |
| // Now that all references to sym have been redirected to wrap, if there are | |
| // no references to real (which has been redirected to sym), we only need to | |
| // keep sym if it was defined, otherwise it's unused and can be dropped. | |
| sym->isUsedInRegularObj = false; | |
| // Now renaming is complete, and no one refers to real. We drop real from | |
| // .symtab and .dynsym. If real is undefined, it is important that we don't | |
| // leave it in .dynsym, because otherwise it might lead to an undefined symbol | |
| // error in a subsequent link. If real is defined, we could emit real as an | |
| // alias for sym, but that could degrade the user experience of some tools | |
| // that can print out only one symbol for each location: sym is a preferred | |
| // name than real, but they might print out real instead. | |
| memcpy(static_cast<void *>(real), sym, sizeof(SymbolUnion)); | |
| real->isUsedInRegularObj = false; | |
| } | |
| // Find an existing symbol or create a new one. | |
| Symbol *SymbolTable::insert(StringRef name) { | |
| // <name>@@<version> means the symbol is the default version. In that | |
| // case <name>@@<version> will be used to resolve references to <name>. | |
| // | |
| // Since this is a hot path, the following string search code is | |
| // optimized for speed. StringRef::find(char) is much faster than | |
| // StringRef::find(StringRef). | |
| StringRef stem = name; | |
| size_t pos = name.find('@'); | |
| if (pos != StringRef::npos && pos + 1 < name.size() && name[pos + 1] == '@') | |
| stem = name.take_front(pos); | |
| auto p = symMap.insert({CachedHashStringRef(stem), (int)symVector.size()}); | |
| if (!p.second) { | |
| Symbol *sym = symVector[p.first->second]; | |
| if (stem.size() != name.size()) { | |
| sym->setName(name); | |
| sym->hasVersionSuffix = true; | |
| } | |
| return sym; | |
| } | |
| Symbol *sym = reinterpret_cast<Symbol *>(make<SymbolUnion>()); | |
| symVector.push_back(sym); | |
| // *sym was not initialized by a constructor. Initialize all Symbol fields. | |
| memset(static_cast<void *>(sym), 0, sizeof(Symbol)); | |
| sym->setName(name); | |
| sym->partition = 1; | |
| sym->versionId = VER_NDX_GLOBAL; | |
| if (pos != StringRef::npos) | |
| sym->hasVersionSuffix = true; | |
| return sym; | |
| } | |
| // This variant of addSymbol is used by BinaryFile::parse to check duplicate | |
| // symbol errors. | |
| Symbol *SymbolTable::addAndCheckDuplicate(Ctx &ctx, const Defined &newSym) { | |
| Symbol *sym = insert(newSym.getName()); | |
| if (sym->isDefined()) | |
| sym->checkDuplicate(ctx, newSym); | |
| sym->resolve(ctx, newSym); | |
| sym->isUsedInRegularObj = true; | |
| return sym; | |
| } | |
| Symbol *SymbolTable::find(StringRef name) { | |
| auto it = symMap.find(CachedHashStringRef(name)); | |
| if (it == symMap.end()) | |
| return nullptr; | |
| return symVector[it->second]; | |
| } | |
| // A version script/dynamic list is only meaningful for a Defined symbol. | |
| // A CommonSymbol will be converted to a Defined in replaceCommonSymbols(). | |
| // A lazy symbol may be made Defined if an LTO libcall extracts it. | |
| static bool canBeVersioned(const Symbol &sym) { | |
| return sym.isDefined() || sym.isCommon() || sym.isLazy(); | |
| } | |
| // Initialize demangledSyms with a map from demangled symbols to symbol | |
| // objects. Used to handle "extern C++" directive in version scripts. | |
| // | |
| // The map will contain all demangled symbols. That can be very large, | |
| // and in LLD we generally want to avoid do anything for each symbol. | |
| // Then, why are we doing this? Here's why. | |
| // | |
| // Users can use "extern C++ {}" directive to match against demangled | |
| // C++ symbols. For example, you can write a pattern such as | |
| // "llvm::*::foo(int, ?)". Obviously, there's no way to handle this | |
| // other than trying to match a pattern against all demangled symbols. | |
| // So, if "extern C++" feature is used, we need to demangle all known | |
| // symbols. | |
| StringMap<SmallVector<Symbol *, 0>> &SymbolTable::getDemangledSyms() { | |
| if (!demangledSyms) { | |
| demangledSyms.emplace(); | |
| std::string demangled; | |
| for (Symbol *sym : symVector) | |
| if (canBeVersioned(*sym)) { | |
| StringRef name = sym->getName(); | |
| size_t pos = name.find('@'); | |
| std::string substr; | |
| if (pos == std::string::npos) | |
| demangled = demangle(name); | |
| else if (pos + 1 == name.size() || name[pos + 1] == '@') { | |
| substr = name.substr(0, pos); | |
| demangled = demangle(substr); | |
| } else { | |
| substr = name.substr(0, pos); | |
| demangled = (demangle(substr) + name.substr(pos)).str(); | |
| } | |
| (*demangledSyms)[demangled].push_back(sym); | |
| } | |
| } | |
| return *demangledSyms; | |
| } | |
| SmallVector<Symbol *, 0> SymbolTable::findByVersion(SymbolVersion ver) { | |
| if (ver.isExternCpp) | |
| return getDemangledSyms().lookup(ver.name); | |
| if (Symbol *sym = find(ver.name)) | |
| if (canBeVersioned(*sym)) | |
| return {sym}; | |
| return {}; | |
| } | |
| SmallVector<Symbol *, 0> SymbolTable::findAllByVersion(SymbolVersion ver, | |
| bool includeNonDefault) { | |
| SmallVector<Symbol *, 0> res; | |
| SingleStringMatcher m(ver.name); | |
| auto check = [&](const Symbol &sym) -> bool { | |
| if (!includeNonDefault) | |
| return !sym.hasVersionSuffix; | |
| StringRef name = sym.getName(); | |
| size_t pos = name.find('@'); | |
| return !(pos + 1 < name.size() && name[pos + 1] == '@'); | |
| }; | |
| if (ver.isExternCpp) { | |
| for (auto &p : getDemangledSyms()) | |
| if (m.match(p.first())) | |
| for (Symbol *sym : p.second) | |
| if (check(*sym)) | |
| res.push_back(sym); | |
| return res; | |
| } | |
| for (Symbol *sym : symVector) | |
| if (canBeVersioned(*sym) && check(*sym) && m.match(sym->getName())) | |
| res.push_back(sym); | |
| return res; | |
| } | |
| void SymbolTable::handleDynamicList() { | |
| SmallVector<Symbol *, 0> syms; | |
| for (SymbolVersion &ver : ctx.arg.dynamicList) { | |
| if (ver.hasWildcard) | |
| syms = findAllByVersion(ver, /*includeNonDefault=*/true); | |
| else | |
| syms = findByVersion(ver); | |
| for (Symbol *sym : syms) | |
| sym->isExported = sym->inDynamicList = true; | |
| } | |
| } | |
| // Set symbol versions to symbols. This function handles patterns containing no | |
| // wildcard characters. Return false if no symbol definition matches ver. | |
| bool SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId, | |
| StringRef versionName, | |
| bool includeNonDefault) { | |
| // Get a list of symbols which we need to assign the version to. | |
| SmallVector<Symbol *, 0> syms = findByVersion(ver); | |
| auto getName = [&ctx = ctx](uint16_t ver) -> std::string { | |
| if (ver == VER_NDX_LOCAL) | |
| return "VER_NDX_LOCAL"; | |
| if (ver == VER_NDX_GLOBAL) | |
| return "VER_NDX_GLOBAL"; | |
| return ("version '" + ctx.arg.versionDefinitions[ver].name + "'").str(); | |
| }; | |
| // Assign the version. | |
| for (Symbol *sym : syms) { | |
| // For a non-local versionId, skip symbols containing version info because | |
| // symbol versions specified by symbol names take precedence over version | |
| // scripts. See parseSymbolVersion(ctx). | |
| if (!includeNonDefault && versionId != VER_NDX_LOCAL && | |
| sym->getName().contains('@')) | |
| continue; | |
| // If the version has not been assigned, assign versionId to the symbol. | |
| if (!sym->versionScriptAssigned) { | |
| sym->versionScriptAssigned = true; | |
| sym->versionId = versionId; | |
| } | |
| if (sym->versionId == versionId) | |
| continue; | |
| Warn(ctx) << "attempt to reassign symbol '" << ver.name << "' of " | |
| << getName(sym->versionId) << " to " << getName(versionId); | |
| } | |
| return !syms.empty(); | |
| } | |
| void SymbolTable::assignWildcardVersion(SymbolVersion ver, uint16_t versionId, | |
| bool includeNonDefault) { | |
| // Exact matching takes precedence over fuzzy matching, | |
| // so we set a version to a symbol only if no version has been assigned | |
| // to the symbol. This behavior is compatible with GNU. | |
| for (Symbol *sym : findAllByVersion(ver, includeNonDefault)) | |
| if (!sym->versionScriptAssigned) { | |
| sym->versionScriptAssigned = true; | |
| sym->versionId = versionId; | |
| } | |
| } | |
| // This function processes version scripts by updating the versionId | |
| // member of symbols. | |
| // If there's only one anonymous version definition in a version | |
| // script file, the script does not actually define any symbol version, | |
| // but just specifies symbols visibilities. | |
| void SymbolTable::scanVersionScript() { | |
| SmallString<128> buf; | |
| // First, we assign versions to exact matching symbols, | |
| // i.e. version definitions not containing any glob meta-characters. | |
| for (VersionDefinition &v : ctx.arg.versionDefinitions) { | |
| auto assignExact = [&](SymbolVersion pat, uint16_t id, StringRef ver) { | |
| bool found = | |
| assignExactVersion(pat, id, ver, /*includeNonDefault=*/false); | |
| buf.clear(); | |
| found |= assignExactVersion({(pat.name + "@" + v.name).toStringRef(buf), | |
| pat.isExternCpp, /*hasWildCard=*/false}, | |
| id, ver, /*includeNonDefault=*/true); | |
| if (!found && !ctx.arg.undefinedVersion) | |
| Err(ctx) << "version script assignment of '" << ver << "' to symbol '" | |
| << pat.name << "' failed: symbol not defined"; | |
| }; | |
| for (SymbolVersion &pat : v.nonLocalPatterns) | |
| if (!pat.hasWildcard) | |
| assignExact(pat, v.id, v.name); | |
| for (SymbolVersion pat : v.localPatterns) | |
| if (!pat.hasWildcard) | |
| assignExact(pat, VER_NDX_LOCAL, "local"); | |
| } | |
| // Next, assign versions to wildcards that are not "*". Note that because the | |
| // last match takes precedence over previous matches, we iterate over the | |
| // definitions in the reverse order. | |
| auto assignWildcard = [&](SymbolVersion pat, uint16_t id, StringRef ver) { | |
| assignWildcardVersion(pat, id, /*includeNonDefault=*/false); | |
| buf.clear(); | |
| assignWildcardVersion({(pat.name + "@" + ver).toStringRef(buf), | |
| pat.isExternCpp, /*hasWildCard=*/true}, | |
| id, | |
| /*includeNonDefault=*/true); | |
| }; | |
| for (VersionDefinition &v : llvm::reverse(ctx.arg.versionDefinitions)) { | |
| for (SymbolVersion &pat : v.nonLocalPatterns) | |
| if (pat.hasWildcard && pat.name != "*") | |
| assignWildcard(pat, v.id, v.name); | |
| for (SymbolVersion &pat : v.localPatterns) | |
| if (pat.hasWildcard && pat.name != "*") | |
| assignWildcard(pat, VER_NDX_LOCAL, v.name); | |
| } | |
| // Then, assign versions to "*". In GNU linkers they have lower priority than | |
| // other wildcards. | |
| bool globalAsteriskFound = false; | |
| bool localAsteriskFound = false; | |
| bool asteriskReported = false; | |
| auto assignAsterisk = [&](SymbolVersion &pat, VersionDefinition *ver, | |
| bool isLocal) { | |
| // Avoid issuing a warning if both '--retain-symbol-file' and a version | |
| // script with `global: *` are used. | |
| // | |
| // '--retain-symbol-file' adds a "*" pattern to | |
| // 'versionDefinitions[VER_NDX_LOCAL].nonLocalPatterns', see | |
| // 'readConfigs()' in 'Driver.cpp'. Note that it is not '.localPatterns', | |
| // and may seem counterintuitive, but still works as expected. Here we can | |
| // exploit that and skip analyzing the pattern added for this option. | |
| if (!asteriskReported && (isLocal || ver->id > VER_NDX_LOCAL)) { | |
| if ((isLocal && globalAsteriskFound) || | |
| (!isLocal && localAsteriskFound)) { | |
| Warn(ctx) | |
| << "wildcard pattern '*' is used for both 'local' and 'global' " | |
| "scopes in version script"; | |
| asteriskReported = true; | |
| } else if (!isLocal && globalAsteriskFound) { | |
| Warn(ctx) << "wildcard pattern '*' is used for multiple version " | |
| "definitions in " | |
| "version script"; | |
| asteriskReported = true; | |
| } else { | |
| localAsteriskFound = isLocal; | |
| globalAsteriskFound = !isLocal; | |
| } | |
| } | |
| assignWildcard(pat, isLocal ? (uint16_t)VER_NDX_LOCAL : ver->id, ver->name); | |
| }; | |
| for (VersionDefinition &v : llvm::reverse(ctx.arg.versionDefinitions)) { | |
| for (SymbolVersion &pat : v.nonLocalPatterns) | |
| if (pat.hasWildcard && pat.name == "*") | |
| assignAsterisk(pat, &v, false); | |
| for (SymbolVersion &pat : v.localPatterns) | |
| if (pat.hasWildcard && pat.name == "*") | |
| assignAsterisk(pat, &v, true); | |
| } | |
| // Handle --dynamic-list. If a specified symbol is also matched by local: in a | |
| // version script, the version script takes precedence. | |
| handleDynamicList(); | |
| } | |
| Symbol *SymbolTable::addUnusedUndefined(StringRef name, uint8_t binding) { | |
| return addSymbol(Undefined{ctx.internalFile, name, binding, STV_DEFAULT, 0}); | |
| } |