dynamic-linking
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDynamic Linking
动态链接
Purpose
用途
Guide agents through Linux dynamic linking: shared library creation, RPATH/RUNPATH configuration, soname versioning, / plugin patterns, interposition, and symbol visibility control.
dlopendlsymLD_PRELOAD指导开发者掌握Linux动态链接相关知识:共享库创建、RPATH/RUNPATH配置、soname版本控制、/插件模式、函数拦截,以及符号可见性控制。
dlopendlsymLD_PRELOADTriggers
触发场景
- "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
undefinedbash
undefinedCompile 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
mylib.o -o libmylib.so.1.2.3
gcc -shared -Wl,-soname,libmylib.so.1
mylib.o -o libmylib.so.1.2.3
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
undefinedsudo cp libmylib.so.1.2.3 /usr/local/lib/
sudo ldconfig
undefined2. Soname versioning convention
2. Soname版本控制规范
text
libfoo.so.MAJOR.MINOR.PATCH
│
└── soname = libfoo.so.MAJOR| Version bump | When |
|---|---|
| PATCH | Bug fix, ABI unchanged |
| MINOR | New symbols added, backwards compatible |
| MAJOR | ABI 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 SONAMEtext
libfoo.so.MAJOR.MINOR.PATCH
│
└── soname = libfoo.so.MAJOR| 版本更新类型 | 适用场景 |
|---|---|
| PATCH | 修复Bug,ABI无变更 |
| MINOR | 添加新符号,向后兼容 |
| MAJOR | ABI发生破坏变更——现有二进制文件将无法正常运行 |
查看soname:
bash
readelf -d libmylib.so.1.2.3 | grep SONAME
objdump -p libmylib.so.1.2.3 | grep SONAME3. 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
undefinedtext
二者都会在二进制文件中嵌入库搜索路径。
RPATH → 优先于LD_LIBRARY_PATH被搜索
RUNPATH → 在LD_LIBRARY_PATH之后被搜索(运行时可控制)
推荐:优先使用RUNPATH(-Wl,--enable-new-dtags)
以提升部署灵活性。bash
undefinedEmbed RPATH (old default)
Embed RPATH (old default)
gcc main.c -L./lib -lmylib
-Wl,-rpath,'$ORIGIN/../lib' -o myapp
-Wl,-rpath,'$ORIGIN/../lib' -o myapp
gcc main.c -L./lib -lmylib
-Wl,-rpath,'$ORIGIN/../lib' -o myapp
-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
-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
-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/libDebug with:
bash
LD_DEBUG=libs ./myapp # trace library loading decisions
ldd myapp # show resolved libraries
ldd -v myapp # verbose with version requirementstext
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 requirements5. 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 :
-ldlbash
gcc main.c -ldl -o myappc
#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);
}链接时需添加参数:
-ldlbash
gcc main.c -ldl -o myapp6. LD_PRELOAD interposition
6. LD_PRELOAD函数拦截
LD_PRELOADc
// 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 -ldlLD_PRELOADc
// 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 -ldlApply to any binary
Apply to any binary
LD_PRELOAD=./myinterpose.so ./myapp
LD_PRELOAD=/path/to/libfaketime.so ./myapp # time manipulation
undefinedLD_PRELOAD=./myinterpose.so ./myapp
LD_PRELOAD=/path/to/libfaketime.so ./myapp # time manipulation
undefined7. 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
undefinedmylib.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.cMYLIB_1.0 {
global:
mylib_init;
mylib_process;
local:
*; # hide everything else
};
```bash
gcc -shared -fPIC -Wl,--version-script=mylib.map \
-o libmylib.so mylib.cCheck 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.sonm -D --defined-only libmylib.so
objdump -T libmylib.so
默认使用`-fvisibility=hidden`编译,并显式标记公共API:
```bash
gcc -shared -fPIC -fvisibility=hidden \
mylib.c -o libmylib.so8. Common errors
8. 常见错误
| Error | Cause | Fix |
|---|---|---|
| Library not in search path | Set RPATH, |
| Missing library or wrong version | Check |
| Version requirement mismatch | Rebuild against older glibc |
| Non-PIC code in shared lib | Add |
| Binary built on newer glibc | Rebuild on older system or use |
For RPATH, soname, and configuration details, see references/ld-rpath-soname.md.
ld.so| 错误信息 | 原因 | 解决方法 |
|---|---|---|
| 库不在搜索路径中 | 设置RPATH、 |
| 缺少库或版本错误 | 检查 |
| 版本要求不匹配 | 基于旧版glibc重新编译 |
| 共享库中包含非PIC代码 | 编译时添加 |
| 二进制文件基于新版glibc构建 | 在旧系统上重新编译,或使用 |
关于RPATH、soname和的配置细节,请参考references/ld-rpath-soname.md。
ld.soRelated skills
相关技能
- Use to inspect shared library sections and symbols
skills/binaries/elf-inspection - Use for linker flags and symbol resolution
skills/binaries/linkers-lto - Use for
skills/binaries/binutils,nm,objdumpon shared libsstrip - Use for
skills/compilers/gcc,-fPICand related compiler flags-shared
- 使用检查共享库的段和符号
skills/binaries/elf-inspection - 使用了解链接器标志和符号解析
skills/binaries/linkers-lto - 使用对共享库执行
skills/binaries/binutils、nm、objdump操作strip - 使用了解
skills/compilers/gcc、-fPIC及相关编译标志-shared