Case 12: Function Removed from Shared Library¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break, API break |
Detected ChangeKinds |
func_removed |
| Source files | browse on GitHub |
Category: Symbol API | Abicheck verdict: BREAKING | Verdict: ๐ด BREAKING
What breaks¶
Any binary dynamically linked against v1 will fail to load with
undefined symbol: fast_add when upgraded to v2. The .so no longer exports
the symbol, so pre-built binaries have nowhere to resolve it. Even if the function
were moved to a header as an inline, already-compiled binaries cannot benefit from
that โ they need the dynamic symbol.
Why abidiff catches it¶
Reports 1 Removed function: fast_add with exit 12 (breaking removal).
Code diff¶
| v1.c | v2.c |
|---|---|
int fast_add(int a, int b) { return a+b; } |
(function removed from .so) |
int other_func(int x) { return x; } |
Reproduce manually¶
gcc -shared -fPIC -g v1.c -o libfoo_v1.so
gcc -shared -fPIC -g v2.c -o libfoo_v2.so
abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml
echo "exit: $?" # โ 12
How to fix¶
Keep the exported wrapper in the .so even if the implementation moves to an inline.
The wrapper can simply call the inline: int fast_add(int a, int b) { return _fast_add_impl(a,b); }.
Only remove it on a SONAME-bumping major release.
Real-world example¶
Several C++ standard library implementors have moved functions to inlines for
performance and then had to keep exported stubs for ABI compatibility โ libstdc++'s
std::string refactor in GCC 5 is the canonical cautionary tale.
Real Failure Demo¶
Severity: CRITICAL
Scenario: app calls fast_add() compiled against v1. v2 removes it from the .so.
# Build v1 + app
gcc -shared -fPIC -g v1.c -o libfoo.so
gcc -g app.c -I. -L. -lfoo -Wl,-rpath,. -o app
./app
# โ fast_add(3, 4) = 7
# โ other_func(5) = 5
# Swap in v2 (fast_add gone from .so)
gcc -shared -fPIC -g v2.c -o libfoo.so
./app
# โ ./app: symbol lookup error: ./app: undefined symbol: fast_add
Why CRITICAL: With default lazy binding (RTLD_LAZY), the error surfaces on the
first call through the PLT โ the app starts but immediately dies when fast_add
is called. With LD_BIND_NOW=1 or RTLD_NOW, it fails at load time. Either way,
every binary that ever called fast_add is broken until recompiled against v2 headers.
Runtime behavior¶
The function is removed from the .so โ the dynamic linker emits an undefined symbol error at load time (with RTLD_NOW) or at first call (lazy binding). Runtime result and verdict both agree: BREAKING.
References¶
Source files¶
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.