侧边栏壁纸
博主头像
noerror

虚灵不寐,众理具而万事出。

  • 累计撰写 238 篇文章
  • 累计创建 9 个标签
  • 累计收到 2 条评论
标签搜索

目 录CONTENT

文章目录

dlopen函数用法详解

noerror
2022-10-19 / 0 评论 / 0 点赞 / 36 阅读 / 3,603 字 / 正在检测是否收录...

dlopen函数用法详解

dlopen函数简介

  • 头文件包含
#include <dlfcn.h>
#include <dlfcn.h>
  • 函数定义
void *dlopen(const char * filename , int  flags );
int dlclose(void * handle );
#define _GNU_SOURCE
void *dlmopen(Lmid_t  lmid , const char * filename , int  flags );
  • 编译链接选项
-ldl

dlopen函数常见使用错误

  • 链接错误
    undefined reference to `dlopen'
    解决办法:添加链接选项
-ldl
  • 编译错误
    warning: implicit declaration of function ‘dlopen’ [-Wimplicit-function-declaration]
    解决办法:包含头文件
#include <dlfcn.h>
#include <dlfcn.h>

dlopen函数详细描述

dlopen()函数dlopen ()加载由以空结尾的字符串filename命名的动态共享对象(共享库)文件,并为加载的对象返回一个不透明的“句柄”。这个句柄与dlopen API中的其他函数一起使用,比如dlsym (3),dladdr (3),dlinfo (3)和dlclose ()
如果filename为NULL,则返回的句柄是主程序的句柄。如果filename包含斜杠(“/”),则将其解释为(相对或绝对)路径名。否则,动态链接器将按以下方式搜索对象(有关详细信息,请参阅ldso (8)):

  • (仅适用于ELF)如果调用对象(即调用dlopen ()的共享库或可执行文件)包含DT_RPATH标记,而不包含DT_RUNPATH标记,则搜索DT_RPATH标记中列出的目录。
  • 如果在程序启动时,环境变量 LD_LIBRARY_PATH 被定义为包含以冒号分隔的目录列表,那么将搜索这些目录。(作为安全措施,对于set-user-ID和set-group-ID程序,将忽略此变量。)
  • (仅适用于ELF)如果调用对象包含DT_RUNPATH标记,则搜索该标记中列出的目录。
  • 检查缓存文件/etc/ldsocache(由ldconfig (8))维护)以查看它是否包含filename 的条目
  • 目录/lib和/usr/lib被搜索(按该顺序)。

如果filename指定的对象依赖于其他共享对象,那么动态链接器也会使用相同的规则自动加载这些对象。(如果这些对象依次具有依赖关系,则此过程可能递归地发生,以此类推。)
flags :中必须包含以下两个值之一

  • RTLD_LAZY 执行惰性绑定。仅在执行引用符号的代码时解析符号。如果符号从未被引用,那么它将永远不会被解析。(只对函数引用执行惰性绑定;在加载共享对象时,总是立即绑定对变量的引用。)从glibc 2.1.1开始,这个标志被 LD_BIND_NOW 环境变量的影响覆盖。
  • RTLD_NOW 如果指定了此值,或者环境变量 LD_BIND_NOW 设置为非空字符串,则在dlopen ()返回之前解析共享对象中所有未定义的符号。如果不能这样做,将返回一个错误。

在flags :中还可以编辑以下零个或多个值

  • RTLD_GLOBAL 此共享对象定义的符号将用于随后加载的共享对象的符号解析。
  • RTLD_LOCAL 这是RTLD_GLOBAL 的相反,如果两个标志都没有指定,则为默认值。此共享对象中定义的符号不能用于解析随后加载的共享对象中的引用。
  • 在dlclose ()期间,RTLD_NODELETE " (since glibc 22)"不卸载共享对象,因此,如果稍后用dlopen ()重新加载对象,则不会重新初始化该对象的静态和全局变量。
  • RTLD_NOLOAD " (since glibc 22)"不加载共享对象。这可以用来测试对象是否已经驻留,如果不是,则返回NULL,如果是驻留,则返回对象的句柄)。这个标志也可以用来提升已经加载的共享对象上的标志。例如,以前用 RTLD_LOCAL 加载的共享对象可以用RTLD_NOLOAD\ |\ RTLD_GLOBAL 重新打开
  • RTLD_DEEPBIND " (since glibc 234)"将此共享对象中符号的查找范围置于全局范围之前。这意味着自包含对象将优先使用自己的符号,而不是已经加载的对象中包含的同名全局符号。

如果filename为NULL,则返回的句柄是主程序的句柄。当给到dlsym (3)时,这个句柄会导致在主程序中搜索一个符号,然后是在程序启动时加载的所有共享对象,然后是由dlopen ()加载的带有标志RTLD_GLOBAL 的所有共享对象
共享对象中的符号引用使用(按顺序)解析:为主程序及其依赖项加载的对象的链接映射中的符号;共享对象(及其依赖项)中以前使用RTLD_GLOBAL标志用dlopen ()打开的符号;和共享对象本身中的定义(以及为该对象加载的任何依赖项)。
可执行文件中由ld (1)放入其动态符号表中的任何全局符号也可用于解析动态加载的共享对象中的引用。符号可能被放置在动态符号表中,这可能是因为可执行文件是用标志“-rdynamic”(或同义的“--export-dynamic”)链接的,这导致所有可执行文件的全局符号都被放置在动态符号表中,或者是因为ld (1)在静态链接期间注意到了对另一个对象中的符号的依赖。
如果使用dlopen ()再次打开相同的共享对象,则返回相同的对象句柄。动态链接器维护对象句柄的引用计数,因此动态加载的共享对象不会被释放,直到dlclose ()被调用的次数与dlopen ()被调用的次数一样多。只有当对象实际加载到内存中时(即,当引用计数增加到1时)才调用构造函数(见下文)。
随后用 RTLD_NOW 加载相同共享对象的dlopen ()调用可能会强制对先前用RTLD_LAZY 加载的共享对象进行符号解析。类似地,先前用RTLD_LOCAL打开的对象可以在随后的dlopen ()中提升为RTLD_GLOBAL
如果dlopen ()由于任何原因失败,它将返回null。dlmopen()此函数执行与dlopen ()(emthe相同的任务,filename和flags参数以及返回值都是相同的,除了下面提到的不同之处。
dlmopen ()函数与dlopen ()的主要区别在于它接受一个额外的参数,lmid 指定链接映射列表(也称为应该加载共享对象的namespace ))。(相比之下,dlopen ()将动态加载的共享对象添加到与进行dlopen ()调用的共享对象相同的命名空间中。)Lmid_t类型是一个引用命名空间的不透明句柄。
lmid参数可以是现有命名空间的ID(可以通过dlinfo (3) RTLD_DI_LMID 请求获得),也可以是以下特殊值之一:

  • LM_ID_BASE 在初始命名空间(即应用程序的命名空间)中加载共享对象。
  • LM_ID_NEWLM 创建一个新的命名空间并在该命名空间中加载共享对象。该对象必须被正确链接以引用它所需的所有其他共享对象,因为新命名空间最初是空的。

如果filename为空,则lmid的唯一允许值是LM_ID_BASE dlclose()函数dlclose ()递减handle 所引用的动态加载共享对象的引用计数
如果对象的引用计数降至零,并且其他对象不需要此对象中的符号,则在首先调用为该对象定义的任何析构函数后卸载该对象。(在另一个对象中可能需要此对象中的符号,因为此对象是用RTLD_GLOBAL标志打开的,并且它的一个符号满足了在另一个对象中的重新定位。)
在handle引用的对象上调用dlopen ()时自动加载的所有共享对象都以相同的方式递归关闭。
从dlclose ()成功返回并不能保证与handle关联的符号从调用方的地址空间中移除。除了由显式dlopen ()调用产生的引用之外,由于其他共享对象中的依赖关系,共享对象可能被隐式加载(并计算引用数)。只有当所有引用都已释放时,共享对象才能从地址空间中移除。

dlopen函数返回值

成功后,dlopen ()和dlmopen ()返回加载对象的非空句柄。如果出现错误(文件找不到、不可读、格式错误或在加载过程中导致错误),这些函数将返回NULL。
成功时,dlclose ()返回0;出错时,它返回一个非零值。
可以使用dlerror (3)诊断来自这些函数的错误

dlopen函数其他说明

dlmopen() and namespaces链接映射列表定义了一个独立的命名空间,用于动态链接器解析符号。在命名空间中,依赖的共享对象根据通常的规则隐式加载,符号引用同样根据通常的规则解析,但这种解析仅限于已(显式和隐式)加载到命名空间中的对象提供的定义。
dlmopen ()函数允许对象加载隔离(EM)在新命名空间中加载共享对象的能力,而不会将应用程序的其余部分暴露给新对象提供的符号。请注意, RTLD_LOCAL 标志的使用不足以实现这一目的,因为它防止共享对象的符号对any其他共享对象可用。在某些情况下,我们可能希望使动态加载的共享对象提供的符号可供其他共享对象(子集)使用,而不向整个应用程序公开这些符号。这可以通过使用单独的命名空间和 RTLD_GLOBAL 标志来实现。
dlmopen ()函数还可用于提供比RTLD_LOCAL标志更好的隔离。特别地,如果用RTLD_LOCAL加载的共享对象是用RTLD_GLOBAL 加载的另一个共享对象的依赖项,则用PPPP2加载的共享对象可以被提升到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的一部分。

dlopen函数使用举例

下面的程序加载(glibc)数学库,查找cos (3)函数的地址,并打印2.0的余弦。下面是构建和运行该程序的示例:

$ \fBcc dlopen_demo.c \-ldl\fP
$ \fB./a.out\fP
\-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\en", 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 \(aqvoid *\(aq, 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
      \(aqvoid *\(aq to a function pointer.  Nevertheless, some compilers
      (e.g., gcc with the \(aq\-pedantic\(aq option) may complain about the
      cast used in this program. */

   error = dlerror();
   if (error != NULL) {
       fprintf(stderr, "%s\en", error);
       exit(EXIT_FAILURE);
   }

   printf("%f\en", (*cosine)(2.0));
   dlclose(handle);
   exit(EXIT_SUCCESS);
}
0

评论区