Skip to content

Case 104: libstdc++ dual-ABI flip (glibcxx_dual_abi_flip_detected)

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: breaking | Verdict: BREAKING

What breaks

GNU libstdc++ ships two incompatible ABIs for std::string (and std::list), selected at compile time by the _GLIBCXX_USE_CXX11_ABI macro. With =1 (the modern default) std::string mangles as std::__cxx11::basic_string<...>; with =0 it mangles 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 โ€” v1 builds with _GLIBCXX_USE_CXX11_ABI=0, v2 with =1. 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 โ€” worse โ€” a partially-rebuilt process silently pairs a legacy-ABI string with a cxx11-ABI string and corrupts memory.

Why abicheck catches it

A naive symbol diff would report dozens of unrelated func_removed / func_added / type_removed churn entries. abicheck recognises the signature of a dual-ABI flip: a large set of public symbols disappears while an equally large set appears, and a significant fraction of the churned mangled names carry the __cxx11 marker. It collapses that pattern into a single glibcxx_dual_abi_flip_detected diagnostic (alongside the underlying func_removed/func_added evidence), and the overall verdict is BREAKING.

Code diff

The sources are identical; the build configuration differs:

 # v1 build
-g++ -shared -fPIC -g -D_GLIBCXX_USE_CXX11_ABI=0 -o libv1.so v1.cpp
 # v2 build
+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

Reproduce manually

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

# Hundreds of mangled-name differences, all driven by the ABI tag
nm -D --defined-only libv1.so | c++filt | sort > v1.syms
nm -D --defined-only libv2.so | c++filt | sort > v2.syms
diff v1.syms v2.syms | head

# abicheck collapses it into one dual-ABI diagnostic + BREAKING verdict
python -m abicheck compare libv1.so libv2.so

How to fix

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 change.


Source files

  • CMakeLists.txt
  • app.cpp
  • v1.cpp
  • v2.cpp

See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.