linux dlopen 相关
dlopen(3) - Linux手册页
名称
dladdr,dlclose,dlerror,dlopen,dlsym,dlvsym - 动态链接加载器的编程接口
概要
#include < dlfcn.h >
void * dlopen(const char * filename ,int flag );
char * dlerror(void);
void * dlsym(void * handle ,const char * symbol );
int dlclose(void * handle );
与-ldl链接。
描述
四个函数dlopen(),dlsym(),dlclose(),dlerror()实现了动态链接加载器的接口。
dlerror获得()
函数dlerror()返回一个人类可读的字符串,描述自从上次调用dlerror()以来dlopen(),dlsym()或 dlclose()发生的最新错误。如果从初始化或上次调用以来没有发生错误,它将返回NULL。
dlopen的()
函数dlopen()加载由以null结尾的字符串文件名命名的动态库文件,并为动态库返回一个不透明的“句柄”。如果文件名为NULL,则返回的句柄用于主程序。如果filename包含一个斜线(“/”),则它被解释为(相对或绝对)路径名。否则,动态链接器将按如下方式搜索库(有关更多详细信息,请参阅ld.so(8)):
Ø
(仅限ELF)如果调用程序的可执行文件包含DT_RPATH标记,并且不包含DT_RUNPATH标记,则搜索DT_RPATH标记中列出的目录。
Ø
如果在程序启动时,环境变量LD_LIBRARY_PATH被定义为包含冒号分隔的目录列表,则会搜索这些目录。(作为一个安全措施,这个变量在set-user-ID和set-group-ID程序中被忽略。)
Ø
(仅限ELF)如果调用程序的可执行文件包含DT_RUNPATH标记,则搜索该标记中列出的目录。
Ø
将检查缓存文件/etc/ld.so.cache(由ldconfig(8)维护)以查看它是否包含文件名条目。
Ø
搜索目录/ lib和/ usr / lib(按此顺序)。
如果库对其他共享库有依赖关系,那么它们也会由动态链接程序使用相同的规则自动加载。(这个过程可能会递归发生,如果这些库依次具有依赖性,依此类推。)
标志中必须包含以下两个值之一:
RTLD_LAZY执行延迟绑定。只有在执行引用它们的代码时才解析符号。如果符号从未被引用,那么它永远不会被解析。(懒惰绑定仅针对函数引用执行;对于变量的引用总是在加载该库时立即绑定。)RTLD_NOW如果指定了该值,或者环境变量LD_BIND_NOW设置为非空字符串,则在dlopen()返回之前解析库中的所有未定义符号。如果无法完成,则返回错误。以下值中的零个或多个值也可以在标志中进行或运算:RTLD_GLOBAL由该库定义的符号将可用于随后加载的库的符号解析。RTLD_LOCAL这是RTLD_GLOBAL的反过来,如果既没有指定标志,也是默认值。此库中定义的符号不可用于解决随后加载的库中的引用。RTLD_NODELETE(自glibc 2.2以来)在dlclose()期间不要卸载该库。因此,如果库稍后用dlopen()重新加载,则库的静态变量不会重新初始化 。该标志在POSIX.1-2001中未被指定。RTLD_NOLOAD(自glibc 2.2以来)不要加载库。这可以用来测试库是否已经驻留(dlopen()如果不是,则返回NULL,如果库驻留则返回库)。该标志也可用于提升已加载库的标志。例如,以前加载RTLD_LOCAL的库 可以使用RTLD_NOLOAD |重新打开 RTLD_GLOBAL。该标志在POSIX.1-2001中未被指定。RTLD_DEEPBIND(自glibc 2.3.4开始)将该库中符号的查找范围放在全局范围之前。这意味着一个独立的库将使用它自己的符号而不是全局符号,这些符号包含在已经加载的库中。该标志在POSIX.1-2001中未被指定。如果filename是一个NULL指针,那么返回的句柄就是主程序。当提供给dlsym()时,该句柄将导致在主程序中搜索一个符号,然后在程序启动时加载所有共享库,然后使用dlopen()加载标志为 RTLD_GLOBAL的所有共享库。
库中的外部引用通过使用该库的依赖项列表中的库以及之前使用RTLD_GLOBAL标志打开的其他库来解决 。如果可执行文件与标志“-rdynamic”(或同义词,“--export-dynamic”)链接,则可执行文件中的全局符号也将用于解析动态加载的库中的引用。
如果使用dlopen()再次加载相同的库,则会返回相同的文件句柄。dl库维护库句柄的引用计数,因此动态库不会被释放,直到dlclose()被调用多次,直到dlopen()成功完成为止。在_init()例程,如果存在的话,只调用一次。但随后使用RTLD_NOW调用可能会强制使用先前加载RTLD_LAZY的库的符号解析 。
如果dlopen()因任何原因失败,则返回NULL。
对dlsym()
函数dlsym()采用由dlopen()返回的动态库的“句柄” 和空终止的符号名称,并返回该符号加载到内存中的地址。如果未找到符号,则在指定库或dlopen()加载该库时自动加载的任何 库中,dlsym()返回NULL。(由dlsym()执行的搜索首先通过这些库的依赖树宽度)。由于符号的值实际上可以是NULL(所以dlsym()的NULL返回不需要指示错误),所以正确的方法测试错误是调用dlerror()清除任何旧的错误条件,然后调用dlsym(),然后再次调用dlerror(),将其返回值保存到变量中,并检查此保存的值是否不为NULL。
有两个特殊的伪句柄RTLD_DEFAULT和RTLD_NEXT。前者将使用默认库搜索顺序查找所需符号的第一个匹配项。后者将在当前库之后的搜索顺序中查找下一个函数。这使得人们可以在另一个共享库中的函数中提供一个包装。
dlclose()
函数dlclose()递减动态库句柄句柄的引用计数。如果引用计数下降到零,并且没有其他加载的库使用它中的符号,则动态库将被卸载。
函数dlclose()成功时返回0,错误时返回非零值。
过时的符号_init()和_fini()
链接器识别特殊符号_init和_fini。如果动态库导出名为_init()的例程,那么在dlopen()返回之前,该代码会在加载后执行。如果动态库导出名为_fini()的例程,那么在库被卸载之前调用该例程。如果您需要避免链接到系统启动文件,可以使用gcc(1)-nostartfiles 命令行选项完成此操作。
不建议使用这些例程或gcc- nostartfiles或-nostdlib选项。它们的使用可能会导致不希望的行为,因为构造函数/析构函数将不会执行(除非采取特殊措施)。
相反,库应该使用__attribute __((构造函数))和__attribute __((析构函数))函数属性导出例程。有关这些信息,请参阅gcc信息页面。构造函数例程在dlopen()返回之前执行,而析构函数例程在dlclose()返回之前执行 。
Glibc扩展名:dladdr()和dlvsym()
Glibc增加了POSIX未描述的两个功能,其中包含原型
#define _GNU_SOURCE / *请参阅feature_test_macros(7)* /
#include < dlfcn.h >
int dladdr(void * addr ,Dl_info * info );
void * dlvsym(void * handle ,char * symbol ,char * version );
/ *请参阅feature_test_macros(7)* /
#include < dlfcn.h >
int dladdr(void * addr ,Dl_info * info );
void * dlvsym(void * handle ,char * symbol ,char * version );
函数dladdr()接受函数指针并尝试解析它所在的名称和文件。信息存储在Dl_info 结构中:
typedef struct {
const char * dli_fname; / *共享对象的路径名
包含地址* /
void * dli_fbase; / *共享对象的地址
已加载* /
const char * dli_sname; / *地址最近的符号的名称
低于地址 * /
void * dli_saddr; / *指定符号的确切地址
在dli_sname * /
} Dl_info;
const char * dli_fname; / *共享对象的路径名
包含地址* /
void * dli_fbase; / *共享对象的地址
已加载* /
const char * dli_sname; / *地址最近的符号的名称
低于地址 * /
void * dli_saddr; / *指定符号的确切地址
在dli_sname * /
} Dl_info;
如果找不到符号匹配addr,则将dli_sname和dli_saddr设置为NULL。
dladdr()在错误时返回0,在成功时返回非零值。
函数dlvsym(),自2.1版以来由glibc提供,与dlsym()相同,但将版本字符串作为附加参数。
符合
POSIX.1-2001介绍了dlclose(),dlerror(),dlopen()和dlsym()。
笔记
符号RTLD_DEFAULT和RTLD_NEXT被定义< dlfcn.h中 >仅当_GNU_SOURCE包括之前被定义。
由于glibc 2.2.3,atexit(3)可用于注册卸载库时自动调用的退出处理函数。
历史
dlopen接口标准来自SunOS。该系统也有dladdr(),但不包括dlvsym()。
错误
有时候,你传递给dladdr()的函数指针可能会让你感到惊讶。在某些体系结构(特别是i386和x86_64)中,即使用作参数的函数应来自动态链接库,dli_fname和 dli_fbase最终可能会指向您称为dladdr()的对象。
问题在于函数指针在编译时仍然会被解析,但只是指向原始对象的plt(过程链接表)部分(在请求动态链接器解析该符号后调度该调用)。要解决这个问题,你可以尝试将代码编译为位置无关的:然后,编译器不能在编译时间准备好指针,今天的gcc(1)将生成代码,只是从得到的代码中加载最后的符号地址(全局偏移量表)在传递给dladdr()之前的运行时间。
例
加载数学库,并打印2.0的余弦:
#include < stdio.h >
#include < stdlib.h >
#include < dlfcn.h >
INT
main(int argc,char ** argv)
{
void * handle;
双(*余弦)(双);
char *错误;
handle = dlopen(“libm.so”,RTLD_LAZY);
if(!handle){
fprintf(stderr,“%s \ n”,dlerror());
出口(EXIT_FAILURE);
}
dlerror获得(); / *清除任何现有的错误* /
/ *写入:cosine =(double(*)(double))dlsym(句柄,“cos”);
似乎更自然,但C99标准离开了
从“void *”转换为未定义的函数指针。
下面使用的任务是POSIX.1-2003(技术文档
勘误1)解决方法; 请参阅
POSIX规范的dlsym()。* /
*(void **)(&cosine)= dlsym(句柄,“cos”);
如果((error = dlerror())!= NULL){
fprintf(stderr,“%s \ n”,错误);
出口(EXIT_FAILURE);
}
printf(“%f \ n”,(* cosine)(2.0));
dlclose(手柄);
出口(EXIT_SUCCESS);
}
stdio.h >
#include < stdlib.h >
#include < dlfcn.h >
INT
main(int argc,char ** argv)
{
void * handle;
双(*余弦)(双);
char *错误;
handle = dlopen(“libm.so”,RTLD_LAZY);
if(!handle){
fprintf(stderr,“%s \ n”,dlerror());
出口(EXIT_FAILURE);
}
dlerror获得(); / *清除任何现有的错误* /
/ *写入:cosine =(double(*)(double))dlsym(句柄,“cos”);
似乎更自然,但C99标准离开了
从“void *”转换为未定义的函数指针。
下面使用的任务是POSIX.1-2003(技术文档
勘误1)解决方法; 请参阅
POSIX规范的dlsym()。* /
*(void **)(&cosine)= dlsym(句柄,“cos”);
如果((error = dlerror())!= NULL){
fprintf(stderr,“%s \ n”,错误);
出口(EXIT_FAILURE);
}
printf(“%f \ n”,(* cosine)(2.0));
dlclose(手柄);
出口(EXIT_SUCCESS);
}
如果此程序位于名为“foo.c”的文件中,则可以使用以下命令构建程序:
gcc -rdynamic -o foo foo.c -ldl
导出_init()和_fini()的库需要按如下方式编译,使用bar.c作为示例名称:
gcc -shared -nostartfiles -o bar bar.c
也可以看看
ld(1), ldd(1), dl_iterate_phdr(3), rtld-audit(7), ld.so(8), ldconfig(8)
ld.so信息页面,gcc信息页面,ld信息页面
以上内容 来自 https://linux.die.net/man/3/dlopen 翻译
更多推荐
所有评论(0)