rpath: Embedding Library Search Directories

 

rpath: Embedding Library Search Directories
TLPI Chapter 41 ยท Section 41.10 โ€” Specifying Library Search Directories in an Object File
๐Ÿ“
rpath
๐Ÿ”
Search Order
๐Ÿ› ๏ธ
LD_RUN_PATH

Key Concepts
-rpath linker option rpath list LD_RUN_PATH LD_LIBRARY_PATH dynamic linker objdump -p non-standard directory

What is rpath and Why Do You Need It?

When a program uses a shared library, the dynamic linker (ld.so) must find that library at runtime. By default it looks in standard directories like /lib, /usr/lib, and directories listed in /etc/ld.so.conf.

But what if your library is in a non-standard location like /home/user/myproject/lib or /opt/vendor/lib64? You have three options:

Method How Scope Drawback
Standard dirs Install to /usr/lib System-wide Requires root, pollutes system
LD_LIBRARY_PATH Set env variable Per-session Must set before every run; security risk
rpath Embed in executable at link time Per-binary Path is baked in; less flexible

rpath is the cleanest for distributing self-contained applications because the search path travels with the binary.

Dynamic Linker Search Order (Inline Diagram)

When a program is executed, the dynamic linker searches for shared libraries in this exact order:

1
DT_RPATH list embedded in the binary (old ELF tag, highest priority)
2
LD_LIBRARY_PATH environment variable (colon-separated directory list)
3
DT_RUNPATH list embedded in the binary (new ELF tag)
4
/etc/ld.so.cache (managed by ldconfig, lists standard dirs)
5
Default dirs: /lib, /usr/lib (and /lib64, /usr/lib64 on 64-bit)
Note: DT_RPATH (step 1) has higher priority than LD_LIBRARY_PATH (step 2). DT_RUNPATH (step 3) has lower priority than LD_LIBRARY_PATH. This is the key difference between the two ELF tags.

Basic rpath Usage

The -rpath option is passed to the linker via gcc’s -Wl, mechanism:

# Basic: embed /home/mtk/pdir into the rpath of prog
gcc -g -Wall \
    -Wl,-rpath,/home/mtk/pdir \
    -o prog prog.c libdemo.so

# The dynamic linker will search /home/mtk/pdir at runtime

You can specify multiple rpath directories in two ways:

# Method 1: Multiple -rpath options (each adds to the list)
gcc -o prog prog.c \
    -Wl,-rpath,/opt/myapp/lib \
    -Wl,-rpath,/usr/local/vendor/lib \
    -ldemo

# Method 2: Colon-separated list in one -rpath option
gcc -o prog prog.c \
    -Wl,-rpath,/opt/myapp/lib:/usr/local/vendor/lib \
    -ldemo

# Verify what got embedded:
objdump -p prog | grep RPATH
# Output: RPATH /opt/myapp/lib:/usr/local/vendor/lib
The directories are searched in the order you specify them. Put the most specific/preferred path first.

Alternative: LD_RUN_PATH Environment Variable

Instead of specifying -rpath on the gcc command line, you can set the LD_RUN_PATH environment variable before building:

# Set LD_RUN_PATH before the build command
export LD_RUN_PATH=/home/mtk/pdir:/opt/myapp/lib

# Now build without -rpath; LD_RUN_PATH is used instead
gcc -g -Wall -o prog prog.c -L/home/mtk/pdir -ldemo

# Verify it was embedded
objdump -p prog | grep RPATH
# Output: RPATH /home/mtk/pdir:/opt/myapp/lib
Priority rule: If -rpath is specified on the command line, LD_RUN_PATH is completely ignored. LD_RUN_PATH is only used when -rpath is absent.

Coding Example 1: Build and Use a Library in a Non-Standard Directory
#!/bin/bash
# rpath_demo.sh โ€” demonstrates -rpath for non-standard library location

