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_DEFAULTRTLD_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_snamedli_saddr设置为NULL。

dladdr()在错误时返回0,在成功时返回非零值。

函数dlvsym(),自2.1版以来由glibc提供,与dlsym()相同,但将版本字符串作为附加参数。

符合

POSIX.1-2001介绍了dlclose(),dlerror(),dlopen()和dlsym()。

笔记

符号RTLD_DEFAULTRTLD_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  翻译

猜你喜欢

转载自blog.csdn.net/teleger/article/details/80857900