ABI Compatibility: Why Your Binary Breaks Without Recompiling

2026-04-28

You've learned how linkers resolve symbols and how the GOT enables position-independent code. But there's a silent contract governing all of this: the Application Binary Interface (ABI). The ABI defines how compiled code interacts at the machine level — struct layout, function calling conventions, name mangling, vtable ordering, and type sizes. Break it, and binaries segfault without a single source code change.

What the ABI actually specifies:

Real-world example: The infamous libstdc++ ABI break in GCC 5.1 (2015) changed std::string from a copy-on-write implementation to Small String Optimization (SSO). The internal layout of std::string changed entirely — sizeof(std::string) went from 8 bytes (a single pointer) to 32 bytes. Any library compiled with GCC 4.x that passed a std::string to code compiled with GCC 5+ would corrupt memory. The solution was the _GLIBCXX_USE_CXX11_ABI macro and years of dual-ABI support.

Rule of thumb for C struct ABI stability: you can safely append fields to a struct if callers always allocate via your API (so you control sizeof), and you version the struct with a size field. This is the pattern used by the Linux kernel's perf_event_attr — its first member is __u32 size, letting the kernel distinguish old callers from new ones. Deleting, reordering, or resizing existing fields is always an ABI break.

Checking ABI compatibility: Tools like abidiff (from libabigail) compare two shared library versions and report added/removed symbols, changed type sizes, and altered vtables. Run it in CI before releasing a library: abidiff libfoo.so.1 libfoo.so.2. Any flagged incompatibility means you must bump the SONAME.

Why C dominates system interfaces: C's ABI is simple and stable — no mangling, no vtables, no templates. That's why virtually every OS, language FFI, and plugin system defines its boundary in C, even when the implementation behind it is C++ or Rust.

See it in action: Check out maintaining c abi compatibility by CodeGen to see this theory applied.
Key Takeaway: The ABI is the binary-level contract between separately compiled code; breaking it causes silent memory corruption, so treat struct layout and symbol interfaces as a versioned public API.

All newsletters