dynamic-linking

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

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.
指导开发者掌握Linux动态链接相关知识:共享库创建、RPATH/RUNPATH配置、soname版本控制、
dlopen
/
dlsym
插件模式、
LD_PRELOAD
函数拦截,以及符号可见性控制。

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?"
  • “无法打开共享对象文件:No such file or directory”
  • “如何设置RPATH让二进制文件找到其共享库?”
  • “如何使用dlopen/dlsym构建插件系统?”
  • “RPATH和RUNPATH有什么区别?”
  • “如何使用LD_PRELOAD拦截函数?”
  • “如何通过soname为共享库做版本控制?”

Workflow

操作流程

1. Creating a shared library

1. 创建共享库

bash
undefined
bash
undefined

Compile with -fPIC (position-independent code)

Compile with -fPIC (position-independent code)

gcc -fPIC -c src/mylib.c -o mylib.o
gcc -fPIC -c src/mylib.c -o mylib.o

Link shared library with soname

Link shared library with soname

gcc -shared -Wl,-soname,libmylib.so.1
mylib.o -o libmylib.so.1.2.3
gcc -shared -Wl,-soname,libmylib.so.1
mylib.o -o libmylib.so.1.2.3

Create symlinks (standard convention)

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)
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)

Register with ldconfig (system-wide)

sudo cp libmylib.so.1.2.3 /usr/local/lib/ sudo ldconfig
undefined
sudo cp libmylib.so.1.2.3 /usr/local/lib/ sudo ldconfig
undefined

2. Soname versioning convention

2. Soname版本控制规范

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 SONAME
objdump -p libmylib.so.1.2.3 | grep SONAME
text
libfoo.so.MAJOR.MINOR.PATCH
         └── soname = libfoo.so.MAJOR
版本更新类型适用场景
PATCH修复Bug,ABI无变更
MINOR添加新符号,向后兼容
MAJORABI发生破坏变更——现有二进制文件将无法正常运行
查看soname:
bash
readelf -d libmylib.so.1.2.3 | grep SONAME
objdump -p libmylib.so.1.2.3 | grep SONAME

3. RPATH vs RUNPATH

3. RPATH vs RUNPATH

text
Both embed a library search path in the binary.

RPATH  → searched BEFORE LD_LIBRARY_PATH
RUNPATH → searched AFTER LD_LIBRARY_PATH (controllable at runtime)

Recommendation: prefer RUNPATH (-Wl,--enable-new-dtags)
                for deployment flexibility.
bash
undefined
text
二者都会在二进制文件中嵌入库搜索路径。

RPATH  → 优先于LD_LIBRARY_PATH被搜索
RUNPATH → 在LD_LIBRARY_PATH之后被搜索(运行时可控制)

推荐:优先使用RUNPATH(-Wl,--enable-new-dtags)
     以提升部署灵活性。
bash
undefined

Embed RPATH (old default)

Embed RPATH (old default)

gcc main.c -L./lib -lmylib
-Wl,-rpath,'$ORIGIN/../lib' -o myapp
gcc main.c -L./lib -lmylib
-Wl,-rpath,'$ORIGIN/../lib' -o myapp

Embed RUNPATH (new default with --enable-new-dtags)

Embed RUNPATH (new default with --enable-new-dtags)

gcc main.c -L./lib -lmylib
-Wl,-rpath,'$ORIGIN/../lib'
-Wl,--enable-new-dtags -o myapp
gcc main.c -L./lib -lmylib
-Wl,-rpath,'$ORIGIN/../lib'
-Wl,--enable-new-dtags -o myapp

Inspect

Inspect

readelf -d myapp | grep -E 'RPATH|RUNPATH' chrpath -l myapp # show chrpath -r '/new/path' myapp # modify existing

`$ORIGIN` resolves to the directory of the binary at runtime — use it for relocatable installations.
readelf -d myapp | grep -E 'RPATH|RUNPATH' chrpath -l myapp # show chrpath -r '/new/path' myapp # modify existing

