C/C++链接过程相关
1、dlclose(), dlerror(), dlopen(), dlsym()等:动态链接加载器的编程接口。链接时需要指定-ldl。
1)dlopen():
// 加载由filename指定的动态库文件,并返回其“句柄” // 程序中使用dlopen()多次加载同一个库时,返回相同的句柄 // 失败则返回NULL void *dlopen(const char *filename, int flag);
(1)若filename包含"/",则认为它是相对路径或绝对路径。否则,动态链接器(ld.so)按如下顺序搜索名为filename的库文件:
I、仅适用于ELF文件(可执行文件+可重定位的目标文件+core文件+共享库)。在ELF文件的动态节(dynamic section)中,当DT_RUNPATH属性没有指定时,使用DT_RPATH属性(如有)指定的目录。如:
~/programming/dlopen$ gcc main.c -ldl -Wl,-rpath=. <——-Wl,-rpath=.:将-rpath=.作为选项传递给链接器 ~/programming/dlopen$ readelf -d a.out Dynamic section at offset 0xf04 contains 26 entries: 标记 类型 名称/值 0x00000001 (NEEDED) 共享库:[libdl.so.2] 0x00000001 (NEEDED) 共享库:[libc.so.6] 0x0000000f (RPATH) Library rpath: [.] <——hard code的一个在运行时搜索库文件的路径 0x0000000c (INIT) 0x80483bc 0x0000000d (FINI) 0x8048624 ... ...
~/programming/dlopen$ chrpath -d a.out <——chrpath: change the rpath or runpath in binaries
~/programming/dlopen$ readelf -d a.out
Dynamic section at offset 0xf04 contains 25 entries:
标记 类型 名称/值
0x00000001 (NEEDED) 共享库:[libdl.so.2]
0x00000001 (NEEDED) 共享库:[libc.so.6]
0x0000000c (INIT) 0x80483bc
0x0000000d (FINI) 0x8048624
...
不建议使用。
II、程序启动时,如果环境变量LD_LIBRARY_PATH已经定义,则搜索它指定的目录。如:
~/programming/dlopen$ LD_LIBRARY_PATH=. ./a.out
~/programming/dlopen$ export LD_LIBRARY_PATH=. ~/programming/dlopen$ ./a.out
安全起见,对于SUID和SGID程序,这个变量被忽略。
另外,可以调用动态链接器/加载器并指定--library-path选项以覆盖LD_LIBRARY_PATH:
~/programming/dlopen$ /lib/ld-linux.so.2 --library-path .. ./a.out
III、仅适用于ELF文件。使用ELF文件的DT_RUNPATH动态节属性指定的目录。
IV、检查/etc/ld.so.cache(该文件由ldconfig更新)。它包含那些在ld.so.conf指定目录、/lib和/usr/lib中找到的库文件。
两个相关文件:/lib/ld-linux.so*:动态(运行时)链接器/加载器;/etc/ld.so.conf:它指定的目录被用于搜索库文件。
V、依次搜索/lib和/usr/lib。
(2)接下来是dlopen()的第二个参数flag:
I、下面两个值必须选其一:
RTLD_LAZY:延迟绑定(lazy binding)。仅当引用了符号的代码被执行时,才对符号进行解析(resolve)。延迟绑定只适用于函数,变量总是在加载库时被立即绑定。
RTLD_NOW:如果指定了该值,或者环境变量LD_BIND_NOW被设置成非空字符串,则将加载的库的所有未定义符号都在dlopen()返回前被解析。
II、还有以下可选的值:
RTLD_GLOBAL:该库定义的符号可用于后续加载的库的符号解析过程。
解析库的外部(external)引用时,使用该库的依赖列表和其他先前已使用RTLD_GLOBAL打开的库。如果可执行程序链接时使用-rdynamic,则它的全局符号也将被用于解析动态加载库中的引用。
RTLD_LOCAL:与RTLD_GLOBAL相反(也是这两个值中的默认值)。
RTLD_NODELETE(glibc 2.2之后):在dlclose()期间不卸载(unload)该库。这样的结果是,如果该库在后续被dlopen()重新加载,则它的静态变量不会重新初始化。这个flag不是POSIX.1-2001的标准。
RTLD_NOLOAD(glibc 2.2之后):不加载。测试该库是否已经加载:如果还没加载则dlopen()返回NULL,否则返回句柄。也可以用来改变已加载库的flag,如一个用RTLD_LOCAL加载的库,可用RTLD_NOLOAD | RTLD_GLOBAL重新打开。这个flag不是POSIX.1-2001的标准。
RTLD_DEEPBIND(glibc 2.3.4之后):优先在该库内查找符号,而不是在全局范围内。这意味着一个自包含的库将优先使用它自己的符号,而不是其他已加载库的同名全局符号。这个flag不是POSIX.1-2001的标准。
2)dlerror()
// 返回自初始化或上次调用该函数以来,描述dlopen()、dlsym()或dlclose()最近发生的错误的(人可读的)字符串
// 没有错误则返回NULL char *dlerror(void);
3)dlsym()
// 返回symbol被加载到内存的地址。若找不到该symbol,则返回NULL // dlsym()对库的依赖树进行宽度优先遍历,以查找该符号 void *dlsym(void *handle, const char *symbol);
测试是否出现错误的正确方法:调用dlerror(),清除旧的错误条件 -> 调用dlsym() -> 调用dlerror(),检查其返回值是否为NULL。
4)dlclose():减少动态库句柄handle的引用计数。
int dlclose(void *handle);
dl库维护着库句柄的引用计数。若某个库的引用计数变为0且其他加载的库没有在使用它的符号,则卸载该库。
glibc 2.2.3之后,atexit()可用于注册一个退出处理函数,它在库被卸载时自动调用。
5)例子:
// libfoo.cpp // extern "C"要求按C语言方式编译该部分代码。若没有这个限定,由于C++的函数重载,foo()的符号名将是_Z3foov // 此时dlopen.cpp中相应的行也应该改成*(void **) (&foo) = dlsym(handle, "_Z3foov"); extern "C" { void foo() { cout << "foo() in libfoo" << endl; } }
// dlopen.cpp int main(int argc, char **argv) { void (*foo)(); void *handle = dlopen("libfoo.so", RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); exit(EXIT_FAILURE); } dlerror(); /* Clear any existing error */ // foo = (void (*)(void)) dlsym(handle, "_Z3foov");这种写法更自然 // 但在C99标准下,从void *到函数指针的转换结果是未定义的 *(void **) (&foo) = dlsym(handle, "foo"); char *error; if ((error = dlerror()) != NULL) { fprintf(stderr, "%s\n", error); exit(EXIT_FAILURE); } (*foo)(); dlclose(handle); exit(EXIT_SUCCESS); }
编译运行:
~/programming$ g++ -o libfoo.so -shared libfoo.cpp ~/programming$ sudo mv libfoo.so /usr/lib ~/programming$ g++ dlopen.cpp -ldl ~/programming$ ./a.out foo() in libfoo
参考资料:
http://blog.csdn.net/dbzhang800/article/details/6918413
不断学习中。。。