Case 13: Symbol Versioning Script¶
| Field | Value |
|---|---|
| Verdict | 🟢 COMPATIBLE |
| Category | Quality (Compatible) |
| Classification | Rule |
| Platforms | Linux |
| Flags | — |
Detected ChangeKinds |
symbol_version_defined_added |
| Source files | catalog/cases/case13_symbol_versioning/ |
| Rule family | symbol-versioning |
| Subject | Symbol versioning and kABI |
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¶
new/old/CMakeLists.txtapp.clibfoo.map
See also: Compatibility Catalog · All COMPATIBLE cases · Category: Quality (Compatible) · Rule: Symbol versioning introduced · Subject: Symbol versioning and kABI.