Chained Shared Library Dependencies
TLPI Chapter 41 ยท Section 41.10 โ When a shared library depends on another shared library
๐
lib โ lib
๐
Nonstandard Dirs
๐
ldd / objdump
Key Concepts
Transitive dependencies libx1 depends on libx2 rpath in shared lib -L option ldd objdump -p Link-time vs runtime
The Problem: Library Depending on Another Library
A common real-world scenario: you have a shared library libx1.so that internally calls functions from another shared library libx2.so. Both live in non-standard directories. Your application only directly links with libx1.so.
The dynamic linker must be able to find both libraries at runtime. But since your application only lists libx1 in its NEEDED entries, how does it find libx2?
Answer: Embed the rpath for
libx2 inside libx1.so itself. The linker will follow the rpath chain transitively.Dependency Chain Diagram (Inline HTML)
prog (pdir/prog.c)
calls x1()
rpath: pdir/d1
calls x1()
rpath: pdir/d1
NEEDED: libx1.so
โ
d1/libx1.so (pdir/d1/modx1.c)
x1() calls x2()
rpath: pdir/d2
x1() calls x2()
rpath: pdir/d2
NEEDED: libx2.so
โ
d2/libx2.so (pdir/d2/modx2.c)
x2() โ leaf function
no rpath needed
x2() โ leaf function
no rpath needed
Key Insight: The
prog binary does NOT need to mention libx2.so at all. The rpath inside libx1.so tells the dynamic linker where to find libx2.so when libx1.so is loaded.Complete Build Steps for Chained Libraries
Step 1: Build libx2.so (the leaf library, no dependencies)
$ cd /home/mtk/pdir/d2
# Compile the source
$ gcc -g -c -fPIC -Wall modx2.c
# Link into shared library (no -rpath needed โ no external deps)
$ gcc -g -shared -o libx2.so modx2.o
Step 2: Build libx1.so (depends on libx2.so in d2)
$ cd /home/mtk/pdir/d1
# Compile the source
$ gcc -g -c -Wall -fPIC modx1.c
# Link into shared library โ embed rpath pointing to d2 for libx2.so
$ gcc -g -shared \
-o libx1.so modx1.o \
-Wl,-rpath,/home/mtk/pdir/d2 \
-L/home/mtk/pdir/d2 \
-lx2
# -rpath,d2 = runtime: dynamic linker looks in d2 for libx2.so
# -L,d2 = link time: linker finds libx2.so in d2 to verify symbols
The
-L and -rpath paths can be different! -L is only for the build machine; -rpath is the runtime path on the target machine.Step 3: Build the main program (depends on libx1.so in d1)
$ cd /home/mtk/pdir
# Build prog โ embed rpath pointing to d1 for libx1.so
$ gcc -g -Wall \
-o prog prog.c \
-Wl,-rpath,/home/mtk/pdir/d1 \
-L/home/mtk/pdir/d1 \
-lx1
# NOTE: We did NOT specify -lx2 here!
# The linker analyzes libx1.so's rpath and finds libx2.so automatically.
The linker is smart enough to follow the rpath in
libx1.so to find libx2.so and verify all symbols are resolvable at static link time โ even though we never mentioned libx2.so directly when building prog.Verification: Inspecting rpath and Dependencies
After building, verify the rpath entries and dependency chains:
# Check rpath in the main program
$ objdump -p prog | grep PATH
RPATH /home/mtk/pdir/d1 # libx1.so will be sought here at run time
# Check rpath in libx1.so
$ objdump -p d1/libx1.so | grep PATH
RPATH /home/mtk/pdir/d2 # libx2.so will be sought here at run time
# Check NEEDED entries in prog (what prog directly depends on)
$ objdump -p prog | grep NEEDED
NEEDED libx1.so # only libx1.so โ libx2.so is transitive!
# Check NEEDED entries in libx1.so
$ objdump -p d1/libx1.so | grep NEEDED
NEEDED libx2.so
# Use ldd to see the COMPLETE transitive dependency chain
$ ldd prog
libx1.so => /home/mtk/pdir/d1/libx1.so (0x40017000)
libc.so.6 => /lib/tls/libc.so.6 (0x40024000)
libx2.so => /home/mtk/pdir/d2/libx2.so (0x4014c000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
# ldd shows ALL transitive dependencies resolved!
Coding Example 1: Full Chained Library Build (Runnable Script)
#!/bin/bash
# chained_libs.sh โ builds prog โ libx1.so โ libx2.so chain
set -e
BASE=/tmp/chained_demo
D1=$BASE/d1
D2=$BASE/d2
mkdir -p $D1 $D2 $BASE
# ---- Source: modx2.c (leaf library) ----
cat > $D2/modx2.c << 'EOF'
#include <stdio.h>
void x2(void) {
printf("[libx2] x2() called โ leaf function\n");
}
EOF
# ---- Source: modx1.c (middle library, calls x2) ----
cat > $D1/modx1.c << 'EOF'
#include <stdio.h>
extern void x2(void); // from libx2.so
void x1(void) {
printf("[libx1] x1() called, now calling x2...\n");
x2();
}
EOF
# ---- Source: prog.c (main program, calls x1 only) ----
cat > $BASE/prog.c << 'EOF'
#include <stdio.h>
extern void x1(void); // from libx1.so
int main(void) {
printf("[prog] Starting...\n");
x1();
printf("[prog] Done.\n");
return 0;
}
EOF
echo "=== Step 1: Build libx2.so in $D2 ==="
cd $D2
gcc -g -c -fPIC -Wall modx2.c
gcc -g -shared -o libx2.so modx2.o
ls -la libx2.so
echo ""
echo "=== Step 2: Build libx1.so in $D1 (depends on libx2.so) ==="
cd $D1
gcc -g -c -fPIC -Wall modx1.c
gcc -g -shared \
-o libx1.so modx1.o \
-Wl,-rpath,$D2 \
-L$D2 -lx2
echo "rpath in libx1.so:"
objdump -p $D1/libx1.so | grep -E 'RPATH|NEEDED'
echo ""
echo "=== Step 3: Build prog (depends on libx1.so only) ==="
cd $BASE
gcc -g -Wall \
-o prog prog.c \
-Wl,-rpath,$D1 \
-L$D1 -lx1
echo ""
echo "rpath in prog:"
objdump -p $BASE/prog | grep -E 'RPATH|NEEDED'
echo ""
echo "=== ldd โ complete transitive dependencies ==="
ldd $BASE/prog
echo ""
echo "=== Running prog ==="
$BASE/prog
Coding Example 2: What Happens Without rpath in libx1.so
#!/bin/bash
# broken_chain.sh โ shows failure when rpath is missing from libx1.so
BASE=/tmp/broken_chain_demo
D1=$BASE/d1
D2=$BASE/d2
mkdir -p $D1 $D2 $BASE
# (same source files as before)
# ... setup source files ...
echo "=== Build libx2.so (same as before) ==="
cd $D2
gcc -g -c -fPIC -Wall modx2.c
gcc -g -shared -o libx2.so modx2.o
echo ""
echo "=== Build libx1.so WITHOUT embedding rpath for libx2 ==="
cd $D1
gcc -g -c -fPIC -Wall modx1.c
# Note: NO -Wl,-rpath here! Only -L for link time.
gcc -g -shared \
-o libx1.so modx1.o \
-L$D2 -lx2
# This builds successfully, because -L finds libx2.so at link time.
echo ""
echo "=== Build prog ==="
cd $BASE
gcc -g -Wall \
-o prog prog.c \
-Wl,-rpath,$D1 \
-L$D1 -lx1
echo ""
echo "=== ldd โ libx2.so cannot be found! ==="
ldd $BASE/prog
# You will see: libx2.so => not found
echo ""
echo "=== Running prog โ should fail ==="
$BASE/prog 2>&1 || echo ""
echo "ERROR: libx2.so not found because libx1.so has no rpath for d2!"
echo ""
echo "=== Fix: set LD_LIBRARY_PATH as workaround ==="
LD_LIBRARY_PATH=$D2 $BASE/prog && echo "Worked with LD_LIBRARY_PATH"
Lesson: When building a shared library that depends on another library in a non-standard location, you MUST embed the rpath into the dependent library itself โ not just into the main program.
Coding Example 3: C Source Files for the Three-Tier Chain
/* modx2.c โ libx2.so: leaf library (no dependencies) */
#include <stdio.h>
#include <string.h>
/* Returns the length of a string โ our "low level" utility */
int string_length(const char *s) {
return (int)strlen(s);
}
void x2_print_info(void) {
printf("[libx2] version 1.0, directory: d2\n");
}
/* modx1.c โ libx1.so: middle library (depends on libx2.so) */
#include <stdio.h>
/* Declarations from libx2.so */
extern int string_length(const char *s);
extern void x2_print_info(void);
/* x1 is the function exposed to the main program */
void x1_process(const char *msg) {
printf("[libx1] Processing message: %s\n", msg);
printf("[libx1] Message length (via libx2): %d\n", string_length(msg));
x2_print_info();
}
/* prog.c โ main program (depends on libx1.so only) */
#include <stdio.h>
/* Declaration from libx1.so */
extern void x1_process(const char *msg);
int main(void) {
printf("[prog] Program started\n");
x1_process("Hello from EmbeddedPathashala!");
x1_process("Linux Shared Libraries");
printf("[prog] Program finished\n");
return 0;
}
/*
* Build chain:
* gcc -g -c -fPIC modx2.c
* gcc -g -shared -o d2/libx2.so modx2.o
*
* gcc -g -c -fPIC modx1.c
* gcc -g -shared -o d1/libx1.so modx1.o \
* -Wl,-rpath,$(pwd)/d2 -L$(pwd)/d2 -lx2
*
* gcc -g -o prog prog.c \
* -Wl,-rpath,$(pwd)/d1 -L$(pwd)/d1 -lx1
*/
Summary: What Goes in Each Binary
| Binary | rpath (runtime) | -L (link time) | NEEDED entries |
|---|---|---|---|
libx2.so |
none | none | libc.so |
libx1.so |
path/to/d2 | path/to/d2 | libx2.so, libc.so |
prog |
path/to/d1 | path/to/d1 | libx1.so, libc.so |
prog does NOT need -lx2 at link time. The static linker follows libx1.so’s rpath to find libx2.so and resolves all symbols at build time. At runtime, the dynamic linker does the same.Interview Questions
Q1. If prog depends on libx1.so which depends on libx2.so, does prog need to explicitly link against libx2.so?
No. As long as libx1.so has an rpath entry pointing to the directory containing libx2.so, the static linker at build time and the dynamic linker at runtime can both find libx2.so transitively. The prog binary only needs to list
-lx1 at link time. However, if libx1.so does NOT have the rpath set correctly, you may need to add it to prog’s rpath list or use LD_LIBRARY_PATH.Q2. Can the -L path and the -rpath path be different? Why would you do that?
Yes.
-L is used only at static link time (build time) to find libraries on the developer’s machine. -rpath is embedded in the binary and used at runtime on the target machine. If the library is at /home/dev/libs on the build machine but will be at /opt/product/lib on the deployment machine, you use -L/home/dev/libs for the build and -Wl,-rpath,/opt/product/lib for the runtime.Q3. What command shows the complete transitive dependency chain of a binary?
ldd prog shows all libraries that will be loaded at runtime, including transitive dependencies (libraries of libraries). It follows rpath entries and LD_LIBRARY_PATH recursively. Note: never run ldd on untrusted binaries as it executes initialization code in some implementations.Q4. What does “objdump -p libx1.so | grep NEEDED” show?
It shows the shared libraries that libx1.so directly depends on โ i.e., the libraries whose functions libx1.so calls. These are the NEEDED entries recorded in libx1.so’s ELF dynamic section. For example, if libx1.so calls x2(), the output would include
NEEDED libx2.so.Q5. What error message would you see if libx2.so is missing at runtime even though libx1.so is found?
You would see an error like:
./prog: error while loading shared libraries: libx2.so: cannot open shared object file: No such file or directory. This happens when the dynamic linker loads libx1.so, reads its NEEDED entry for libx2.so, but cannot find libx2.so in any of the search locations (rpath in libx1.so, LD_LIBRARY_PATH, /etc/ld.so.cache, standard dirs).Continue Learning
