Skip to content

Limitations & Known Boundaries

abicheck is designed to catch real ABI and API breaks with high accuracy, but has specific limitations you should understand before relying on it in production.


Platform support matrix

Platform Binary format Binary metadata Header AST (castxml) Debug info cross-check
Linux ELF (.so) Yes (pyelftools) Yes (GCC, Clang) Yes (DWARF)
Windows PE/COFF (.dll) Yes (pefile) Yes (MSVC, MinGW) Yes (PDB)
macOS Mach-O (.dylib) Yes (macholib) Yes (Clang, GCC) Yes (DWARF)

Header AST analysis (via castxml) is available on all platforms. castxml is maintained by Kitware and available via conda-forge, Homebrew, apt, or direct download.

Debug info cross-check uses DWARF (Linux and macOS) and PDB (Windows). PDB support extracts struct/class/union layouts, enum types, calling conventions, and toolchain info from PDB files produced by MSVC (/Zi flag). Use --pdb-path to specify the PDB file location if automatic discovery fails.


Windows toolchain distinction

Windows support depends on the compiler/toolchain used for headers and binary production:

Toolchain Status Notes
MinGW (GCC) Experimental Covered by current CI smoke/integration jobs.
MSVC (cl.exe) Untested in CI Expected to work in many cases, but not yet validated end-to-end in project CI.

Tracked ABICC compatibility issues for this area: #9, #50, #56, #121. For detailed matrix + per-issue notes, see Platform Support.

Header / Binary Mismatch Risk

The most important limitation. abicheck uses castxml to parse headers and compares the result against the compiled .so. If the headers passed to analysis don't exactly match what was compiled, results will be unreliable.

This happens when: - You pass generic system headers but the library was compiled with custom #define flags - Preprocessor macros change the public API surface (#ifdef FEATURE_X) - Third-party dependency headers differ between versions - Platform-specific code paths (#ifdef __linux__) differ between compile and analysis environments

Mitigation: - Always use the exact same headers that were used to build the .so - Pass compile-time defines to castxml: abicheck dump libfoo.so -H foo.h --castxml-arg=-DFEATURE_X - For abicheck compat, use -s (strict mode) to promote COMPATIBLE/API_BREAK to BREAKING: abicheck compat check -lib foo -old OLD.xml -new NEW.xml -s (use --strict-mode api to promote only API_BREAK; -s is not available on abicheck compare) - Cross-check with abicheck compat check (ABICC mode) for independent validation


Stripped Production Binaries

Tiers 3 and 4 (DWARF layout + Advanced DWARF) require debug symbols (-g). Production .so files are typically stripped — in this case:

  • Struct field offset changes may be missed (Tier 3 unavailable)
  • Calling convention drift, struct packing changes not detected (Tier 4 unavailable)
  • Tier 1 (castxml/headers) and Tier 2 (ELF symbols) still run — most critical breaks caught

Mitigation: Use --debug-root to point abicheck at separate debug files (distro debuginfo packages, build-id trees, or dSYM bundles). abicheck automatically searches for debug artifacts via a resolver chain. For Fedora/RHEL, use --debuginfod to fetch debug info by build-id from debuginfod servers. See the CLI usage guide for details. For production binaries without debug info, Tier 1+2 analysis covers the majority of real-world ABI breaks.


Template Instantiation

C++ template instantiations with complex type parameters can produce unexpected results: - Explicit instantiations in .so are analyzed; implicit instantiations in headers are not - Template specializations may not all be captured - case17_template_abi in the examples demonstrates a detectable case

Mitigation: Use explicit template instantiation (template class Foo<int>;) for ABI-sensitive types you want to guarantee are tracked.


COMPATIBLE Does Not Mean "Invisible"

COMPATIBLE changes are detected and reported — they are not silent. Examples: - Adding a new export symbol is COMPATIBLE but grows the library's API surface (relevant for semver policy: additive changes may still require a minor version bump) - Enum member addition is COMPATIBLE but can affect exhaustive switch statements

For abicheck compat pipelines, use -s to treat COMPATIBLE as blocking. For abicheck compare pipelines, enforce via CI exit code logic (treat exit 2 as failure).


compat Mode Verdict Limitations

abicheck compat does emit exit code 2 for API_BREAK conditions, but the report text uses ABICC-style phrasing rather than a bare API_BREAK verdict string. Source-level-only breaks (e.g. case31_enum_rename, case34_access_level) will appear as warnings in the compat HTML/text report.

Use abicheck compare --format json for precise machine-readable API_BREAK verdicts.


Inline / Header-Only Code

Functions defined entirely in headers (inline, constexpr, template) may not appear in the .so symbol table. abicheck analyzes the public exported ABI — header-only changes that don't affect exported symbols will not be detected.


Dependency Limitations & Known Bugs

Known issues in third-party dependencies that affect abicheck behavior.

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.


Troubleshooting

See troubleshooting.md for a diagnostic decision tree covering common false positives, false negatives, and unexpected verdicts.


ELF-Only Mode and Symbol Filtering

When abicheck compare (or abicheck dump) is run without header files — i.e. directly against .so binaries — the tool operates in ELF-only mode. In this mode the public ABI surface is inferred entirely from exported ELF symbols (.dynsym), with no source-level type information available.

Why false positives can occur in ELF-only mode

Shared libraries often contain exported symbols that are not part of their intended public ABI:

Symbol category Example Root cause
GCC / compiler internals ix86_tune_indices, _ZGVbN2v_sin Statically-linked compiler runtime (libgcc, SVML) leaks symbols into .dynsym
Transitive C++ stdlib symbols _ZNSt6thread8_M_startEv, _ZTISt9exception Weak-linked libstdc++ / libc++ symbols that appear in .dynsym
Private C namespace separators H5C__flush_marked_entries, MPI__send Internal LibPrefix__FunctionName naming convention — globally visible but not public API

Comparing two versions of a library that differ in which compiler or stdlib they were built against can trigger hundreds of spurious BREAKING findings (e.g. mpfr 4.2.0→4.2.1 reported 91 false-positive breaks caused by ix86_* symbols).

How abicheck filters these symbols

abicheck applies an ABI-relevance filter (_is_abi_relevant_symbol) when parsing .dynsym in ELF-only mode. Symbols are excluded when they match any of the following:

GCC / compiler-internal prefixes (ix86_, x86_64_, __cpu_model, __cpu_features, _ZGV*, __svml_*, __libm_sse2_*, __libm_avx_*)

C++ standard-library prefixes (_ZNSt, _ZNKSt, _ZNSt3__1, _ZdlPv, _ZnwSt, _ZnaSt, _ZdaPv, _ZTVN10__cxxabiv, _ZTI, _ZTS, _ZSt)

Private C double-underscore separator — any non-C++-mangled symbol (i.e. not starting with _Z) whose name contains __ after the first two characters. This matches patterns like H5C__flush or MPI__send while leaving system symbols (which start with __ or _[A-Z]) unaffected.

Limitations of the filter

  • The filter is heuristic. A library that intentionally exports a symbol matching one of the filtered prefixes (unlikely but possible) will have it silently ignored.
  • Non-standard SIMD / math libraries with different naming conventions are not covered; open an issue if you encounter new patterns causing false positives.
  • In header mode (when headers are supplied), this filter is not applied — castxml provides accurate type information and the ELF surface is used only for visibility decisions, not for inferring the API surface.

Mitigation for header mode

For the most accurate results, always supply public headers:

abicheck compare old.so new.so -H include/foo.h

This eliminates ELF-only mode entirely and removes the need for heuristic filtering.