Skip to content

Case 137: DT_RUNPATH Changed

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

Category: Quality | Verdict: ๐ŸŸข COMPATIBLE

Verdict and consumer impact

Both libraries export identical symbols with identical signatures โ€” no recompilation is ever required. v2 is linked with an embedded -rpath (-Wl,-rpath,/opt/vendor/lib -Wl,--enable-new-dtags), which the linker records as a DT_RUNPATH dynamic entry; v1 has none. The functional ABI is unchanged, but the runtime library search path the dynamic loader uses to resolve this library's own dependencies has changed โ€” that can silently alter which build of a transitive dependency gets picked up on a different host, so it's worth surfacing without treating it as an ABI break.

Old/new diff

old/lib.c new/lib.c
linked with no -rpath (DT_RUNPATH absent) linked with -Wl,-rpath,/opt/vendor/lib -Wl,--enable-new-dtags
(library source is identical โ€” see old/lib.c / new/lib.c)

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,-rpath,/opt/vendor/lib -Wl,--enable-new-dtags
abicheck compare libfoo_v1.so libfoo_v2.so

Expected abicheck finding

Verdict: COMPATIBLE (exit 0)

- runpath_changed: RUNPATH changed: '' -> '/opt/vendor/lib'

Minimum evidence

min_evidence: L0 โ€” DT_RUNPATH is a dynamic-section tag read directly from the ELF .dynamic section; no debug info or headers are needed to see the change.

Why abicheck catches it

abicheck reads each library's DT_RUNPATH/DT_RPATH entry from the ELF dynamic section and diffs the string value (including the empty-string "absent" case) between versions โ€” a pure L0 ELF fact, unrelated to the exported symbol surface.

Runtime failure demonstration

No observable effect on existing binaries โ€” the exported symbols and their behavior are byte-identical between versions:

readelf -dW libfoo_v1.so | grep RUNPATH || echo "v1: none"
# โ†’ v1: none

readelf -dW libfoo_v2.so | grep RUNPATH
# โ†’ Library runpath: [/opt/vendor/lib]

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

gcc -shared -fPIC -g new/lib.c -o libfoo.so -Wl,-rpath,/opt/vendor/lib -Wl,--enable-new-dtags
./app
# โ†’ compute(7) = 50
# โ†’ transform(3, 4) = 11   โ† identical

compute()/transform() compute the same results with either .so loaded; the change only affects which paths the loader searches when resolving this library's own transitive dependencies.

Safe redesign

Avoid baking absolute RUNPATH/RPATH values into distributed shared objects; prefer $ORIGIN-relative paths (see case52) or leave dependency resolution to system search paths / loader configuration (/etc/ld.so.conf). If an embedded RUNPATH is genuinely required for a vendored deployment, document it in release notes so packagers and downstream integrators know to check it against their environment.

References


Source files

  • CMakeLists.txt
  • app.c

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