Dynamic Linking

mohitmishra786/low-level-dev-skills/skills/binaries/dynamic-linking

by mohitmishra786bdc58472fa9fNo license253 starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 3 months ago

Dynamic linking skill for Linux/ELF shared libraries. Use when debugging library loading failures, configuring RPATH vs RUNPATH, understanding soname versioning, using dlopen/dlsym for plugin systems, LD_PRELOAD interposition, or controlling symbol visibility. Activates on queries about shared libraries, dlopen, LD_LIBRARY_PATH, RPATH, soname, LD_PRELOAD, symbol visibility, or "cannot open shared object file" errors.

Instructions onlySoftware Development
AI-generated overview

Guides agents through Linux ELF dynamic linking: shared libraries, RPATH/RUNPATH, soname, dlopen plugins, LD_PRELOAD and symbol visibility.

What it does
Provides reference instructions for creating and configuring Linux shared libraries, covering -fPIC compilation, soname versioning, RPATH versus RUNPATH search order, and ldconfig registration. It also documents dlopen/dlsym plugin loading, LD_PRELOAD interposition, symbol visibility control with attributes and version scripts, and a table of common loader errors with fixes. It produces guidance and command examples rather than files.
When to use it
Use when debugging library loading failures such as "cannot open shared object file", or when configuring RPATH/RUNPATH, soname versioning, dlopen-based plugin systems, LD_PRELOAD interposition, or symbol visibility. It is aimed at Linux/ELF shared library work.
Requirements
No scripts ship with the skill; it is instructions only. Applying its guidance assumes a Linux environment with toolchain and inspection tools such as gcc, ld, readelf, objdump, nm, ldd, chrpath and ldconfig, plus the libdl library for dlopen examples.

Dynamic Linking

Purpose

Guide agents through Linux dynamic linking: shared library creation, RPATH/RUNPATH configuration, soname versioning, dlopen/dlsym plugin patterns, LD_PRELOAD interposition, and symbol visibility control.

Triggers

  • "Cannot open shared object file: No such file or directory"
  • "How do I set RPATH so my binary finds its shared library?"
  • "How do I use dlopen/dlsym for a plugin system?"
  • "What's the difference between RPATH and RUNPATH?"
  • "How do I use LD_PRELOAD to intercept a function?"
  • "How do I version my shared library with soname?"

Workflow

1. Creating a shared library

bash
# Compile with -fPIC (position-independent code)gcc -fPIC -c src/mylib.c -o mylib.o
# Link shared library with sonamegcc -shared -Wl,-soname,libmylib.so.1 \    mylib.o -o libmylib.so.1.2.3
# Create symlinks (standard convention)ln -s libmylib.so.1.2.3 libmylib.so.1   # soname link (used by ldconfig)ln -s libmylib.so.1     libmylib.so      # link link (used at compile time)
# Register with ldconfig (system-wide)sudo cp libmylib.so.1.2.3 /usr/local/lib/sudo ldconfig

2. Soname versioning convention

text
libfoo.so.MAJOR.MINOR.PATCH         │         └── soname = libfoo.so.MAJOR
Version bumpWhen
PATCHBug fix, ABI unchanged
MINORNew symbols added, backwards compatible
MAJORABI break — existing binaries will break

Inspect soname:

bash
readelf -d libmylib.so.1.2.3 | grep SONAMEobjdump -p libmylib.so.1.2.3 | grep SONAME

3. RPATH vs RUNPATH

text
Both embed a library search path in the binary.
RPATH  → searched BEFORE LD_LIBRARY_PATHRUNPATH → searched AFTER LD_LIBRARY_PATH (controllable at runtime)
Recommendation: prefer RUNPATH (-Wl,--enable-new-dtags)                for deployment flexibility.
bash
# Embed RPATH (old default)gcc main.c -L./lib -lmylib \    -Wl,-rpath,'$ORIGIN/../lib' -o myapp
# Embed RUNPATH (new default with --enable-new-dtags)gcc main.c -L./lib -lmylib \    -Wl,-rpath,'$ORIGIN/../lib' \    -Wl,--enable-new-dtags -o myapp
# Inspectreadelf -d myapp | grep -E 'RPATH|RUNPATH'chrpath -l myapp        # showchrpath -r '/new/path' myapp  # modify existing

