Core CLI Workflows¶
What abicheck is¶
abicheck checks C/C++ library compatibility on both API and ABI layers. It is designed to be a practical, modern replacement for legacy ABI tooling in CI, especially when you need structured output and automation.
abicheck is inspired by:
Huge thanks to both projects for pioneering ABI compatibility analysis.
Not sure which command fits your situation? See Choose Your Workflow — a decision guide that maps your artifacts (single library, release bundle, package, application, stripped binaries…) and CI policy to the exact command and options. This page covers the core
dump/compareworkflow; for the exhaustive, generated per-command flag list see the CLI Reference.
How to use abicheck¶
1) Compare two libraries directly (primary flow)¶
The simplest way — pass .so files and their public headers directly to
compare. Each library version gets its own header(s):
# Each version has its own header
abicheck compare libfoo.so.1 libfoo.so.2 \
--header old=include/v1/foo.h --header new=include/v2/foo.h
# Multiple headers per version, with include dirs and version labels
abicheck compare libfoo.so.1 libfoo.so.2 \
--header old=include/v1/foo.h --header old=include/v1/bar.h \
--header new=include/v2/foo.h --header new=include/v2/bar.h \
-I include/ --version old=1.0 --version new=2.0
# Shorthand: -H applies the same header to both sides
# (only when the header itself didn't change between versions)
abicheck compare libfoo.so.1 libfoo.so.2 -H include/foo.h
# Header directory input is supported (recursive)
abicheck compare libfoo.so.1 libfoo.so.2 -H include/
# Output formats
abicheck compare libfoo.so.1 libfoo.so.2 \
--header old=v1/foo.h --header new=v2/foo.h -o sarif=abi.sarif
abicheck compare libfoo.so.1 libfoo.so.2 \
--header old=v1/foo.h --header new=v2/foo.h -o junit=results.xml
Public headers vs. include roots¶
-H/--header and -I/--include look similar but answer different questions:
| Flag | Question | Role |
|---|---|---|
-H / --header (--header old=/--header new=) |
What to analyse | The public headers — the files a consumer #includes. These are the API surface abicheck parses to decide what's public and to read types. Pass a directory to establish a public/internal boundary. |
-I / --include (--include old=/--include new=) |
How to parse it | The include roots — directories added to the parser's search path so the public headers' own #include "…"/<…> lines resolve. They are not analysed; they only make the parse succeed. |
Often a single include/ is both the public header dir and the include root. But
they diverge when a public header pulls in a dependency from elsewhere — e.g.
include/foo/api.h doing #include <bar/baz.h> needs third_party/ added as an
include root (-I third_party) even though bar/baz.h itself is not part of
foo's public API. If the parser can't find an included file, add its directory
as an include root.
compare auto-detects each input: .so files are dumped on-the-fly, .json
snapshots are loaded directly.
You can mix them freely (see below).
If headers are not provided, compare uses whatever debug info is available
instead — DWARF (falling back to BTF/CTF) on ELF, PDB on PE — falling back further to L0
binary-metadata analysis (exported symbols plus platform-specific facts —
SONAME/dependencies/rpaths on ELF, machine type/imports/delay-load/hardening
on PE, install name/dependencies/rpaths on Mach-O — never a bare symbol
list) only when neither headers nor debug info are present. Only the ELF
path prints an explicit no-headers warning today; PE and Mach-O degrade the
same way without one. Less evidence means a weaker analysis that may miss
signature/type-level ABI breaks; see
Evidence & Detectability.
2) Dump snapshots and compare later (for CI baselines)¶
When you want to cache ABI baselines as CI artifacts or commit them to the repo:
# Step 1: Dump snapshots (each version uses its own header)
abicheck dump libfoo.so.1 -H include/v1/foo.h --version 1.0 -o libfoo-1.0.json
abicheck dump libfoo.so.2 -H include/v2/foo.h --version 2.0 -o libfoo-2.0.json
# Step 2: Compare snapshots (no headers needed — already baked in)
abicheck compare libfoo-1.0.json libfoo-2.0.json
A JSON snapshot's evidence is fixed at dump time: compare loads it
verbatim, so passing -H at dump time (as above) means compare sees
the same full header-AST evidence either side captured, with no headers
flag of its own to pass and no re-resolution against DWARF/PDB/binary
metadata happening at compare time. The no-headers fallback described
above only applies when a native binary (.so/.dll/.dylib) is
compared directly with no -H; see
Evidence & Detectability.
Going beyond a plain
.so+ headers? C vs C++ mode, cross-compilation, feeding in the exact build flags (-p build/, evidence layer L3), embedding build/source evidence packs (L3/L4), resolving debug info that isn't in the binary itself, and-v/--verboseare all on their own reference page: Evidence, Build-Context, and Debug Flags.
Related flags and pages¶
For the exhaustive, generated list of every command/subcommand/option (the
same help= text --help-all shows), see the CLI Reference.
compare and dump each show only a curated, everyday subset by
default (-H, --depth, --output, and the like); the long tail —
toolchain overrides, debug-info resolution, per-category severity, release-
only knobs — folds behind --help-all on each command. Nothing is removed,
only hidden from the default view; every folded option still works exactly
as documented when passed explicitly.
Beyond the core compare/dump flow:
- Evidence, Build-Context, and Debug Flags — language
mode, cross-compilation,
compile_commands.json(L3), evidence packs (L3/L4), debug artifact resolution,--dry-run. - Output Formats —
--view show=...filtering,-o oneline=...'s one-line summary,--view impact|root-cause, redundancy filtering, SARIF/JUnit output, evidence-tier confidence, JSON schema. --used-by/--required-symbol(s)oncomparescope the comparison to an application's actual imports or a plugin host's required entrypoints — see Application Compatibility and Plugin Systems.- Generating/validating/diffing Debian
dpkg-gensymbols-style symbols files is a Python API only now (abicheck.debian_symbols), not a CLI subcommand — see Debian Symbols File Integration.
--severity-* (controlling exit codes and report labels) is covered in full
on Severity Configuration.
There is no --profile shortcut¶
An earlier revision offered --profile NAME (ci-gate/release-cut/quick)
to bundle a handful of flags into one token. It was removed in 0.6: it bundled three independent axes — evidence
depth, report rendering, and CI gate policy — behind one word, and a
rendering choice may never carry a gate setting. State the three
independently instead:
# What --profile ci-gate used to expand to
abicheck compare old.json new.json --depth headers -o review=- \
--severity-preset default
# What --profile release-cut used to expand to
abicheck compare old.json new.json --depth source -o markdown=-
# What --profile quick used to expand to (-o oneline=... replaces the
# one-line summary; there is no depth-bundling replacement)
abicheck compare old.json new.json -o oneline=-
A project that wants ci-gate's behavior on every run states depth, view,
and severity in .abicheck.yml instead of retyping a preset.
--view show=...filtering,scope.show_redundant: true,-o oneline=...'s one-line summary format, and--view impact|root-causeare covered in full on Output Formats.--view show=.../show_redundant/--view impact|root-causeare display-only and do not affect the verdict or exit code.
3) Mixed mode: snapshot baseline vs live build¶
# CI baseline snapshot vs current build
abicheck compare baseline-1.0.json ./build/libfoo.so \
--header new=include/foo.h --version new=2.0-dev
# Live old build vs stored new snapshot
abicheck compare ./build-old/libfoo.so new-release.json \
--header old=include/foo.h --version old=1.0-rc1
Writing a snapshot: -o/--output versus shell redirection¶
Both work, and they are for different things.
Use -o/--output whenever a file is the deliverable — a CI step, a
baseline, anything a later step reads back:
It is abicheck-owned output, which buys four things the shell cannot give you:
- Atomic replacement. The destination is written via a temporary file and
os.replace(), so a serialization or write failure leaves the previous file intact and never a truncated one.> foo.jsontruncates the destination before abicheck starts, so a failed run leaves an empty file that the next step happily reads as a snapshot. - A real diagnostic and a stable exit code. An unwritable destination
reports
Cannot write to <path>: …and exits 1; a missing parent directory is created rather than failing. - Native compression.
.json.gz/.json.zstsuffixes (or an explicit--compression) select the storage envelope. Compression requires an output file: stdout is always plain JSON, and asking for a compressed stdout is a usage error rather than a corrupt pipe. - A clean separation of streams that survives either way: machine-readable output on stdout, diagnostics on stderr.
Use redirection or a pipe when a stream really is the point — composing with another process, not producing a file:
Plain stdout is a supported, clean-JSON transport and is tested as such; it is simply not the right tool for "write this file for the next CI step".
Change classification and detection coverage¶
What each verdict (BREAKING/API_BREAK/COMPATIBLE/COMPATIBLE_WITH_RISK/NO_CHANGE)
means is covered in full on Verdicts; the per-case
matrix comparing abicheck, abidiff, and ABICC detection coverage across the
example catalog is the
Tool Comparison & Benchmarks reference.
Dependency-stack commands¶
deps tree/deps compare (full dependency-closure resolution and
cross-sysroot ABI diffing, Linux ELF) and the --follow-deps flag are
covered with worked examples and exit codes on
Migrating to the Current CLI. Packaging
integration — generating/validating/diffing Debian dpkg-gensymbols-style
symbols files — is its own page:
Debian Symbols File Integration.
Architecture and runtime dependencies¶
For the internal pipeline and module map (dumper → checker → resolver → reporters), see the Codebase Overview and the Architecture concept page. For the runtime dependencies (Python 3.11+, castxml, pyelftools, …) and per-platform setup, see Install abicheck.