Troubleshooting¶
Use this page when a run fails to start (setup/environment) or when results look surprising (false positive, false negative, or unexpected verdict).
0) Setup & environment failures¶
"castxml not found in PATH"¶
Header AST analysis requires castxml. pip install abicheck does not install it,
so any command that passes headers (--header / -H) fails with
this error until castxml is on your PATH.
# conda (any OS) — bundles castxml + compiler automatically
conda install -c conda-forge abicheck
# macOS
brew install castxml
# Windows (PowerShell, admin)
choco install castxml
On Ubuntu CI, prefer a checksum-pinned
CastXML Superbuild
over apt install castxml. Ubuntu 24.04 currently packages CastXML with bundled
Clang 17, which is too old for some GCC 13 libstdc++ headers. The abicheck
GitHub Action installs the pinned v2026.01.30 Superbuild automatically.
No castxml and can't install it? Run binary-only mode by omitting the header flags — abicheck falls back to DWARF/symbols analysis (weaker, but catches symbol- and layout-level breaks):
"command not found: abicheck" or wrong tool runs¶
Some distros ship unrelated tools with similar names (abi-compliance-checker
wrappers in Debian devscripts, or abicheck in Fedora's libabigail-tools).
Confirm you're running this project:
If a different tool shadows it, invoke via the module form: python -m abicheck.
Header parsing fails or finds nothing¶
If castxml runs but reports parse errors or an empty surface, the inputs usually
don't match the build environment of the analyzed .so:
- Pass the same include dirs the library was built with:
-I include/ -I deps/include/. - Pass the same preprocessor macros with
-D/--define(repeatable, and applied to both sides of acompare):-DFEATURE_X=1 -DNDEBUG. For a stable project/CI contract, record them in.abicheck.ymlinstead — see Headers that require a macro. - Best option: feed the real build flags from
compile_commands.jsonwith-p build/(see CLI Usage → Build-context capture). - For pure C libraries, set
compile.lang: cin.abicheck.yml(the default isc++).
Headers that require a macro before inclusion¶
Some libraries gate an opt-in public surface — or refuse to compile at all — behind a feature macro:
#ifndef PVXS_ENABLE_EXPERT_API
#error Define PVXS_ENABLE_EXPERT_API before including this header
#endif
Without the macro those declarations are absent, not merely undetailed, so a
signature break inside that surface reads as compatible. Two supported ways to
supply it, for two different situations:
# One-off run, experiment, or an integration that builds the compile context
# at invocation time. Repeatable; applies to both sides of a compare.
abicheck dump libpvxs.so -H include/ -DPVXS_ENABLE_EXPERT_API -o pvxs.json
abicheck dump libpcre2.so -H include/ -DPCRE2_CODE_UNIT_WIDTH=8 -o pcre2.json
# .abicheck.yml -- preferred for stable CI and baseline generation: the macro
# set is part of the project's reviewed contract, not a per-run choice.
compile:
defines:
- PVXS_ENABLE_EXPERT_API
- PCRE2_CODE_UNIT_WIDTH=8
The two compose: a CLI -D overrides the config entry for that macro name
only, leaving every other compile.defines entry in force.
-D/--define takes a macro definition (NAME or NAME=VALUE), not a
compiler flag. A value containing whitespace, a function-like definition
(F(x)=...), and anything that is not a bare macro name are rejected with a
message naming compile.options, which is where general compiler flags live.
Quoting is the shell's job and abicheck never re-splits the operand, so a
string value is written -DNAME=\"text\" (the quotes reach the compiler); a
value with a space in it has no portable spelling and belongs in
compile.options.
Because the macro set changes what was extracted, it is part of extraction
identity: comparing a snapshot dumped without the macro against one dumped with
it is refused as profile_mismatch, not silently diffed. Re-dump both sides
under the same macro context.
castxml aborts in system headers (_Float32, __assume__)¶
castxml drives an internal Clang while emulating your host GCC. If that bundled Clang is older than your host gcc/glibc, parsing your library's headers can fail inside the system headers — before abicheck compares anything — with errors like:
unknown type name '_Float32'(also_Float64/_Float128) — glibc's sized-float types, understood by Clang ≥ 16.- a parse failure on the GCC 13+ libstdc++
__assume__attribute — understood by Clang ≥ 18.
The fix is a castxml built against a newer Clang — the recommended floor is
bundled Clang ≥ 18. Ubuntu 24.04's castxml package currently bundles Clang
17 and is therefore not suitable for GCC 13 C++ header analysis. Use either the
conda-forge package or a release- and checksum-pinned official Superbuild:
conda install -c conda-forge castxml
# Reproducible CI: download the matching asset from this pinned release,
# verify its published SHA256, extract it, then prepend its bin/ to PATH.
# https://github.com/CastXML/CastXMLSuperbuild/releases/tag/v2026.01.30
The pinned abicheck Linux CI release bundles Clang 21.1.8. Always inspect the
full castxml --version output: the CastXML version alone does not identify the
bundled Clang frontend.
abicheck detects this case and appends your detected castxml --version plus the
recommended floor to the error. As an alternative, point abicheck at a
clang-parsable toolchain/sysroot with --compiler / --sysroot. A
#ifdef __cplusplus extern "C" C header that fails only under --lang c should
be scanned without --lang c (castxml always parses in a C++-aware mode).
"CastXML \<version> ... is not a supported default scanner setup"¶
An authoritative L2 scan runs a version gate (castxml_policy.py) before
parsing any header: it rejects a resolved castxml build outside the
supported range (currently >=0.6.11,<0.8.0, bundled/linked Clang >=18).
This most commonly fires against the legacy PyPI castxml distribution
(pip install castxml), which is not a supported default scanner setup —
pip install abicheck deliberately never installs CastXML for you, and the
PyPI castxml package's own release line predates this floor.
Fix: install a supported CastXML from conda-forge (recommended) or a pinned Superbuild release, same as the two sections above:
Only for deliberate legacy reproduction (never as a normal workflow), the gate
can be overridden explicitly with ABICHECK_ALLOW_UNSUPPORTED_CASTXML=1. The
resulting snapshot records ast_toolchain_supported: false and the specific
ast_toolchain_unsupported_reasons, so it is never silently indistinguishable
from a normal, policy-compliant scan — treat it as a degraded, non-baseline
result.
castxml: __has_cpp_attribute not defined on macOS (Xcode 16.4+)¶
Status: Open — upstream castxml issue to be filed.
Affected platforms: macOS with Xcode 16.4+ (Apple Clang headers).
Symptom: When castxml processes a C header that includes <stddef.h>, the
macOS SDK resolves this through the libc++ __config header, which uses the
__has_cpp_attribute preprocessor macro. castxml does not define this macro,
causing parse failures:
.../MacOSX.sdk/usr/include/c++/v1/__config:1009:7: error:
function-like macro '__has_cpp_attribute' is not defined
Multiple lines in __config trigger the same error wherever
__has_cpp_attribute(...) appears in #if / #elif directives.
Root cause: Per the C++ standard, __has_cpp_attribute should be a built-in
macro that evaluates to 0 for unknown attributes. castxml's internal
preprocessor does not predefine it, so the preprocessor treats the bare
identifier as an error rather than defaulting to 0.
Workaround: In castxml-specific shim headers (not general project headers),
replace #include <stddef.h> with typedef __SIZE_TYPE__ size_t; to avoid the
libc++ header chain entirely. __SIZE_TYPE__ is a GCC/Clang built-in that
castxml supports.
Caution: This typedef only supplies
size_t— other<stddef.h>definitions (NULL,ptrdiff_t,offsetof,max_align_t) are not available. Do not use this substitution in normal build headers as it will break compilation that depends on those definitions. Safer alternatives: create an isolated shim header used only by castxml invocations, or provide a minimal custom header that supplies all needed type definitions.
1) "Why did I get API_BREAK/BREAKING unexpectedly?"¶
Check header/binary mismatch first¶
- Are these the exact headers used to build the analyzed
.so? - Are required
-Dmacros the same as build time? - Is include search path the same as build environment?
If not, fix input parity and rerun.
2) "Why is verdict COMPATIBLE, but I expected NO_CHANGE?"¶
COMPATIBLE means real differences exist (new symbols, policy changes) but no binary break.
Run JSON output for detail:
abicheck compare old.json new.json -o json=result.json
python3 -c "import json; r=json.load(open('result.json')); print(r['verdict']); print(len(r['changes']))"
"old and new snapshots keep dependency declarations differently" / "were narrowed by different frontend prefilters"¶
Two refusals from the extraction-scope check. Each side records the rules its declarations were classified under;
a pair that recorded a different scope.dependency_evidence, or a
different frontend prefilter, did not keep the same declarations, so a
dependency declaration present on one side only would read as an addition
or a removal. Re-dump both sides under the same .abicheck.yml. The same
refusal names an unrecorded side (a pre-v52 snapshot) when the other side
was narrowed, since nothing proves what the old one kept.
Differing ownership rules alone are not refused while both sides keep
everything (dependency_evidence: full): the comparison runs and the report
lists the declarations that changed owner or contract. See
Target ownership.
3) "Why are deep type changes not detected?"¶
Check if the binary has DWARF debug info:
# Check for embedded DWARF sections
readelf -S libfoo.so | grep -E "\.debug_info|\.zdebug_info" || echo "No DWARF sections"
# Check for externally linked split-debug files
readelf --debug-dump=links libfoo.so # shows .gnu_debuglink / .gnu_debugaltlink references
readelf --debug-dump=follow-links libfoo.so # follows the link and inspects linked debug-info
Without DWARF, the layout-level checks that depend on debug info (L1) are
limited — abicheck falls back toward symbol-only (L0) analysis. Use debug
builds (-g) for deeper analysis.
If the binary uses split debug (separate .debug file), the linked debug info is still
analysed automatically when --debug-dump=follow-links can resolve the path.
4) CI script says success but report shows changes¶
Remember: compare exit code 0 includes both NO_CHANGE and COMPATIBLE.
If you need exact policy, parse JSON verdict instead of checking $? == 0.
5) Still unsure?¶
Open an issue with:
- command line used
- tool version (abicheck --version)
- minimal header + .so pair
- JSON output (-o json=-)