Case 127: Exported Data Object Size Changed¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux |
| Flags | ABI break, Bad practice |
Detected ChangeKinds |
symbol_size_changed |
| Source files | examples/case127_data_object_size_changed/ |
Category: Symbol / Data Layout | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
The library exports a global data object, config_table. In v1 it is
int[16] (64 bytes); in v2 it grows to int[32] (128 bytes) โ the exported
symbol's st_size changes. When an executable references an exported data
object, the static linker emits a copy relocation sized for the old
definition: the executable reserves 64 bytes in its own BSS, and the dynamic
loader copies the library's initial value into that fixed-size slot at
startup. After upgrading to v2, any library code that indexes
config_table[16..31] reads or writes past the consumer's 64-byte copy โ
silent out-of-bounds memory access, without recompilation.
Old/new diff¶
| v1.h | v2.h |
|---|---|
#define CONFIG_SLOTS 16 |
#define CONFIG_SLOTS 32 |
extern int config_table[CONFIG_SLOTS]; (64 bytes) |
extern int config_table[CONFIG_SLOTS]; (128 bytes) |
abicheck command¶
gcc -shared -fPIC -g v1.c -o libcfg_v1.so
gcc -shared -fPIC -g v2.c -o libcfg_v2.so
abicheck compare libcfg_v1.so libcfg_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- symbol_size_changed: Symbol size changed: config_table (64 -> 128 bytes)
> ELF symbol size changed; copy relocations or memcpy-based consumers
get truncated/oversized data.
Minimum evidence¶
min_evidence: L0 โ the object's size lives directly in the ELF symbol
table's st_size field; no debug info or headers are required to detect it.
Why abicheck catches it¶
abicheck diffs the exported-symbol tables of both binaries directly,
including each data symbol's st_size โ the same field the static linker
itself reads to size a copy relocation. A changed size is reported
regardless of whether DWARF or headers are available.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: app compiled against v1 gets a 64-byte copy relocation for
config_table; swap in v2 without recompiling.
# Build old library + app
gcc -shared -fPIC -g v1.c -o libcfg.so
gcc -g app.c -I. -L. -lcfg -Wl,-rpath,. -o app
./app
# -> config_table[15] = 99
# -> exit: 0
# Swap in new library (no recompile)
gcc -shared -fPIC -g v2.c -o libcfg.so
./app
# -> ./app: Symbol `config_table' has different size in shared object,
# consider re-linking
# -> config_table[15] = 99
# -> exit: 0
Why CRITICAL: glibc's dynamic linker itself detects and warns about the
symbol-size regression on load ("different size... consider re-linking") โ
real, observable evidence of the ABI break โ but does not refuse to run: it
keeps the app's old 64-byte copy relocation. This particular access
(config_table[15]) still falls inside the old 64-byte reservation, so it
doesn't crash. Any access the new library considers valid but the old
copy relocation doesn't cover โ index 16 through 31, which v2's own
config_get() bounds check (0 <= index < 32) happily allows โ reads or
writes past the consumer's fixed-size copy: silent out-of-bounds corruption
with no further warning, exactly the failure mode the runtime linker's
one-time notice is warning about.
Safe redesign¶
Do not change the size of an exported data object within a SONAME; bump the
SONAME (major version) if the layout must change. Better: never export
mutable global arrays at all โ expose accessor functions
(config_get/config_set) and keep the storage private, so its size is an
implementation detail no consumer copy-relocates.
Real-world example: this is the exact class of regression the glibc testsuite guards with its static-variable-size checks, and that Apple's dynamic-library guidelines classify as a major change ("changing a symbol's size").
Cross-tool comparison¶
abidiff also reads st_size from the ELF symbol table the same way
abicheck does, so it would catch this pure L0 size change too โ this is the
kind of break every ABI-diffing tool is built to catch, not one that
distinguishes them.
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.