Skip to content

Commit 7ed22a6

Browse files
committed
[ELF] Apply version script patterns to non-default version symbols
Currently version script patterns are ignored for .symver produced non-default version (single @) symbols. This makes such symbols not localizable by `local:`, e.g. ``` .symver foo3_v1,foo3@v1 .globl foo_v1 foo3_v1: ld.lld --version-script=a.ver -shared a.o # In a.out, foo3@v1 is incorrectly exported. ``` This patch adds the support: * Move `config->versionDefinitions[VER_NDX_LOCAL].patterns` to `config->versionDefinitions[versionId].localPatterns` * Rename `config->versionDefinitions[versionId].patterns` to `config->versionDefinitions[versionId].nonLocalPatterns` * Allow `findAllByVersion` to find non-default version symbols when `includeNonDefault` is true. (Note: `symtab` keys do not have `@@`) * Make each pattern check both the unversioned `pat.name` and the versioned `${pat.name}@${v.name}` * `localPatterns` can localize `${pat.name}@${v.name}`. `nonLocalPatterns` can prevent localization by assigning `verdefIndex` (before `parseSymbolVersion`). --- If a user notices new `undefined symbol` errors with a version script containing `local: *;`, the issue is likely due to a missing `global:` pattern. Reviewed By: peter.smith Differential Revision: https://reviews.llvm.org/D107234
1 parent 3bc8ce5 commit 7ed22a6

12 files changed

+121
-224
lines changed

lld/ELF/Config.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ struct SymbolVersion {
8686
struct VersionDefinition {
8787
llvm::StringRef name;
8888
uint16_t id;
89-
std::vector<SymbolVersion> patterns;
89+
std::vector<SymbolVersion> nonLocalPatterns;
90+
std::vector<SymbolVersion> localPatterns;
9091
};
9192

9293
// This struct contains the global configuration for the linker.

lld/ELF/Driver.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,18 +1351,19 @@ static void readConfigs(opt::InputArgList &args) {
13511351
}
13521352

13531353
assert(config->versionDefinitions.empty());
1354-
config->versionDefinitions.push_back({"local", (uint16_t)VER_NDX_LOCAL, {}});
13551354
config->versionDefinitions.push_back(
1356-
{"global", (uint16_t)VER_NDX_GLOBAL, {}});
1355+
{"local", (uint16_t)VER_NDX_LOCAL, {}, {}});
1356+
config->versionDefinitions.push_back(
1357+
{"global", (uint16_t)VER_NDX_GLOBAL, {}, {}});
13571358

