Keep IFUNCs exported from a shared object preemptible

A shared object's references to an IFUNC that it exports were bound
locally with IRELATIVE relocations, so a definition that interposes the
IFUNC didn't reach them. They now get JUMP_SLOT and GLOB_DAT relocations
against the symbol, as with GNU ld and lld.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Rui UeyamaandClaude Opus 5.5 committed 2026-10-20 11:17:49 +09:00
1 parent 77d78b9a0e
commit 605749d515
6 files changed
+62 -6

No files matched your search

+1 -1
View File
@@ -128,7 +128,7 @@ impl Target for M68k {
continue;
}
let sym = &ctx.symbols[file.base.symbols[rel.r_sym() as usize]];
if sym.is_ifunc() {
if sym.ty() == STT_GNU_IFUNC {
error!("{sym}: GNU ifunc symbol is not supported on m68k");
}
+1 -1
View File
@@ -277,7 +277,7 @@ impl<const LE: bool> Target for Sh4Target<LE> {
continue;
}
let sym = &ctx.symbols[file.base.symbols[rel.r_sym() as usize]];
if sym.is_ifunc() {
if sym.ty() == STT_GNU_IFUNC {
error!("{sym}: GNU ifunc symbol is not supported on sh4");
}
+1 -1
View File
@@ -1179,7 +1179,7 @@ fn sym_type(sym: &Symbol) -> usize {
0
} else if !sym.is_imported() {
1
} else if sym.ty() != STT_FUNC {
} else if !matches!(sym.ty(), STT_FUNC | STT_GNU_IFUNC) {
2
} else {
3
+3 -2
View File
@@ -3028,12 +3028,13 @@ fn is_protected<E: Target>(ctx: &Context<E>, sym: &Symbol) -> bool {
if sym.visibility() == STV_PROTECTED {
return true;
}
let is_func = matches!(sym.ty(), STT_FUNC | STT_GNU_IFUNC);
match ctx.args.bsymbolic {
BsymbolicKind::All => true,
BsymbolicKind::None => false,
BsymbolicKind::Functions => sym.ty() == STT_FUNC,
BsymbolicKind::Functions => is_func,
BsymbolicKind::NonWeak => !sym.is_weak(),
BsymbolicKind::NonWeakFunctions => !sym.is_weak() && sym.ty() == STT_FUNC,
BsymbolicKind::NonWeakFunctions => !sym.is_weak() && is_func,
}
}
+7 -1
View File
@@ -952,9 +952,15 @@ impl Symbol {
}
}
/// Whether the symbol is an IFUNC that the output resolves itself
/// with IRELATIVE relocations. An imported IFUNC is resolved by the
/// dynamic loader through the symbol like any other imported
/// function. That includes an IFUNC that the DSO being created
/// exports without binding it locally, so that the DSO's own
/// references to it reach a definition that interposes it.
#[inline]
pub fn is_ifunc(&self) -> bool {
self.ty() == STT_GNU_IFUNC
self.ty() == STT_GNU_IFUNC && !self.is_imported()
}
// A remaining weak undefined symbol is promoted to a dynamic symbol
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
supports_ifunc || skip
cat <<EOF | $CC -fPIC -o $t/a.o -c -xc -
#include <stdio.h>
typedef void Func(void);
__attribute__((ifunc("resolve_foo")))
void foo(void);
static void real_foo(void) {
printf("dso ");
}
static Func *resolve_foo(void) {
return real_foo;
}
void call_foo(void) { foo(); }
Func *get_foo(void) { return foo; }
Func *foo_ptr = foo;
EOF
$CC -B. -shared -o $t/b.so $t/a.o
cat <<EOF | $CC -o $t/c.o -c -xc -
#include <stdio.h>
typedef void Func(void);
void foo(void) {
printf("exe ");
}
void call_foo(void);
Func *get_foo(void);
extern Func *foo_ptr;
int main() {
call_foo();
printf("%d %d\n", get_foo() == foo, foo_ptr == foo);
}
EOF
$CC -B. -o $t/exe $t/c.o $t/b.so
$QEMU $t/exe | grep '^exe 1 1$'