Case 40: Field Layout Changes¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break, API break |
Detected ChangeKinds |
โ |
| Source files | browse on GitHub |
Category: Struct Field Layout | Verdict: BREAKING
What changes¶
| Version | Definition |
|---|---|
| v1 | struct Packet { int version; int sequence; int payload_size; unsigned flags:4; } |
| v2 | struct Packet { long version; /* sequence removed */ int payload_size; unsigned flags:8; int priority; } |
Why this is a binary ABI break¶
Every field-level change here corrupts the struct layout that old binaries were compiled against:
versiontype changedinttolongโ on LP64,intis 4 bytes andlongis 8 bytes. The field is now twice as wide, pushing every subsequent field to a different offset.sequenceremoved โ old binaries still read/write at offset 4 expectingsequence, but v2 haspayload_sizethere (or padding from the widenedversion).payload_sizeoffset shifted โ was at offset 8 in v1, now at a different offset in v2 due to the type change and removal above.flagsbitfield width 4 to 8 โ changes how bits are packed within the storage unit. Old code masks to 4 bits; new code uses 8 bits.priorityadded โ appending a field to a struct is compatible only if no one relies onsizeof(struct Packet)for allocation or array indexing. But combined with the other changes, the struct is completely different.
Code diff¶
struct Packet {
- int version;
+ long version;
- int sequence;
+ /* sequence REMOVED */
int payload_size;
- unsigned flags : 4;
+ unsigned flags : 8;
+ int priority;
};
Real Failure Demo¶
Severity: CRITICAL
Scenario: compile app against v1, swap in v2 .so without recompile.
# Build v1 lib + app
gcc -shared -fPIC -g v1.c -o libfoo.so
gcc -g app.c -I. -L. -lfoo -Wl,-rpath,. -o app
./app
# โ sizeof(Packet) = 16
# โ version = 1
# โ sequence = 42
# โ payload_size = 1024
# โ flags = 15
# โ packet_send = 1
# Swap to v2 (no recompile of app)
gcc -shared -fPIC -g v2.c -o libfoo.so
./app
# โ packet_send returns wrong value!
# The app passes a v1-layout Packet (16 bytes) but the library
# reads it as a v2-layout Packet (24+ bytes). The library reads
# pkt->version as a long starting at offset 0, picking up both
# the old version and sequence fields as one 8-byte value.
# Result: corrupted data, wrong return value.
Why CRITICAL: Struct layout is baked into every compilation unit. When the library and the application disagree on field offsets and sizes, every field access reads garbage. There is no runtime error โ just silently wrong data, which is the most dangerous class of ABI break.
Reproduce manually¶
gcc -shared -fPIC -g v1.c -o libv1.so
gcc -shared -fPIC -g v2.c -o libv2.so
abidw --out-file v1.xml libv1.so
abidw --out-file v2.xml libv2.so
abidiff v1.xml v2.xml
echo "exit: $?" # โ 12 (ABI change + breaking)
How to fix¶
Never change field types, remove fields, or reorder fields in a public struct.
Use opaque pointers (struct Packet *) with accessor functions to allow internal layout
evolution. If layout must change, bump the SONAME.
Runtime note¶
This case now checks checksum mismatch to make field-layout reinterpretation observable at runtime (behavioral break, not guaranteed crash).
References¶
Source files¶
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.