ABI Cheat Sheet¶
Quick-reference card for shared-library maintainers. Scannable in 2 minutes.
For deeper explanations see ABI Breaks Explained and Verdicts.
Safe Changes (COMPATIBLE)¶
These changes preserve binary compatibility. Existing consumers continue to work without recompilation.
| Change | Why Safe | Example |
|---|---|---|
| Add new exported function | Existing binaries never reference it; linker ignores unknown symbols | case03 |
| Append enum member (end, no value shift) | Compiled binaries use integer values; existing values unchanged | case25 |
| Add union field without growing size | Union size = max(fields); fits within existing allocation | case26b |
| Weaken symbol binding (GLOBAL to WEAK) | Symbol still resolves; interposition semantics relax | case27 |
| Add IFUNC dispatch | Transparent to callers; resolver picks implementation at load time | case29 |
| Outline an inline function (add export) | New symbol appears; callers with inlined copy still work | case47 |
| Add new global variable | No existing code references it | case61 |
| Add field to opaque struct | Callers access through pointers only; layout is hidden | case62 |
Breaking Changes (NEVER do in a minor release)¶
These cause crashes, wrong results, or link failures in pre-compiled consumers.
| Change | What Happens at Runtime | Example |
|---|---|---|
| Remove exported symbol | undefined symbol on dlopen/startup |
case01 |
| Change parameter types | Caller passes args in wrong registers/format; garbage or crash | case02 |
| Change struct layout/size | Stack corruption; reads/writes past allocation boundary | case07 |
| Change enum member values | Switch/lookup tables use stale integer values; wrong branch taken | case08 |
| Reorder virtual methods | Vtable slot mismatch; call dispatches to wrong method silently | case09 |
| Change return type | Caller interprets return register/memory as wrong type | case10 |
| Change class size (add members) | new/stack allocation undersized; heap corruption, SIGSEGV |
case14 |
| Remove enum member | Code referencing removed constant fails at compile time or uses stale value | case19 |
Change type alignment (alignas) |
Misaligned access; SIGBUS on strict-alignment architectures | case42 |
Change struct packing (pragma pack) |
Field offsets shift; every member read is wrong | case56 |
| Change calling convention | Parameters read from wrong registers; total data corruption | case64 |
| Remove symbol version node | Dynamic linker refuses to load; version 'FOO_1.0' not found |
case65 |
See the full breaking catalog in Breaking Cases Catalog.
Source-Only Breaks (API_BREAK)¶
Binary-compatible, but recompilation against new headers fails. Verdict: 🟠API_BREAK.
| Change | Impact | Example |
|---|---|---|
| Rename enum member (same value) | LOG_ERR no longer compiles; binary still uses integer 1 |
case31 |
| Narrow access level (public to private) | Downstream code calling helper() gets compile error |
case34 |
| Remove default parameter | Call sites relying on default fail to compile; ABI unchanged | -- |
Risk Changes (deployment concern)¶
Binary-compatible, but may break at deployment time. Verdict: 🟡 COMPATIBLE_WITH_RISK.
| Change | Risk | Example |
|---|---|---|
| New GLIBC/GLIBCXX version requirement | Binaries won't load on older distros missing the required symbol version | -- (detected via SYMBOL_VERSION_REQUIRED_ADDED) |
| Leaked dependency symbol changed | Transitive dependency update shifts symbols your consumers never directly linked | -- |
noexcept removed |
C++17 callers compiled with noexcept in function type get UB on throw |
case15 |
Quality Warnings¶
No immediate breakage, but these compromise the ABI contract or security posture. abicheck flags these as 🟡 COMPATIBLE quality checks (SONAME_MISSING, VISIBILITY_LEAK, EXECUTABLE_STACK, RPATH_CHANGED). Fixing them later often causes 🔴 BREAKING changes.
| Warning | Why It Matters | Example |
|---|---|---|
| Missing SONAME | Consumers record bare filename; library versioning breaks | case05 |
Visibility leak (no -fvisibility=hidden) |
Internal symbols become public ABI surface you must maintain forever | case06 (fixing later = BREAKING) |
Executable stack (GNU_STACK RWX) |
Disables NX protection process-wide; trivial exploit target | case49 |
| RPATH leak (hardcoded build path) | Library only works on the build machine; deployment fails everywhere else | case52 |
| Namespace pollution (generic names) | Unprefixed symbols like init() collide across libraries |
case53 (fixing later = BREAKING) |
Prevention Patterns¶
| Pattern | Protects Against | How |
|---|---|---|
-fvisibility=hidden + explicit exports |
Visibility leaks, accidental ABI surface | Only annotated symbols enter .dynsym |
| Pimpl / opaque handles | Struct layout breaks | Callers see T* only; fields are private |
| Symbol versioning (version script) | Symbol removal, version node breaks | Map file controls what's exported per version |
| SONAME with major-version bump | All breaking changes | libfoo.so.1 to libfoo.so.2 on ABI break |
| Reserved fields in public structs | Future field additions | void *_reserved[4] absorbs growth without size change |
| CI ABI check with abicheck | All of the above | Catches regressions before merge (see below) |
CI One-Liner¶
abicheck compare libfoo.so.old libfoo.so.new \
--old-header include/old/foo.h \
--new-header include/new/foo.h \
--policy strict_abi
Exits non-zero on any 🔴 BREAKING or 🟠API_BREAK finding. Add --suppress suppressions.yaml to allowlist known acceptable changes. See CLI Usage and Policies for options.
Verdict Quick Reference¶
| Icon | Verdict | Meaning |
|---|---|---|
| 🔴 | BREAKING | Binary incompatible -- consumers crash or misbehave |
| 🟠| API_BREAK | Source incompatible -- recompilation fails, binary works |
| 🟡 | COMPATIBLE_WITH_RISK | Binary works, deployment risk present |
| 🟡 | COMPATIBLE (quality) | Binary works, bad practice detected |
| 🟢 | COMPATIBLE (addition) | New API surface, fully backward-compatible |
| ✅ | NO_CHANGE | Identical ABI |
Full verdict semantics: Verdicts | All example cases: Scenario Catalog