• linux动态库加载RPATH, RUNPATH


    摘自http://gotowqj.iteye.com/blog/1926771

    linux动态库加载RPATH, RUNPATH

    链接动态库

    如何程序在连接时使用了共享库,就必须在运行的时候能够找到共享库的位置。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

  • 相关阅读:
    tcp连接建立和断开
    端口状态说明 LISTENING、ESTABLISHED、TIME_WAIT及CLOSE_WAIT
    window IIS6/IIS7取消脚本执行权限,禁止运行脚本木马
    java 各种架构图汇总
    .NET平台常见技术框架整理汇总
    SQL Server中的Merge Into
    iText从LGPL改成AGPL历史来龙去脉
    工具系列 | 分布式日志管理graylog 实战
    目前博客使用的主题模板
    RLChina 文章
  • 原文地址:https://www.cnblogs.com/LiuYanYGZ/p/5546242.html
Copyright © 2020-2023  润新知