Case 13: Symbol Versioning Script¶
| Field | Value |
|---|---|
| Verdict | ๐ข COMPATIBLE |
| Category | Quality (Compatible) |
| Platforms | Linux |
| Flags | โ |
Detected ChangeKinds |
symbol_version_defined_added |
| Source files | examples/case13_symbol_versioning/ |
Category: ELF/Linker | Verdict: โ COMPATIBLE
Verdict and consumer impact¶
Nothing breaks: a binary already linked against unversioned foo/bar (old)
keeps running unmodified when the library adds GNU symbol versioning (new).
ld.so resolves the old, unversioned symbol references against the new
foo@@LIBFOO_1.0/bar@@LIBFOO_1.0 definitions transparently โ no
DT_VERNEED entry means the loader doesn't require a specific version, so it
accepts the newest one. This is the safe direction to add symbol versioning
to a previously-unversioned library.
Old/new diff¶
| old/lib.c | new/lib.c |
|---|---|
int foo(void) { return 0; } |
int foo(void) { return 0; } |
int bar(void) { return 1; } |
int bar(void) { return 1; } |
Identical source. The only difference is new's link step adds a version
script, libfoo.map:
abicheck command¶
gcc -shared -fPIC -g old/lib.c -o libfoo_v1.so
gcc -shared -fPIC -g new/lib.c -o libfoo_v2.so -Wl,--version-script=libfoo.map
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: COMPATIBLE (exit 0)
Quality Issues:
- symbol_version_defined_added: Symbol version definition added: LIBFOO_1.0
Minimum evidence¶
min_evidence: L0 โ version definitions live in the ELF .gnu.version_d
section, readable straight from the binary alongside the dynamic symbol
table. No debug info or headers needed.
Why abicheck catches it¶
abicheck reads each library's .gnu.version_d section as part of its L0 ELF
metadata pass (skipping the VER_FLG_BASE self-entry) and diffs the sets of
version names between old and new. LIBFOO_1.0 present only on the new side
is classified symbol_version_defined_added and placed in the
compatible/quality bucket, not the breaking one โ going the other direction
(a version definition removed, which breaks any consumer whose
DT_VERNEED requires it) is a separate, breaking ChangeKind.
Runtime failure demonstration¶
Severity: INFORMATIONAL โ no observable effect on existing binaries.
Scenario: compile app against old (unversioned), swap in new (versioned)
.so without recompile.
# Build old library + app
gcc -shared -fPIC -g old/lib.c -o libfoo.so
gcc -g app.c -L. -lfoo -Wl,-rpath,. -o app
./app
# โ foo() = 0
# โ bar() = 1
# Swap in new library (no recompile)
gcc -shared -fPIC -g new/lib.c -o libfoo.so -Wl,--version-script=libfoo.map
./app
# โ foo() = 0 (same โ no breakage)
# โ bar() = 1
nm -D libfoo.so | grep -E 'foo|bar'
# โ 0000000000001108 T bar@@LIBFOO_1.0
# โ 00000000000010f9 T foo@@LIBFOO_1.0
Why INFORMATIONAL: app was linked against unversioned symbols, so its
ELF has no DT_VERNEED entry requiring LIBFOO_1.0; the dynamic linker
resolves foo/bar against the new library's default (@@) versioned
definitions without complaint. The opposite direction โ a library going from
versioned to unversioned, dropping a version consumers already require โ
is breaking (symbol_version_defined_removed), because dlvsym() lookups
pinned to that version would then fail.
Safe redesign¶
No fix needed โ adding a version script to a previously-unversioned library
is the correct way to start managing symbol versions going forward. Keep
existing symbols in the first version node (as done here) so already-built
consumers keep resolving against them; only later API changes need a new
LIBFOO_2.0 node.
Real-world example: glibc and most core system libraries ship symbol
versioning so multiple ABI generations of the same function (e.g.
realpath@GLIBC_2.2.5 vs. realpath@GLIBC_2.3) can coexist in one .so.
Cross-tool comparison¶
readelf --version-info is the lower-level way to inspect the same fact:
readelf --version-info libfoo_v1.so
# โ No version information found in this file.
readelf --version-info libfoo_v2.so
# โ Version definition section '.gnu.version_d' contains 2 entries:
# Index: 1 Cnt: 1 Name: libfoo_v2.so โ skipped (VER_FLG_BASE)
# Index: 2 Cnt: 1 Name: LIBFOO_1.0 โ recorded as versions_defined
References¶
Source files¶
CMakeLists.txtapp.clibfoo.map
See also: Examples overview ยท All COMPATIBLE cases ยท Category: Quality (Compatible).