diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -87,6 +87,7 @@ llvm::StringRef name; uint16_t id; std::vector patterns; + std::vector localPatterns; }; // This struct contains the global configuration for the linker. diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1351,9 +1351,10 @@ } assert(config->versionDefinitions.empty()); - config->versionDefinitions.push_back({"local", (uint16_t)VER_NDX_LOCAL, {}}); config->versionDefinitions.push_back( - {"global", (uint16_t)VER_NDX_GLOBAL, {}}); + {"local", (uint16_t)VER_NDX_LOCAL, {}, {}}); + config->versionDefinitions.push_back( + {"global", (uint16_t)VER_NDX_GLOBAL, {}, {}}); // If --retain-symbol-file is used, we'll keep only the symbols listed in // the file and discard all others. diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp --- a/lld/ELF/ScriptParser.cpp +++ b/lld/ELF/ScriptParser.cpp @@ -1510,13 +1510,12 @@ std::vector locals; std::vector globals; std::tie(locals, globals) = readSymbols(); - for (const SymbolVersion &pat : locals) - config->versionDefinitions[VER_NDX_LOCAL].patterns.push_back(pat); // Create a new version definition and add that to the global symbols. VersionDefinition ver; ver.name = verStr; - ver.patterns = globals; + ver.patterns = std::move(globals); + ver.localPatterns = std::move(locals); ver.id = config->versionDefinitions.size(); config->versionDefinitions.push_back(ver); diff --git a/lld/ELF/SymbolTable.h b/lld/ELF/SymbolTable.h --- a/lld/ELF/SymbolTable.h +++ b/lld/ELF/SymbolTable.h @@ -65,12 +65,13 @@ private: std::vector findByVersion(SymbolVersion ver); - std::vector findAllByVersion(SymbolVersion ver); + std::vector findAllByVersion(SymbolVersion ver, bool checkAt); llvm::StringMap> &getDemangledSyms(); - void assignExactVersion(SymbolVersion ver, uint16_t versionId, + bool assignExactVersion(SymbolVersion ver, uint16_t versionId, StringRef versionName); - void assignWildcardVersion(SymbolVersion ver, uint16_t versionId); + void assignWildcardVersion(SymbolVersion ver, uint16_t versionId, + bool checkAt); // The order the global symbols are in is not defined. We can use an arbitrary // order, but it has to be reproducible. That is true even when cross linking. diff --git a/lld/ELF/SymbolTable.cpp b/lld/ELF/SymbolTable.cpp --- a/lld/ELF/SymbolTable.cpp +++ b/lld/ELF/SymbolTable.cpp @@ -150,19 +150,23 @@ return {}; } -std::vector SymbolTable::findAllByVersion(SymbolVersion ver) { +std::vector SymbolTable::findAllByVersion(SymbolVersion ver, + bool checkAt) { std::vector res; SingleStringMatcher m(ver.name); if (ver.isExternCpp) { for (auto &p : getDemangledSyms()) if (m.match(p.first())) - res.insert(res.end(), p.second.begin(), p.second.end()); + for (Symbol *sym : p.second) + if (checkAt || !sym->getName().contains('@')) + res.push_back(sym); return res; } for (Symbol *sym : symVector) - if (canBeVersioned(*sym) && m.match(sym->getName())) + if (canBeVersioned(*sym) && (checkAt || !sym->getName().contains('@')) && + m.match(sym->getName())) res.push_back(sym); return res; } @@ -172,7 +176,7 @@ for (SymbolVersion &ver : config->dynamicList) { std::vector syms; if (ver.hasWildcard) - syms = findAllByVersion(ver); + syms = findAllByVersion(ver, /*checkAt=*/true); else syms = findByVersion(ver); @@ -183,19 +187,10 @@ // Set symbol versions to symbols. This function handles patterns // containing no wildcard characters. -void SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId, +bool SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId, StringRef versionName) { - if (ver.hasWildcard) - return; - // Get a list of symbols which we need to assign the version to. std::vector syms = findByVersion(ver); - if (syms.empty()) { - if (!config->undefinedVersion) - error("version script assignment of '" + versionName + "' to symbol '" + - ver.name + "' failed: symbol not defined"); - return; - } auto getName = [](uint16_t ver) -> std::string { if (ver == VER_NDX_LOCAL) @@ -207,10 +202,10 @@ // Assign the version. for (Symbol *sym : syms) { - // Skip symbols containing version info because symbol versions - // specified by symbol names take precedence over version scripts. - // See parseSymbolVersion(). - if (sym->getName().contains('@')) + // For a non-local versionId, skip symbols containing version info because + // symbol versions specified by symbol names take precedence over version + // scripts. See parseSymbolVersion(). + if (versionId != VER_NDX_LOCAL && sym->getName().contains('@')) continue; // If the version has not been assigned, verdefIndex is -1. Use an arbitrary @@ -225,13 +220,15 @@ warn("attempt to reassign symbol '" + ver.name + "' of " + getName(sym->versionId) + " to " + getName(versionId)); } + return !syms.empty(); } -void SymbolTable::assignWildcardVersion(SymbolVersion ver, uint16_t versionId) { +void SymbolTable::assignWildcardVersion(SymbolVersion ver, uint16_t versionId, + bool checkAt) { // 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)) + for (Symbol *sym : findAllByVersion(ver, checkAt)) if (sym->verdefIndex == UINT32_C(-1)) { sym->verdefIndex = 0; sym->versionId = versionId; @@ -244,26 +241,63 @@ // 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 : config->versionDefinitions) + std::vector syms; + for (VersionDefinition &v : config->versionDefinitions) { + auto reportUndefinedVersion = [&](StringRef ver, StringRef symName) { + errorOrWarn("version script assignment of '" + ver + "' to symbol '" + + symName + "' failed: symbol not defined"); + }; for (SymbolVersion &pat : v.patterns) - assignExactVersion(pat, v.id, v.name); + if (!pat.hasWildcard) { + bool found = assignExactVersion(pat, v.id, v.name); + found |= assignExactVersion({(pat.name + "@" + v.name).toStringRef(buf), + pat.isExternCpp, false}, + v.id, v.name); + if (!found && !config->undefinedVersion) + reportUndefinedVersion(v.name, pat.name); + } + for (SymbolVersion pat : v.localPatterns) + if (!pat.hasWildcard) { + bool found = assignExactVersion(pat, VER_NDX_LOCAL, "local"); + found |= assignExactVersion({(pat.name + "@" + v.name).toStringRef(buf), + pat.isExternCpp, false}, + VER_NDX_LOCAL, "local"); + if (!found && !config->undefinedVersion) + reportUndefinedVersion("local", pat.name); + } + } // 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. - for (VersionDefinition &v : llvm::reverse(config->versionDefinitions)) + auto assignWildcard = [&](SymbolVersion pat, uint16_t id, StringRef ver) { + assignWildcardVersion(pat, id, false); + assignWildcardVersion( + {(pat.name + "@" + ver).toStringRef(buf), pat.isExternCpp, true}, id, + true); + }; + for (VersionDefinition &v : llvm::reverse(config->versionDefinitions)) { for (SymbolVersion &pat : v.patterns) if (pat.hasWildcard && pat.name != "*") - assignWildcardVersion(pat, v.id); + 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. - for (VersionDefinition &v : config->versionDefinitions) + for (VersionDefinition &v : config->versionDefinitions) { for (SymbolVersion &pat : v.patterns) if (pat.hasWildcard && pat.name == "*") - assignWildcardVersion(pat, v.id); + assignWildcard(pat, v.id, v.name); + for (SymbolVersion &pat : v.localPatterns) + if (pat.hasWildcard && pat.name == "*") + assignWildcard(pat, VER_NDX_LOCAL, v.name); + } // Symbol themselves might know their versions because symbols // can contain versions in the form of @. diff --git a/lld/ELF/Symbols.cpp b/lld/ELF/Symbols.cpp --- a/lld/ELF/Symbols.cpp +++ b/lld/ELF/Symbols.cpp @@ -208,6 +208,9 @@ // If a symbol name contains '@', the characters after that is // a symbol version name. This function parses that. void Symbol::parseSymbolVersion() { + // Return if localized by a local: pattern in a version script. + if (versionId == VER_NDX_LOCAL) + return; StringRef s = getName(); size_t pos = s.find('@'); if (pos == 0 || pos == StringRef::npos) diff --git a/lld/test/ELF/version-script-symver.s b/lld/test/ELF/version-script-symver.s --- a/lld/test/ELF/version-script-symver.s +++ b/lld/test/ELF/version-script-symver.s @@ -18,7 +18,6 @@ # WC: UND # WC-NEXT: [[#]] foo4@@v2 # WC-NEXT: [[#]] _start{{$}} -# WC-NEXT: [[#]] foo3@v1 # WC-NOT: {{.}} # RUN: echo 'v1 { global: *; local: foo*; }; v2 {};' > %t3.script @@ -27,7 +26,6 @@ # MIX1: UND # MIX1-NEXT: [[#]] foo4@@v2 # MIX1-NEXT: [[#]] _start@@v1 -# MIX1-NEXT: [[#]] foo3@v1 # MIX1-NOT: {{.}} # RUN: echo 'v1 { global: foo*; local: *; }; v2 { global: foo4; local: *; };' > %t4.script