DLOPEN - Linux手册页
Linux程序员手册 第3部分
更新日期: 2020-06-09
名称
dlclose,dlopen,dlmopen-打开和关闭共享对象
语法
#包括
void * dlopen(const char *文件名,int标志);
诠释dlclose(void * handle);
#定义_GNU_SOURCE
#包括
无效* dlmopen(Lmid_t lmid,const char * filename,int flags);
与-ldl链接。
说明
dlopen()
函数dlopen()加载以空终止的字符串文件名命名的动态共享对象(共享库)文件,并为所加载的对象返回不透明的"句柄"。该句柄与dlopen API中的其他函数一起使用,例如dlsym(3),dladdr(3),dlinfo(3)和dlclose()。
如果filename为NULL,则返回的句柄用于主程序。如果filename包含斜杠(" /"),则将其解释为(相对或绝对)路径名。否则,动态链接器将按以下方式搜索对象(有关更多详细信息,请参见ld.so(8)):
- o
- (仅ELF)如果调用程序的可执行文件包含DT_RPATH标记,但不包含DT_RUNPATH标记,则将搜索DT_RPATH标记中列出的目录。
- o
- 如果在程序启动时将环境变量LD_LIBRARY_PATH定义为包含用冒号分隔的目录列表,则将搜索这些目录。 (作为安全措施,对于set-user-ID和set-group-ID程序,将忽略此变量。)
- o
- (仅ELF)如果调用程序的可执行文件包含DT_RUNPATH标记,则将搜索该标记中列出的目录。
- o
- 检查高速缓存文件/etc/ld.so.cache(由ldconfig(8)维护)以查看其是否包含文件名条目。
- o
- 搜索目录/ lib和/ usr / lib(按此顺序)。
如果文件名指定的对象具有其他共享库的依赖关系,则动态链接程序也会使用相同的规则自动加载这些共享库。 (如果这些对象又具有依赖关系,则依次进行此过程,依此类推。)
标志中必须包含以下两个值之一:
- RTLD_LAZY
- 执行延迟绑定。仅在执行引用符号的代码时才解析它们。如果从未引用过该符号,则永远不会解析该符号。 (仅对函数引用执行延迟绑定;加载共享库时,始终会立即绑定对变量的引用。)从glibc 2.1.1开始,此标志被LD_BIND_NOW环境变量的影响所覆盖。
- RTLD_NOW
- 如果指定了此值,或者环境变量LD_BIND_NOW设置为非空字符串,则在dlopen()返回之前,将解析共享库中所有未定义的符号。如果无法完成,则返回错误。
标志中还可以对以下零个或多个值进行"或"运算:
- RTLD_GLOBAL
- 该共享库定义的符号将可用于后续加载的共享库的符号解析。
- RTLD_LOCAL
- 这与RTLD_GLOBAL相反,如果未指定任何标志,则为默认值。此共享库中定义的符号不可用于解析后续加载的共享库中的引用。
- RTLD_NODELETE(since glibc 2.2)
- 不要在dlclose()期间卸载共享对象。因此,如果以后使用dlopen()重新加载对象,则不会重新初始化该对象的静态变量和全局变量。
- RTLD_NOLOAD(since glibc 2.2)
- 不要加载共享对象。这可用于测试对象是否已驻留(如果不是,则dlopen()返回NULL,如果驻留了,则返回对象的句柄)。此标志还可以用于提升已经加载的共享库上的标志。例如,可以使用RTLD_NOLOAD重新打开先前已加载RTLD_LOCAL的共享库。 RTLD_GLOBAL。
- RTLD_DEEPBIND(since glibc 2.3.4)
- 将符号的查找范围放在此共享库中的全局范围之前。这意味着自包含的对象将优先使用自己的符号,而不要使用已包含在对象中的具有相同名称的全局符号。
如果filename为NULL,则返回的句柄用于主程序。当赋予dlsym(3)时,此句柄将在主程序中搜索符号,然后在程序启动时加载所有共享对象,然后在dlopen()中使用标志RTLD_GLOBAL加载所有共享对象。
共享对象中的符号引用使用(按顺序)解析:为主程序及其依赖项加载的对象的链接图中的符号;以前使用dlopen()使用RTLD_GLOBAL标志打开的共享库中的符号(及其依赖项);和共享对象本身中的定义(以及为该对象加载的所有依赖项)。
ld(1)放置在其动态符号表中的可执行文件中的所有全局符号也可以用于解析动态加载的共享库中的引用。可以将符号放置在动态符号表中,因为可执行文件与标志" -rdynamic"(或同义为" --export-dynamic")链接,这导致所有可执行文件的全局符号都放置在动态文件中。符号表,或者因为ld(1)在静态链接期间注意到了另一个对象中符号的依赖关系。
如果使用dlopen()再次打开相同的共享对象,则返回相同的对象句柄。动态链接器维护对象句柄的引用计数,因此,直到对dlclose()成功调用了dlclose()之前,动态释放的共享对象才被释放。仅当对象实际加载到内存中时(即,当引用计数增加到1时),才调用构造函数(请参见下文)。
后续的使用RTLD_NOW加载同一共享库的dlopen()调用可能会强制为较早使用RTLD_LAZY加载的共享库解析符号。类似地,以前使用RTLD_LOCAL打开的对象可以在后续的dlopen()中提升为RTLD_GLOBAL。
如果dlopen()由于任何原因失败,则返回NULL。
dlmopen()
该函数执行与dlopen()相同的任务-文件名和标志参数以及返回值相同,除了以下所述的区别。
dlmopen()函数与dlopen()的主要区别在于,它接受一个附加参数lmid,该参数指定应在其中加载共享库的链接映射列表(也称为名称空间)。 (通过比较,dlopen()将动态加载的共享库添加到与从其进行dlopen()调用的共享库相同的名称空间。)Lmid_t类型是一个不透明的句柄,它引用一个命名空间。
lmid参数是现有名称空间的ID(可以使用dlinfo(3)RTLD_DI_LMID请求获得)或以下特殊值之一:
- LM_ID_BASE
- 将共享对象加载到初始名称空间(即应用程序的名称空间)中。
- LM_ID_NEWLM
- 创建一个新的命名空间并将共享对象加载到该命名空间中。由于新的名称空间最初是空的,因此必须正确链接该对象以引用它需要的所有其他共享对象。
如果filename为NULL,则lmid唯一允许的值为LM_ID_BASE。
dlclose()
函数dlclose()减少由handle引用的动态加载的共享对象上的引用计数。
如果对象的引用计数降至零,并且其他对象不需要该对象中的任何符号,则在首先调用为该对象定义的所有析构函数之后,将卸载该对象。 (在另一个对象中可能需要该对象中的符号,因为该对象是使用RTLD_GLOBAL标志打开的,并且其中一个符号满足在另一个对象中的重定位。)
以相同方式递归关闭在handle引用的对象上调用dlopen()时自动加载的所有共享对象。
从dlclose()成功返回并不保证与句柄关联的符号已从调用者的地址空间中删除。除了由显式dlopen()调用产生的引用之外,由于其他共享库中的依赖关系,可能还隐式加载了共享库(并计算了引用数)。只有释放所有引用后,才能从地址空间中删除共享对象。
返回值
成功后,dlopen()和dlmopen()将为装入的对象返回非NULL句柄。发生错误(找不到文件,不可读,格式错误或在加载过程中导致错误)时,这些函数将返回NULL。
成功时,dlclose()返回0;否则,返回0。如果出错,则返回非零值。
可以使用dlerror(3)诊断来自这些函数的错误。
版本
dlopen()和dlclose()存在于glibc 2.0及更高版本中。 dlmopen()首先出现在glibc 2.3.4中。
属性
有关本节中使用的术语的说明,请参见attribute(7)。
Interface | Attribute | Value |
dlopen(),dlmopen(),dlclose() | Thread safety | MT-Safe |
遵循规范
POSIX.1-2001描述了dlclose()和dlopen()。 dlmopen()函数是GNU扩展。
RTLD_NOLOAD,RTLD_NODELETE和RTLD_DEEPBIND标志是GNU扩展。这些标志的前两个也出现在Solaris上。
备注
dlmopen() and namespaces
链接映射列表定义了一个隔离的名称空间,用于由动态链接程序解析符号。在名称空间中,依存的共享对象将按照通常的规则隐式加载,并且符号引用也将按照通常的规则进行解析,但是这种解析仅限于已(显式和隐式)加载到对象中的定义。命名空间。
dlmopen()函数允许对象加载隔离-能够在新的名称空间中加载共享对象,而无需将应用程序的其余部分暴露给新对象提供的符号。请注意,使用RTLD_LOCAL标志不足以达到此目的,因为它会阻止共享对象的符号对任何其他共享对象可用。在某些情况下,我们可能希望使动态加载的共享对象提供的符号可用于其他共享对象(的子集),而又不将这些符号暴露给整个应用程序。这可以通过使用单独的名称空间和RTLD_GLOBAL标志来实现。
dlmopen()函数还可以用于提供比RTLD_LOCAL标志更好的隔离。特别是,如果加载了RTLD_LOCAL的共享库是另一个加载了RTLD_GLOBAL的共享库的依存关系,则它们可以升级为RTLD_GLOBAL。因此,RTLD_LOCAL不足以隔离已加载的共享库,除非在(罕见)情况下,该共享库具有对所有共享库依赖项的显式控制。
dlmopen()的可能用途是插件,其中插件加载框架的作者不信任插件作者,并且不希望插件框架中的任何未定义符号都解析为插件符号。另一个用途是多次加载同一对象。如果不使用dlmopen(),则将需要创建共享库文件的不同副本。使用dlmopen(),可以通过将同一共享库文件加载到不同的名称空间中来实现。
glibc实现最多支持16个名称空间。
Initialization and finalization functions
共享对象可以使用__attribute __((constructor))和__attribute __((destructor))函数属性导出函数。构造函数在dlopen()返回之前执行,而析构函数在dlclose()返回之前执行。共享库可以导出多个构造函数和析构函数,并且可以将优先级与每个函数相关联,以确定它们的执行顺序。有关更多信息,请参见gcc信息页面(在"函数属性"下)。
一种(部分)获得相同结果的较旧方法是使用链接器识别的两个特殊符号:_init和_fini。如果动态加载的共享库导出了名为_init()的例程,则该代码将在加载共享库之后,dlopen()返回之前执行。如果共享库导出了一个名为_fini()的例程,则该例程将在卸载该对象之前被调用。在这种情况下,必须避免链接包含这些文件的默认版本的系统启动文件。这可以通过使用gcc(1)-nostartfiles命令行选项来完成。
现在不推荐使用_init和_fini,而推荐使用上述构造函数和析构函数,这些构造函数和析构函数除其他优点外,还可以定义多个初始化和终结函数。
从glibc 2.2.3开始,可以使用atexit(3)注册退出处理程序,该卸载程序在卸载共享库时会自动调用。
History
这些功能是从SunOS派生的dlopen API的一部分。
BUGS
与glibc 2.24一样,在调用dlmopen()时指定RTLD_GLOBAL标志会生成错误。此外,如果调用是从初始名称空间以外的其他名称空间中加载的任何对象进行的,则在调用dlopen()时指定RTLD_GLOBAL会导致程序崩溃(SIGSEGV)。
示例
下面的程序加载(glibc)数学库,查找cos(3)函数的地址,并输出2.0的余弦值。以下是构建和运行程序的示例:
$ cc dlopen_demo.c -ldl $ ./a.out -0.416147
Program source
#include <stdio.h> #include <stdlib.h> #include <dlfcn.h> #include <gnu/lib-names.h> /* Defines LIBM_SO (which will be a string such as "libm.so.6") */ int main(void) { void *handle; double (*cosine)(double); char *error; handle = dlopen(LIBM_SO, RTLD_LAZY); if (!handle) { fprintf(stderr, "%s\n", dlerror()); exit(EXIT_FAILURE); } dlerror(); /* Clear any existing error */ cosine = (double (*)(double)) dlsym(handle, "cos"); /* According to the ISO C standard, casting between function pointers and 'void *', as done above, produces undefined results. POSIX.1-2001 and POSIX.1-2008 accepted this state of affairs and proposed the following workaround: *(void **) (&cosine) = dlsym(handle, "cos"); This (clumsy) cast conforms with the ISO C standard and will avoid any compiler warnings. The 2013 Technical Corrigendum 1 to POSIX.1-2008 improved matters by requiring that conforming implementations support casting 'void *' to a function pointer. Nevertheless, some compilers (e.g., gcc with the '-pedantic' option) may complain about the cast used in this program. */ error = dlerror(); if (error != NULL) { fprintf(stderr, "%s\n", error); exit(EXIT_FAILURE); } printf("%f\n", (*cosine)(2.0)); dlclose(handle); exit(EXIT_SUCCESS); }
另外参见
ld(1),ldd(1),pldd(1),dl_iterate_phdr(3),dladdr(3),dlerror(3),dlinfo(3),dlsym(3),rtld-audit(7),ld.so (8),ldconfig(8)
gcc信息页面,ld信息页面
出版信息
这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/。