Case 104: libstdc++ Dual-ABI Flip¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux |
| Flags | ABI break, Bad practice |
Detected ChangeKinds |
glibcxx_dual_abi_flip_detected, func_removed |
| Source files | examples/case104_glibcxx_dual_abi_flip/ |
Category: Symbol API | Verdict: โ BREAKING
Verdict and consumer impact¶
GNU libstdc++ ships two incompatible ABIs for std::string (and
std::list), selected at compile time by the _GLIBCXX_USE_CXX11_ABI
macro: =1 (the modern default) mangles std::string as
std::__cxx11::basic_string<...>; =0 mangles it as the legacy
std::basic_string<...>. The two are not link-compatible. Here the
source is byte-for-byte identical between v1 and v2 โ only the macro
flips. Every exported function that takes or returns std::string gets a
different mangled name, so a caller linked against one build cannot
resolve the symbols of the other: the dynamic linker fails at load, or โ
in a partially-rebuilt process โ a legacy-ABI string gets silently paired
with a cxx11-ABI string and corrupts memory.
Old/new diff¶
Source is identical; only the build flag differs:
g++ -shared -fPIC -g -D_GLIBCXX_USE_CXX11_ABI=0 -o libv1.so v1.cpp
-g++ -shared -fPIC -g -D_GLIBCXX_USE_CXX11_ABI=0 -o libv2.so v2.cpp
+g++ -shared -fPIC -g -D_GLIBCXX_USE_CXX11_ABI=1 -o libv2.so v2.cpp
Symbol-level effect:
-_Z4joinRKSsS0_ # std::string (legacy ABI)
+_Z4joinRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES5_ # std::__cxx11::string
abicheck command¶
g++ -shared -fPIC -g -D_GLIBCXX_USE_CXX11_ABI=0 -o libv1.so v1.cpp
g++ -shared -fPIC -g -D_GLIBCXX_USE_CXX11_ABI=1 -o libv2.so v2.cpp
abicheck compare libv1.so libv2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
## Breaking Changes
- func_removed: Public function removed: concat
- func_removed: Public function removed: split
- func_removed: Public function removed: trim
- func_removed: Public function removed: repeat
- func_removed: Public function removed: upper
- func_removed: Public function removed: join
- instantiation_missing_from_binary: Template instantiation 'concat' was
exported by the old library but is missing from the new binary.
## Deployment Risk Changes / Quality Issues
- glibcxx_dual_abi_flip_detected: libstdc++ dual ABI flip detected
(legacy ABI -> CXX11 ABI): 6 of 12 churned symbols contain CXX11 ABI
markers; likely caused by _GLIBCXX_USE_CXX11_ABI toggle
(Full run: 7 breaking changes, 88 deployment-risk changes, 18 compatible
changes โ the churn is every std::string-bearing symbol getting a new
mangled name.)
Minimum evidence¶
min_evidence: L0 โ the mangled names themselves carry the ABI tag
(__cxx11 present or absent); the exported-symbol table alone is enough
to see six symbols disappear and six new ones with __cxx11 markers
appear. No headers are passed here deliberately: the case is validated
binary-only by design, matching the fixture's own comment that public
headers are irrelevant to this phenomenon.
Why abicheck catches it¶
A naive symbol diff would report a dozen unrelated func_removed/
func_added entries with no unifying explanation. abicheck's dual-ABI
detector recognizes the shape of the churn: a set of public symbols
disappears while an equally large set appears, and a significant fraction
of the churned mangled names carry the __cxx11 marker on one side only.
It collapses that pattern into a single glibcxx_dual_abi_flip_detected
diagnostic layered on top of the underlying func_removed/func_added
evidence, so a reviewer sees "this is one systemic cause" instead of a
wall of unrelated-looking symbol churn.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: compile app against v1 (legacy ABI), swap in v2's .so
(cxx11 ABI) without recompiling.
# Build old library + app (app also built with the legacy ABI macro,
# matching the library it links against)
ln -sf libv1.so libv.so
g++ -D_GLIBCXX_USE_CXX11_ABI=0 app.cpp -L. -lv -Wl,-rpath,. -o app
./app
# โ exits 0 (join()/repeat() resolve to the legacy-ABI symbols)
# Swap in new library (no recompile)
ln -sf libv2.so libv.so
./app
# โ ./app: symbol lookup error: ./app: undefined symbol: _Z4joinRKSsS0_
Why CRITICAL: the app's call sites are hard-linked against the
legacy-mangled symbols (_Z4joinRKSsS0_); v2's .so only exports the
__cxx11-mangled equivalents, so the dynamic linker cannot resolve them
and the process fails to start. In a partially-rebuilt program mixing
objects from both ABIs, the failure mode can be worse than a clean load
error โ silent memory corruption from mismatched std::string layouts.
Safe redesign¶
Pick one _GLIBCXX_USE_CXX11_ABI value and use it consistently across
the library, all of its dependencies, and every consumer that exchanges
std::string/std::list across the boundary. Never expose libstdc++
container types on a public ABI surface unless the dual-ABI setting is
part of the documented build contract; prefer a C ABI or opaque handles
at boundaries that must survive a toolchain/distro upgrade.
Real-world example: this is the exact failure mode GCC 5's
introduction of the C++11 std::string/std::list ABI caused across the
Linux distro ecosystem โ packages built against old and new libstdc++
defaults couldn't exchange strings, and distros had to either dual-ship or
rebuild entire dependency chains in lockstep.
Cross-tool comparison¶
abidiff (binary-only) would see the same dozen-symbol churn but reports
each removal/addition independently, without recognizing it as one
systemic dual-ABI event โ that collapsing/labeling is unique to
abicheck's dedicated detector. Not re-verified numerically in this
environment (abidiff is not installed here).
References¶
Source files¶
CMakeLists.txtapp.cppv1.cppv2.cpp
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.