Imagine you are distributing an application called MyApp that ships with its own set of shared libraries (perhaps custom versions that differ from what the system has installed). You want users to be able to:
- Unpack the application into any directory they choose (e.g.,
/home/user/myapp/or/opt/myapp/orD:\Programs\MyAppon different systems) - Run it immediately without any extra configuration
- Not require root access to install to standard directories
The problem with hardcoded rpath like -Wl,-rpath,/opt/myapp/lib is that if the user installs to /home/alice/myapp/, the hardcoded path doesn’t match and the binary fails to find its libraries.
• Requiring users to set
LD_LIBRARY_PATH before every run — cumbersome and error-prone• Providing a shell wrapper script — adds complexity
• Running an installation script to patch the binary — defeats the purpose
The dynamic linker (ld.so) understands a special token $ORIGIN (or equivalently ${ORIGIN}) in rpath specifications. At runtime, the dynamic linker expands $ORIGIN to the directory containing the executable (or the containing directory of the shared library, if $ORIGIN appears in a shared library’s rpath).
${ORIGIN} when embedding in shell scripts to prevent shell variable expansion. When passing directly to the linker, you can use either form.bin/
prog ← executable (rpath = $ORIGIN/../lib)
lib/
libfoo.so.1 ← private library v1
libbar.so.2 ← private library v2
libfoo.so.1.2.3 ← real file
libbar.so.2.0.1 ← real file
When prog is in bin/ and libraries are in lib/ (sibling directories), the rpath should be:
# $ORIGIN/../lib means: directory of binary / up one level / lib
# This works whether installed at /home/alice/myapp or /opt/tools/myapp
gcc -o bin/prog main.c \
-Wl,-rpath,'$ORIGIN/../lib' \
-L./lib -lfoo -lbar
'$ORIGIN/../lib' when passing to the shell, or use \$ORIGIN. If you use double quotes, the shell will try to expand $ORIGIN as a shell variable (which is empty), and the rpath will be wrong.#!/bin/bash
# origin_demo.sh — builds a fully portable self-contained application
set -e
BASE=/tmp/origin_demo
# Create the portable app directory structure
mkdir -p $BASE/bin $BASE/lib
# ---- Library source ----
cat > $BASE/myengine.c << 'EOF'
#include <stdio.h>
void engine_init(void) {
printf("[libengine] Initializing engine...\n");
}
void engine_run(const char *task) {
printf("[libengine] Running task: %s\n", task);
}
EOF
# ---- Main program source ----
cat > $BASE/main.c << 'EOF'
#include <stdio.h>
extern void engine_init(void);
extern void engine_run(const char *task);
int main(int argc, char *argv[]) {
printf("[myapp] Starting at: %s\n", argv[0]);
engine_init();
engine_run("data_processing");
engine_run("report_generation");
printf("[myapp] Complete!\n");
return 0;
}
EOF
echo "=== Step 1: Build the shared library ==="
cd $BASE
gcc -g -c -fPIC -Wall myengine.c
gcc -g -shared -Wl,-soname,libengine.so.1 \
-o lib/libengine.so.1.0.0 myengine.o
ln -sf libengine.so.1.0.0 lib/libengine.so.1
ln -sf libengine.so.1 lib/libengine.so
echo ""
echo "=== Step 2: Build the executable with \$ORIGIN rpath ==="
# IMPORTANT: Single quotes prevent shell from expanding $ORIGIN
gcc -g -Wall \
-Wl,-rpath,'$ORIGIN/../lib' \
-o bin/myapp main.c \
-L./lib -lengine
echo ""
echo "=== Step 3: Verify rpath ==="
objdump -p bin/myapp | grep RPATH
# Should show: RPATH $ORIGIN/../lib
# (the literal string, NOT expanded yet)
echo ""
echo "=== Step 4: Show ldd with $ORIGIN expanded at runtime ==="
ldd bin/myapp
# Shows: libengine.so.1 => /tmp/origin_demo/lib/libengine.so.1
echo ""
echo "=== Step 5: Run from installation location ==="
./bin/myapp
echo ""
echo "=== Step 6: Simulate installing to a different directory ==="
INSTALL_DIR=/tmp/myapp_install_$(date +%s)
cp -r $BASE $INSTALL_DIR
echo "Installed to: $INSTALL_DIR"
echo "Running from new location:"
$INSTALL_DIR/bin/myapp # works without any LD_LIBRARY_PATH!
echo ""
echo "=== Done! Works from any install directory ==="
-Wl,-rpath,'$ORIGIN/../lib' with single quotes. The binary stores the literal string $ORIGIN/../lib. At runtime, the dynamic linker expands $ORIGIN to the actual directory of the binary.$ORIGIN can also appear in a shared library’s rpath. In that case, $ORIGIN expands to the directory containing the shared library, not the main executable.
#!/bin/bash
# origin_in_lib.sh — $ORIGIN in a shared library's rpath
# Structure:
# myapp/
# bin/prog
# lib/
# libmain.so (rpath = $ORIGIN/extra)
# extra/
# libextra.so
BASE=/tmp/origin_lib_demo
mkdir -p $BASE/bin $BASE/lib/extra
# ---- libextra.so source (leaf) ----
cat > $BASE/extra_utils.c << 'EOF'
#include <stdio.h>
void extra_func(void) {
printf("[libextra] extra_func called\n");
}
EOF
# ---- libmain.so source (calls libextra) ----
cat > $BASE/main_lib.c << 'EOF'
#include <stdio.h>
extern void extra_func(void);
void main_func(void) {
printf("[libmain] main_func calling extra...\n");
extra_func();
}
EOF
# ---- prog source ----
cat > $BASE/prog.c << 'EOF'
#include <stdio.h>
extern void main_func(void);
int main(void) {
main_func();
return 0;
}
EOF
echo "=== Build libextra.so ==="
gcc -shared -fPIC -o $BASE/lib/extra/libextra.so $BASE/extra_utils.c
echo "=== Build libmain.so with \$ORIGIN/extra rpath ==="
gcc -g -c -fPIC $BASE/main_lib.c -o $BASE/main_lib.o
gcc -g -shared \
-Wl,-rpath,'$ORIGIN/extra' \
-o $BASE/lib/libmain.so $BASE/main_lib.o \
-L$BASE/lib/extra -lextra
# $ORIGIN here = /tmp/origin_lib_demo/lib (lib dir of libmain.so)
# So rpath expands to /tmp/origin_lib_demo/lib/extra at runtime
echo ""
echo "rpath in libmain.so:"
objdump -p $BASE/lib/libmain.so | grep RPATH
echo ""
echo "=== Build prog ==="
gcc -g -Wall \
-Wl,-rpath,'$ORIGIN/../lib' \
-o $BASE/bin/prog $BASE/prog.c \
-L$BASE/lib -lmain
echo ""
echo "rpath in prog:"
objdump -p $BASE/bin/prog | grep RPATH
echo ""
echo "=== ldd: complete dependency chain ==="
ldd $BASE/bin/prog
echo ""
echo "=== Run ==="
$BASE/bin/prog
/*
* Common $ORIGIN rpath patterns for different directory structures
*
* Pattern 1: Binary and libs in the same directory
* ------------------------------------------------
* app/
* prog <-- rpath: $ORIGIN
* libfoo.so.1
*/
// gcc -o app/prog main.c -Wl,-rpath,'$ORIGIN' -L./app -lfoo
/*
* Pattern 2: Binary in bin/, libs in lib/ (sibling dirs)
* -------------------------------------------------------
* app/
* bin/
* prog <-- rpath: $ORIGIN/../lib
* lib/
* libfoo.so.1
*/
// gcc -o bin/prog main.c -Wl,-rpath,'$ORIGIN/../lib' -L./lib -lfoo
/*
* Pattern 3: Multiple lib directories
* ------------------------------------
* app/
* bin/
* prog <-- rpath: $ORIGIN/../lib:$ORIGIN/../lib64
* lib/
* libfoo.so
* lib64/
* libbar.so
*/
// gcc -o bin/prog main.c \
// -Wl,-rpath,'$ORIGIN/../lib:$ORIGIN/../lib64' \
// -L./lib -L./lib64 -lfoo -lbar
/*
* Pattern 4: Using ${ORIGIN} syntax (useful in Makefiles)
* ---------------------------------------------------------
*/
// RPATH_FLAG = -Wl,-rpath,'$${ORIGIN}/../lib' (in Makefile)
// Or use \$\${ORIGIN} depending on Makefile quoting rules
/*
* Pattern 5: Combining $ORIGIN with absolute path
* -------------------------------------------------
* Try private lib first, fall back to system lib
*/
// gcc -o bin/prog main.c \
// -Wl,-rpath,'$ORIGIN/../lib:/usr/local/lib' \
// -L./lib -lfoo
/*
* Verification: read the literal rpath from binary
*/
// objdump -p bin/prog | grep RPATH
// Expected: RPATH $ORIGIN/../lib
// (NOT expanded — the $ is literal in the ELF file)
| Approach | Pros | Cons | Best For |
|---|---|---|---|
| Install to /usr/lib | Standard, always found | Requires root; pollutes system; conflicts possible | System-wide packages (apt/rpm packages) |
| LD_LIBRARY_PATH | Simple, no rebuild needed | Must set every session; not inherited; security risk | Development/testing |
| Hardcoded rpath | Self-contained | Breaks if user moves the app | Fixed-location deployment |
| $ORIGIN rpath | Self-contained AND relocatable | Slightly complex to set up correctly | Portable application distribution |
$ORIGIN is a special token understood by the Linux dynamic linker (ld.so). At runtime, when resolving library paths, the dynamic linker expands $ORIGIN to the directory containing the executable (or the directory containing the shared library, if $ORIGIN appears in a shared library’s rpath). This allows the rpath to be relative to the binary’s location rather than an absolute path, making the binary relocatable.$ORIGIN as a shell variable before passing arguments to gcc. Since $ORIGIN is not a defined shell variable, it expands to an empty string, resulting in a broken rpath. Single quotes prevent shell expansion and pass the literal string $ORIGIN/../lib to gcc, which then passes it to the linker, which stores it literally in the ELF file. Alternatively, you can escape it as \$ORIGIN.$ORIGIN/../lib. This works as follows: $ORIGIN expands to the bin/ directory where the binary lives, then /.. goes up one level to the app root, then /lib enters the lib/ directory. So regardless of where the whole app is installed, the path is always correctly resolved relative to the binary.libmain.so is in /app/lib/ and has rpath $ORIGIN/extra, the dynamic linker searches /app/lib/extra/ for that library’s dependencies.objdump -p binary | grep RPATH or readelf -d binary | grep RPATH. If $ORIGIN is stored correctly, you will see the literal string $ORIGIN/../lib (with the dollar sign) in the output. If you forgot single quotes and the shell expanded it, you would see an empty path or no RPATH entry at all.$ORIGIN and ${ORIGIN} are equivalent and understood by the dynamic linker. The ${ORIGIN} form (with braces) can be useful to disambiguate boundaries, for example in ${ORIGIN}/../lib versus $ORIGIN/../lib — both mean the same thing here, but braces become necessary when the name is immediately followed by alphanumeric characters./opt/myapp/lib only works when the application is installed exactly at /opt/myapp/. If a user installs it at /home/bob/tools/myapp/, the hardcoded path is wrong and the libraries won’t be found. $ORIGIN solves this by making the path relative to wherever the binary actually is at runtime, so the binary works from any installation directory without requiring the user to set LD_LIBRARY_PATH or run an installation script.