Skip to content

Case 113: ABI-tag set change ([abi:cxx11] lost on a single symbol)

Field Value
Verdict ๐Ÿ”ด BREAKING
Category Breaking
Platforms Linux
Flags ABI break
Detected ChangeKinds abi_tag_changed
Source files examples/case113_abi_tag_changed/

Category: Binary ABI break / C++ mangling | Verdict: ๐Ÿ”ด BREAKING

Verdict and consumer impact

In v1, get_id() carries an explicit Itanium ABI tag via [[gnu::abi_tag("cxx11")]], so its mangled symbol is _Z6get_idB5cxx11v (the B5cxx11 component encodes the tag). In v2 the tag is removed, so the symbol becomes _Z6get_idv. The demangled declaration is identical, but the two are different linker symbols โ€” any consumer binary linked against the v1 tagged symbol gets undefined symbol at load time against v2.

Old/new diff

v1.cpp v2.cpp
[[gnu::abi_tag("cxx11")]] int get_id(); int get_id();

abicheck command

g++ -shared -fPIC -g v1.cpp -o libfoo_v1.so
g++ -shared -fPIC -g v2.cpp -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so

Expected abicheck finding

Verdict: BREAKING (exit 4)

- abi_tag_changed: ABI-tag set changed for 'get_id': lost [abi:cxx11].
  The mangled name encodes the tag, so the old symbol
  (get_id[abi:cxx11]()) no longer exists under that name (get_id()).
  (cxx11 -> (none))
  > The Itanium ABI-tag set on a symbol changed; old binaries reference
    a symbol that no longer exists under that name.
- func_removed: Public function removed: get_id

Additions:
- func_added: New public function: get_id

Minimum evidence

min_evidence: L0 โ€” the ABI tag is part of the mangled name itself, so the exported-symbol table alone shows _Z6get_idB5cxx11v present in v1 and absent from v2, with a plain _Z6get_idv in its place. No headers or debug info needed (this case has no public header on purpose โ€” the snapshot comes from the compiled .so's own symbol table / DWARF).

Why abicheck catches it

abicheck demangles each exported symbol, strips any Itanium ABI-tag components, and compares the tag-stripped base name across versions. When two symbols share a base name but differ only in their ABI-tag set, it reports abi_tag_changed โ€” distinct from a mass dual-ABI flip (which churns hundreds of symbols at once and is reported separately as glibcxx_dual_abi_flip_detected) โ€” plus the ordinary func_removed / func_added pair for the underlying mangled-symbol change.

Runtime failure demonstration

Severity: BREAKING

Scenario: app.cpp in this case is a minimal stub (int main() { return 0; }) that doesn't call get_id(), so the real substitution failure is demonstrated with a small standalone consumer instead:

cat > consumer.cpp <<'EOF'
#include <cstdio>
[[gnu::abi_tag("cxx11")]] int get_id();
int main() {
    std::printf("get_id() = %d\n", get_id());
    return 0;
}
EOF

# Build old library + consumer
g++ -shared -fPIC -g v1.cpp -o libfoo.so
g++ consumer.cpp -L. -lfoo -Wl,-rpath,. -o consumer
./consumer
# โ†’ get_id() = 7

# Swap in new library (no recompile)
g++ -shared -fPIC -g v2.cpp -o libfoo.so
./consumer
# โ†’ ./consumer: symbol lookup error: ./consumer: undefined symbol:
#   _Z6get_idB5cxx11v

Why BREAKING: the consumer was compiled against the tagged declaration, so its call site references _Z6get_idB5cxx11v specifically; v2's .so only exports the untagged _Z6get_idv, so the dynamic linker cannot resolve the call.

Safe redesign

Never change a symbol's ABI-tag set in a compatible release โ€” adding or removing [[gnu::abi_tag(...)]] is exactly as breaking as renaming the function, because the tag is part of the mangled name. If the tag was added by mistake, ship it as a new overload/alias and deprecate the tagged one across a release cycle instead of silently dropping it.

Real-world example: libstdc++'s dual-ABI mechanism (_GLIBCXX_USE_CXX11_ABI) uses the same [abi:cxx11] tag mechanism at a mass scale โ€” flipping the macro changes hundreds of symbols at once, which is why abicheck reports that scenario as a single glibcxx_dual_abi_flip_detected finding rather than hundreds of individual abi_tag_changed findings. This case isolates the same underlying mechanism down to one symbol.

References


Source files

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

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