linux下动态库
今天无意间发现在linux下share object(dynamic library)中的函数竟然可以不通过回调的方式直接访问主程序中的函数,瞬间颠覆以前对于动态库的观念.
1、如下代码所示,ibhi.so中有一个函数hello, 主程序main中有一个函数hi_out, 那么在main中调用libhi.so中的hello时,hello会自动找到main程序中的hi_output函数地址, 然后进行调用.
=================hi.c 编译为 libhi.so===========
extern void hi_out();
void hello(){
hi_out();
...;
}
=================main.c 编译为 elf_lnk=============
#include <stdio.h>
void hi_out(){
printf("hi out.\n");
}
extern void hello(); //语句1
int main() {
hello(); //语句2
return 0;
}
利用命令gcc -shared -fPIC -o libhi.so hi.c 把hi.c源文件编译成libhi.so
利用命令gcc main.c -L. -lhi -Wl,-rpath,. -o elf_lnk 把main.c和libhi.so一起编译链接,生成可执行文件(elf_lnk)指向动态库libhi.so
在感叹linux下动态库强大的同时, 对于其实现机制也产生了好奇. 经过一番努力终于在《程序员的自我修养》中第7.6.2章找到答案.
“动态链接器在完成基本自举后, 动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表中, 我们可以称它为全局符号表(Global Symbol Table)…..当一个新的share object被装载进来的时侯, 它的符号表会被合并到全局符号表中”, 因此其实libhi.so在调用hello函数时实际上是从全局符号表中找到hi_out函数的地址并进行调用, 本质上libhi.so并不知道这个hi_out是属于另一个share object还是属于main程序中.
2、但当我使用dlopen系列函数动态加载libhi.so时, 却总是加载失败提示找不到hi_out函数. 理论上静态加载与动态加载上的行为应该是一样的, 只不过静态加载时dlopen将会被隐式调用而已.
代码如下,把上面的语句1和语句2删除,增加了一些dlopen的代码。
利用命令gcc main.c -ldl -o elf_dl 编译成可执行文件elf_dl,运行时会报错 语句3的打印
load dlopen(/root/libhi.so): /root/libhi.so: undefined symbol: hi_out
=================hi.c 编译为 libhi.so===========
extern void hi_out();
void hello(){
hi_out();
...;
}
=================main.c 编译为 elf_dl=============
#include <stdio.h>
#include <dlfcn.h> //dlopen的头文件
void hi_out(){
printf("hi out.\n");
}
int main() {
const char* pstr = "/root/libhi.so";
void *library = dlopen(pstr, RTLD_NOW); //如果用RTLD_LAZY,则会在语句4报错
if (!library ) {
printf("load dlopen(%s): %s\n", pstr, dlerror() ); //语句3,RTLD_NOW模式在这报错
return -1;
}
void (*pfun) ();
pfun = (void(*)()) dlsym(library, "hello");
if (pfun){
pfun(); //语句4
}
return 0;
}
在 ld手册 找到了答案, ld在生成可执行文件时, 默认只导出被其他动态库使用的符号. 因为是使用dlopen去动态加载libhi.so, 那么链接时ld并不知道可执行文件中的hi_out会被外部引用, 也就不会导出hi_out到动态符号表去. 当dlopen打开libhi.so时, 动态链接器在全局符号表中找不到hi_out符号, 理所当然就报错了.
要解决这个问题只要给链接器加上参数-E将主程序中所有全局符号放到动态符号表中即可, 由于生成可执行文件一般都是gcc直接生成, 因此可以使用gcc -Wl,-E来将-E参数传给ld来完成创建一个可以被动态链接的可执行文件.
编译主程序: gcc main.c -Wl,-E -ldl -o elf_dl , 编译成可执行文件elf_dl,运行ok。
或者把-Wl,-E换成 -rdynamic,可以实现一眼的效果,用来通知链接器,把全部符号加入到动态符号表中(目的是dlopen的so库可以通过使用这些符号)
3、后记
可以用nm,或者ldd来查看libhi.so里面的未定义符号
[root]# nm libhi.so | grep hi_out
U hi_out
[root]# ldd -r libhi.so
....省略
undefined symbol: hi_out (./libhi.so)