LD_PRELOAD能否替换通过dlopen()/dlsym()解析的函数?
关于LD_PRELOAD拦截dlsym解析函数的问题解答
你的猜测完全正确——默认情况下,LD_PRELOAD确实无法拦截通过dlsym()直接从指定共享库句柄中解析的函数。下面我会详细解释原因,以及给出可行的解决办法:
为什么默认情况下拦截失败?
LD_PRELOAD的工作原理是让动态链接器优先加载预加载库中的符号,覆盖其他库中的同名符号,但这个优先级只作用于常规的全局符号解析流程(比如程序直接调用未定义的符号,或者动态链接器自动解析依赖库的符号)。
而dlsym(handle, "func1")是直接从你传入的liba.so句柄对应的符号表中查找符号,完全跳过了全局符号的查找逻辑。动态链接器不会因为LD_PRELOAD有同名符号就替换这个直接查找的结果,所以你的libb.so里的func1()不会被调用。
可行的解决方案
方案1:修改liba.so,将func1设为弱符号
如果你有权限修改liba.so的源代码或编译流程,可以把func1()定义为弱符号,这样LD_PRELOAD中的强符号就能覆盖它。
具体操作:
- 在
liba.so的func1()定义前添加__attribute__((weak))属性:__attribute__((weak)) void func1() { // 原函数实现 } - 重新编译
liba.so。
此时,当程序通过dlsym()查找func1时,动态链接器会优先使用全局符号表中LD_PRELOAD提供的强符号(即libb.so的func1()),而不是liba.so里的弱符号。
方案2:拦截dlopen()和dlsym()本身(无需修改原程序或liba.so)
如果无法修改liba.so或原程序,最通用的方法是在libb.so中重新实现dlopen()和dlsym(),拦截对liba.so中func1()的查找请求,替换成自己的函数实现。
实现步骤:
编写
libb.c代码,拦截两个函数并保存原函数指针:#define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> #include <string.h> // 保存原函数的指针,避免递归调用 void* (*original_dlopen)(const char*, int) = NULL; void* (*original_dlsym)(void*, const char*) = NULL; // 我们要替换的func1实现 void func1() { printf("[HOOKED] This is func1 from libb.so\n"); } // 拦截dlopen void* dlopen(const char* filename, int flags) { // 初始化原函数指针(只执行一次) if (!original_dlopen) { original_dlopen = dlsym(RTLD_NEXT, "dlopen"); if (!original_dlopen) { fprintf(stderr, "Failed to get original dlopen: %s\n", dlerror()); return NULL; } } // 可选:打印调试信息 printf("Intercepted dlopen for: %s\n", filename); return original_dlopen(filename, flags); } // 拦截dlsym void* dlsym(void* handle, const char* symbol) { if (!original_dlsym) { original_dlsym = dlsym(RTLD_NEXT, "dlsym"); if (!original_dlsym) { fprintf(stderr, "Failed to get original dlsym: %s\n", dlerror()); return NULL; } } // 判断是否是查找liba.so中的func1 if (symbol && strcmp(symbol, "func1") == 0) { printf("Hooking func1 request\n"); return (void*)func1; } // 其他符号请求转发给原dlsym return original_dlsym(handle, symbol); }编译
libb.so:gcc -shared -fPIC -ldl libb.c -o libb.so运行程序时预加载
libb.so:LD_PRELOAD=./libb.so ./your_target_program
注意事项:
RTLD_NEXT是GNU扩展,需要定义_GNU_SOURCE才能使用,它用于获取当前库之后加载的原函数指针,避免递归调用。- 如果程序使用
liba.so的绝对路径调用dlopen(),你可能需要在dlopen拦截逻辑中匹配完整路径(比如/usr/lib/liba.so)。 - 这种方法可以拦截所有通过
dlsym()查找func1的请求,无论目标库是谁,如果你需要精准匹配liba.so的句柄,可以额外通过dladdr()等函数判断句柄对应的库路径。
内容的提问来源于stack exchange,提问作者Cillian
相关产品推荐
相关产品推荐

