Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -61,6 +61,7 @@ llvm::StringRef Name; size_t Id; std::vector Globals; + std::vector Locals; size_t NameOff; // Offset in string table. }; Index: ELF/LinkerScript.cpp =================================================================== --- ELF/LinkerScript.cpp +++ ELF/LinkerScript.cpp @@ -667,7 +667,7 @@ }); if (HeaderSize <= MinVA && FirstPTLoad != Phdrs.end()) { - // If linker script specifies program headers and first PT_LOAD doesn't + // If linker script specifies program headers and first PT_LOAD doesn't // have both PHDRS and FILEHDR attributes then do nothing if (!Opt.PhdrsCommands.empty()) { size_t SegNum = std::distance(Phdrs.begin(), FirstPTLoad); @@ -970,7 +970,7 @@ void readExtern(std::vector *Globals); void readVersionDeclaration(StringRef VerStr); void readGlobal(StringRef VerStr); - void readLocal(); + void readLocal(StringRef VerStr); ScriptConfiguration &Opt = *ScriptConfig; bool IsUnderSysroot; @@ -1780,7 +1780,7 @@ if (consume("global:") || peek() != "local:") readGlobal(VerStr); if (consume("local:")) - readLocal(); + readLocal(VerStr); expect("}"); // Each version may have a parent version. For example, "Ver2" defined as @@ -1792,10 +1792,22 @@ expect(";"); } -void ScriptParser::readLocal() { - Config->DefaultSymbolVersion = VER_NDX_LOCAL; - expect("*"); - expect(";"); +void ScriptParser::readLocal(StringRef VerStr) { + if (consume("*")) { + Config->DefaultSymbolVersion = VER_NDX_LOCAL; + expect(";"); + return; + } + + if (VerStr.empty()) + setError("locals list for anonymous version is not supported"); + + std::vector &Locals = Config->VersionDefinitions.back().Locals; + while (!Error && peek() != "}") { + StringRef Tok = next(); + Locals.push_back({unquote(Tok), false, hasWildcard(Tok)}); + expect(";"); + } } void ScriptParser::readExtern(std::vector *Globals) { Index: ELF/SymbolTable.cpp =================================================================== --- ELF/SymbolTable.cpp +++ ELF/SymbolTable.cpp @@ -587,7 +587,8 @@ } static void setVersionId(SymbolBody *Body, StringRef VersionName, - StringRef Name, uint16_t Version) { + StringRef Name, uint16_t Version, + DenseSet &Proccessed) { if (!Body || Body->isUndefined()) { if (Config->NoUndefinedVersion) error("version script assignment of " + VersionName + " to symbol " + @@ -596,7 +597,8 @@ } Symbol *Sym = Body->symbol(); - if (Sym->VersionId != Config->DefaultSymbolVersion) + auto It = Proccessed.insert(Body); + if (!It.second) warn("duplicate symbol " + Name + " in version script"); Sym->VersionId = Version; } @@ -646,7 +648,7 @@ } // If there's only one anonymous version definition in a version -// script file, the script does not actullay define any symbol version, +// script file, the script does not actually define any symbol version, // but just specifies symbols visibilities. We assume that the script was // in the form of { global: foo; bar; local *; }. So, local is default. // In this function, we make specified symbols global. @@ -693,6 +695,7 @@ if (hasExternCpp()) Demangled = getDemangledSyms(); + DenseSet Proccessed; // First, we assign versions to exact matching symbols, // i.e. version definitions not containing any glob meta-characters. for (VersionDefinition &V : Config->VersionDefinitions) { @@ -703,10 +706,15 @@ StringRef N = Sym.Name; if (Sym.IsExternCpp) { for (SymbolBody *B : findDemangled(Demangled, N)) - setVersionId(B, V.Name, N, V.Id); + setVersionId(B, V.Name, N, V.Id, Proccessed); continue; } - setVersionId(find(N), V.Name, N, V.Id); + setVersionId(find(N), V.Name, N, V.Id, Proccessed); + } + for (SymbolVersion Sym : V.Locals) { + if (Sym.HasWildcards) + continue; + setVersionId(find(Sym.Name), V.Name, Sym.Name, VER_NDX_LOCAL, Proccessed); } } @@ -714,22 +722,28 @@ // i.e. version definitions containing glob meta-characters. // Note that because the last match takes precedence over previous matches, // we iterate over the definitions in the reverse order. + auto assignFuzzyVersion = [&](SymbolVersion &Sym, size_t Version) { + if (!Sym.HasWildcards) + return; + StringMatcher M({Sym.Name}); + std::vector Syms = + Sym.IsExternCpp ? findAllDemangled(Demangled, M) : findAll(M); + // Exact matching takes precendence 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 (SymbolBody *B : Syms) { + auto It = Proccessed.insert(B); + if (It.second) + B->symbol()->VersionId = Version; + } + }; + for (size_t I = Config->VersionDefinitions.size() - 1; I != (size_t)-1; --I) { VersionDefinition &V = Config->VersionDefinitions[I]; - for (SymbolVersion &Sym : V.Globals) { - if (!Sym.HasWildcards) - continue; - StringMatcher M({Sym.Name}); - std::vector Syms = - Sym.IsExternCpp ? findAllDemangled(Demangled, M) : findAll(M); - - // Exact matching takes precendence 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 (SymbolBody *B : Syms) - if (B->symbol()->VersionId == Config->DefaultSymbolVersion) - B->symbol()->VersionId = V.Id; - } + for (SymbolVersion &Sym : V.Locals) + assignFuzzyVersion(Sym, VER_NDX_LOCAL); + for (SymbolVersion &Sym : V.Globals) + assignFuzzyVersion(Sym, V.Id); } } Index: test/ELF/version-script-locals.s =================================================================== --- test/ELF/version-script-locals.s +++ test/ELF/version-script-locals.s @@ -0,0 +1,50 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o + +# RUN: echo "VERSION_1.0 { local: foo1; };" > %t.script +# RUN: ld.lld --version-script %t.script -shared %t.o -o %t.so +# RUN: llvm-readobj -dyn-symbols %t.so | FileCheck --check-prefix=EXACT %s +# EXACT: DynamicSymbols [ +# EXACT: _start +# EXACT-NOT: foo1 +# EXACT: foo2 +# EXACT: foo3 + +# RUN: echo "VERSION_1.0 { local: foo*; };" > %t.script +# RUN: ld.lld --version-script %t.script -shared %t.o -o %t.so +# RUN: llvm-readobj -dyn-symbols %t.so | FileCheck --check-prefix=WC %s +# WC: DynamicSymbols [ +# WC: _start +# WC-NOT: foo1 +# WC-NOT: foo2 +# WC-NOT: foo3 + +# RUN: echo "VERSION_1.0 { global: *; local: foo*; };" > %t.script +# RUN: ld.lld --version-script %t.script -shared %t.o -o %t.so +# RUN: llvm-readobj -dyn-symbols %t.so | FileCheck --check-prefix=MIX %s +# MIX: DynamicSymbols [ +# MIX: _start@@VERSION_1.0 +# MIX-NOT: foo1 +# MIX-NOT: foo2 +# MIX-NOT: foo3 + +# RUN: echo "VERSION_1.0 { global: *; local: extern \"C++\" { foo*; } };" > %t.script +# RUN: not ld.lld --version-script %t.script -shared %t.o -o %t.so 2>&1 \ +# RUN: | FileCheck --check-prefix=EXTERNERR %s +# EXTERNERR: ; expected, but got "C++" + +.globl foo1 +foo1: + ret + +.globl foo2 +foo2: + ret + +.globl foo3 +foo3: + ret + +.globl _start +_start: + ret