关于 dynamic-linking
为处理 Linux 动态链接问题的 Agent 提供操作指南,涵盖共享库、搜索路径、插件加载和符号控制。
主要功能
- 01创建共享库与 soname 版本管理
- 02配置 RPATH 和 RUNPATH
- 03使用 dlopen 与 dlsym 加载插件
- 04通过 LD_PRELOAD 拦截函数
- 05控制导出符号的可见性
- 06诊断常见链接错误
适用场景
- 01解决程序找不到共享库的问题
- 02为应用配置可重定位的库路径
- 03构建运行时插件加载机制
- 04排查符号缺失和 ABI 版本问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill dynamic-linking安装后在 Claude Code 中输入/dynamic-linking确认已加载。
附带文件references/ld-rpath-soname.md
详细介绍
动态链接
用途
指导 Agent 完成 Linux 动态链接:创建共享库、配置 RPATH/RUNPATH、soname 版本管理、dlopen/dlsym 插件模式、LD_PRELOAD 拦截,以及符号可见性控制。
触发条件
- “Cannot open shared object file: No such file or directory”
- “如何设置 RPATH,让二进制文件找到它的共享库?”
- “如何使用 dlopen/dlsym 构建插件系统?”
- “RPATH 和 RUNPATH 有什么区别?”
- “如何使用 LD_PRELOAD 拦截函数?”
- “如何使用 soname 为共享库进行版本管理?”
工作流
1. 创建共享库
# Compile with -fPIC (position-independent code)
gcc -fPIC -c src/mylib.c -o mylib.o
# Link shared library with soname
gcc -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 版本管理约定
libfoo.so.MAJOR.MINOR.PATCH
│
└── soname = libfoo.so.MAJOR
| 版本变更 | 适用情况 |
|---|---|
| PATCH | 修复错误,ABI 不变 |
| MINOR | 添加新符号,向后兼容 |
| MAJOR | ABI 不兼容——现有二进制文件将无法运行 |
检查 soname:
readelf -d libmylib.so.1.2.3 | grep SONAME
objdump -p libmylib.so.1.2.3 | grep SONAME
3. RPATH 与 RUNPATH
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.
# 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
# Inspect
readelf -d myapp | grep -E 'RPATH|RUNPATH'
chrpath -l myapp # show
chrpath -r '/new/path' myapp # modify existing
$ORIGIN 在运行时会解析为二进制文件所在目录,可用于创建可重定位安装。
4. 库搜索顺序
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
使用以下命令调试:
LD_DEBUG=libs ./myapp # trace library loading decisions
ldd myapp # show resolved libraries
ldd -v myapp # verbose with version requirements
5. dlopen / dlsym 插件模式
#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 链接:
gcc main.c -ldl -o myapp
6. LD_PRELOAD 拦截
LD_PRELOAD 会先于所有其他库加载指定库,其符号会覆盖应用程序中的符号。
// 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;
}
gcc -shared -fPIC -o myinterpose.so myinterpose.c -ldl
# Apply to any binary
LD_PRELOAD=./myinterpose.so ./myapp
LD_PRELOAD=/path/to/libfaketime.so ./myapp # time manipulation
7. 符号可见性控制
限制导出的符号,以减小二进制文件体积并避免冲突:
// 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; }
也可以使用链接器版本脚本:
# mylib.map
MYLIB_1.0 {
global:
mylib_init;
mylib_process;
local:
*; # hide everything else
};
gcc -shared -fPIC -Wl,--version-script=mylib.map \
-o libmylib.so mylib.c
# Check exported symbols
nm -D --defined-only libmylib.so
objdump -T libmylib.so
默认使用 -fvisibility=hidden 进行构建,并显式标记公共 API:
gcc -shared -fPIC -fvisibility=hidden \
mylib.c -o libmylib.so
8. 常见错误
| 错误 | 原因 | 修复方法 |
|---|---|---|
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。
相关技能
- 使用
skills/binaries/elf-inspection检查共享库的段和符号 - 使用
skills/binaries/linkers-lto了解链接器标志和符号解析 - 使用
skills/binaries/binutils对共享库使用nm、objdump、strip - 使用
skills/compilers/gcc了解-fPIC、-shared和相关编译器标志
评论
支持 Markdown