diff --git a/elf/chunks/dynsym.rs b/elf/chunks/dynsym.rs index c03673111..4f4f55702 100644 --- a/elf/chunks/dynsym.rs +++ b/elf/chunks/dynsym.rs @@ -42,6 +42,11 @@ impl DynsymSection { 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; diff --git a/elf/context.rs b/elf/context.rs index bfdf23d57..a92a98fc7 100644 --- a/elf/context.rs +++ b/elf/context.rs @@ -261,6 +261,12 @@ pub struct Context { pub dynamic_list_patterns: Vec, 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 Context { 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, diff --git a/elf/lto.rs b/elf/lto.rs index 87cdea8bd..7efeda4a3 100644 --- a/elf/lto.rs +++ b/elf/lto.rs @@ -508,7 +508,7 @@ unsafe fn get_symbols( 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 diff --git a/elf/passes.rs b/elf/passes.rs index 53631c72a..fdd900744 100644 --- a/elf/passes.rs +++ b/elf/passes.rs @@ -384,6 +384,7 @@ fn clear_symbol(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(ctx: &mut Context) { 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(ctx: &mut Context) { 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(ctx: &mut Context) { { 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(ctx: &mut Context) { } }); } + + // 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. diff --git a/elf/symbol.rs b/elf/symbol.rs index 2a8d19745..e240ad3bf 100644 --- a/elf/symbol.rs +++ b/elf/symbol.rs @@ -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; diff --git a/tests/elf/symbol-version-multi.sh b/tests/elf/symbol-version-multi.sh index a091b86bc..36e3447b0 100755 --- a/tests/elf/symbol-version-multi.sh +++ b/tests/elf/symbol-version-multi.sh @@ -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 < $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' diff --git a/tests/elf/symver-preemptible.sh b/tests/elf/symver-preemptible.sh new file mode 100755 index 000000000..9fd3d212f --- /dev/null +++ b/tests/elf/symver-preemptible.sh @@ -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 < $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 < +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