mirror of
https://github.com/rui314/mold.git
synced 2026-11-07 09:16:04 -10:00
Keep symbols versioned with .symver preemptible
If an object file defined foo and its alias foo@VER, references to foo in a shared object were bound locally, although the definition is exported as foo@VER, so an executable's copy relocation or interposing definition didn't reach them. Now foo, if not exported itself, shares foo@VER's dynamic symbol and is as preemptible as foo@VER. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
605749d515
commit
11fa24033d
7 files changed
+145
-1
No files matched your search
@@ -42,6 +42,11 @@ impl<E: Target> DynsymSection<E> {
|
||||
if self.symbols.is_empty() {
|
||||
self.symbols.push(None);
|
||||
}
|
||||
// A symbol that shares another's dynamic symbol gets that one's index
|
||||
// (sort_dynsyms()).
|
||||
if symbols[sym].shares_dynsym() {
|
||||
return;
|
||||
}
|
||||
if symbols[sym].dynsym_idx(symbols).is_none() {
|
||||
// Mark the symbol as queued before sort_dynsyms assigns its real index.
|
||||
*symbols.aux_mut(sym).dynsym_idx.get_mut() = u32::MAX - 1;
|
||||
|
||||
@@ -261,6 +261,12 @@ pub struct Context<E: Target> {
|
||||
pub dynamic_list_patterns: Vec<DynamicPattern>,
|
||||
pub default_version: u16,
|
||||
|
||||
// `foo` and `foo@VERSION` that an object file defines as one, with
|
||||
// `.symver foo, foo@VERSION` (see parse_symbol_version()). Once
|
||||
// compute_import_export() has run, only those where `foo` shares
|
||||
// `foo@VERSION`'s dynamic symbol.
|
||||
pub symver_aliases: Vec<(SymbolId, SymbolId)>,
|
||||
|
||||
// For thread-local variables
|
||||
pub tls_begin: u64,
|
||||
pub tp_addr: u64,
|
||||
@@ -351,6 +357,7 @@ impl<E: Target> Context<E> {
|
||||
version_patterns: Vec::new(),
|
||||
dynamic_list_patterns: Vec::new(),
|
||||
default_version: crate::elf::VER_NDX_UNSPECIFIED as u16,
|
||||
symver_aliases: Vec::new(),
|
||||
tls_begin: 0,
|
||||
tp_addr: 0,
|
||||
dtp_addr: 0,
|
||||
|
||||
+1
-1
@@ -508,7 +508,7 @@ unsafe fn get_symbols<E: Target>(
|
||||
Some(owner) if owner == this => {
|
||||
if sym.referenced_by_regular_obj() {
|
||||
LDPR_PREVAILING_DEF
|
||||
} else if sym.is_exported() {
|
||||
} else if sym.is_exported() || sym.is_imported() {
|
||||
if is_v2 { LDPR_PREVAILING_DEF } else { LDPR_PREVAILING_DEF_IRONLY_EXP }
|
||||
} else {
|
||||
LDPR_PREVAILING_DEF_IRONLY
|
||||
|
||||
@@ -384,6 +384,7 @@ fn clear_symbol<E: Target>(sym: &mut Symbol) {
|
||||
sym.set_imported(false);
|
||||
sym.set_exported(false);
|
||||
sym.set_versioned_default(false);
|
||||
sym.set_shares_dynsym(false);
|
||||
}
|
||||
|
||||
/// Resets the resolution of every global symbol. Local symbols belong to
|
||||
@@ -2795,6 +2796,12 @@ pub fn sort_dynsyms<E: Target>(ctx: &mut Context<E>) {
|
||||
ctx.dynsym.dynstr_entries = dynstr_entries;
|
||||
ctx.dynsym.symbols[1..].par_iter_mut().zip(syms).for_each(|(slot, id)| *slot = Some(id));
|
||||
|
||||
// A symbol that shares another's dynamic symbol takes its index.
|
||||
for &(id, alias) in &ctx.symver_aliases {
|
||||
let idx = ctx.symbols[alias].dynsym_idx(&ctx.symbols).unwrap();
|
||||
*ctx.symbols.aux_mut(id).dynsym_idx.get_mut() = idx;
|
||||
}
|
||||
|
||||
// ELF's symbol table sh_info holds the index of the first global symbol.
|
||||
ctx.dynsym.hdr.shdr.sh_info.set(num_locals as u32 + 1);
|
||||
}
|
||||
@@ -2916,6 +2923,7 @@ pub fn parse_symbol_version<E: Target>(ctx: &mut Context<E>) {
|
||||
return;
|
||||
}
|
||||
let _t = ctx.timer("parse_symbol_version");
|
||||
ctx.symver_aliases.clear();
|
||||
|
||||
let verdefs: HashMap<&[u8], u16> = args
|
||||
.version_definitions
|
||||
@@ -2994,6 +3002,16 @@ pub fn parse_symbol_version<E: Target>(ctx: &mut Context<E>) {
|
||||
{
|
||||
ctx.symbols[id2].ver_idx = VER_NDX_LOCAL as u16;
|
||||
}
|
||||
|
||||
// If they are one definition, as `.symver foo, foo@VERSION`
|
||||
// makes them, `foo` may be reached through `foo@VERSION` if it
|
||||
// is not exported itself (compute_import_export()).
|
||||
let esyms = &file.base.elf_syms;
|
||||
if file.shndx_at(i) == file.shndx_at(sym2_idx)
|
||||
&& esyms[i].st_value() == esyms[sym2_idx].st_value()
|
||||
{
|
||||
ctx.symver_aliases.push((id2, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3163,6 +3181,25 @@ pub fn compute_import_export<E: Target>(ctx: &mut Context<E>) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// A `foo` that is not exported may still have its definition exported
|
||||
// as `foo@VERSION` (parse_symbol_version()). References to `foo` must
|
||||
// then be as preemptible as those to `foo@VERSION`, so `foo` shares that
|
||||
// dynamic symbol: the first such one, as in GNU ld and lld. Only the
|
||||
// pairs that share are kept, for sort_dynsyms().
|
||||
ctx.symver_aliases.retain(|&(id, alias)| {
|
||||
let sym = &ctx.symbols[id];
|
||||
let shares = !sym.is_exported()
|
||||
&& !sym.shares_dynsym()
|
||||
&& sym.visibility() == STV_DEFAULT
|
||||
&& ctx.symbols[alias].is_imported();
|
||||
if shares {
|
||||
let sym = &mut ctx.symbols[id];
|
||||
sym.set_imported(true);
|
||||
sym.set_shares_dynsym(true);
|
||||
}
|
||||
shares
|
||||
});
|
||||
}
|
||||
|
||||
// Compute the "address-taken" bit for each input section.
|
||||
|
||||
@@ -420,6 +420,11 @@ const WRAPPED: u16 = 1 << 7; // for --wrap
|
||||
// For symbols with a default symbol version, e.g. foo@@VERSION.
|
||||
const VERSIONED_DEFAULT: u16 = 1 << 8;
|
||||
|
||||
// For `foo` that has no dynamic symbol of its own but shares the one of
|
||||
// `foo@VERSION`, the second name `.symver` gives its definition. See
|
||||
// compute_import_export().
|
||||
const SHARES_DYNSYM: u16 = 1 << 9;
|
||||
|
||||
// For --gc-sections
|
||||
const GC_ROOT: u16 = 1 << 10;
|
||||
|
||||
@@ -472,6 +477,7 @@ symbol_bits! {
|
||||
is_traced, set_traced: TRACED;
|
||||
is_wrapped, set_wrapped: WRAPPED;
|
||||
is_versioned_default, set_versioned_default: VERSIONED_DEFAULT;
|
||||
shares_dynsym, set_shares_dynsym: SHARES_DYNSYM;
|
||||
gc_root, set_gc_root: GC_ROOT;
|
||||
referenced_by_regular_obj, set_referenced_by_regular_obj: REFERENCED_BY_REGULAR_OBJ;
|
||||
comdat_claimed_by_ir, set_comdat_claimed_by_ir: COMDAT_CLAIMED_BY_IR;
|
||||
|
||||
@@ -18,3 +18,28 @@ $CC -B. -o $t/c.so -shared $t/a.o -Wl,--version-script=$t/b.version
|
||||
|
||||
readelf -W --dyn-syms $t/c.so | grep -F 'foo@TEST1'
|
||||
readelf -W --dyn-syms $t/c.so | grep -F 'foo@@TEST2'
|
||||
|
||||
# The directives in the other order put the versions in the other order
|
||||
# in the symbol table.
|
||||
cat <<EOF | $CC -o $t/d.o -c -xc - -fPIC
|
||||
void foo() {}
|
||||
__asm__(".symver foo, foo@@TEST2");
|
||||
__asm__(".symver foo, foo@TEST1");
|
||||
EOF
|
||||
|
||||
$CC -B. -o $t/e.so -shared $t/d.o -Wl,--version-script=$t/b.version
|
||||
|
||||
readelf -W --dyn-syms $t/e.so | grep -F 'foo@TEST1'
|
||||
readelf -W --dyn-syms $t/e.so | grep -F 'foo@@TEST2'
|
||||
|
||||
# A version script can give the default version instead.
|
||||
cat <<EOF | $CC -o $t/f.o -c -xc - -fPIC
|
||||
void foo() {}
|
||||
__asm__(".symver foo, foo@TEST1");
|
||||
EOF
|
||||
|
||||
echo 'TEST1 {}; TEST2 { foo; };' > $t/g.version
|
||||
$CC -B. -o $t/h.so -shared $t/f.o -Wl,--version-script=$t/g.version
|
||||
|
||||
readelf -W --dyn-syms $t/h.so | grep -F 'foo@TEST1'
|
||||
readelf -W --dyn-syms $t/h.so | grep -F 'foo@@TEST2'
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
. $(dirname $0)/common.inc
|
||||
|
||||
# References to `foo` and to its alias `foo@VER1` bind to the exported
|
||||
# `foo@VER1`, so that the executable's copy of `foo` and its `bar`
|
||||
# preempt them.
|
||||
|
||||
cat <<EOF | $CC -fPIC -c -o $t/a.o -xc -
|
||||
int foo = 1;
|
||||
__asm__(".symver foo, foo@VER1");
|
||||
int bar() { return 1; }
|
||||
__asm__(".symver bar, bar@VER1");
|
||||
int get_foo() { return foo; }
|
||||
int call_bar() { return bar(); }
|
||||
EOF
|
||||
|
||||
cat <<EOF > $t/b.c
|
||||
extern int foo_v1;
|
||||
__asm__(".symver foo_v1, foo@VER1");
|
||||
int bar_v1();
|
||||
__asm__(".symver bar_v1, bar@VER1");
|
||||
int get_foo_v1() { return foo_v1; }
|
||||
int call_bar_v1() { return bar_v1(); }
|
||||
EOF
|
||||
|
||||
echo 'VER1 {}; VER2 { global: get_*; call_*; local: *; };' > $t/c.ver
|
||||
|
||||
cat <<EOF | $CC -fno-PIC -c -o $t/d.o -xc -
|
||||
#include <stdio.h>
|
||||
extern int foo;
|
||||
__asm__(".symver foo, foo@VER1");
|
||||
int bar() { return 2; }
|
||||
int get_foo();
|
||||
int get_foo_v1();
|
||||
int call_bar();
|
||||
int call_bar_v1();
|
||||
int main() {
|
||||
foo = 42;
|
||||
printf("%d %d %d %d\n", get_foo(), get_foo_v1(), call_bar(), call_bar_v1());
|
||||
}
|
||||
EOF
|
||||
|
||||
check() {
|
||||
$CC -B. -shared -o $t/e.so "$@" -Wl,--version-script=$t/c.ver
|
||||
$CC -B. -no-pie -Wl,-E -o $t/exe $t/d.o $t/e.so
|
||||
$QEMU $t/exe | grep '^42 42 2 2$'
|
||||
}
|
||||
|
||||
$CC -fPIC -c -o $t/b.o $t/b.c
|
||||
check $t/a.o $t/b.o
|
||||
|
||||
# After GNU ld's -r, as in glibc's libc_pic.os, the references to
|
||||
# `foo@VER1` refer to its definition.
|
||||
if test_cflags -fuse-ld=bfd; then
|
||||
$CC -fuse-ld=bfd -r -o $t/ab.o $t/a.o $t/b.o
|
||||
check $t/ab.o
|
||||
fi
|
||||
|
||||
# With LTO, the references to `foo@VER1` appear only after the first
|
||||
# symbol resolution.
|
||||
if test_cflags -flto; then
|
||||
$CC -fPIC -flto -c -o $t/b.o $t/b.c
|
||||
check -flto $t/a.o $t/b.o
|
||||
fi
|
||||
Reference in new issue
Block a user