1
1
mirror of https://github.com/rui314/mold.git synced 2024-10-04 08:37:28 +03:00

[ELF] Fix regression in symbol name resolution

This fix is needed to build Gentoo's net-fs/fuse package.
This commit is contained in:
Rui Ueyama 2022-04-26 14:59:04 +08:00
parent 3015f5e418
commit d391fd9a59
5 changed files with 104 additions and 23 deletions

View File

@ -447,27 +447,53 @@ void ObjectFile<E>::initialize_symbols(Context<E> &ctx) {
// Initialize global symbols
for (i64 i = this->first_global; i < this->elf_syms.size(); i++) {
const ElfSym<E> &esym = this->elf_syms[i];
// Get a symbol name
std::string_view key = symbol_strtab.data() + esym.st_name;
std::string_view name = key;
// Parse symbol version after atsign
if (i64 pos = name.find('@'); pos != name.npos) {
std::string_view ver = name.substr(pos + 1);
name = name.substr(0, pos);
if (!ver.empty() && ver != "@") {
if (ver.starts_with('@'))
key = name;
if (esym.is_defined())
symvers[i - this->first_global] = ver.data();
}
}
this->symbols[i] = insert_symbol(ctx, esym, key, name);
if (esym.is_common())
has_common_symbol = true;
// Get a symbol name
std::string_view name = symbol_strtab.data() + esym.st_name;
// Parse symbol version. There are two types of versioned symbols.
//
// 1. The default version is in the form of `foo@@version`, and
// such symbol is used to resolve both `foo` and `foo@version`.
//
// 2. The non-default version is in the form of `foo@version`,
// and such symbol is used to resolve only `foo@version`.
i64 pos = name.find('@');
// Usual non-versioned symbol
if (pos == name.npos) {
this->symbols[i] = insert_symbol(ctx, esym, name, name);
continue;
}
std::string_view stem = name.substr(0, pos);
std::string_view verstr = name.substr(pos + 1);
// If version part is empty (i.e. `foo@` or `foo@@`), that
// version part is ignored for compatibility with GNU linkers.
if (verstr.empty() || verstr == "@") {
this->symbols[i] = insert_symbol(ctx, esym, stem, stem);
continue;
}
if (esym.is_defined())
symvers[i - this->first_global] = verstr.data();
// Default versioned symbol
if (verstr.starts_with('@')) {
std::string_view decorated =
save_string(ctx, std::string(stem) + std::string(verstr));
this->symbols[i] = insert_symbol(ctx, esym, decorated, stem);
typename decltype(ctx.symbol_map2)::accessor acc;
ctx.symbol_map2.insert(acc, {stem, this->symbols[i]});
continue;
}
// Non-default versioned symbol
this->symbols[i] = insert_symbol(ctx, esym, name, stem);
}
}
@ -487,7 +513,7 @@ void ObjectFile<E>::sort_relocations(Context<E> &ctx) {
sorted_rels.resize(sections.size());
for (i64 i = 1; i < sections.size(); i++) {
std::unique_ptr<InputSection<E>> &isec = sections[i];;
std::unique_ptr<InputSection<E>> &isec = sections[i];
if (!isec || !isec->is_alive || !(isec->shdr().sh_flags & SHF_ALLOC))
continue;
@ -965,6 +991,17 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
(!sym.esym().is_undef() || sym.file->priority <= this->priority))
continue;
// `foo` can be resolved to `foo@@symbol_version` (with two at-signs).
// So, check if such a versioned symbol exists.
if (typename decltype(ctx.symbol_map2)::accessor acc;
ctx.symbol_map2.find(acc, sym.name())) {
Symbol<E> *sym2 = acc->second;
if (sym2->file) {
this->symbols[i] = sym2;
continue;
}
}
// If a symbol name is in the form of "foo@version", search for
// symbol "foo" and check if the symbol has version "version".
std::string_view key = symbol_strtab.data() + esym.st_name;

View File

@ -1698,6 +1698,7 @@ struct Context {
// Symbol table
tbb::concurrent_hash_map<std::string_view, Symbol<E>, HashCmp> symbol_map;
tbb::concurrent_hash_map<std::string_view, Symbol<E> *> symbol_map2;
tbb::concurrent_hash_map<std::string_view, ComdatGroup, HashCmp> comdat_groups;
tbb::concurrent_vector<std::unique_ptr<MergedSection<E>>> merged_sections;
tbb::concurrent_vector<std::unique_ptr<Chunk<E>>> output_chunks;

View File

@ -1121,10 +1121,13 @@ void parse_symbol_version(Context<E> &ctx) {
// If both symbol `foo` and `foo@VERSION` are defined, `foo@VERSION`
// hides `foo` so that all references to `foo` are resolved to a
// versioned symbol.
// versioned symbol. Likewise, if `foo@VERSION` and `foo@@VERSION` are
// defined, the default one takes precedence.
Symbol<E> *sym2 = get_symbol(ctx, sym->name());
if (sym2->file == file && !file->symvers[sym2->sym_idx - file->first_global])
sym2->ver_idx = VER_NDX_LOCAL;
if (sym2->ver_idx == ctx.default_version ||
(sym2->ver_idx & ~VERSYM_HIDDEN) == (sym->ver_idx & ~VERSYM_HIDDEN))
sym2->ver_idx = VER_NDX_LOCAL;
}
});
}

View File

@ -32,7 +32,7 @@ EOF
echo 'VER1 { local: *; }; VER2 { local: *; }; VER3 { local: *; };' > $t/b.ver
$CC -B. -shared -o $t/c.so $t/a.o -Wl,--version-script=$t/b.ver
readelf --symbols $t/c.so > $t/log
readelf --dyn-syms $t/c.so > $t/log
fgrep -q 'foo@VER1' $t/log
fgrep -q 'foo@VER2' $t/log

40
test/elf/symbol-version3.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
export LC_ALL=C
set -e
CC="${CC:-cc}"
CXX="${CXX:-c++}"
GCC="${GCC:-gcc}"
GXX="${GXX:-g++}"
OBJDUMP="${OBJDUMP:-objdump}"
MACHINE="${MACHINE:-$(uname -m)}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
cd "$(dirname "$0")"/../..
mold="$(pwd)/mold"
t=out/test/elf/$testname
mkdir -p $t
cat <<EOF | $CC -o $t/a.o -c -xc -
void foo() {}
void foo2() {}
void foo3() {}
__asm__(".symver foo2, foo@TEST2");
__asm__(".symver foo3, foo@TEST3");
EOF
cat <<EOF > $t/b.version
TEST1 { global: foo; };
TEST2 {};
TEST3 {};
EOF
$CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
readelf -W --dyn-syms $t/c.so > $t/log
grep -q ' foo@@TEST1$' $t/log
grep -q ' foo@TEST2$' $t/log
grep -q ' foo@TEST3$' $t/log
! grep -q ' foo$' $t/log || false
echo OK