Case 180: Symbol Binding Lost GNU_UNIQUE¶
| Field | Value |
|---|---|
| Verdict | ๐ก COMPATIBLE_WITH_RISK |
| Category | Risk |
| Platforms | Linux |
| Flags | Bad practice |
Detected ChangeKinds |
symbol_binding_lost_unique |
| Source files | examples/case180_symbol_binding_lost_unique/ |
Category: Deployment Risk (ELF/Loader) | Verdict: โ ๏ธ COMPATIBLE_WITH_RISK
Verdict and consumer impact¶
v1 and v2 share byte-identical source โ a Meyers-singleton
Registry<Widget>::instance() function-local static inside a class
template. Only the build flag differs: v2 adds -fno-gnu-unique. By
default GCC marks this vague-linkage guard variable STB_GNU_UNIQUE, a GNU
ELF extension where the dynamic linker guarantees exactly one definition is
used process-wide, no matter how many DSOs independently instantiate
Registry<Widget>. Dropping it falls back to ordinary weak-symbol binding:
get_widget_id() still returns the same value from a single DSO, but if
the same template is independently instantiated by a second DSO loaded
into the same process, each can end up with its own, diverging "singleton"
instead of sharing one.
Old/new diff¶
Source is byte-identical between v1 and v2; only the compile flags differ:
| v1 | v2 |
|---|---|
g++ -shared -fPIC v1.cpp -o libv1.so (GNU_UNIQUE, the default) |
g++ -shared -fPIC -fno-gnu-unique v2.cpp -o libv2.so (ordinary weak binding) |
abicheck command¶
g++ -shared -fPIC -g -O2 v1.cpp -o libfoo_v1.so
g++ -shared -fPIC -g -O2 -fno-gnu-unique v2.cpp -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: COMPATIBLE_WITH_RISK (exit 0)
Deployment Risk Changes:
- symbol_binding_lost_unique: Symbol binding lost GNU_UNIQUE:
Registry<Widget>::instance()::value โ process-wide uniqueness guarantee
removed
Minimum evidence¶
min_evidence: L0 โ read directly from the ELF symbol table's st_info
binding field; no DWARF, no headers.
Toolchain note: clang never emits STB_GNU_UNIQUE for vague-linkage
template statics (a GCC ELF extension), so this finding only exists to
detect under a GCC-built pair like the one below.
Why abicheck catches it¶
abicheck reads each defined symbol's ELF binding directly. In v1,
Registry<Widget>::instance()::value's guard variable carries
STB_GNU_UNIQUE binding; in v2 (built with -fno-gnu-unique) the same
symbol carries an ordinary weak/global binding instead. The detector
reports the transition without needing to understand what the symbol's
type or template instantiation actually is.
Runtime failure demonstration¶
Severity: informational โ no observable effect in this single-DSO demo.
g++ -shared -fPIC -g v1.cpp -o libfoo.so
g++ -g app.cpp -I. -L. -lfoo -Wl,-rpath,. -o app
./app
# โ get_widget_id() = 42 (expected 42)
The single-DSO demo always prints the same value โ that's expected;
STB_GNU_UNIQUE's guarantee only becomes observable in a multi-DSO
process. To see the actual divergence, build the same v1.cpp twice under
two different SONAMEs, dlopen() both into one process, and compare the
address of Registry<Widget>::instance() as seen from each: under
GNU_UNIQUE binding both DSOs resolve to the same address; under
-fno-gnu-unique, glibc version and load order determine whether they
coincide or diverge. The artifact fact above โ the binding itself
changing โ is the deterministic, CI-graded proof; the multi-DSO divergence
is illustrative, not something this catalog gates CI on.
Safe redesign¶
Do not build parts of a project with -fno-gnu-unique and other parts
without it โ a template-heavy library and its plugins should agree on this
flag, or singleton-shaped vague-linkage statics can silently diverge across
the boundary. If a true process-wide singleton is required regardless of
DSO topology, consider an explicit, named exported symbol instead of
relying on vague-linkage template statics.
Cross-tool comparison¶
libfoo_v1.so:
5: 0000000000004008 4 OBJECT UNIQUE DEFAULT 19 _ZZN8RegistryI6W[...]
libfoo_v2.so:
(no matches โ ordinary weak binding, no UNIQUE entries)
abidiff/ABICC do not model ELF symbol-binding transitions at all, so this
class of finding is abicheck-specific; readelf confirms the raw
difference abicheck's symbol_binding_lost_unique detector reads.
References¶
- GNU ELF extensions:
STB_GNU_UNIQUE - Related cases:
case128_symbol_binding_strengthened,case27_symbol_binding_weakened
Source files¶
CMakeLists.txtapp.cppv1.cppv1.hppv2.cppv2.hpp
See also: Examples overview ยท All COMPATIBLE_WITH_RISK cases ยท Category: Risk.