set -e
WORKDIR=/tmp/rpath_demo
mkdir -p $WORKDIR/mylib $WORKDIR/myapp
cd $WORKDIR

# ---- Create the shared library source ----
cat > mylib/myutils.c << 'EOF'
#include <stdio.h>

void say_hello(const char *who) {
    printf("Hello from myutils library, %s!\n", who);
}

int add_numbers(int a, int b) {
    return a + b;
}
EOF

# ---- Create the application source ----
cat > myapp/main.c << 'EOF'
#include <stdio.h>

void say_hello(const char *who);
int add_numbers(int a, int b);

int main(void) {
    say_hello("EmbeddedPathashala");
    int result = add_numbers(10, 32);
    printf("10 + 32 = %d\n", result);
    return 0;
}
EOF

echo "=== Building shared library in non-standard dir: $WORKDIR/mylib ==="
cd $WORKDIR/mylib
gcc -g -c -fPIC -Wall myutils.c
gcc -g -shared -Wl,-soname,libmyutils.so.1 \
    -o libmyutils.so.1.0.0 myutils.o
ln -sf libmyutils.so.1.0.0 libmyutils.so.1
ln -sf libmyutils.so.1 libmyutils.so

echo "=== Building application with -rpath embedded ==="
cd $WORKDIR/myapp
gcc -g -Wall \
    -Wl,-rpath,$WORKDIR/mylib \
    -L$WORKDIR/mylib \
    -o myapp main.c \
    -lmyutils

echo ""
echo "=== Inspecting rpath embedded in myapp ==="
objdump -p myapp | grep RPATH
# Expected: RPATH /tmp/rpath_demo/mylib

echo ""
echo "=== ldd shows the library is found via rpath ==="
ldd myapp

echo ""
echo "=== Running myapp (no LD_LIBRARY_PATH needed!) ==="
./myapp
Notice that we don’t need export LD_LIBRARY_PATH=... before running myapp. The rpath is baked into the binary itself.

Coding Example 2: Comparing LD_LIBRARY_PATH vs rpath
#!/bin/bash
# compare_methods.sh โ€” show difference between LD_LIBRARY_PATH and rpath

WORKDIR=/tmp/compare_demo
mkdir -p $WORKDIR/lib $WORKDIR/bin
cd $WORKDIR

# Assume libdemo.so is already built in $WORKDIR/lib
# Build with rpath:
echo "=== Build WITH rpath ==="
gcc -o bin/prog_rpath main.c \
    -Wl,-rpath,$WORKDIR/lib \
    -L$WORKDIR/lib \
    -ldemo
echo "prog_rpath rpath:"
objdump -p bin/prog_rpath | grep RPATH

# Build WITHOUT rpath:
echo ""
echo "=== Build WITHOUT rpath ==="
gcc -o bin/prog_norpath main.c \
    -L$WORKDIR/lib \
    -ldemo
echo "prog_norpath rpath (should be empty):"
objdump -p bin/prog_norpath | grep RPATH || echo "  (no RPATH embedded)"

echo ""
echo "=== Running prog_rpath (works without LD_LIBRARY_PATH) ==="
bin/prog_rpath && echo "Success!" || echo "Failed"

echo ""
echo "=== Running prog_norpath WITHOUT LD_LIBRARY_PATH (should fail) ==="
bin/prog_norpath 2>&1 || echo "Expected failure: library not found"

echo ""
echo "=== Running prog_norpath WITH LD_LIBRARY_PATH (works) ==="
LD_LIBRARY_PATH=$WORKDIR/lib bin/prog_norpath && echo "Success!"

echo ""
echo "=== NOTE: LD_LIBRARY_PATH overrides rpath with DT_RUNPATH, ==="
echo "=== but NOT with DT_RPATH (old ELF tag). ==="

Coding Example 3: Using LD_RUN_PATH During Build
#!/bin/bash
# ld_run_path_demo.sh โ€” using LD_RUN_PATH as alternative to -rpath

