Skip to content

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:

  1. version type changed int to long โ€” on LP64, int is 4 bytes and long is 8 bytes. The field is now twice as wide, pushing every subsequent field to a different offset.
  2. sequence removed โ€” old binaries still read/write at offset 4 expecting sequence, but v2 has payload_size there (or padding from the widened version).
  3. payload_size offset shifted โ€” was at offset 8 in v1, now at a different offset in v2 due to the type change and removal above.
  4. flags bitfield width 4 to 8 โ€” changes how bits are packed within the storage unit. Old code masks to 4 bits; new code uses 8 bits.
  5. priority added โ€” appending a field to a struct is compatible only if no one relies on sizeof(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.