Case 209: Calling Convention Changed via a Macro¶
| Field | Value |
|---|---|
| Verdict | 🔴 BREAKING |
| Category | Breaking |
| Classification | Rule |
| Platforms | Linux |
| Flags | ABI break |
Detected ChangeKinds |
calling_convention_changed |
| Source files | catalog/cases/case209_calling_convention_changed_via_macro/ |
| Rule family | calling-convention-changed-via-macro |
| Subject | Calling convention and integer-model changes |
Category: Function ABI | Verdict: 🔴 BREAKING
Verdict and consumer impact¶
The macro-spelled sibling of
case64_calling_convention_changed.
vector_dot/vector_scale are declared double MATHLIB_CALL vector_dot(...)
in a public header that is textually identical between versions. Only the
included config header changes: MATHLIB_CALL expands to
__attribute__((sysv_abi)) (the System V AMD64 default, spelled explicitly)
in v1 and to __attribute__((ms_abi)) in v2. After preprocessing this is
exactly case64's break — parameters move from rdi/rsi/rdx to
rcx/rdx/r8 — so an unchanged, already-built consumer reads stale
registers and computes garbage. Recompilation against v2 is mandatory.
Its negative control is
case210_calling_convention_respelled_via_macro,
where a macro is introduced but expands to the convention the literal
spelling already had. The pair proves two things about these two fixtures:
a convention change hidden behind a macro is reported, and a pure respelling
of the same convention is not.
Old/new diff¶
| v1_config.h | v2_config.h |
|---|---|
#define MATHLIB_CALL __attribute__((sysv_abi)) |
#define MATHLIB_CALL __attribute__((ms_abi)) |
v1.h and v2.h differ only in which config header they include; both
declare double MATHLIB_CALL vector_dot(const double *a, const double *b, int len);.
abicheck command¶
gcc -shared -fPIC -g v1.c -o libv1.so
gcc -shared -fPIC -g v2.c -o libv2.so
abicheck compare libv1.so libv2.so --header old=v1.h --header new=v2.h
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
calling_convention_changed: Calling-convention attribute changed for vector_dot: (default) → ms_abi
calling_convention_changed: Calling-convention attribute changed for vector_scale: (default) → ms_abi
Verified identically with ABICHECK_AST_FRONTEND=castxml and
ABICHECK_AST_FRONTEND=clang. An explicit sysv_abi is the platform
default on x86-64 Linux and is reported as (default).
Minimum evidence¶
min_evidence: L1 — as in case64, Clang records the convention in DWARF's
DW_AT_calling_convention, so a Clang-built pair is detected from debug info
alone, whatever spelling the source used. GCC emits no such attribute, so a
GCC-built pair needs the public headers (L2) — and there the convention is
only visible after macro expansion.
Why abicheck catches it¶
CastXML 0.7 drops GNU x86-64 conventions (ms_abi/sysv_abi) from its own
attributes output, so abicheck recovers them from the declaration; this
case pins that the recovery sees through the MATHLIB_CALL macro to its
expansion rather than reading only the literal declaration text (which here
is identical on both sides). The clang AST backend records the expanded
attribute natively. Both backends then compare the effective convention per
function.
Runtime failure demonstration¶
Severity: CRITICAL
gcc -shared -fPIC -g v1.c -o libv1.so
gcc -g app.c -L. -lv1 -Wl,-rpath,'$ORIGIN' -o app -lm
./app
# → dot product = 32.0 (expected 32.0)
# → scaled = {2.0, 4.0, 6.0} (expected {2.0, 4.0, 6.0}) exit 0
gcc -shared -fPIC -g v2.c -o libv1.so # swap in v2, no recompile
./app
# → dot product = 0.0 (expected 32.0)
# → scaled = {0.0, 0.0, 0.0} (expected {2.0, 4.0, 6.0})
# → WRONG RESULT: calling convention mismatch — parameters passed in wrong registers! exit 1
Safe redesign¶
Treat a convention macro as part of the ABI: once a release has shipped,
its expansion for a given platform must never change. Introduce a new entry
point (vector_dot_ms()) with the new convention instead, and keep the old
symbol on the old one.
Real-world example: Windows-facing libraries commonly route conventions
through macros such as WINAPI/CALLBACK/APIENTRY or a project-specific
FOO_CALL; redefining such a macro silently re-conventions every function
that uses it.
Cross-tool comparison¶
abidiff sees the change only if the producer emitted
DW_AT_calling_convention (Clang, not GCC); ABICC works from the
preprocessed headers.
Source files¶
CMakeLists.txtapp.cv1.cv1.hv1_config.hv2.cv2.hv2_config.h
See also: Compatibility Catalog · All BREAKING cases · Category: Breaking · Rule: Calling convention changed via a macro · Subject: Calling convention and integer-model changes.