Skip to content

Case 63: Bitfield Width Changed

Field Value
Verdict ๐Ÿ”ด BREAKING
Category Breaking
Platforms Linux
Flags ABI break, API break
Detected ChangeKinds field_bitfield_changed
Source files examples/case63_bitfield_changed/

Category: Type Layout | Verdict: ๐Ÿ”ด BREAKING

Verdict and consumer impact

The mode bitfield in RegMap widens from 3 bits to 5 bits. sizeof(RegMap) stays 4 bytes โ€” there's no alignment padding between bitfields sharing a storage unit โ€” but every subsequent bitfield (channel, priority, reserved) shifts to different bit positions. A consumer compiled against v1 reads priority from the old bit range and gets a value from the middle of an unrelated field: no crash, no diagnostic, just a silently wrong number. Recompilation against v2 is mandatory.

Old/new diff

Field v1 (bits) v2 (bits) Change
enable 0 0 unchanged
mode 1-3 (3 bits) 1-5 (5 bits) widened +2 bits
channel 4-7 6-9 shifted +2
priority 8-15 10-17 shifted +2
reserved 16-31 (16 bits) 18-31 (14 bits) shrunk -2 bits

abicheck command

gcc -shared -fPIC -g v1.c -o libfoo_v1.so
gcc -shared -fPIC -g v2.c -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so

Expected abicheck finding

Verdict: BREAKING (exit 4)

- type_field_offset_changed: Field offset changed: RegMap::channel (4 -> 6 bits)
  > Old code reads/writes fields at stale offsets; silent data corruption.
- type_field_offset_changed: Field offset changed: RegMap::priority (8 -> 10 bits)
- type_field_offset_changed: Field offset changed: RegMap::reserved (16 -> 18 bits)
- field_bitfield_changed: Bitfield layout changed: RegMap::mode (bits=3 -> bits=5)
  > Bit-field width or offset changed; old code reads/writes wrong bits.
- field_bitfield_changed: Bitfield layout changed: RegMap::reserved (bits=16 -> bits=14)

Minimum evidence

min_evidence: L1 โ€” DWARF's DW_AT_bit_size/DW_AT_bit_offset (or DW_AT_data_bit_offset) attributes on each bitfield member record the exact bit layout for both versions; no public headers needed.

Why abicheck catches it

abicheck compares each struct member's bit width and bit offset from DWARF directly. Because sizeof(RegMap) doesn't change and the member names and declared types (uint32_t) are identical, only the bit-level DWARF attributes distinguish v1 from v2 โ€” a check that requires debug info, since plain symbol-table or type-name comparison would report no change at all.

Runtime failure demonstration

Severity: CRITICAL

Scenario: compile app against v1, swap in v2 .so without recompile.

# Build old library + app
gcc -shared -fPIC -g v1.c -o libfoo.so
gcc -g app.c -L. -lfoo -Wl,-rpath,. -o app
./app
# โ†’ mode     = 2 (expected 2)
# โ†’ channel  = 5 (expected 5)
# โ†’ priority = 128 (expected 128)
# โ†’ raw word = 0x00008055

# Swap in new library (no recompile)
gcc -shared -fPIC -g v2.c -o libfoo.so
./app
# โ†’ mode     = 2 (expected 2)
# โ†’ channel  = 4 (expected 5)
# โ†’ priority = 1 (expected 128)
# โ†’ raw word = 0x00020145
# โ†’ CORRUPTION: bitfield layout mismatch โ€” app reads v1 bit positions but
#    library wrote v2 bit positions!

Why CRITICAL: v2's regmap_init() writes priority=128 into bits 10-17, but the app (compiled against v1) reads bits 8-15 โ€” extracting a completely different value. No crash occurs; the data is silently wrong. In a hardware register context, this could program the wrong DMA priority, causing system instability.

Safe redesign

Never change bitfield widths in a public struct. If more bits are needed, consume them from an existing reserved field instead of widening a field that shifts everything after it, ship a new struct version (RegMapV2), or hide the register layout entirely behind accessor functions.

Real-world example: the Linux kernel's iphdr (IP header) structure uses bitfields for version and IHL โ€” any width change there would corrupt every packet parsed by userspace tools like tcpdump that embed the struct layout at compile time. The Windows BITMAPINFOHEADER uses bitfields for color masks; driver compatibility across Windows versions depends on those widths staying frozen.

Cross-tool comparison

abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml

References


Source files

  • CMakeLists.txt
  • app.c
  • v1.c
  • v1.h
  • v2.c
  • v2.h

See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.