内容简介:dlopen函数将名libfilename的共享库加载进调用进程的虚拟地址空间并增加该库的打开引用计数。flags的取值可以为RTLD_LAZY和RTLD_NOW使用命令生成libtest.so
动态加载库
核心dlopen API由以下函数(所有这些函数都在SUSv3进行了规定)构成。
dlopen()函数打开一个共享库,返回一个供后续调用使用的句柄。
dlsym()函数在库中搜索一个符号并返回其地址。
dlclose()函数关闭之前由dlopen()打开的库。
dlerror()函数返回一个错误消息字符串,在调用上述函数中的某个函数发生错误时可以使用这个函数来获取错误消息。dlopen函数
dlopen函数将名libfilename的共享库加载进调用进程的虚拟地址空间并增加该库的打开引用计数。
#include <dlfcn.h> void *dlopen(const char *libfilename, int flags);
如果filename包含了一个斜线(/),那么dlopen()会将其解释成一个绝对或相对路径名,否则动态链接器会按照相应的规则搜索共享库。
如果filename指定的共享库依赖于其它共享库,那么dlopen()会自动加载那些库。这种被加载进来的库被称为这个库的依赖树。
在同一个库文件中可以多次调用dlopen(),但dlopen API会为每个库句柄维护一个引用计数,每次调用dlopen()时都会增加引用计数,每次调用dlclose()都会减小引用计数,只有当计数为0时dlclose()才会从内存中删除这个库。flags的取值可以为RTLD_LAZY和RTLD_NOW
RTLD_LAZY:
只有当代码被执行的时候才解析库中未定义的函数符号。延迟解析只适用于函数引用,对变量的引用会被立即解析。RTLD_NOW:
在dlopen()结束之前立即加载库中所有的未定义符号,不管是否需要用到这些符号,这种做法的结果是打开库变得更慢了,但能够立即检测到任何潜在的未定义函数符号错误,而不是在后面某个时刻使用时检测到这种错误。在调试应用程序时这种做法是比较有用的,因为它能够确保应用程序在碰到未解析的符号时立即发生错误,而不是在执行了很长一段时间之后才发生错误。
我们可以结合如下例子理解RTLD_LAZY和RTLD_NOW之间的区别:void bar(); void foo() { bar(); }
使用命令生成libtest.so
[root@centos-7 shared]# gcc -g -c -fPIC -Wall test.c [root@centos-7 shared]# gcc -g -shared -o libtest.so test.o
$> nm libtest.so 000085d0 a _DYNAMIC 000086b0 a _GLOBAL_OFFSET_TABLE_ w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable w _Jv_RegisterClasses 000005c0 r __FRAME_END__ 000085cc d __JCR_END__ 000085cc d __JCR_LIST__ 000086e0 d __TMC_END__ 000086e4 B __bss_end__ 000086e0 B __bss_start 000086e0 B __bss_start__ w __cxa_finalize@@GLIBC_2.4 000004f8 t __do_global_dtors_aux 000085c8 t __do_global_dtors_aux_fini_array_entry 000086dc d __dso_handle 000086e4 B __end__ 000085c4 t __frame_dummy_init_array_entry w __gmon_start__ 000086e4 B _bss_end__ 000086e0 D _edata 000086e4 B _end 000005b8 T _fini 000003e0 T _init U bar 00000424 t call_weak_fn 000086e0 b completed.8847 00000448 t deregister_tm_clones 000005a8 T foo 00000560 t frame_dummy 0000049c t register_tm_clones
T 该符号位于代码区text section。
U 该符号在当前文件中是未定义的,即该符号的定义在别的文件中。main.c (flag == RTLD_LAZY): #include <dlfcn.h> #include <stdio.h> int main(int argc, char **argv) { void *lib = dlopen("./libtest.so", RTLD_LAZY); if (!lib) { printf("error: %s\n", dlerror()); return 0; } int (*a)() = dlsym(lib, "foo"); printf("a: %p\n", a); (*a)(); dlclose(lib); return 1; }
使用命令gcc -g -o main main.c -ldl
运行结果如下:[root@centos-7 shared]# ./main a: 0x7f6df68ac665 ./main: symbol lookup error: ./libtest.so: undefined symbol: bar
如果将main函数dlopen的flag修改为RTLD_NOW:
main.c#include <dlfcn.h> #include <stdio.h> int main(int argc, char **argv) { void *lib = dlopen("./libtest.so", RTLD_NOW); if (!lib) { printf("error: %s\n", dlerror()); return 0; } int (*a)() = dlsym(lib, "foo"); printf("a: %p\n", a); (*a)(); dlclose(lib); return 1; }
执行结果:
[root@centos-7 shared]# vi main.c [root@centos-7 shared]# gcc -g -o main main.c -ldl [root@centos-7 shared]# ./main error: ./libtest.so: undefined symbol: bar
dlsym()
#include <dlfcn.h> void *dlsym(void *handle, char *symbol);
常见用法:
如果symbol参数是一个变量的名称,那么可以使用dlsym返回的指针来调用该函数。可以将dlsym()返回的值存储到一个类型合适的指针中。int *ip; ip = (int *) dlsym(symbol, "myvar"); if (ip != NULL) printf("Value is %d\n", *ip);”
如果symbol参数是一个函数的名称,那么可以使用dlsym返回的指针来调用该函数。可以将dlsym()返回的值存储到一个类型合适的指针中.
int (*funcp)(int); /* Pointer to a function taking an integer argument and returning an integer */
由于C99标准进制函数指针和void *之间的赋值操作。可以使用如下方案解决:
*(void **) (&funcp) = dlsym(handle, symbol);
示例:
/*************************************************************************\ * Copyright (C) Michael Kerrisk, 2018. * * * * This program is free software. You may use, modify, and redistribute it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation, either version 3 or (at your option) any * * later version. This program is distributed without any warranty. See * * the file COPYING.gpl-v3 for details. * \*************************************************************************/ /* Listing 42-1 */ /* dynload.c Usage: dynload library-path function-name Demonstrate dynamic loading of libraries. The program loads the named library and then executes the named function in that library. */ #include <dlfcn.h> #include "tlpi_hdr.h" int main(int argc, char *argv[]) { void *libHandle; /* Handle for shared library */ void (*funcp)(void); /* Pointer to function with no arguments */ const char *err; if (argc != 3 || strcmp(argv[1], "--help") == 0) usageErr("%s lib-path func-name\n", argv[0]); /* Load the shared library and get a handle for later use */ libHandle = dlopen(argv[1], RTLD_LAZY); if (libHandle == NULL) fatal("dlopen: %s", dlerror()); /* Search library for symbol named in argv[2] */ (void) dlerror(); /* Clear dlerror() */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" funcp = (void (*)(void)) dlsym(libHandle, argv[2]); #pragma GCC diagnostic pop /* In the book, instead of the preceding line, the code uses a rather clumsy looking cast of the form: *(void **) (&funcp) = dlsym(libHandle, argv[2]); This was done because the ISO C standard does not require compilers to allow casting of pointers to functions back and forth to 'void *'. (See TLPI pages 863-864.) SUSv3 TC1 and SUSv4 accepted the ISO C requirement and proposed the clumsy cast as the workaround. However, the 2013 Technical Corrigendum to SUSv4 requires implementations to support casts of the more natural form (now) used in the code above. However, various current compilers (e.g., gcc with the '-pedantic' flag) may still complain about such casts. Therefore, we use a gcc pragma to disable the warning. Note that this pragma is available only since gcc 4.6, released in 2010. If you are using an older compiler, the pragma will generate an error. In that case, simply edit this program to remove the lines above that begin with '#pragma". See also the erratum note for page 864 at http://www.man7.org/tlpi/errata/. */ err = dlerror(); if (err != NULL) fatal("dlsym: %s", err); /* Try calling the address returned by dlsym() as a function that takes no arguments */ (*funcp)(); dlclose(libHandle); /* Close the library */ exit(EXIT_SUCCESS); }
dlclose()关闭共享库
dlclose()函数会减小handle所引用的库的打开引用的系统计数,如果这个引用计数变成了0并且其他库已经不需要用到该库中的符号了,那么就会卸载掉这个库。
获取与加载的符号相关的信息 dladdr()
#define _GNU_SOURCE #include <dlfcn.h> int dladdr(const void *addr, Dl_info *info);
typedef struct { const char *dli_fname; /* Pathname of shared library containing 'addr' */ void *dli_fbase; /* Base address at which shared library is loaded */ const char *dli_sname; /* Name of nearest run-time symbol with an address <= 'addr' */ void *dli_saddr; /* Actual value of the symbol returned in 'dli_sname' */ } Dl_info;
Dl_info结构中的前两个字段指定了包含地址addr的共享库的路径名和运行时基地址。最后两个字段返回地址相关的信息。假设addr指向共享库中一个符号的确切地址,那么dli_saddr返回的值与传入的addr值一样。
在主程序中访问符号
假设使用dlopen()动态加载了一个共享库,然后使用dlsym()获取了共享库中x()函数的地址,接着调用x()。如果在x()中调用了函数y(),那么通常会在程序加载的其中一个共享库中搜索y().
有些时候需要让x()调用主程序中的y(),为了达到这一目的就必须要使主程序中的符号对动态链接器可用,即在链接的时候使用--export-dynamic连接器选项。gcc -Wl,--export-dynamic main.c gcc -export-dynamic main.c
例子:
控制符号可见性
void __attribute__ ((visibility("hidden"))) func(void) { /* Code */ }
static关键字使一个符号私有于一个源代码模块,从而使得它无法被其它目标文件绑定。
GNU C编译器gcc提供了一个特有的特性声明,它执行与static关键字类似的任务,static关键词将一个符号的可见性限制在单个源代码模文件中,而hidden特性使得一个符号对构成共享库的所有源代码文件都可见,但对库之外的文件不可见。监控动态链接器:LD_DEBUG
查看LD_DEBUG的帮助信息:
$ LD_DEBUG=help date Valid options for the LD_DEBUG environment variable are: libs display library search paths reloc display relocation processing files display progress for input file symbols display symbol table processing bindings display information about symbol binding versions display version dependencies all all previous options combined statistics display relocation statistics unused determine unused DSOs help display this help message and exit
当请求与跟踪库相关的信息时会产生很多输出,下面的例子对输出进行了删减:
$ LD_DEBUG=libs date 10687: find library=librt.so.1 [0]; searching 10687: search cache=/etc/ld.so.cache 10687: trying file=/lib/librt.so.1 10687: find library=libc.so.6 [0]; searching 10687: search cache=/etc/ld.so.cache 10687: trying file=/lib/libc.so.6 10687: find library=libpthread.so.0 [0]; searching 10687: search cache=/etc/ld.so.cache 10687: trying file=/lib/libpthread.so.0 10687: calling init: /lib/libpthread.so.0 10687: calling init: /lib/libc.so.6 10687: calling init: /lib/librt.so.1 10687: initialize program: date 10687: transferring control: date Tue Dec 28 17:26:56 CEST 2010 10687: calling fini: date [0] 10687: calling fini: /lib/librt.so.1 [0] 10687: calling fini: /lib/libpthread.so.0 [0] 10687: calling fini: /lib/libc.so.6 [0]
每一行开头处的10687是指所跟踪的进程的进程ID,当监控多个进程时会用到这个值。
默认情况下LD_DEBUG的输出会被写到标准错误上,但可以将一个路径名赋值给环境变量LD_DEBUG_OUTPUT来将输出重定向。以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Chrome 75 Beta 发布,Web 共享功能支持共享文件
- ios – 如何使用OpenGL ES共享组在iPad上共享屏幕镜像的渲染缓冲区?
- nfs 共享实验
- 进程间通信---共享内存
- “伪共享” 凌乱记
- 共享测试的团队介绍
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
孵化Twitter
[美]尼克·比尔顿(Nick Bilton) / 欧常智、张宇、单旖 / 浙江人民出版社 / 2014-1 / 49.90元
一个在挣扎中生存的博客平台Odeo,一小撮龙蛇混杂的无政府主义者员工,经历了怎样的涅槃,摇身一变,成为纽交所最闪耀的上市企业Twitter? 一个野心勃勃的农场小男孩,一个满身纹身的“无名氏“,一个爱开玩笑的外交家,一位害羞而又充满活力的极客,这四位各有特色的创始人如何从兢兢业业、每日劳作的工程师,成为了登上杂志封面、奥普拉秀和每日秀的富裕名人?而在Twitter日益茁壮成长的过程中,他们又......一起来看看 《孵化Twitter》 这本书的介绍吧!