`$ORIGIN`会在运行时解析为二进制文件所在的目录——适用于可重定位的安装场景。

4. Library search order

4. 库搜索顺序

text
1. DT_RPATH (if no DT_RUNPATH present)
2. LD_LIBRARY_PATH (env var, ignored for suid binaries)
3. DT_RUNPATH
4. /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 decisions
ldd myapp                  # show resolved libraries
ldd -v myapp               # verbose with version requirements
text
1. DT_RPATH(如果没有DT_RUNPATH)
2. LD_LIBRARY_PATH(环境变量,suid二进制文件会忽略)
3. DT_RUNPATH
4. /etc/ld.so.cache(由ldconfig从/etc/ld.so.conf生成)
5. /lib、/usr/lib
调试命令:
bash
LD_DEBUG=libs ./myapp      # trace library loading decisions
ldd myapp                  # show resolved libraries
ldd -v myapp               # verbose with version requirements

5. dlopen / dlsym plugin pattern

5. dlopen/dlsym插件模式

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
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);
}
链接时需添加
-ldl
参数:
bash
gcc main.c -ldl -o myapp

6. LD_PRELOAD interposition

6. LD_PRELOAD函数拦截

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
LD_PRELOAD
会在所有其他库之前加载指定库——其符号会覆盖应用程序的符号。
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 binary

Apply to any binary

LD_PRELOAD=./myinterpose.so ./myapp LD_PRELOAD=/path/to/libfaketime.so ./myapp # time manipulation
undefined
LD_PRELOAD=./myinterpose.so ./myapp LD_PRELOAD=/path/to/libfaketime.so ./myapp # time manipulation
undefined

7. Symbol visibility control

7. 符号可见性控制

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
undefined
限制导出的符号可减小二进制文件大小并避免符号冲突:
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; }
或使用链接器版本脚本:
text
undefined

mylib.map

mylib.map

MYLIB_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
MYLIB_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 symbols

Check exported symbols

nm -D --defined-only libmylib.so objdump -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
nm -D --defined-only libmylib.so objdump -T libmylib.so

默认使用`-fvisibility=hidden`编译,并显式标记公共API:

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

8. Common errors

8. 常见错误

ErrorCauseFix
cannot open shared object file
Library not in search pathSet RPATH,
LD_LIBRARY_PATH
, or run
ldconfig
symbol lookup error: undefined symbol
Missing library or wrong versionCheck
ldd
, add
-l
flag or fix link order
FATAL: kernel too old
Version requirement mismatchRebuild against older glibc
relocation R_X86_64_32 against .rodata
Non-PIC code in shared libAdd
-fPIC
to compilation
version 'GLIBC_2.29' not found
Binary built on newer glibcRebuild on older system or use
-static
For RPATH, soname, and
ld.so
configuration details, see references/ld-rpath-soname.md.
错误信息原因解决方法
cannot open shared object file
库不在搜索路径中设置RPATH、
LD_LIBRARY_PATH
,或执行
ldconfig
symbol lookup error: undefined symbol
缺少库或版本错误检查
ldd
输出,添加
-l
参数或修正链接顺序
FATAL: kernel too old
版本要求不匹配基于旧版glibc重新编译
relocation R_X86_64_32 against .rodata
共享库中包含非PIC代码编译时添加
-fPIC
参数
version 'GLIBC_2.29' not found
二进制文件基于新版glibc构建在旧系统上重新编译,或使用
-static
参数
关于RPATH、soname和
ld.so
的配置细节,请参考references/ld-rpath-soname.md

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
  • 使用
    skills/binaries/elf-inspection
    检查共享库的段和符号
  • 使用
    skills/binaries/linkers-lto
    了解链接器标志和符号解析
  • 使用
    skills/binaries/binutils
    对共享库执行
    nm
    objdump
    strip
    操作
  • 使用
    skills/compilers/gcc
    了解
    -fPIC
    -shared
    及相关编译标志