如何让dlopen正确加载待动态加载库的依赖库?
背景
我有两个共享对象:
- libbar.so:提供
int bar(char const*)函数 - libfoo.so:提供
int foo(char const*)函数,内部调用bar,因此依赖libbar.so
两个库都存放在非标准路径下,例如/whatever/path/to/libfoo.so和/yet/another/path/to/libbar.so。尝试通过dlopen加载并调用foo函数,但程序成功加载libbar.so并调用bar后,加载libfoo.so时报错找不到libbar.so。
测试代码
#include <stdio.h> #include <dlfcn.h> int main() { char const* libbar_path; void* libbar_so; int (*bar)(char const*); char const* libfoo_path; void* libfoo_so; int (*foo)(char const*); char const* err; libbar_path = get_libbar_path(); libfoo_path = get_libfoo_path(); libbar_so = dlopen(LIBBAR_SO, RTLD_GLOBAL | RTLD_NOW); if ((err = dlerror())) { fprintf(stderr, "failed to open libbar: %s\n", err); return 1; } bar = (int (*)(char const*)) dlsym(libbar_so, "bar"); if ((err = dlerror())) { fprintf(stderr, "failed to lookup for bar: %s\n", err); return 1; } printf("main bar: %d\n", bar("hello dlopen!")); libfoo_so = dlopen(LIBFOO_SO, RTLD_GLOBAL | RTLD_NOW); if ((err = dlerror())) { fprintf(stderr, "failed to open libfoo: %s\n", err); return 1; } foo = (int (*)(char const*)) dlsym(libfoo_so, "foo"); if ((err = dlerror())) { fprintf(stderr, "failed to lookup for foo: %s\n", err); return 1; } printf("main foo: %d\n", foo("hello dlopen!")); }
错误输出
$ gcc -ldl demo.c -o demo && ./demo bar: hello dlopen! main bar: 19 failed to open libfoo: libbar.so: cannot open shared object file: No such file or directory
核心结论
dlopen完全可以处理后续加载库的依赖问题,你的问题出在动态链接器的搜索逻辑:加载libfoo.so时,动态链接器会优先按系统默认路径查找它依赖的libbar.so文件,而不是直接复用进程中已加载的libbar.so符号——除非你明确告诉它怎么做。
具体解决方法
方法1:通过环境变量指定库路径
运行程序前,将两个库的路径加入LD_LIBRARY_PATH,让动态链接器能找到依赖:
export LD_LIBRARY_PATH="/yet/another/path:/whatever/path/to:$LD_LIBRARY_PATH" ./demo
这是最简单的方案,适合不需要程序内部控制的场景。
方法2:程序内部设置库搜索路径
在dlopen任何库之前,通过setenv设置LD_LIBRARY_PATH:
#include <stdlib.h> // 在main开头添加 setenv("LD_LIBRARY_PATH", "/yet/another/path:/whatever/path/to", 1);
注意:这个变量必须在第一次dlopen前设置,且部分系统(如SUID程序)会忽略用户设置的LD_LIBRARY_PATH。
方法3:使用RTLD_DEEPBIND标志加载libfoo.so
给dlopen添加RTLD_DEEPBIND标志(GNU扩展,Debian 8支持),让libfoo.so优先使用进程中已加载的符号,而非查找外部库文件:
libfoo_so = dlopen(LIBFOO_SO, RTLD_GLOBAL | RTLD_NOW | RTLD_DEEPBIND);
这个方案不需要修改环境变量或库文件,完全在程序内部控制。
方法4:修改libfoo.so的rpath(永久解决)
用patchelf工具修改libfoo.so的rpath,让它直接指向libbar.so的路径:
patchelf --set-rpath /yet/another/path /whatever/path/to/libfoo.so
修改后,libfoo.so会自动去指定路径查找libbar.so,无需程序做额外处理。
为什么原代码失败?
你虽然用RTLD_GLOBAL加载了libbar.so,但动态链接器处理libfoo.so的依赖时,仍会尝试从文件系统查找名为libbar.so的库——因为libfoo.so的动态节中记录的是依赖文件名,而非已加载的符号。只有通过上述方法告知动态链接器跳过文件查找、或能找到文件、或优先复用已加载符号,才能解决问题。
内容的提问来源于stack exchange,提问作者Karl Liu

