共享库构造函数在dlopen加载时无法找到符号的问题排查
1. 编译时链接与运行时加载的差异
- 编译时链接:
- 链接器在编译阶段完成符号绑定,主程序与共享库的符号表会被合并处理。主程序的全局符号(如
test)会被标记为可被共享库引用,链接器直接将共享库对test的引用绑定到主程序的对应符号地址。 - 共享库的构造函数在程序启动、
main执行前由加载器自动调用,此时整个进程的符号空间已完全初始化,共享库可直接访问主程序导出的符号。
- 链接器在编译阶段完成符号绑定,主程序与共享库的符号表会被合并处理。主程序的全局符号(如
- 运行时加载(dlopen):
- 共享库在程序运行到
dlopen调用时才被加载到进程空间,符号绑定规则由dlopen参数和进程符号可见性规则决定。 - 默认情况下,动态加载的共享库仅能从自身、已加载的其他共享库,以及全局符号表中查找未定义符号,而主程序的符号默认不会被加入全局符号表供动态加载的库引用。
- 共享库在程序运行到
2. 为什么dlopen无法找到test符号
核心原因是主程序的符号默认不对外暴露给动态加载的共享库:
- 编译主程序时,编译器默认将主程序的符号标记为“局部”,即使添加
__attribute__((used))也不会主动导出到进程的全局符号表。 dlopen加载liba.so时,构造函数so_init会被立即触发执行,此时它需要查找test符号,但主程序的test不在全局符号表中,因此触发“undefined symbol”错误。- 构造函数在
dlopen返回前就会执行,后续手动绑定符号的操作根本来不及生效,必须在加载阶段就解决符号依赖。
3. 解决方法
有两种可靠的解决方案:
方案一:编译主程序时导出所有全局符号
编译主程序时添加-rdynamic参数,让主程序将所有全局符号导出到全局符号表,供动态加载的共享库使用。修改Makefile的test目标:
test:m.c $(CC) -fPIC -c m.c -o m.o $(CC) m.o -fPIC -o test -ldl -rdynamic
这样dlopen加载liba.so时,就能从全局符号表找到主程序的test符号,构造函数可正常调用。
方案二:显式指定dlopen的符号查找范围
调用dlopen时使用RTLD_LAZY | RTLD_GLOBAL参数,让加载的共享库可以访问主程序的符号(建议结合-rdynamic使用,确保符号可见性和查找范围都覆盖):
void* so = dlopen("./liba.so", RTLD_LAZY | RTLD_GLOBAL);
内容的提问来源于stack exchange,提问作者yhz
相关产品推荐
相关产品推荐

