zoukankan      html  css  js  c++  java
  • C/C++链接过程相关

      1、dlclose(), dlerror(), dlopen(), dlsym()等:动态链接加载器的编程接口。链接时需要指定-ldl。

      1)dlopen():

    // 加载由filename指定的动态库文件,并返回其“句柄”
    // 程序中使用dlopen()多次加载同一个库时,返回相同的句柄
    // 失败则返回NULL
    void *dlopen(const char *filename, int flag);

      (1)若filename包含"/",则认为它是相对路径或绝对路径。否则,动态链接器(ld.so)按如下顺序搜索名为filename的库文件:

      I、仅适用于ELF文件(可执行文件+可重定位的目标文件+core文件+共享库)。在ELF文件的动态节(dynamic section)中,当DT_RUNPATH属性没有指定时,使用DT_RPATH属性(如有)指定的目录。如:

    ~/programming/dlopen$ gcc main.c -ldl -Wl,-rpath=.    <——-Wl,-rpath=.:将-rpath=.作为选项传递给链接器
    ~/programming/dlopen$ readelf -d a.out
    
    Dynamic section at offset 0xf04 contains 26 entries:
      标记        类型                         名称/值
     0x00000001 (NEEDED)           共享库:[libdl.so.2]
     0x00000001 (NEEDED)           共享库:[libc.so.6]
     0x0000000f (RPATH)            Library rpath: [.]    <——hard code的一个在运行时搜索库文件的路径
     0x0000000c (INIT)             0x80483bc
     0x0000000d (FINI)             0x8048624
    ... ...
    ~/programming/dlopen$ chrpath -d a.out <——chrpath: change the rpath or runpath in binaries
    ~/programming/dlopen$ readelf -d a.out

    Dynamic section at offset 0xf04 contains 25 entries:
      标记        类型                         名称/值
     0x00000001 (NEEDED)                     共享库:[libdl.so.2]
     0x00000001 (NEEDED)                     共享库:[libc.so.6]
     0x0000000c (INIT)                       0x80483bc
     0x0000000d (FINI)                       0x8048624
    ...  

      不建议使用。

      II、程序启动时,如果环境变量LD_LIBRARY_PATH已经定义,则搜索它指定的目录。如:

    ~/programming/dlopen$ LD_LIBRARY_PATH=. ./a.out
    ~/programming/dlopen$ export LD_LIBRARY_PATH=.
    ~/programming/dlopen$ ./a.out

      安全起见,对于SUID和SGID程序,这个变量被忽略。

      另外,可以调用动态链接器/加载器并指定--library-path选项以覆盖LD_LIBRARY_PATH:

    ~/programming/dlopen$ /lib/ld-linux.so.2 --library-path .. ./a.out

      III、仅适用于ELF文件。使用ELF文件的DT_RUNPATH动态节属性指定的目录。

      IV、检查/etc/ld.so.cache(该文件由ldconfig更新)。它包含那些在ld.so.conf指定目录、/lib和/usr/lib中找到的库文件。

      两个相关文件:/lib/ld-linux.so*:动态(运行时)链接器/加载器;/etc/ld.so.conf:它指定的目录被用于搜索库文件。

      V、依次搜索/lib和/usr/lib。

       (2)接下来是dlopen()的第二个参数flag:

      I、下面两个值必须选其一:

      RTLD_LAZY:延迟绑定(lazy binding)。仅当引用了符号的代码被执行时,才对符号进行解析(resolve)。延迟绑定只适用于函数,变量总是在加载库时被立即绑定。

      RTLD_NOW:如果指定了该值,或者环境变量LD_BIND_NOW被设置成非空字符串,则将加载的库的所有未定义符号都在dlopen()返回前被解析。

      II、还有以下可选的值:

      RTLD_GLOBAL:该库定义的符号可用于后续加载的库的符号解析过程。

      解析库的外部(external)引用时,使用该库的依赖列表和其他先前已使用RTLD_GLOBAL打开的库。如果可执行程序链接时使用-rdynamic,则它的全局符号也将被用于解析动态加载库中的引用。

      RTLD_LOCAL:与RTLD_GLOBAL相反(也是这两个值中的默认值)。

      RTLD_NODELETE(glibc 2.2之后):在dlclose()期间不卸载(unload)该库。这样的结果是,如果该库在后续被dlopen()重新加载,则它的静态变量不会重新初始化。这个flag不是POSIX.1-2001的标准。

      RTLD_NOLOAD(glibc 2.2之后):不加载。测试该库是否已经加载:如果还没加载则dlopen()返回NULL,否则返回句柄。也可以用来改变已加载库的flag,如一个用RTLD_LOCAL加载的库,可用RTLD_NOLOAD | RTLD_GLOBAL重新打开。这个flag不是POSIX.1-2001的标准。

      RTLD_DEEPBIND(glibc 2.3.4之后):优先在该库内查找符号,而不是在全局范围内。这意味着一个自包含的库将优先使用它自己的符号,而不是其他已加载库的同名全局符号。这个flag不是POSIX.1-2001的标准。

      2)dlerror()

    // 返回自初始化或上次调用该函数以来,描述dlopen()、dlsym()或dlclose()最近发生的错误的(人可读的)字符串
    // 没有错误则返回NULL
    char *dlerror(void);

      3)dlsym()

    // 返回symbol被加载到内存的地址。若找不到该symbol,则返回NULL
    // dlsym()对库的依赖树进行宽度优先遍历,以查找该符号
    void *dlsym(void *handle, const char *symbol);

      测试是否出现错误的正确方法:调用dlerror(),清除旧的错误条件 -> 调用dlsym() -> 调用dlerror(),检查其返回值是否为NULL。

      4)dlclose():减少动态库句柄handle的引用计数。

    int dlclose(void *handle);

      dl库维护着库句柄的引用计数。若某个库的引用计数变为0且其他加载的库没有在使用它的符号,则卸载该库。

           glibc 2.2.3之后,atexit()可用于注册一个退出处理函数,它在库被卸载时自动调用。

      5)例子:

    // libfoo.cpp
    // extern "C"要求按C语言方式编译该部分代码。若没有这个限定,由于C++的函数重载,foo()的符号名将是_Z3foov
    // 此时dlopen.cpp中相应的行也应该改成*(void **) (&foo) = dlsym(handle, "_Z3foov");
    extern "C"
    {
    void foo()
    {
        cout << "foo() in libfoo" << endl;
    }
    }
    // dlopen.cpp
    int main(int argc, char **argv)
    {
        void (*foo)();
    
        void *handle = dlopen("libfoo.so", RTLD_LAZY);
        if (!handle) 
        {
            fprintf(stderr, "%s
    ", dlerror());
            exit(EXIT_FAILURE);
        }
    
        dlerror();    /* Clear any existing error */
    
        // foo = (void (*)(void)) dlsym(handle, "_Z3foov");这种写法更自然
        // 但在C99标准下,从void *到函数指针的转换结果是未定义的
        *(void **) (&foo) = dlsym(handle, "foo");
    
        char *error;
        if ((error = dlerror()) != NULL)  
        {
            fprintf(stderr, "%s
    ", error);
            exit(EXIT_FAILURE);
        }
    
        (*foo)();
        dlclose(handle);
        exit(EXIT_SUCCESS);
    }

      编译运行:

    ~/programming$ g++ -o libfoo.so -shared libfoo.cpp
    ~/programming$ sudo mv libfoo.so /usr/lib
    ~/programming$ g++ dlopen.cpp -ldl
    ~/programming$ ./a.out
    foo() in libfoo

      参考资料:

      http://blog.csdn.net/dbzhang800/article/details/6918413



    不断学习中。。。

  • 相关阅读:
    NetCore使用Log4Net记录日志
    WCF数据协议中XmlArrayItem标签不起作用的问题
    WTM Blazor,Blazor开发利器
    WTM5.0发布,全面支持.net5
    log4netdemo
    mes 入库单号 锁表方案
    线程基础篇-线程同步
    线程基础篇-线程和进程
    EF基础篇-Code First
    EF基础篇-DB First
  • 原文地址:https://www.cnblogs.com/hanerfan/p/3652847.html
Copyright © 2011-2022 走看看