共享对象显式调用内部函数问题:重写libC函数调用指向系统libC
这绝对是动态链接符号解析里的经典坑——我当初重写内存分配函数时也踩过一模一样的雷,尤其是calloc这种本身依赖其他libC接口的函数,特别容易出现内部调用跑去找系统库版本的情况。
问题根源
当你通过dlsym获取自己共享库中的calloc时,函数内部的同名调用(或者依赖的其他重写函数,比如malloc),默认情况下动态链接器会按照全局符号查找顺序来解析:系统libC的符号优先级高于你后加载的共享库,所以内部调用会直接跳去系统版本,而不是你重写的实现。
calloc之所以格外突出,是因为标准libC的calloc通常基于malloc实现,而且部分实现里还有自调用逻辑(比如做内存对齐或参数校验时),这就导致只要符号绑定没处理好,很容易就绕回系统库了。
三种可行解决方案
1. 编译共享库时使用-Bsymbolic-functions(最推荐)
这个链接器选项会告诉动态链接器:当前库内部的函数调用,优先绑定到库自身的符号,而不是去全局符号表找系统版本。
修改你的库编译命令:
gcc lib.c -fPIC -shared -Wl,-Bsymbolic-functions -o lib.so
这个方案从根源上解决了内部符号绑定的问题,几乎没有副作用(除非你的库依赖其他外部库的同名函数,但重写libC函数的场景下完全适用)。
2. 加载库时使用RTLD_DEEPBIND标志
如果你不想修改库的编译参数,可以在main.c里调用dlopen时加上RTLD_DEEPBIND,让加载的库优先使用自身的符号:
void* handle = dlopen("./lib.so", RTLD_LAZY | RTLD_DEEPBIND);
这个选项会让当前库的符号解析优先级高于全局符号表,同样能避免内部调用跳去系统libC。
3. 显式绑定内部函数指针(适合复杂场景)
如果你的重写逻辑有特殊需求(比如动态切换实现),可以在库内部显式获取自身的符号,避免依赖链接器的自动解析:
#include <dlfcn.h> #include <string.h> // 先声明自己的重写函数 void* my_malloc(size_t size); void* my_calloc(size_t nmemb, size_t size) { // 显式获取本库中的malloc符号 static void* (*local_malloc)(size_t) = NULL; if (!local_malloc) { // RTLD_SELF表示当前共享库 local_malloc = dlsym(RTLD_SELF, "my_malloc"); } size_t total = nmemb * size; void* ptr = local_malloc(total); if (ptr) { memset(ptr, 0, total); } return ptr; } // 你的malloc重写实现 void* my_malloc(size_t size) { // ... 自定义逻辑 }
这种方式更灵活,但代码量会增加,适合需要精细控制符号解析的场景。
验证方法
修改编译/加载逻辑后,你可以在重写的calloc和依赖函数里加日志(比如printf("My custom calloc called\n");),运行main程序时如果能看到所有内部调用都打印自定义日志,就说明问题解决了。
内容的提问来源于stack exchange,提问作者Ra'Jiska

