mcpField
D
dynamic-linking

dynamic-linking

github.com/mohitmishra786开发269611 次安装18 周前更新
代码仓库

关于 dynamic-linking

为处理 Linux 动态链接问题的 Agent 提供操作指南,涵盖共享库、搜索路径、插件加载和符号控制。

主要功能

  1. 01创建共享库与 soname 版本管理
  2. 02配置 RPATH 和 RUNPATH
  3. 03使用 dlopen 与 dlsym 加载插件
  4. 04通过 LD_PRELOAD 拦截函数
  5. 05控制导出符号的可见性
  6. 06诊断常见链接错误

适用场景

  1. 01解决程序找不到共享库的问题
  2. 02为应用配置可重定位的库路径
  3. 03构建运行时插件加载机制
  4. 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添加新符号,向后兼容
MAJORABI 不兼容——现有二进制文件将无法运行

检查 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