Skip to content

Commit

Permalink
Improve compatibility of precedences in version script patterns
Browse files Browse the repository at this point in the history
Previously, symbol names or wildcard patterns in version script files
were processed strictly from top to bottom. When a single symbol
matches two or more patterns, the first one was given precedence.

However, GNU ld appears to treat exact matches differently, assigning
them higher precedence over wildcard patterns. This discrepancy led to
programs linked with Qt 6.6.1 using mold failing to launch.

Fixes #1158
  • Loading branch information
rui314 committed Nov 28, 2023
1 parent cab6014 commit 0fdbace
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 44 deletions.
83 changes: 39 additions & 44 deletions elf/passes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1646,31 +1646,8 @@ template <typename E>
void apply_version_script(Context<E> &ctx) {
Timer t(ctx, "apply_version_script");

auto is_simple = [&] {
for (VersionPattern &v : ctx.version_patterns)
if (v.is_cpp || v.pattern.find_first_of("*?[") != v.pattern.npos)
return false;
return true;
};

// If all patterns are simple (i.e. not containing any meta-
// characters and is not a C++ name), we can simply look up
// symbols.
if (is_simple()) {
for (VersionPattern &v : ctx.version_patterns) {
Symbol<E> *sym = get_symbol(ctx, v.pattern);

if (!sym->file && !ctx.arg.undefined_version)
Warn(ctx) << v.source << ": cannot assign version `" << v.ver_str
<< "` to symbol `" << *sym << "`: symbol not found";

if (sym->file && !sym->file->is_dso)
sym->ver_idx = v.ver_idx;
}
return;
}

// Otherwise, use glob pattern matchers.
// Assign versions to symbols specified with `extern "C++"` or
// wildcard patterns first.
MultiGlob matcher;
MultiGlob cpp_matcher;

Expand All @@ -1679,36 +1656,54 @@ void apply_version_script(Context<E> &ctx) {
if (v.is_cpp) {
if (!cpp_matcher.add(v.pattern, i))
Fatal(ctx) << "invalid version pattern: " << v.pattern;
} else {
} else if (v.pattern.find_first_of("*?[") != v.pattern.npos) {
if (!matcher.add(v.pattern, i))
Fatal(ctx) << "invalid version pattern: " << v.pattern;
}
}

tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file != file)
continue;

std::string_view name = sym->name();
i64 match = INT64_MAX;
if (!matcher.empty() || !cpp_matcher.empty()) {
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->get_global_syms()) {
if (sym->file != file)
continue;

if (std::optional<u32> idx = matcher.find(name))
match = std::min<i64>(match, *idx);
std::string_view name = sym->name();
i64 match = INT64_MAX;

// Match non-mangled symbols against the C++ pattern as well.
// Weird, but required to match other linkers' behavior.
if (!cpp_matcher.empty()) {
if (std::optional<std::string_view> s = cpp_demangle(name))
name = *s;
if (std::optional<u32> idx = cpp_matcher.find(name))
if (std::optional<u32> idx = matcher.find(name))
match = std::min<i64>(match, *idx);

// Match non-mangled symbols against the C++ pattern as well.
// Weird, but required to match other linkers' behavior.
if (!cpp_matcher.empty()) {
if (std::optional<std::string_view> s = cpp_demangle(name))
name = *s;
if (std::optional<u32> idx = cpp_matcher.find(name))
match = std::min<i64>(match, *idx);
}

if (match != INT64_MAX)
sym->ver_idx = ctx.version_patterns[match].ver_idx;
}
});
}

if (match != INT64_MAX)
sym->ver_idx = ctx.version_patterns[match].ver_idx;
// Next, assign versions to symbols specified by exact name.
// In other words, exact matches have higher precedence over
// wildcard or `extern "C++"` patterns.
for (VersionPattern &v : ctx.version_patterns) {
if (!v.is_cpp && v.pattern.find_first_of("*?[") == v.pattern.npos) {
Symbol<E> *sym = get_symbol(ctx, v.pattern);

if (!sym->file && !ctx.arg.undefined_version)
Warn(ctx) << v.source << ": cannot assign version `" << v.ver_str
<< "` to symbol `" << *sym << "`: symbol not found";

if (sym->file && !sym->file->is_dso)
sym->ver_idx = v.ver_idx;
}
});
}
}

template <typename E>
Expand Down
19 changes: 19 additions & 0 deletions test/elf/version-script20.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
. $(dirname $0)/common.inc

cat <<'EOF' > $t/a.ver
VER1 { foo*; };
VER2 { foo_x; };
EOF

cat <<EOF | $CC -fPIC -c -o $t/b.o -xc -
void foo_x() {}
void foo_y() {}
void foo_z() {}
EOF

$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
readelf -W --dyn-syms $t/c.so > $t/log
grep -Fq 'foo_x@@VER2' $t/log
grep -Fq 'foo_y@@VER1' $t/log
grep -Fq 'foo_z@@VER1' $t/log
19 changes: 19 additions & 0 deletions test/elf/version-script21.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
. $(dirname $0)/common.inc

cat <<'EOF' > $t/a.ver
VER1 { foo_x; };
VER2 { foo*; };
EOF

cat <<EOF | $CC -fPIC -c -o $t/b.o -xc -
void foo_x() {}
void foo_y() {}
void foo_z() {}
EOF

$CC -B. -shared -Wl,--version-script=$t/a.ver -o $t/c.so $t/b.o
readelf -W --dyn-syms $t/c.so > $t/log
grep -Fq 'foo_x@@VER1' $t/log
grep -Fq 'foo_y@@VER2' $t/log
grep -Fq 'foo_z@@VER2' $t/log

0 comments on commit 0fdbace

Please sign in to comment.