dlopen动态加载共享库出现符号未定义错误
本文最后更新于:1 分钟前
dlopen动态加载共享库出现符号未定义错误
问题描述
有两个动态库,libfunc.so 和 libbase.so。其中 libfunc.so 依赖 libbase.so,在 libfunc.so 中有一个函数 void func()
调用了 libbase.so 中的函数 void base()
,如下:
1 |
|
1 |
|
在 main.c 中通过 dlopen
函数动态加载 libfunc.so,并调用其中的 func
函数:
1 |
|
但是在编译后提示 undefined symbol: base
错误:
1 |
|
问题分析
由于 main.c 中并没有显式依赖 libfunc.so 和 libbase.so,所以在编译 main.c 的命令中没有进行显式链接这两个库。因此在运行时,dlopen
函数只加载了 libfunc.so,而没有加载 libbase.so,导致在 libfunc.so 中调用 libbase.so 中的函数时出现符号未定义错误。
那么我们尝试将这两个库都进行显式链接,看看能否解决问题:
1 |
|
可以看到仍然提示 base
符号未定义。
根因分析
由于 main.c 通过 dlopen()
加载 libfunc.so,所以不存在显式的依赖关系,我们甚至不需要在 main.c 中包含 func.h 这个头文件。因此在链接过程中,链接器会认为当前的 libfunc.so 和 main.o 无关,并将 libfunc.so 给优化掉。既然 libfunc.so 被视为无关项,那 libbase.so 更没有理由被留下来了。
也就是说,即使我们在命令中显式地去链接 -L. -lfunc -lbase
也是无效的。我们可以通过 readelf -d
命令查看 test 可执行文件的依赖:
1 |
|
因此当执行 func()
函数时,由于 libbase.so 没有被加载,所以会提示 undefined symbol: base
错误。
解决方案
libfunc.so 可以通过 dlopen()
被加载,但是 libbase.so 本身与 main.c 无关,因此我们没有理由通过 dlopen()
来加载 libbase.so。
那有没有什么办法可以让 libbase.so 被加载进来呢?
1、 使用 LD_PRELOAD 强制预加载
LD_PRELOAD
是一个环境变量,用于在运行程序时预先加载指定的共享库。它的作用是在运行目标程序时优先加载指定的共享库,覆盖系统默认的库,从而实现对目标程序的功能增强、修改或者监控等目的。使用 LD_PRELOAD 可以在不修改目标程序源代码的情况下,对目标程序的行为进行改变。
我们可以通过设置 LD_PRELOAD
环境变量来强制预加载 libbase.so:
1 |
|
可以看到此时程序正常运行,base
函数也被调用了。
但是这样有个问题,如果 libfunc.so 依赖了多个共享库,那么我们就需要将所有的共享库都通过 LD_PRELOAD
预加载进来这样就会变得非常麻烦。
因此是否有其他的方法来解决这个问题呢?
2. -Wl,–no-as-needed 选项
在部分默认情况下,链接器会尽可能地链接被指定的库,但如果这个库的符号没有被使用到,链接器也不会链接它,可以认为,链接器在默认状态下使用了 --as-needed
选项对库进行链接。
这种情况导致了我们即使显式链接 libfunc.so 和 libbase.so,由于 main.c 并没有使用这两个库符号,所以链接器会将这两个库优化掉。
因此我们可以通过 -Wl,--no-as-needed
选项来关闭这个优化,使得可执行文件强制保持这个依赖关系:
1 |
|
此时程序正常运行,我们再通过 readelf -d
命令查看 test 可执行文件的依赖:
1 |
|
可以看到此时 test 可执行文件已经显式依赖了 libfunc.so 和 libbase.so 两个共享库。
此时还有一个问题,我们将这个依赖关系交给了应用去链接处理,当模块数量较多,且各个模块有各自的依赖时,应用的链接命令会变得非常复杂,并且应用本身并不想关心各个模块自己的依赖关系,它只想维护自己关心的模块即可。
是否还有其他的方式进行优化解决呢?
3. 构建 libfunc.so 时指定依赖
既然应用不想关心这些依赖关系,那么只能由 libfunc.so 自己来管理自己的依赖关系了。我们可以在构建 libfunc.so 时指定依赖的库,这样在链接 libfunc.so 时,链接器会自动将这些依赖的库链接进来。
我们只需要在编译 libfunc.so 时显式链接它的所有依赖库即可。我们先看看不链接依赖库时,libfunc.so 的信息:
1 |
|
可以看到此时 libfunc.so 只依赖了 libc.so.6 这个库。
我们在编译命令上加上 -lbase
选项,显式链接 libbase.so:
1 |
|
此时 libfunc.so 显式依赖了 libbase.so 这个库。
我们再来编译运行一下 test 试试:
1 |
|
可以看到此时可执行文件 test 并没有显式依赖 libfunc.so 和 libbase.so,通过 dlopen
加载 libfunc.so 时,libbase.so 也被加载进来了,因此程序正常运行。
总结
在使用 dlopen
函数动态加载共享库时,如果共享库之间存在依赖关系,需要注意以下几点:
- 如果共享库之间存在依赖关系,可以通过
LD_PRELOAD
环境变量来强制预加载依赖库。 - 如果共享库之间存在依赖关系,可以通过
-Wl,--no-as-needed
选项来关闭链接器的优化,强制保持这个依赖关系。 - 如果共享库之间存在依赖关系,可以在构建共享库时显式链接它的依赖库,这样在加载共享库时,依赖库也会被加载进来。
在模块化开发中,我们应该尽量使用第三种方法进行处理,将依赖关系交给模块自己去管理,而不是交给应用去处理。
以上就是本文的全部内容,希望对你有所帮助。