Skip to content

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.txt
  • app.c
  • v1.c
  • v1.h
  • v2.c
  • v2.h

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