Skip to content

Case 138: DT_NEEDED Added

Field Value
Verdict ๐ŸŸข COMPATIBLE
Category Quality (Compatible)
Platforms Linux
Flags โ€”
Detected ChangeKinds needed_added
Source files examples/case138_needed_added/

Category: Quality | Verdict: ๐ŸŸข COMPATIBLE

Verdict and consumer impact

v2's exported symbols and their types are unchanged, so binaries already linked against libfoo keep working exactly as before. What changes is the library's own runtime dependency closure: v2 is linked with -Wl,--no-as-needed -lm, so it now carries a DT_NEEDED entry for libm.so.6 even though it calls no math function. This is invisible to existing consumers, but it matters for packaging (an extra Requires:), container image size, and any deployment where libm might not be present.

Old/new diff

old/lib.c new/lib.c
int compute(int x) { return x * x + 1; } int compute(int x) { return x * x + 1; }
int transform(int x, int y) { return x + y * 2; } int transform(int x, int y) { return x + y * 2; }
(linked normally) (linked with -Wl,--no-as-needed -lm)

abicheck command

gcc -shared -fPIC -g old/lib.c -o libfoo_v1.so
gcc -shared -fPIC -g new/lib.c -o libfoo_v2.so -Wl,--no-as-needed -lm
abicheck compare libfoo_v1.so libfoo_v2.so

Expected abicheck finding

Verdict: COMPATIBLE (exit 0)

Quality Issues:
- needed_added: New dependency added: libc.so.6
- needed_added: New dependency added: libm.so.6
- symbol_version_required_added_compat: New symbol version requirement:
  GLIBC_2.2.5 (from libc.so.6) โ€” not newer than previous max, backward-compatible

Minimum evidence

min_evidence: L0 โ€” the .dynamic section's DT_NEEDED entries are read straight from the ELF headers; no debug info or public headers needed.

Why abicheck catches it

abicheck parses the .dynamic section of both binaries and diffs the sets of DT_NEEDED entries directly; a name present in v2 but absent from v1 is reported as needed_added regardless of whether any symbol from that dependency is actually called.

Runtime failure demonstration

Severity: INFORMATIONAL โ€” no observable effect on existing binaries.

# Build old library + app
gcc -shared -fPIC -g old/lib.c -o libfoo.so
gcc -g app.c -L. -lfoo -Wl,-rpath,. -o app
./app
# โ†’ compute(7) = 50
# โ†’ transform(3, 4) = 11

# Swap in new library (no recompile)
gcc -shared -fPIC -g new/lib.c -o libfoo.so -Wl,--no-as-needed -lm
./app
# โ†’ compute(7) = 50
# โ†’ transform(3, 4) = 11   (identical โ€” libm is present on this host)

The added DT_NEEDED only causes a failure on a system where libm.so.6 is unavailable at load time (error while loading shared libraries); on any host with a normal glibc/libm install, behavior is identical.

Safe redesign

Link with --as-needed (the default on most modern toolchains) so only libraries whose symbols are actually referenced become DT_NEEDED. If a dependency is intentionally required for future use, add it deliberately and document it rather than picking it up as a side effect of link-flag ordering.

Real-world example: distro packaging tools (e.g. rpmbuild's auto-Requires generator) derive a package's runtime dependencies from DT_NEEDED entries โ€” a stray added DT_NEEDED silently widens the package's dependency closure and its container/image footprint.

Cross-tool comparison

readelf -dW libfoo_v1.so | grep NEEDED
readelf -dW libfoo_v2.so | grep NEEDED   # adds libm.so.6, libc.so.6

Source files

  • CMakeLists.txt
  • app.c

See also: Examples overview ยท All COMPATIBLE cases ยท Category: Quality (Compatible).