Case 29: GNU IFUNC Transition¶
| Field | Value |
|---|---|
| Verdict | ๐ข COMPATIBLE |
| Category | Quality (Compatible) |
| Platforms | Linux |
| Flags | โ |
Detected ChangeKinds |
ifunc_introduced |
| Source files | examples/case29_ifunc_transition/ |
Category: ELF / Symbol Quality | Verdict: ๐ข COMPATIBLE
Verdict and consumer impact¶
dispatch is a regular function (STT_FUNC) in v1 and a GNU indirect
function (STT_GNU_IFUNC) in v2 โ the symbol now points at a resolver that
the dynamic linker calls once at load time to pick the actual
implementation address, which is then patched into the GOT. This is the
mechanism glibc uses to dispatch memcpy/strlen to CPU-specific
implementations. The transition is transparent to callers: the call site,
signature, and calling convention are unchanged, so any binary compiled
against v1 continues to work against v2 without recompilation.
Old/new diff¶
| old/lib.c | new/lib.c |
|---|---|
int dispatch(int x) { return x * 2; } |
int dispatch(int x) __attribute__((ifunc("resolve_dispatch"))); with a resolve_dispatch() resolver returning dispatch_generic |
abicheck command¶
gcc -shared -fPIC -g old/lib.c -Iold -o libdispatch_v1.so
gcc -shared -fPIC -g new/lib.c -Inew -o libdispatch_v2.so
abicheck compare libdispatch_v1.so libdispatch_v2.so
Expected abicheck finding¶
Minimum evidence¶
min_evidence: L0 โ ELF's symbol table records each symbol's type
directly (STT_FUNC vs. STT_GNU_IFUNC); the exported-symbol table alone
is enough, no debug info or headers needed.
Why abicheck catches it¶
abicheck reads each exported symbol's ELF st_info type field from both
.so files; a symbol that is STT_FUNC in the old snapshot and
STT_GNU_IFUNC in the new one (or vice versa) is reported as
ifunc_introduced, a quality finding rather than a break, since the PLT/GOT
indirection is resolved transparently by the dynamic linker.
Runtime failure demonstration¶
No observable effect on existing binaries โ dispatch(5) returns the
identical value both before and after the swap. The PLT/GOT mechanism
handles the indirection: the dynamic linker calls the resolver once at
load time and patches the GOT entry, so the caller's call site never
changes.
# Build old library + app
gcc -shared -fPIC -g old/lib.c -Iold -o libdispatch.so
gcc -g app.c -Iold -L. -ldispatch -Wl,-rpath,. -o app
./app
# โ dispatch(5) = 10 (expected 10)
# Swap in new library (no recompile)
gcc -shared -fPIC -g new/lib.c -Inew -o libdispatch.so
./app
# โ dispatch(5) = 10 (expected 10) (identical)
Safe redesign¶
No redesign needed โ this is already a safe transition. The only edge
cases are tooling-level: debugger breakpoints set on an IFUNC symbol hit
the resolver on first call rather than the resolved implementation, and
very old ld.so versions predating IFUNC support cannot load the library
at all (a deployment-target concern, not an ABI contract violation).
Real-world example: glibc's memcpy, strlen, and other hot-path
string/memory functions are GNU IFUNCs, dispatching to SSE/AVX/NEON
implementations chosen at load time based on detected CPU features.
Cross-tool comparison¶
abidw --out-file v1.xml libdispatch_v1.so
abidw --out-file v2.xml libdispatch_v2.so
abidiff v1.xml v2.xml
References¶
Source files¶
CMakeLists.txtapp.c
See also: Examples overview ยท All COMPATIBLE cases ยท Category: Quality (Compatible).