13581359
// If --retain-symbol-file is used, we'll keep only the symbols listed in
13591360
// the file and discard all others.
13601361
if (auto *arg = args.getLastArg(OPT_retain_symbols_file)) {
1361-
config->versionDefinitions[VER_NDX_LOCAL].patterns.push_back(
1362+
config->versionDefinitions[VER_NDX_LOCAL].nonLocalPatterns.push_back(
13621363
{"*", /*isExternCpp=*/false, /*hasWildcard=*/true});
13631364
if (Optional<MemoryBufferRef> buffer = readFile(arg->getValue()))
13641365
for (StringRef s : args::getLines(*buffer))
1365-
config->versionDefinitions[VER_NDX_GLOBAL].patterns.push_back(
1366+
config->versionDefinitions[VER_NDX_GLOBAL].nonLocalPatterns.push_back(
13661367
{s, /*isExternCpp=*/false, /*hasWildcard=*/false});
13671368
}
13681369

lld/ELF/ScriptParser.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,9 +1496,9 @@ void ScriptParser::readAnonymousDeclaration() {
14961496
std::vector<SymbolVersion> globals;
14971497
std::tie(locals, globals) = readSymbols();
14981498
for (const SymbolVersion &pat : locals)
1499-
config->versionDefinitions[VER_NDX_LOCAL].patterns.push_back(pat);
1499+
config->versionDefinitions[VER_NDX_LOCAL].localPatterns.push_back(pat);
15001500
for (const SymbolVersion &pat : globals)
1501-
config->versionDefinitions[VER_NDX_GLOBAL].patterns.push_back(pat);
1501+
config->versionDefinitions[VER_NDX_GLOBAL].nonLocalPatterns.push_back(pat);
15021502

15031503
expect(";");
15041504
}
@@ -1510,13 +1510,12 @@ void ScriptParser::readVersionDeclaration(StringRef verStr) {
15101510
std::vector<SymbolVersion> locals;
15111511
std::vector<SymbolVersion> globals;
15121512
std::tie(locals, globals) = readSymbols();
1513-
for (const SymbolVersion &pat : locals)
1514-
config->versionDefinitions[VER_NDX_LOCAL].patterns.push_back(pat);
15151513

15161514
// Create a new version definition and add that to the global symbols.
15171515
VersionDefinition ver;
15181516
ver.name = verStr;
1519-
ver.patterns = globals;
1517+
ver.nonLocalPatterns = std::move(globals);
1518+
ver.localPatterns = std::move(locals);
15201519
ver.id = config->versionDefinitions.size();
15211520
config->versionDefinitions.push_back(ver);
15221521

lld/ELF/SymbolTable.cpp

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -150,19 +150,24 @@ std::vector<Symbol *> SymbolTable::findByVersion(SymbolVersion ver) {
150150
return {};
151151
}
152152

153-
std::vector<Symbol *> SymbolTable::findAllByVersion(SymbolVersion ver) {
153+
std::vector<Symbol *> SymbolTable::findAllByVersion(SymbolVersion ver,
154+
bool includeNonDefault) {
154155
std::vector<Symbol *> res;
155156
SingleStringMatcher m(ver.name);
156157

157158
if (ver.isExternCpp) {
158159
for (auto &p : getDemangledSyms())
159160
if (m.match(p.first()))
160-
res.insert(res.end(), p.second.begin(), p.second.end());
161+
for (Symbol *sym : p.second)
162+
if (includeNonDefault || !sym->getName().contains('@'))
163+
res.push_back(sym);
161164
return res;
162165
}
163166

164167
for (Symbol *sym : symVector)
165-
if (canBeVersioned(*sym) && m.match(sym->getName()))
168+
if (canBeVersioned(*sym) &&
169+
(includeNonDefault || !sym->getName().contains('@')) &&
170+
m.match(sym->getName()))
166171
res.push_back(sym);
167172
return res;
168173
}
@@ -172,7 +177,7 @@ void SymbolTable::handleDynamicList() {
172177
for (SymbolVersion &ver : config->dynamicList) {
173178
std::vector<Symbol *> syms;
174179
if (ver.hasWildcard)
175-
syms = findAllByVersion(ver);
180+
syms = findAllByVersion(ver, /*includeNonDefault=*/true);
176181
else
177182
syms = findByVersion(ver);
178183

@@ -181,21 +186,12 @@ void SymbolTable::handleDynamicList() {
181186
}
182187
}
183188

184-
// Set symbol versions to symbols. This function handles patterns
185-
// containing no wildcard characters.
186-
void SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId,
189+
// Set symbol versions to symbols. This function handles patterns containing no
190+
// wildcard characters. Return false if no symbol definition matches ver.
191+
bool SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId,
187192
StringRef versionName) {
188-
if (ver.hasWildcard)
189-
return;
190-
191193
// Get a list of symbols which we need to assign the version to.
192194
std::vector<Symbol *> syms = findByVersion(ver);
193-
if (syms.empty()) {
194-
if (!config->undefinedVersion)
195-
error("version script assignment of '" + versionName + "' to symbol '" +
196-
ver.name + "' failed: symbol not defined");
197-
return;
198-
}
199195

200196
auto getName = [](uint16_t ver) -> std::string {
201197
if (ver == VER_NDX_LOCAL)
@@ -207,10 +203,10 @@ void SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId,
207203

208204
// Assign the version.
209205
for (Symbol *sym : syms) {
210-
// Skip symbols containing version info because symbol versions
211-
// specified by symbol names take precedence over version scripts.
212-
// See parseSymbolVersion().
213-
if (sym->getName().contains('@'))
206+
// For a non-local versionId, skip symbols containing version info because
207+
// symbol versions specified by symbol names take precedence over version
208+
// scripts. See parseSymbolVersion().
209+
if (versionId != VER_NDX_LOCAL && sym->getName().contains('@'))
214210
continue;
215211

216212
// If the version has not been assigned, verdefIndex is -1. Use an arbitrary
@@ -225,13 +221,15 @@ void SymbolTable::assignExactVersion(SymbolVersion ver, uint16_t versionId,
225221
warn("attempt to reassign symbol '" + ver.name + "' of " +
226222
getName(sym->versionId) + " to " + getName(versionId));
227223
}
224+
return !syms.empty();
228225
}
229226

230-
void SymbolTable::assignWildcardVersion(SymbolVersion ver, uint16_t versionId) {
227+
void SymbolTable::assignWildcardVersion(SymbolVersion ver, uint16_t versionId,
228+
bool includeNonDefault) {
231229
// Exact matching takes precedence over fuzzy matching,
232230
// so we set a version to a symbol only if no version has been assigned
233231
// to the symbol. This behavior is compatible with GNU.
234-
for (Symbol *sym : findAllByVersion(ver))
232+
for (Symbol *sym : findAllByVersion(ver, includeNonDefault))
235233
if (sym->verdefIndex == UINT32_C(-1)) {
236234
sym->verdefIndex = 0;
237235
sym->versionId = versionId;
@@ -244,26 +242,57 @@ void SymbolTable::assignWildcardVersion(SymbolVersion ver, uint16_t versionId) {
244242
// script file, the script does not actually define any symbol version,
245243
// but just specifies symbols visibilities.
246244
void SymbolTable::scanVersionScript() {
245+
SmallString<128> buf;
247246
// First, we assign versions to exact matching symbols,
248247
// i.e. version definitions not containing any glob meta-characters.
249-
for (VersionDefinition &v : config->versionDefinitions)
250-
for (SymbolVersion &pat : v.patterns)
251-
assignExactVersion(pat, v.id, v.name);
248+
std::vector<Symbol *> syms;
249+
for (VersionDefinition &v : config->versionDefinitions) {
250+
auto assignExact = [&](SymbolVersion pat, uint16_t id, StringRef ver) {
251+
bool found = assignExactVersion(pat, id, ver);
252+
found |= assignExactVersion({(pat.name + "@" + v.name).toStringRef(buf),
253+
pat.isExternCpp, /*hasWildCard=*/false},
254+
id, ver);
255+
if (!found && !config->undefinedVersion)
256+
errorOrWarn("version script assignment of '" + ver + "' to symbol '" +
257+
pat.name + "' failed: symbol not defined");
258+
};
259+
for (SymbolVersion &pat : v.nonLocalPatterns)
260+
if (!pat.hasWildcard)
261+
assignExact(pat, v.id, v.name);
262+
for (SymbolVersion pat : v.localPatterns)
263+
if (!pat.hasWildcard)
264+
assignExact(pat, VER_NDX_LOCAL, "local");
265+
}
252266

253267
// Next, assign versions to wildcards that are not "*". Note that because the
254268
// last match takes precedence over previous matches, we iterate over the
255269
// definitions in the reverse order.
256-
for (VersionDefinition &v : llvm::reverse(config->versionDefinitions))
257-
for (SymbolVersion &pat : v.patterns)
270+
auto assignWildcard = [&](SymbolVersion pat, uint16_t id, StringRef ver) {
271+
assignWildcardVersion(pat, id, /*includeNonDefault=*/false);
272+
assignWildcardVersion({(pat.name + "@" + ver).toStringRef(buf),
273+
pat.isExternCpp, /*hasWildCard=*/true},
274+
id,
275+
/*includeNonDefault=*/true);
276+
};
277+
for (VersionDefinition &v : llvm::reverse(config->versionDefinitions)) {
278+
for (SymbolVersion &pat : v.nonLocalPatterns)
258279
if (pat.hasWildcard && pat.name != "*")
259-
assignWildcardVersion(pat, v.id);
280+
assignWildcard(pat, v.id, v.name);
281+
for (SymbolVersion &pat : v.localPatterns)
282+
if (pat.hasWildcard && pat.name != "*")
283+
assignWildcard(pat, VER_NDX_LOCAL, v.name);
284+
}
260285

261286
// Then, assign versions to "*". In GNU linkers they have lower priority than
262287
// other wildcards.
263-
for (VersionDefinition &v : config->versionDefinitions)
264-
for (SymbolVersion &pat : v.patterns)
288+
for (VersionDefinition &v : config->versionDefinitions) {
289+
for (SymbolVersion &pat : v.nonLocalPatterns)
265290
if (pat.hasWildcard && pat.name == "*")
266-
assignWildcardVersion(pat, v.id);
291+
assignWildcard(pat, v.id, v.name);
292+
for (SymbolVersion &pat : v.localPatterns)
293+
if (pat.hasWildcard && pat.name == "*")
294+
assignWildcard(pat, VER_NDX_LOCAL, v.name);
295+
}
267296

268297
// Symbol themselves might know their versions because symbols
269298
// can contain versions in the form of <name>@<version>.

lld/ELF/SymbolTable.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,14 @@ class SymbolTable {
6565

6666
private:
6767
std::vector<Symbol *> findByVersion(SymbolVersion ver);
68-
std::vector<Symbol *> findAllByVersion(SymbolVersion ver);
68+
std::vector<Symbol *> findAllByVersion(SymbolVersion ver,
69+
bool includeNonDefault);
6970

7071
llvm::StringMap<std::vector<Symbol *>> &getDemangledSyms();
71-
void assignExactVersion(SymbolVersion ver, uint16_t versionId,
72+
bool assignExactVersion(SymbolVersion ver, uint16_t versionId,
7273
StringRef versionName);
73-
void assignWildcardVersion(SymbolVersion ver, uint16_t versionId);
74+
void assignWildcardVersion(SymbolVersion ver, uint16_t versionId,
75+
bool includeNonDefault);
7476

7577
// The order the global symbols are in is not defined. We can use an arbitrary
7678
// order, but it has to be reproducible. That is true even when cross linking.

lld/ELF/Symbols.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ OutputSection *Symbol::getOutputSection() const {
208208
// If a symbol name contains '@', the characters after that is
209209
// a symbol version name. This function parses that.
210210
void Symbol::parseSymbolVersion() {
211+
// Return if localized by a local: pattern in a version script.
212+
if (versionId == VER_NDX_LOCAL)
213+
return;
211214
StringRef s = getName();
212215
size_t pos = s.find('@');
213216
if (pos == 0 || pos == StringRef::npos)

lld/test/ELF/version-script-extern-exact.s

Lines changed: 0 additions & 30 deletions
This file was deleted.

lld/test/ELF/version-script-extern-wildcards.s

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)