WORKDIR=/tmp/ldrunpath_demo
mkdir -p $WORKDIR/lib $WORKDIR/app
cd $WORKDIR

# Suppose libfoo is in /opt/locallibs/lib
LIBDIR=/opt/locallibs/lib
mkdir -p $LIBDIR

# Build the library
cat > /tmp/foo.c << 'EOF'
#include <stdio.h>
void foo_init(void) { printf("foo library initialized\n"); }
EOF
gcc -shared -fPIC -o $LIBDIR/libfoo.so /tmp/foo.c

# Build using LD_RUN_PATH (no -rpath on command line)
echo "=== Method 1: LD_RUN_PATH during build ==="
export LD_RUN_PATH=$LIBDIR
gcc -o app/prog_ldrunpath main.c -L$LIBDIR -lfoo
objdump -p app/prog_ldrunpath | grep RPATH
unset LD_RUN_PATH

# Build using -rpath directly (overrides LD_RUN_PATH)
echo ""
echo "=== Method 2: -rpath on command line ==="
export LD_RUN_PATH=/some/other/path   # this will be IGNORED
gcc -o app/prog_rpath main.c \
    -Wl,-rpath,$LIBDIR \
    -L$LIBDIR -lfoo
objdump -p app/prog_rpath | grep RPATH
# Shows $LIBDIR, NOT /some/other/path โ€” -rpath wins
unset LD_RUN_PATH

Interview Questions
Q1. What is rpath and what problem does it solve?
rpath (run-time library path) is a list of directories embedded inside an executable or shared library at link time. It tells the dynamic linker where to search for shared libraries at runtime. It solves the problem of using shared libraries installed in non-standard directories (not /lib, /usr/lib, or /etc/ld.so.conf paths) without requiring the user to set LD_LIBRARY_PATH or the administrator to modify /etc/ld.so.conf.
Q2. How do you specify an rpath using gcc?
Use the -Wl,-rpath,/path/to/dir option. The -Wl, prefix passes the following comma-separated argument directly to the linker (ld). Example: gcc -o myprog main.c -Wl,-rpath,/opt/mylibs/lib -L/opt/mylibs/lib -lmylib. Multiple paths can be given with multiple -rpath options or as a colon-separated list.
Q3. What is the relationship between LD_RUN_PATH and -rpath?
LD_RUN_PATH is an environment variable that serves as an alternative to the -rpath linker option. If -rpath is specified on the command line, LD_RUN_PATH is completely ignored. LD_RUN_PATH is only used when -rpath is not present. Both result in an rpath list being embedded in the executable, but -rpath takes priority.
Q4. How do you inspect the rpath embedded inside an executable?
Use objdump -p executable | grep RPATH or objdump -p executable | grep RUNPATH. You can also use readelf -d executable | grep -E 'RPATH|RUNPATH'. These commands display the DT_RPATH or DT_RUNPATH entries from the ELF dynamic section of the file.
Q5. In what order does the dynamic linker search for shared libraries?
The search order is: (1) DT_RPATH list in the binary (if DT_RUNPATH is absent), (2) LD_LIBRARY_PATH environment variable, (3) DT_RUNPATH list in the binary, (4) /etc/ld.so.cache (maintained by ldconfig), (5) default system directories (/lib, /usr/lib). Note that DT_RPATH has higher priority than LD_LIBRARY_PATH, while DT_RUNPATH has lower priority.
Q6. What is the difference between -L and -rpath options in gcc?
-L/path is a compile-time (static link time) option that tells the linker where to find libraries when building the executable. It is NOT embedded in the final binary and has no effect at runtime. -Wl,-rpath,/path is embedded in the binary and is used by the dynamic linker at runtime to find shared libraries. Typically you need both: -L to build successfully, and -rpath so the binary can find its libraries when run.

Leave a Reply

Your email address will not be published. Required fields are marked *