$ORIGIN resolves to the directory of the binary at runtime — use it for relocatable installations.

4. Library search order

text
1. DT_RPATH (if no DT_RUNPATH present)2. LD_LIBRARY_PATH (env var, ignored for suid binaries)3. DT_RUNPATH4. /etc/ld.so.cache  (populated by ldconfig from /etc/ld.so.conf)5. /lib, /usr/lib

Debug with:

bash
LD_DEBUG=libs ./myapp      # trace library loading decisionsldd myapp                  # show resolved librariesldd -v myapp               # verbose with version requirements

5. dlopen / dlsym plugin pattern

c
#include <dlfcn.h>
typedef int (*plugin_fn_t)(const char *input);
void load_plugin(const char *path) {    // RTLD_NOW: resolve all symbols immediately (fail fast)    // RTLD_LAZY: resolve on first call (default)    // RTLD_LOCAL: symbols not visible to other loaded libs    // RTLD_GLOBAL: symbols visible globally    void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);    if (!handle) {        fprintf(stderr, "dlopen: %s\n", dlerror());        return;    }
    // Clear previous errors    dlerror();
    plugin_fn_t fn = (plugin_fn_t)dlsym(handle, "plugin_run");    const char *err = dlerror();    if (err) {        fprintf(stderr, "dlsym: %s\n", err);        dlclose(handle);        return;    }
    fn("hello");    dlclose(handle);}

Link with -ldl:

bash
gcc main.c -ldl -o myapp

6. LD_PRELOAD interposition

LD_PRELOAD loads a library before all others — its symbols override the application's.

c
// myinterpose.c — intercept malloc#define _GNU_SOURCE#include <stdio.h>#include <dlfcn.h>
void *malloc(size_t size) {    static void *(*real_malloc)(size_t) = NULL;    if (!real_malloc)        real_malloc = dlsym(RTLD_NEXT, "malloc");  // find next malloc in chain
    void *ptr = real_malloc(size);    fprintf(stderr, "malloc(%zu) = %p\n", size, ptr);    return ptr;}
bash
gcc -shared -fPIC -o myinterpose.so myinterpose.c -ldl
# Apply to any binaryLD_PRELOAD=./myinterpose.so ./myappLD_PRELOAD=/path/to/libfaketime.so ./myapp  # time manipulation

7. Symbol visibility control

Limit exported symbols to reduce binary size and avoid clashes:

c
// Mark default: visible to linker__attribute__((visibility("default")))int public_api(void) { return 42; }
// Hidden: internal, not exported__attribute__((visibility("hidden")))static int internal_helper(void) { return 0; }

Or use a linker version script:

text
# mylib.mapMYLIB_1.0 {    global:        mylib_init;        mylib_process;    local:        *;          # hide everything else};
bash
gcc -shared -fPIC -Wl,--version-script=mylib.map \    -o libmylib.so mylib.c
# Check exported symbolsnm -D --defined-only libmylib.soobjdump -T libmylib.so

Build with -fvisibility=hidden by default and explicitly mark public API:

bash
gcc -shared -fPIC -fvisibility=hidden \    mylib.c -o libmylib.so

8. Common errors

ErrorCauseFix
cannot open shared object fileLibrary not in search pathSet RPATH, LD_LIBRARY_PATH, or run ldconfig
symbol lookup error: undefined symbolMissing library or wrong versionCheck ldd, add -l flag or fix link order
FATAL: kernel too oldVersion requirement mismatchRebuild against older glibc
relocation R_X86_64_32 against .rodataNon-PIC code in shared libAdd -fPIC to compilation
version 'GLIBC_2.29' not foundBinary built on newer glibcRebuild on older system or use -static

For RPATH, soname, and ld.so configuration details, see references/ld-rpath-soname.md [blocked].

Related skills

  • Use skills/binaries/elf-inspection to inspect shared library sections and symbols
  • Use skills/binaries/linkers-lto for linker flags and symbol resolution
  • Use skills/binaries/binutils for nm, objdump, strip on shared libs
  • Use skills/compilers/gcc for -fPIC, -shared and related compiler flags

Source and attribution

Source:mohitmishra786/low-level-dev-skillsinskills/binaries/dynamic-linkingat commitbdc5847

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal