ABI compatibility checker for C/C++ shared libraries
Project description
abicheck
abicheck is a command-line tool that detects breaking changes in C/C++ shared libraries before they reach production. It compares two versions of a .so library — along with their public headers — and reports whether existing binaries will continue to work or break at runtime.
Typical problems it catches: removed or renamed symbols, changed function signatures, struct layout drift, vtable reordering, enum value reassignment, and dozens of other ABI/API incompatibilities that cause crashes, silent data corruption, or linker failures after a library upgrade.
Platform: Linux (ELF binaries + DWARF debug info + C/C++ headers). Windows PE and macOS Mach-O are not yet supported.
Installation
Note: abicheck is not yet published to PyPI or conda-forge. Install from source for now.
Prerequisites
| Requirement | Notes |
|---|---|
| Linux | ELF/DWARF + header-based analysis |
| Python >= 3.10 | |
castxml |
Clang-based C/C++ AST parser for header analysis |
g++ or clang++ |
Must be accessible to castxml |
# Ubuntu / Debian
sudo apt install castxml g++
# conda
conda install -c conda-forge castxml
Install from source
git clone https://github.com/napetrov/abicheck.git
cd abicheck
pip install -e .
For development (includes test & lint dependencies):
pip install -e ".[dev]"
Quick start
Compare two library versions
The simplest way to check ABI compatibility — pass two .so files and their public headers:
abicheck compare libfoo.so.1 libfoo.so.2 \
--old-header include/v1/foo.h --new-header include/v2/foo.h
If the header file is the same for both versions, use the shorthand:
abicheck compare libfoo.so.1 libfoo.so.2 -H include/foo.h
Use saved snapshots (for CI baselines)
Save an ABI snapshot once per release, then compare against new builds:
# Save baseline
abicheck dump libfoo.so -H include/foo.h --version 1.0 -o baseline.json
# Compare new build against saved baseline
abicheck compare baseline.json ./build/libfoo.so --new-header include/foo.h
Key flags
| Flag | Description |
|---|---|
--old-header / --new-header |
Public headers for each version |
-H |
Same header for both versions (shorthand) |
--format |
Output format: markdown (default), json, sarif, html |
-o |
Write report to file |
--policy |
Verdict policy: strict_abi (default), sdk_vendor, plugin_abi |
--policy-file |
Custom YAML policy overrides |
--lang |
Language mode: c++ (default) or c |
-v / --verbose |
Debug output |
For full CLI reference and advanced options (cross-compilation, suppression files, symbol filtering), see the documentation.
Output formats and reports
abicheck supports four output formats:
# Markdown (default) — human-readable, printed to stdout
abicheck compare old.so new.so -H foo.h
# JSON — machine-readable, includes precise verdict field for CI parsing
abicheck compare old.so new.so -H foo.h --format json -o result.json
# SARIF — for GitHub Code Scanning integration
abicheck compare old.so new.so -H foo.h --format sarif -o abi.sarif
# HTML — standalone report for review
abicheck compare old.so new.so -H foo.h --format html -o report.html
Example report (markdown output)
# ABI Report: libfoo.so
| | |
|---|---|
| **Old version** | `1.0` |
| **New version** | `2.0` |
| **Verdict** | ❌ `BREAKING` |
| Breaking changes | 2 |
| Source-level breaks | 0 |
| Deployment risk changes | 0 |
| Compatible additions | 1 |
## ❌ Breaking Changes
- **func_removed**: Public function removed: helper (`helper`)
> Old binaries call a symbol that no longer exists; dynamic linker will refuse to load or crash at call site.
- **type_size_changed**: Size changed: Point (64 → 96 bits) (`64` → `96`)
> Old code allocates or copies the type with the old size; heap/stack corruption, out-of-bounds access.
## ✅ Compatible Additions
- Field added: Point::z
---
## Legend
| Verdict | Meaning |
|---------|---------|
| ✅ NO_CHANGE | Identical ABI |
| ✅ COMPATIBLE | Only additions (backward compatible) |
| ⚠️ COMPATIBLE_WITH_RISK | Binary-compatible; verify target environment |
| ⚠️ API_BREAK | Source-level API change — recompilation required |
| ❌ BREAKING | Binary ABI break — recompilation required |
_Generated by [abicheck](https://github.com/napetrov/abicheck)_
Policy profiles
Policies control how detected changes are classified. A change that is BREAKING under strict_abi might be downgraded to COMPATIBLE under sdk_vendor.
Built-in profiles:
| Profile | Use case | Behavior |
|---|---|---|
strict_abi (default) |
System libraries, public SDKs | Every ABI change at maximum severity |
sdk_vendor |
Vendor SDKs, optional extensions | Source-only changes (renames, access) downgraded to COMPATIBLE |
plugin_abi |
Plugins rebuilt with host | Calling-convention changes downgraded to COMPATIBLE |
abicheck compare old.so new.so -H foo.h --policy sdk_vendor
Custom policy file
Create a YAML file to override classification of specific change kinds:
base_policy: strict_abi
overrides:
enum_member_renamed: ignore # break | warn | ignore
field_renamed: ignore
abicheck compare old.so new.so -H foo.h --policy-file project_policy.yaml
Semantics: break = BREAKING (exit 4), warn = API_BREAK (exit 2), ignore = COMPATIBLE (exit 0). Kinds not listed in overrides use the base_policy.
See Policy Profiles for full details.
Exit codes
| Exit code | Verdict | Meaning |
|---|---|---|
0 |
NO_CHANGE, COMPATIBLE, COMPATIBLE_WITH_RISK |
Safe — no binary ABI break (risk report may have warnings) |
1 |
— | Tool/runtime error |
2 |
API_BREAK |
Source-level break (recompile needed, binary may work) |
4 |
BREAKING |
Binary ABI break (old binaries will crash or misbehave) |
Use exit codes directly in CI gates. For precise verdicts, parse --format json output.
GitHub Actions integration
A typical CI flow: dump the ABI snapshot once at release time, then compare every new build against that saved baseline.
name: ABI check
on: [push, pull_request]
jobs:
abi-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install abicheck
run: pip install git+https://github.com/napetrov/abicheck.git
# ── Release step (run once when cutting a release) ─────────────
# Dump the ABI baseline and upload it as a release artifact:
#
# abicheck dump ./build/libfoo.so -H include/foo.h \
# --version ${{ github.ref_name }} -o abi-baseline.json
#
# Then download it in CI (e.g. from a release asset or artifact).
- name: Download ABI baseline
uses: actions/download-artifact@v4
with:
name: abi-baseline
# abi-baseline.json saved from the last release
- name: Build current library
run: make -C src/ # produces ./build/libfoo.so
# ── CI step: compare new build against saved baseline ──────────
- name: Compare ABI
run: |
abicheck compare abi-baseline.json ./build/libfoo.so \
--new-header include/foo.h \
--format sarif -o abi.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: abi.sarif
Exit codes for CI gates: 0 = compatible, 1 = tool error, 2 = API break, 4 = breaking ABI change.
ABICC drop-in replacement
For teams migrating from ABI Compliance Checker (ABICC) — swap one command, keep your existing XML descriptors:
# Before (ABICC):
abi-compliance-checker -lib libfoo -old old.xml -new new.xml -report-path r.html
# After (abicheck — same flags):
abicheck compat check -lib libfoo -old old.xml -new new.xml -report-path r.html
When ready, migrate to the simpler native workflow:
abicheck compare libfoo.so.1 libfoo.so.2 -H include/foo.h
See ABICC Migration Guide for full flag reference, exit code differences, and migration checklist.
Examples and ABI breakage catalog
The examples/ directory contains 48 real-world ABI break scenarios — each with paired v1/v2 source code, a consumer app that demonstrates the actual failure, and a Makefile to reproduce it.
Try an example
cd examples/case01_symbol_removal
make
abicheck compare libv1.so libv2.so --old-header v1.h --new-header v2.h
# Verdict: BREAKING — symbol 'helper' was removed
What the examples cover
Breaking changes — changes that crash or corrupt existing binaries:
| Category | Cases | Examples |
|---|---|---|
| Symbol removal / rename | 01, 12 | Function removed from export table |
| Type/signature changes | 02, 10, 33 | Parameter type, return type, pointer level changed |
| Struct/class layout | 07, 14, 40, 43, 44 | Field added/reordered, class size changed |
| Enum changes | 08, 19, 20 | Value reassigned, member removed |
| C++ vtable / virtual | 09, 23, 38 | Vtable reorder, pure virtual added |
| Qualifiers / binding | 21, 22, 30, 39 | Method became static, const changed |
| Templates / typedefs | 17, 28, 45, 46, 48 | Template layout, typedef opaque |
| Complex types | 24, 26, 35, 36, 37 | Union field removed, field renamed, base class changed |
Compatible changes — safe, no binary break:
| Cases | Examples |
|---|---|
| 03, 25, 26b | New symbol added, enum member appended, union in reserved space |
| 04, 32 | No change, parameter defaults changed |
| 05, 06 | SONAME policy, visibility leak (bad practice but binary-safe) |
| 13, 27, 29, 47 | Symbol versioning, weak binding, IFUNC, inline-to-outlined |
| 16 | Inline to non-inline (ODR concern, not binary break) |
API-only breaks — source-level break, binary still works:
| Cases | Examples |
|---|---|
| 31, 34 | Enum renamed, access level changed |
Benchmarks
abicheck detects 100+ change types across ELF, AST, and DWARF layers. The examples/ directory contains 48 representative test cases with expected verdicts in examples/ground_truth.json. Cross-tool comparison on 42 of these cases:
| Tool | Correct / Scored | Accuracy |
|---|---|---|
| abicheck (compare) | 42/42 | 100% |
| abicheck (compat) | 40/42 | 95% |
| ABICC (xml) | 25/41 | 61% |
| ABICC (abi-dumper) | 20/30 | 66% |
| abidiff | 11/42 | 26% |
abicheck passes all 48 cases. Run python3 scripts/benchmark_comparison.py to reproduce.
See Benchmark & Tool Comparison for per-case results, methodology, and timing data.
ABI compatibility guide
Understanding what breaks ABI and what doesn't is essential for library maintainers. Here is a quick reference:
Changes that break binary compatibility
- Removing or renaming an exported function or variable
- Changing function signature — parameter types, return type, calling convention
- Modifying struct/class layout — adding/removing/reordering fields, changing field types
- Changing enum values — reassigning numeric values, removing members
- C++ vtable changes — reordering virtual methods, adding pure virtuals
- Changing method qualifiers —
const,static,noexcept(when it affects mangling) - Changing global variable type — size/alignment mismatch
Changes that are safe (binary compatible)
- Adding new exported functions or variables
- Adding new enum members at the end (without shifting existing values)
- Weakening symbol binding (GLOBAL to WEAK)
- Adding IFUNC resolvers
- Adding symbol version tags
Best practices for library maintainers
- Treat public headers as ABI contracts — any change is potentially breaking
- Use SONAME versioning — bump major version on incompatible changes
- Hide implementation details — use Pimpl pattern, opaque handles,
-fvisibility=hidden - Add, don't modify — introduce
foo_v2()instead of changingfoo() - Freeze enum values — never renumber released constants
- Don't expose third-party types in public API — wrap them behind stable project-owned types
See Examples Breakage Guide for detailed code examples and failure demonstrations for each case.
Architecture
abicheck uses a 3-layer comparison pipeline:
- ELF metadata (via pyelftools) — exported symbols, SONAME, visibility, binding, symbol versions
- Header AST (via castxml/Clang) — function signatures, classes, fields, typedefs, enums, vtable layout
- DWARF cross-check (optional) — validates actual compiled struct sizes, member offsets, vtable slots
Each layer provides independent signals. Combining all three gives abicheck higher accuracy than tools that rely on only one or two sources (e.g., abidiff uses only DWARF; ABICC uses only GCC AST).
Key modules
| Module | Responsibility |
|---|---|
cli.py |
CLI entrypoint (dump, compare, compat check/dump) |
dumper.py |
Snapshot generation from .so + headers |
checker.py |
Diff orchestration and change collection |
checker_policy.py |
Change classification, built-in policies, verdict logic |
detectors.py |
ABI change detection rules |
reporter.py |
Output formatting (markdown, JSON, SARIF, HTML) |
suppression.py |
Suppression rules and symbol filtering |
policy_file.py |
Custom YAML policy file parsing |
See Architecture reference for the full design documentation.
Documentation
Full documentation is available at napetrov.github.io/abicheck.
Getting started:
Concepts:
- Verdicts explained — NO_CHANGE / COMPATIBLE / COMPATIBLE_WITH_RISK / API_BREAK / BREAKING
- Limitations
- Troubleshooting
User guide:
Reference:
Contributing
See CONTRIBUTING.md for setup instructions, testing, code style, and PR workflow.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file abicheck-0.1.0.tar.gz.
File metadata
- Download URL: abicheck-0.1.0.tar.gz
- Upload date:
- Size: 301.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0da7255e3e0fd80fba505c4138307c1d76b854e7757e449d95b88d92d7536e7
|
|
| MD5 |
d0acc02bfe0d092d7992e53782bfbf26
|
|
| BLAKE2b-256 |
f41bf42957b3cd53cf76e0cc2782143ec3f31a59405d995d7f811d52da530e33
|
Provenance
The following attestation bundles were made for abicheck-0.1.0.tar.gz:
Publisher:
publish.yml on napetrov/abicheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
abicheck-0.1.0.tar.gz -
Subject digest:
c0da7255e3e0fd80fba505c4138307c1d76b854e7757e449d95b88d92d7536e7 - Sigstore transparency entry: 1100836651
- Sigstore integration time:
-
Permalink:
napetrov/abicheck@85748326829cd0dfc77b2829da8bb576f37b26c8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/napetrov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@85748326829cd0dfc77b2829da8bb576f37b26c8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file abicheck-0.1.0-py3-none-any.whl.
File metadata
- Download URL: abicheck-0.1.0-py3-none-any.whl
- Upload date:
- Size: 139.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79cad6957297dc32303cab411a675098130c830c107d062030acb7c9270984b1
|
|
| MD5 |
317df938e49172489a3773578ad438b6
|
|
| BLAKE2b-256 |
eea847ae5dbefc480d9fbb2c8d9227b7199772dd5bd25a3af36384d839ea7ff0
|
Provenance
The following attestation bundles were made for abicheck-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on napetrov/abicheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
abicheck-0.1.0-py3-none-any.whl -
Subject digest:
79cad6957297dc32303cab411a675098130c830c107d062030acb7c9270984b1 - Sigstore transparency entry: 1100836656
- Sigstore integration time:
-
Permalink:
napetrov/abicheck@85748326829cd0dfc77b2829da8bb576f37b26c8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/napetrov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@85748326829cd0dfc77b2829da8bb576f37b26c8 -
Trigger Event:
release
-
Statement type: