zoukankan      html  css  js  c++  java
  • linux动态库加载RPATH,RUNPATH

    引用自:http://gotowqj.iteye.com/blog/1926771

    链接动态库

    如何程序在连接时使用了共享库,就必须在运行的时候能够找到共享库的位置。linux的可执行程序在执行的时候默认是先搜索/lib和/usr/lib这两个目录,然后按照/etc/ld.so.conf里面的配置搜索绝对路径。同时,Linux也提供了环境变量LDLIBRARYPATH供用户选择使用,用户可以通过设定它来查找除默认路径之外的其他路径,如查找/work/lib路径,你可以在/etc/rc.d/rc.local或其他系统启动后即可执行到的脚本添加如下语句:LDLIBRARYPATH =/work/lib:$(LDLIBRARYPATH)。并且LDLIBRARYPATH路径优先于系统默认路径之前查找(详细参考《使用LDLIBRARYPATH》)。

    不过LDLIBRARYPATH的设定作用是全局的,过多的使用可能会影响到其他应用程序的运行,所以多用在调试。(LDLIBRARYPATH的缺陷和使用准则,可以参考《Why LDLIBRARYPATH is bad》 )。通常情况下推荐还是使用gcc的-R或-rpath选项来在编译时就指定库的查找路径,并且该库的路径信息保存在可执行文件中,运行时它会直接到该路径查找库,避免了使用LDLIBRARYPATH环境变量查找。

    链接选项和路径

    现代连接器在处理动态库时将链接时路径(Link-time path)和运行时路径(Run-time path)分开,用户可以通过-L指定连接时库的路径,通过-R(或-rpath)指定程序运行时库的路径,大大提高了库应用的灵活性。比如我们做嵌入式移植时#arm-linux-gcc $(CFLAGS) –o target –L/work/lib/zlib/ -llibz-1.2.3 (work/lib/zlib下是交叉编译好的zlib库),将target编译好后我们只要把zlib库拷贝到开发板的系统默认路径下即可。或者通过-rpath(或-R )、LDLIBRARYPATH指定查找路径。

    链接器ld的选项有 -L,-rpath 和 -rpath-link,看了下 man ld,大致是这个意思:

    -L: “链接”的时候,去找的目录,也就是所有的 -lFOO 选项里的库,都会先从 -L 指定的目录去找,然后是默认的地方。编译时的-L选项并不影响环境变量LDLIBRARYPATH,-L只是指定了程序编译连接时库的路径,并不影响程序执行时库的路径,系统还是会到默认路径下查找该程序所需要的库,如果找不到,还是会报错,类似cannot open shared object file。

    -rpath-link:这个也是用于“链接”的时候的,例如你显示指定的需要 FOO.so,但是 FOO.so 本身是需要 BAR.so 的,后者你并没有指定,而是 FOO.so 引用到它,这个时候,会先从 -rpath-link 给的路径里找。

    -rpath: “运行”的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找。对于交叉编译,交叉编译链接器需已经配置 --with-sysroot 选项才能起作用。也就是说,-rpath指定的路径会被记录在生成的可执行程序中,用于运行时查找需要加载的动态库。-rpath-link 则只用于链接时查找。

    链接搜索顺序

    直接man ld。The linker uses the following search paths to locate required shared libraries:

           1.  Any directories specified by -rpath-link options.
           2.  Any directories specified by -rpath options.  The difference between -rpath and -rpath-link is that directories specified by -rpath options are included in the executable and used at runtime, whereas the -rpath-link option is only effective at link time. Searching -rpath in this way is only supported by native linkers and cross linkers which have been configured with the --with-sysroot option.
           3.  On an ELF system, for native linkers, if the -rpath and -rpath-link options were not used, search the contents of the environment variable "LD_RUN_PATH".
           4.  On SunOS, if the -rpath option was not used, search any directories specified using -L options.
           5.  For a native linker, the search the contents of the environment variable "LD_LIBRARY_PATH".
           6.  For a native ELF linker, the directories in "DT_RUNPATH" or "DT_RPATH" of a shared library are searched for shared libraries needed by it. The "DT_RPATH" entries are ignored if "DT_RUNPATH" entries exist.
           7.  The default directories, normally /lib and /usr/lib.
           8.  For a native linker on an ELF system, if the file /etc/ld.so.conf exists, the list of directories found in that file.
           If the required shared library is not found, the linker will issue a warning and continue with the link.
    

    gcc和链接选项的使用

    在gcc中使用ld链接选项时,需要在选项前面加上前缀-Wl(是字母l,不是1,我曾多次弄错),以区别不是编译器的选项。 if the linker is being invoked indirectly, via a compiler driver (e.g. gcc) then all the linker command line options should be prefixed by -Wl, (or whatever is appropriate for the particular compiler driver) like this:

    1 gcc -Wl,--start-group foo.o bar.o -Wl,--end-group

    This is important, because otherwise the compiler driver program may silently drop the linker options, resulting in a bad link.

    用例子说话

     

    二进制

    对应源码

    有一个程序

    a.out

    main.c

    需要加载插件A

    libA.so

    liba.c

    A需要另一个动态库

    libB.so

    libB1.c 或 libB2.c

    本文的关注点就是:到底是哪一个libB.so被加载

    目录结构:

    /home/debao/ttt/a.out
    /home/debao/ttt/libA.so
    /home/debao/ttt/libB.so
    /usr/lib/libB.so

    具体源码

    • main.c ==> ./a.out

     

    #include <stdio.h>
    #include <dlfcn.h>
    typedef int (*funcA)(int, int);
    int main()
    {
        void * plugin = dlopen("./libA.so", RTLD_LAZY);
        funcA f = (funcA)dlsym(plugin, "funcA");
        printf("main: %d
    ", f(3,4));
        return 0;
    }
    • liba.c ==> ./libA.so

     

    #include <stdio.h>
    int funcB(int, int);
    int funcA(int a, int b)
    {
        printf("hello from funcA
    ");
        return funcB(a, b);
    }
    • libb1.c ==> ./libB.so

     

    #include <stdio.h>
    int funcB(int a, int b)
    {
        printf("Hello from funcB 1
    ");
        return a*b;
    }  
    • libb2.c ==> /usr/lib/libB.so

     

    #include <stdio.h>
    int funcB(int a, int b)
    {
        printf("Hello from funcB 2
    ");
        return a*b;
    }  

    编译库文件

    • 编译动态库libB.so

     

    $ gcc -shared -fPIC libb2.c -o libB2.so
    $ sudo mv libB2.so /usr/lib/libB.so
    $ gcc -shared -fPIC libb.c -o libB.so
    • 编译动态库libA.so

     

    $ gcc -shared -fPIC liba.c -o libA.so -L. -lB

    顺便看看该elf文件的头部信息:

    $ readelf libA.so -d
    
    Dynamic section at offset 0xf20 contains 21 entries:
      Tag        Type      Name/Value
     0x00000001 (NEEDED)   Shared library: [libB.so]
     0x00000001 (NEEDED)   Shared library: [libc.so.6]
    ...

    恩,只有库的文件名信息,而没有路径信息。

    编译程序

    • 第一次编译运行(什么路径都不加)

     

    $ gcc main.c -ldl
    $ ./a.out 
    hello from funcA
    Hello from funcB 2
    main: 12

    程序:dlopen从当前目录找到libA.so,然后却在/usr/lib/中找到libB.so(没有使用当前目录的libB.so,这是我们需要的么?)

    • 第二次编译运行(使用DT_RPATH)

     

    $ gcc main.c -ldl  -Wl,--rpath=.
    $ ./a.out 
    hello from funcA
    Hello from funcB 1
    main: 12

    恩,使用当前目录的libB.so,很理想的东西

    • 可是,由于DT_RPATH无法被环境变量LD_LIBRARY_PATH覆盖,不是不建议被使用,而是建议使用DT_RUNPATH么?

    • 第三次编译运行(使用DT_RUNPATH)

     

    $ gcc main.c -ldl -Wl,--rpath=.,--enable-new-dtags 
    $ ./a.out 
    hello from funcA
    Hello from funcB 2
    main: 12

    问题重新出现,使用的系统路径中的libB.so 而不是当前目录下的。

    程序头部信息

    通过下列命令可以查看:

    $ readelf -d a.out

    为了完整起见,列出前面3次编译的程序的信息:

    • 没有rpath和runpath

     

    Dynamic section at offset 0xf20 contains 21 entries:
      Tag        Type                         Name/Value
     0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
     0x00000001 (NEEDED)                     Shared library: [libc.so.6]
     0x0000000c (INIT)                       0x8048360
    ...
    • 包含rpath

     

    Dynamic section at offset 0xf18 contains 22 entries:
      Tag        Type                         Name/Value
     0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
     0x00000001 (NEEDED)                     Shared library: [libc.so.6]
     0x0000000f (RPATH)                      Library rpath: [.]
     0x0000000c (INIT)                       0x8048360
    ....
    • 包含rpath和runpath

     

    Dynamic section at offset 0xf10 contains 23 entries:
      Tag        Type                         Name/Value
     0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
     0x00000001 (NEEDED)                     Shared library: [libc.so.6]
     0x0000000f (RPATH)                      Library rpath: [.]
     0x0000001d (RUNPATH)                    Library runpath: [.]

    原因

    RPATH and RUNPATH给出这个问题的答案:

     

    Unless loading object has RUNPATH:
        RPATH of the loading object,
            then the RPATH of its loader (unless it has a RUNPATH), ...,
            until the end of the chain, which is either the executable
            or an object loaded by dlopen
        Unless executable has RUNPATH:
            RPATH of the executable
    LD_LIBRARY_PATH
    RUNPATH of the loading object
    ld.so.cache
    default dirs

    用它解释第一个程序:

    • libA.so 没有RUNPATH,故而
      • 使用其RPATH (没有)
      • 递归查找其loader直到链条的顶端(可执行程序或被dlopen打开的对象)的RPATH或者遇RUNPATH退出 (没有命中)
      • 可执行程序没有RUNPATH,故而
        • 使用其RPATH (没有)
    • 环境变量LD_LIBRARY_PATH,(没有)
    • libA.so 的RUNPATH (没有)
    • ld.so.cache (没有命中)
    • 默认路径/usr/lib (命中)

    用它解释第二个程序:

    • libA.so 没有RUNPATH,故而
      • 使用其RPATH (没有)
      • 递归查找其loader直到链条的顶端(可执行程序或被dlopen打开的对象)的RPATH或者遇RUNPATH退出 (没有命中)
      • 可执行程序没有RUNPATH,故而
        • 使用其RPATH (命中)

    用它解释第三个程序:

    • libA.so 没有RUNPATH,故而
      • 使用其RPATH (没有)
      • 递归查找其loader直到链条的顶端(可执行程序或被dlopen打开的对象)的RPATH或者遇RUNPATH退出 (没有命中)
      • 可执行程序有RUNPATH,(继续前行)
    • 环境变量LD_LIBRARY_PATH,(没有)
    • libA.so 的RUNPATH (没有)
    • ld.so.cache (没有命中)
    • 默认路径/usr/lib (命中)

    有意思的就是这个程序了,可执行程序的RUNPATH是一个重要的判断条件,却并不被做为这儿搜索路径!!

    结束

    本文是在kubuntu 11.10下编写测试的。为了尽可能简单,例子也都是认为制造的。而且我们看到,在使用RPATH的时候是正常的,RUNPATH一般来说,被推荐使用,但这儿它却不能正常工作。

    所以,当使用RUNPATH时,我们需要明白:某些情况下可能需要设置环境变量 LD_LIBRARY_PATH

  • 相关阅读:
    将Nginx添加到windows服务中
    springboot使用redis管理session
    GIT常用命令
    阻止360、谷歌浏览器表单自动填充
    谈谈对Spring IOC的理解
    同一个Nginx服务器同一端口配置多个代理服务
    LeetCode 653. Two Sum IV
    109. Convert Sorted List to Binary Search Tree(根据有序链表构造平衡的二叉查找树)
    108. Convert Sorted Array to Binary Search Tree(从有序数组中构造平衡的BST)
    LeetCode 236. Lowest Common Ancestor of a Binary Tree(二叉树求两点LCA)
  • 原文地址:https://www.cnblogs.com/langqi250/p/7514935.html
Copyright © 2011-2022 走看看