美文网首页
linux手册翻译——dlopen(3)

linux手册翻译——dlopen(3)

作者: 蟹蟹宁 | 来源:发表于2021-07-14 21:46 被阅读0次

    \color{#A00000}{NAME}
    dlclose, dlopen, dlmopen - open and close a shared object

    \color{#A00000}{SYNOPSIS}

           #include <dlfcn.h>
    
           void *dlopen(const char *filename, int flags);
           int dlclose(void *handle);
    
           #define _GNU_SOURCE
           #include <dlfcn.h>
    
           void *dlmopen(Lmid_t lmid, const char *filename, int flags);
    
           Link with -ldl.
    

    \color{#A00000}{DESCRIPTION}

    dlopen()

    函数dlopen()用于加载动态共享对象(或叫动态链接库,下或称DL)文件,文件名由filename指定,函数返回一个不透明的“句柄”。该句柄与dlopen API中的其他函数一起使用,如dlsym(3)、dladdr(3)、dlinfo(3)和dlclose()。

    如果 filename 为 NULL,则返回的句柄是用于主程序的。 如果文件名包含斜杠(“/”),则将其解释为相对或绝对路径名。 否则,动态链接器按如下顺序在指定目录的搜索DL(有关详细信息,请参见 ld.so(8)):

    1. (仅限ELF)如果调用DL的可执行文件或其他DL包含了DT_RPATH标记,而不包含DT_RUNPATH标记,则搜索DT_RPATH标记中列出的目录。
      当使用gcc编译时,使用-L参数指定链接时路径,即在链接阶段寻找链接库的路径(包括静态和动态链接库),使用-Wl,-rpath将指定运行时路径,即在运行时寻找动态链接库的路径,其中使用了-Wl,-rpath,就等价于给可执行程序标记了DT_RPATH标志,这将写到可执行程序当中,因此优先级是是最高的。当指定-Wl,--disable-new-dtags时,等价于启用了DT_RUNPATH标志。更详细的参考资料可以参考以下三个:动态库加载路径之RPATH与RUNPATHRPATH and RUNPATHHow to set RPATH and RUNPATH with GCC/LD?

    2. 搜索环境变量 LD_LIBRARY_PATH 定义的以冒号分隔的目录列表。(为了安全,set-user-ID 和 set-group-ID 程序忽略此变量。)

    3. (仅限 ELF)如果调用对象包含 DT_RUNPATH 标记,则搜索该标记中列出的目录。

    4. 检查缓存文件 /etc/ld.so.cache(由 ldconfig(8) 维护)以查看它是否包含filename条目。

    5. 顺序搜索目录 /lib/usr/lib

    如果 filename 指定的链接库依赖于其他DL,那么动态链接器也会使用相同的规则自动加载这些链接库。 (如果这些链接库又具有依赖关系,则此过程可能会递归发生,依此类推。)

    标志中必须包含以下两个值之一:

    • ** RTLD_LAZY**
      执行延迟绑定。 仅在执行引用它们的代码时才解析符号。 如果该符号从未被引用,则它永远不会被解析。
      (延迟绑定仅对函数引用执行;当加载动态链接库时,对变量的引用总是立即绑定。)
      从 glibc 2.1.1 开始,该标志被 LD_BIND_NOW 环境变量的影响覆盖。

    • ** RTLD_NOW**
      如果指定了此值,或者环境变量 LD_BIND_NOW 设置为非空字符串,则在 dlopen() 返回之前解析动态链接库中的所有未定义符号。 如果这不能完成,则返回错误。

    零个或多个以下值也可以在标志中进行或运算:

    • ** RTLD_GLOBAL**
      此动态链接库定义的符号将可用于随后加载的动态链接库的符号解析。
    • ** RTLD_LOCAL**
      这与 RTLD_GLOBAL 相反,如果没有指定标志,则为默认值。 此动态链接库中定义的符号不可用于解析随后加载的动态链接库中的引用。
    • ** RTLD_NODELETE**
      不要在 dlclose() 期间卸载动态链接库。 因此,如果稍后使用 dlopen() 重新加载链接库,则不会重新初始化对象的静态和全局变量。
    • ** RTLD_NOLOAD**
      不要加载动态链接库。 这可用于测试对象是否已经被加载到内存(如果不是,则 dlopen() 返回 NULL,如果是,则返回链接库的句柄)。 此标志还可用于提升(promote)已加载的动态链接库上的标志。 例如,如果以前使用的是 RTLD_LOCAL标志加载的,则可以用 RTLD_NOLOAD | RTLD_GLOBAL重新open一次。
    • ** RTLD_DEEPBIND**
      将此动态库中符号的查找范围置于全局范围之前。这意味着自包含库(self-contained)将优先使用自己的符号,而不是已加载库中包含的同名全局符号。

    如果 filename 为 NULL,则返回的句柄用于主程序。当提供给 dlsym(3) 时,此句柄会导致在主程序中搜索符号,然后是程序启动时加载的所有动态链接库,然后是 dlopen() 加载的带有标志 RTLD_GLOBAL 的所有动态链接库。

    动态链接库中的符号引用按一下顺序解析:主程序及其依赖项加载的链接库中映射的符号;先前使用 RTLD_GLOBAL 标志使用 dlopen() 打开的动态链接库(及其依赖项)中的符号;和动态链接库本身中的定义(以及为该对象加载的任何依赖项)。如果使用了RTLD_DEEPBIND则优先查找自身定义
    *Symbol references in the shared object are resolved using (in order): symbols in the link map of objects loaded for the main program and its dependencies; symbols in shared objects (and their dependencies) that were previously opened with dlopen() using the RTLD_GLOBAL flag; and definitions in the shared object itself (and any dependencies that were loaded for that object). *

    Any global symbols in the executable that were placed into its dynamic symbol table by ld(1) can also be used to resolve references in a dynamically loaded shared object. Symbols may be placed in the dynamic symbol table either because the executable was linked with the flag "-rdynamic" (or, synonymously, "--export-dynamic"), which causes all of the executable's global symbols to be placed in the dynamic symbol table, or because ld(1) noted a dependency on a symbol in another object during static linking.

    如果使用 dlopen() 再次打开相同的动态链接库,则返回相同的句柄。动态链接器维护句柄的引用计数,因此动态加载的动态链接库不会被释放,直到 dlclose() 调用它的次数与 dlopen() 成功调用它的次数一样。仅当动态链接库加载到内存中时(即,当引用计数增加到 1 时)才会调用构造函数(见下文)。

    如果先前使用了 RTLD_LAZY 加载动态链接库,之后又使用RTLD_NOW 加载相同链接库,可能会强制执行符号解析。类似地,先前使用 RTLD_LOCAL 打开的对象可以在后续的 dlopen() 中提升为 RTLD_GLOBAL。

    如果 dlmopen() 因任何原因失败,则返回 NULL。

    dlopen()

    除了附加参数lmid之外,dlmopen()与dlopen()完全相同。lmid 参数指定应在其中加载动态链接库的链接映射列表(也称为命名空间)。dlopen() 默认将动态加载的链接库添加到与执行 dlopen() 调用的链接库相同的命名空间中。Lmid_t 类型是一个不透明的句柄,它引用了一个命名空间。

    mid 参数是现有命名空间的 ID(可使用 dlinfo(3) RTLD_DI_LMID 请求获得)或以下特殊值之一:

    • LM_ID_BASE
      在初始命名空间(即应用程序的命名空间)中加载动态链接库。

    • LM_ID_NEWLM
      创建一个新的命名空间并在该命名空间中加载动态链接库。该链接库必须已正确链接以引用它所需的所有其他动态链接库,因为新命名空间最初是空的。

    如果 filename 为 NULL,则 lmid 的唯一允许值是 LM_ID_BASE。

    \color{#A00000}{RETURN VALUE}
    成功时,dlopen() 和 dlmopen() 为加载的对象返回一个非 NULL 句柄。 出现错误(文件找不到、不可读、格式错误或在加载过程中导致错误)时,这些函数返回 NULL。

    成功时,dlclose() 返回 0; 出错时,它返回一个非零值。

    可以使用 dlerror(3) 诊断来自这些函数的错误。

    \color{#A00000}{VERSIONS}
    dlopen() and dlclose() are present in glibc 2.0 and later.
    dlmopen() first appeared in glibc 2.3.4.

    \color{#A00000}{ATTRIBUTES}

    Interface Attribute Value
    dlopen(), dlmopen(), dlclose() Thread safety MT-Safe

    \color{#A00000}{CONFORMING TO}
    POSIX.1-2001 describes dlclose() and dlopen(). The dlmopen() function is a GNU extension.

    The RTLD_NOLOAD, RTLD_NODELETE, and RTLD_DEEPBIND flags are GNU extensions; the first two of these flags are also present on Solaris.

    \color{#A00000}{NOTES}

    dlmopen() and namespaces

    A link-map list defines an isolated namespace for the resolution of symbols by the dynamic linker. Within a namespace, dependent shared objects are implicitly loaded according to the usual rules, and symbol references are likewise resolved according to the usual rules, but such resolution is confined to the definitions provided by the objects that have been (explicitly and implicitly) loaded into the namespace.

    The dlmopen() function permits object-load isolation—the ability to load a shared object in a new namespace without exposing the rest of the application to the symbols made available by the new object. Note that the use of the RTLD_LOCAL flag is not sufficient for this purpose, since it prevents a shared object's symbols from being available to any other shared object. In some cases, we may want to make the symbols provided by a dynamically loaded shared object available to (a subset of) other shared objects without exposing those symbols to the entire application. This can be achieved by using a separate namespace and the RTLD_GLOBAL flag.

    The dlmopen() function also can be used to provide better isolation than the RTLD_LOCAL flag. In particular, shared objects loaded with RTLD_LOCAL may be promoted to RTLD_GLOBAL if they are dependencies of another shared object loaded with RTLD_GLOBAL. Thus, RTLD_LOCAL is insufficient to isolate a loaded shared object except in the (uncommon) case where one has explicit control over all shared object dependencies.

    Possible uses of dlmopen() are plugins where the author of the plugin-loading framework can't trust the plugin authors and does not wish any undefined symbols from the plugin framework to be resolved to plugin symbols. Another use is to load the same object more than once. Without the use of dlmopen(), this would require the creation of distinct copies of the shared object file. Using dlmopen(), this can be achieved by loading the same shared object file into different namespaces.

    The glibc implementation supports a maximum of 16 namespaces.

    Initialization and finalization functions

    Shared objects may export functions using the attribute((constructor)) and attribute((destructor)) function attributes. Constructor functions are executed before dlopen() returns, and destructor functions are executed before dlclose() returns. A shared object may export multiple constructors and destructors, and priorities can be associated with each function to determine the order in which they are executed. See the gcc info pages (under "Function attributes") for further information.

    An older method of (partially) achieving the same result is via the use of two special symbols recognized by the linker: _init and _fini. If a dynamically loaded shared object exports a routine named _init(), then that code is executed after loading a shared object, before dlopen() returns. If the shared object exports a routine named _fini(), then that routine is called just before the object is unloaded. In this case, one must avoid linking against the system startup files, which contain default versions of these files; this can be done by using the gcc(1) -nostartfiles command-line option.

    Use of _init and _fini is now deprecated in favor of the aforementioned constructors and destructors, which among other advantages, permit multiple initialization and finalization functions to be defined.

    Since glibc 2.2.3, atexit(3) can be used to register an exit handler that is automatically called when a shared object is unloaded.

    History

    These functions are part of the dlopen API, derived from SunOS.

    \color{#A00000}{BUGS}
    As at glibc 2.24, specifying the RTLD_GLOBAL flag when calling dlmopen() generates an error. Furthermore, specifying RTLD_GLOBAL when calling dlopen() results in a program crash (SIGSEGV) if the call is made from any object loaded in a namespace other than the initial namespace.

    \color{#A00000}{EXAMPLES}
    下面的程序加载 (glibc) math library,查找 cos(3) 函数的地址,并打印 2.0.0 的余弦值。
    以下是构建和运行程序的示例:

               $ cc dlopen_demo.c -ldl
               $ ./a.out
               -0.416147
    

    源代码:

    #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);
    }
    

    相关文章

      网友评论

          本文标题:linux手册翻译——dlopen(3)

          本文链接:https://www.haomeiwen.com/subject/ydvypltx.html