How a Break Shows Up¶
A break is a symptom before it is a mechanism¶
The rest of this series is organised by mechanism — symbols, type layout, C++ specifics, the linker — because that is how you fix a break. But nobody meets a break as a mechanism. You meet it as something that happened: a link failed, a program would not start, a customer's machine crashed, a rebuild stopped compiling. This page starts from that side. Each symptom names the mechanism family behind it and the first kind of evidence a checker needs before it can see it at all — the evidence levels that Evidence & Detectability defines and What Each Level Sees walks one by one.
The eight symptoms¶
1. The link fails¶
You rebuild your program against the new library and the linker stops:
A symbol the program needs is gone from the library's export table — the most direct break there is (Part 2). The export table is in the binary itself, so the cheapest comparison, a symbol-only diff of the two libraries (Level 0), already shows it: case01.
2. The program will not start¶
Nothing was rebuilt; the library was upgraded in place, and the loader refuses the program at startup:
./app: symbol lookup error: ./app: undefined symbol: foo_open
./app: /lib/libfoo.so.1: version `FOO_1.2' not found (required by ./app)
The loader's own contract broke — a symbol or a symbol version node disappeared (Part 5). Both facts live in the binary, so Level 0 sees them too: case65.
3. It crashes, or quietly corrupts data, after an upgrade nobody rebuilt for¶
The program starts, every symbol resolves, and it fails later — a segfault, or worse, wrong numbers with no error at all. A struct grew, a field moved, a vtable slot was renumbered; the old caller keeps using the old offsets (Part 3, Part 4). The export table is identical, so Level 0 calls the release clean. Layout is only in the debug information (Level 1): case07.
4. Rebuilding against the new headers fails¶
Prebuilt programs keep working, but a consumer who recompiles gets:
A default argument was removed, a member went private, a constructor
became explicit — a source-only break
(Part 6 § Source-only API breaks).
No binary changed, so only a comparison that parses the public headers
(Level 2)
can see it: case123.
5. A call silently binds to a different value¶
The program compiles and runs, and behaves differently after a rebuild,
because a constant it compiled in — an enum member's value, a header
constexpr — changed underneath it
(Part 6). The value lives in the
declared header, not in any symbol, so this is Level 2 as well:
case124.
6. The source you compile against changed, but no binary did¶
A public #define disappeared, an inline function was removed, an
uninstantiated template's signature moved. No shipped artifact carries any
of these — not the export table, not the debug info, not even the header
AST for the template case — so only replaying the sources themselves
(Level 4)
finds them: case156,
case157,
case122.
A silent behavioural change inside an inline body has no catalog
fixture today; that gap is real, and the series says so rather than
pointing at a case that shows something else.
7. It works on the build machine and fails on the customer's distro¶
The library loads fine where it was built and refuses to load on an older OS release:
Nothing in the library's own contract changed; its floor did — a rebuild
on a newer toolchain now requires a newer runtime
(Dependency & Runtime Floors). The requirement is
recorded in the binary, so Level 0 reads it; deciding whether it is a break
needs a declared supported-OS matrix (.abicheck.yml's deployment: block):
case170.
8. It works for the application and breaks the plugin, or a sibling library¶
Every consumer you tested is fine, and a plugin — or another library in the same release — fails, because it depended on something the "main" consumer never touched. Which consumer shape you promised compatibility to is its own question (Consumer Models), and a release of several libraries is one contract, not several (What Each Level Sees § two orthogonal sources): case90, described with its three sibling bundle cases in Multi-Binary Releases (the bundle fixtures have no generated case pages).
The table¶
| Symptom | Mechanism family | First evidence that shows it |
|---|---|---|
| Link error | Part 2 | L0 |
| Load error, version not found | Part 5 | L0 |
| Crash or silent corruption, no rebuild | Part 3, Part 4 | L1 |
| Compile error after an upgrade | Part 6 | L2 |
| A call binds to a different value | Part 6 | L2 |
| The source changed, no binary did | Part 6 | L4 |
| Fails on the customer's distro | Dependency floors | L0 + a declared matrix |
| Breaks the plugin or a sibling library | Consumer models | bundle scan |
What this means for you¶
- You cannot see the third row without debug information: a stripped binary with no headers makes a genuinely breaking release look clean (Level 1).
- You cannot see the fourth or fifth row without the public headers; the break is in what consumers compile against, not in what you ship (Level 2).
- You cannot see the sixth row from any binary at all; only the sources carry it (Level 4).
Ladder: ← ABI in Five Minutes · Step 1 · Start Here · ABI Cheat Sheet →