通常我们需要从动态库里面直接调用可执行程序中的函数和变量,如果调用了-l选项,linux进程会自动把动态库的函数和变量加入到动态段中,所以直接访问是没有问题的。
我们这里要说的是非显示连接动态库,而是直接从c文件中通过dlopen函数打开动态库访问的方式,此时,gcc编译器不知道SO需要调用哪一个函数,所以不会讲函数放到动态段。故查找函数或者变量的时候,会出现找不到可执行程序中的符号的情况。
为什么会出现需要在c文件中直接dlopen动态库的情况呢? 这种情况一般出现在又不想重启进行,又需要有功能更新的情况。比如nginx服务器,在不重启web服务器的,需要增加一个filter模块,filter模块肯定是需要访问nginx内部函数和变量的,这种情况下,问题就引出来了。
如何共享变量呢?linux是通过什么机制来共享呢?我们通过一个例子来说明看看。
hello.c 用来编译成libhello.so
void hello() { share_fun(); }
main.c主函数
#include <stdio.h> #include <dlfcn.h> void share_fun() { printf("aa\n"); } main() { void *ptr = NULL; void (*hello)(void); ptr = dlopen("/home/mywork/libhello.so", RTLD_LAZY); if (ptr == NULL) { printf("error dlopen\n"); return -1; } hello = dlsym(ptr, "hello"); if (hello == NULL) { printf("error get fun\n"); return -1; } hello(); return 0; }
两个文件中我们可以看到,共享库libhello.so中访问了share_fun函数,而这个函数是定义在主文件中的。
我们通过以下命令讲hello.c编译成libhello.so
gcc -fPIC -shared libhello.so hello.c
通过以下命令生成main可执行文件。
gcc main.c -o main -ldl
执行结果
./main: symbol lookup error: /home/mywork/libhello.so: undefined symbol: share_fun
通过以下命令生成main可执行文件。
gcc main.c -o main -ldl -L./ -lhello
执行结果
aa
为什么会出现这种情况呢?先看看linux中动态库是如何访问未定义符号的,以下是libhello.so中share_fun在elf文件中的位置:
Relocation section ‘.rel.plt‘ at offset 0x374 contains 3 entries:
Offset Info Type Sym.Value Sym. Name
0000200c 00000207 R_386_JUMP_SLOT 00000000 share_fun
00002010 00000307 R_386_JUMP_SLOT 00000000 __cxa_finalize
00002014 00000407 R_386_JUMP_SLOT 00000000 __gmon_start__
The decoding of unwind sections for machine type Intel 80386 is not currently supported.
Symbol table ‘.dynsym‘ contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
2: 00000000 0 NOTYPE GLOBAL DEFAULT UND share_fun
3: 00000000 0 FUNC WEAK DEFAULT UND [email protected]_2.1.3 (2)
4: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
从上面可以看出share_fun因为在动态库中没有定义,从而被安置在了重定向段中,也就是说在编译时无法确定函数的准确地址,需要在运行加载时才能确定。
并且我们还可以看到share_fun被定为动态符号。
第一种情况下,main中动态符号并没有share_fun
[[email protected] mywork]# gcc main.c -o main -ldl
[[email protected] mywork]# ./main
./main: symbol lookup error: /home/mywork/libhello.so: undefined symbol: share_fun
[[email protected] mywork]# nm -D main
0804867c R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
w __gmon_start__
U __libc_start_main
U dlopen
U dlsym
U puts
第二种情况下,main中动态符号表里面含有share_fun
[[email protected] mywork]# gcc main.c -o main -ldl -L./ -lhello
[[email protected] mywork]# ./main
aa
[[email protected] mywork]# nm -D main
0804873c R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
w _Jv_RegisterClasses
0804a024 B __bss_start
w __gmon_start__
U __libc_start_main
0804a024 D _edata
0804a028 B _end
08048724 T _fini
0804847c T _init
U dlopen
U dlsym
U puts
08048610 T share_fun
从上可以看到动态库中访问主程序的函数时,符号的共享是通过动态段来实现的,当没有动态段时,是无法实现共享的,函数和变量都是一样。
从原理上讲,动态库或者主程序中没有定义的函数,都会在重定向表中,需要在程序中加载阶段进行动态映射,所以主程序和动态库共享和静态编译是相冲突的。
知道了原理,要解决办法就容易了,以下编译选项都能将符号导出到动态段,从而实现主程序和动态库符号访问。
-Bsymbolic
When creating a shared library, bind references to global symbols to the definition within the shared library, if any. Normally, it is possible for a
program linked against a shared library to override the definition within the shared library. This option is only meaningful on ELF platforms which
support shared libraries.
-Bsymbolic-functions
When creating a shared library, bind references to global function symbols to the definition within the shared library, if any. This option is only
meaningful on ELF platforms which support shared libraries.
--dynamic-list=dynamic-list-file
Specify the name of a dynamic list file to the linker. This is typically used when creating shared libraries to specify a list of global symbols whose
references shouldn‘t be bound to the definition within the shared library, or creating dynamically linked executables to specify a list of symbols which
should be added to the symbol table in the executable. This option is only meaningful on ELF platforms which support shared libraries.
The format of the dynamic list is the same as the version node without scope and node name. See VERSION for more information.
--dynamic-list-data
Include all global data symbols to the dynamic list.