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 | examples/case12_function_removed/ |
Category: Symbol API | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
Any binary dynamically linked against v1 fails to resolve fast_add after
upgrading to v2. The .so no longer exports the symbol, so pre-built
binaries have nowhere to resolve it. Even if the function were moved into a
header as an inline, already-compiled binaries can't benefit โ they still
need the dynamic symbol that used to exist.
Old/new diff¶
| v1.c | v2.c |
|---|---|
int fast_add(int a, int b) { return a + b; } |
(removed) |
int other_func(int x) { return x; } |
int other_func(int x) { return x; } |
abicheck command¶
gcc -shared -fPIC -g v1.c -o libfoo_v1.so
gcc -shared -fPIC -g v2.c -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- func_removed: Public function removed: fast_add
> Old binaries call a symbol that no longer exists; dynamic linker will
refuse to load or crash at call site.
Minimum evidence¶
min_evidence: L0 โ the exported-symbol table alone is enough: fast_add
is present in v1's .dynsym and absent from v2's. No debug info or headers
needed; -g above is only there so the Runtime failure demonstration
below can build a matching app.
Why abicheck catches it¶
The dynamic symbol table is authoritative L0 evidence โ abicheck diffs the exported-symbol sets directly, no debug info or headers required.
Runtime failure demonstration¶
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 instead. Either way, every binary that
ever called fast_add is broken until recompiled against v2.
Safe redesign¶
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.
Cross-tool comparison¶
abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml
echo "exit: $?" # โ 12 (= 4 | 8: ABI change detected + breaking change)
References¶
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.