Case 64: Calling Convention Changed¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux |
| Flags | ABI break |
Detected ChangeKinds |
calling_convention_changed |
| Source files | examples/case64_calling_convention_changed/ |
Category: Function ABI | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
vector_dot/vector_scale switch from the default System V AMD64 calling
convention to __attribute__((ms_abi)) (Microsoft x64 convention), which
puts parameters in different registers:
| System V ABI (v1) | Microsoft x64 ABI (v2) | |
|---|---|---|
| Integer args | rdi, rsi, rdx, rcx, r8, r9 |
rcx, rdx, r8, r9 |
| Float args | xmm0โxmm7 |
xmm0โxmm3 |
| Callee-saved | rbx, rbp, r12โr15 |
rbx, rbp, rdi, rsi, r12โr15 |
A caller compiled against v1 passes a in rdi, b in rsi, len in
edx. The v2 function reads a from rcx, b from rdx, len from
r8d โ whatever stale values those registers happen to hold. Recompilation
against v2 is mandatory.
Old/new diff¶
/* v1: default System V AMD64 calling convention */
double vector_dot(const double *a, const double *b, int len);
/* v2: Microsoft x64 calling convention โ different register assignment */
__attribute__((ms_abi))
double vector_dot(const double *a, const double *b, int len);
abicheck command¶
clang -shared -fPIC -g v1.c -o libfoo_v1.so
clang -shared -fPIC -g v2.c -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- calling_convention_changed: Calling convention changed: vector_dot (normal -> unknown(0xc1))
> Function calling convention changed; registers/stack usage differs, call crashes.
- calling_convention_changed: Calling convention changed: vector_scale (normal -> unknown(0xc1))
> Function calling convention changed; registers/stack usage differs, call crashes.
Minimum evidence¶
min_evidence: L1 โ DWARF's DW_AT_calling_convention attribute on the
subprogram DIE records the convention directly, so debug info alone (no
public headers) is enough. Toolchain note: GCC does not emit
DW_AT_calling_convention for __attribute__((ms_abi)) โ compiling both
sides with GCC produces NO_CHANGE (a real false negative, not a fixture
bug); Clang does emit the attribute and abicheck reports BREAKING as
expected. The command above builds with Clang for exactly that reason.
Why abicheck catches it¶
abicheck reads each function's DW_AT_calling_convention value from DWARF
and compares it between versions; ms_abi and the default System V
convention encode to different DWARF constants, so the change is visible
without any header/AST evidence โ when the compiler emits the attribute at
all (see the toolchain note above).
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: compile app against v1, swap in v2 .so without recompile.
(GCC is used here since the calling-convention mismatch itself is a runtime
register-passing issue, independent of which compiler emits the DWARF
attribute abicheck reads.)
# Build old library + app
gcc -shared -fPIC -g v1.c -o libfoo.so
gcc -g app.c -L. -lfoo -Wl,-rpath,. -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})
# Swap in new library (no recompile)
gcc -shared -fPIC -g v2.c -o libfoo.so
./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!
Why CRITICAL: the v2 function expects pointer parameters in rcx/rdx
(ms_abi) but receives them in rdi/rsi (System V) โ it reads from
registers holding stale (typically zero) values, producing garbage results.
With different register contents this could also segfault if a stale value
is dereferenced as a pointer.
Safe redesign¶
Never change the calling convention of an exported function. If a new
convention is needed, add a new symbol (vector_dot_fast()), keep the old
entry point as a thin forwarding wrapper, or ship a separate library with a
migration guide.
Real-world example: the Windows ecosystem has dealt with this
extensively โ __stdcall vs __cdecl vs __fastcall vs __vectorcall
have caused countless DLL compatibility issues, which is why the Win32 API
froze on __stdcall specifically. Wine must carefully match calling
conventions between Linux System V and Windows ms_abi for every thunked
function โ getting even one wrong causes the exact crash demonstrated here.
Cross-tool comparison¶
References¶
- System V AMD64 ABI โ Register usage
- Microsoft x64 calling convention
- DWARF5 ยง5.7.1 โ DW_AT_calling_convention
- GCC โ x86 Function Attributes (ms_abi)
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.