`

linux动态库加载RPATH,RUNPATH

gcc 
阅读更多

链接动态库

如何程序在连接时使用了共享库,就必须在运行的时候能够找到共享库的位置。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\n", 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\n");
    return funcB(a, b);
}
  • libb1.c ==> ./libB.so

 

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

 

#include <stdio.h>
int funcB(int a, int b)
{
    printf("Hello from funcB 2\n");
    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

分享到:
评论

相关推荐

    05-rpath解决so动态库依赖1

    总之,`rpath`在解决Linux系统中动态库依赖问题时扮演着重要角色。通过理解`rpath`的工作原理,以及如何使用`ldd`、`readelf`和`patchelf`等工具,我们可以有效地管理和解决库的查找问题,确保程序正常运行。在开发...

    linux应用程序启动动态库加载问题.docx

    总之,理解Linux动态库加载机制对于排查这类问题至关重要。通过仔细分析库的搜索路径、加载过程和重定位步骤,我们可以定位问题并找到解决方案。在实际操作中,应结合`ldd`、`readelf`、`strace`和`ldconfig`等工具...

    Linux动态库连接

    在Linux系统中,动态库(Dynamic Library)是应用程序在运行时加载的共享代码库,它包含了一组可重用的函数和数据。动态库的主要优点是节省内存,因为多个程序可以共享同一份库的副本,而不是每个程序都有自己的私有...

    Linux下使用动态库小结

    在Linux中,程序运行时会自动加载所需的动态库。默认情况下,Linux系统会在`/lib`和`/usr/lib`等目录下查找动态库文件。此外,用户还可以通过以下几种方式指定动态库的查找路径: - **通过`/etc/ld.so.conf`配置...

    linux_dongtai_lib.zip_linux 动态库

    在Linux系统中,动态库(Dynamic Link Libraries,也称为共享对象或.so文件)是程序运行时需要调用的代码库,它们被多个程序共享,节省了内存资源并方便了代码的更新与维护。本资料“linux_dongtai_lib.zip”专注于...

    linux静态库及动态库创建及使用

    ### Linux静态库及动态库创建及使用 #### 一、基本概念 ##### 1.1 什么是库 在计算机编程领域,**库**(Library)是一系列预编译的代码集合,这些代码通常实现了某些功能或服务,可供其他程序在运行时调用。库可以...

    Linux静态库和动态库

    **Linux库的分类**:Linux下的库主要分为两大类——静态库与动态库,它们的核心区别在于代码的加载时机。静态库在编译阶段即与目标程序结合,而动态库则是在程序运行时动态加载。 #### 静态库与动态库的生成与使用 ...

    linux静态库和动态库分析

    本文主要探讨的是Linux下的静态库和动态库。 首先,我们要了解什么是库。库本质上是一组预编译的函数和数据结构,以二进制形式存在,可供操作系统加载执行。由于Windows和Linux的系统架构差异,它们之间的库文件是...

    rPath简化定制Linux.pdf

    rPath公司的核心产品之一是rPath Linux,它不仅提供了一个具有高度可定制性的操作系统基础架构,而且在本质上是一个集成解决方案,让ISV能够通过rPath的rBuilder工具轻松创建符合自身需求的操作系统镜像。...

    chrpath-0.16版本,离线包

    在Linux和类Unix系统中,RPATH和RUNPATH是用来指示动态链接器在何处查找共享库的路径。了解`chrpath`及其工作原理对于系统管理员、软件开发者以及对底层系统操作有兴趣的用户来说是非常重要的。 首先,我们要明确...

    C++创建调用静态动态库

    动态库在运行时被加载,可以节省磁盘空间和内存,因为多个程序可以共享同一份库。在Linux中,动态库的扩展名为`.so`(共享对象),在Windows中是`.dll`。创建C++动态库的步骤如下: 1. 同样,编写源代码,如`...

    QT动态库实现及调用方式

    1. **静态链接**:将动态库中的函数和类直接编译到主程序中,避免了运行时加载动态库的步骤,但会增加程序体积。 2. **动态链接**:在运行时通过操作系统加载动态库,程序启动时小,但需要确保动态库在系统的PATH...

    Linux动态库的编译与使用

    8. 动态库加载路径:可以使用 -Wl,-rpath 选项来指定动态库的加载路径,例如: ``` gcc test.c -L . -ltest -Wl,-rpath,./ -o test ``` Linux 动态库的编译与使用是软件开发中一个非常重要的知识点,需要了解动态...

    Linux下Gcc生成和使用静态库和动态库详解

    ### Linux下Gcc生成和使用静态库和动态库详解 #### 一、基本概念 **1.1 什么是库** 库本质上是一种可执行代码的二进制形式,它可以被操作系统载入内存执行。无论是Windows还是Linux平台,都广泛地使用着库。然而...

    C++动态库和静态库的使用.rar

    运行时,系统通过`dlopen`等函数加载动态库,`dlsym`查找并绑定符号。 三、优缺点对比 1. **静态库优点**:无需担心运行时找不到库的问题,程序更独立;缺点是生成的可执行文件较大,可能导致版本升级困难。 2. **...

    linux生成(加载)动态库静态库和加载示例方法

    下面将详细介绍如何在Linux环境下生成和加载动态库与静态库。 首先,我们来看动态库的生成过程。动态库的基本结构通常包括一个`.c`源文件和对应的头文件`.h`。以给定的例子来说: 1. `mysum.c` 文件包含实际的函数...

    chrpath-0.16.tar.gz

    在Linux环境中,RPATH和RUNPATH是两个关键的构建属性,它们指示操作系统如何查找依赖的动态库。RPATH是在编译时硬编码的路径,而RUNPATH是在可执行文件中动态设置的,允许用户在运行时更改查找路径。`chrpath`工具...

    linux gcc生成动态库和静态库

    ### Linux GCC 生成动态库与静态库详解 #### 一、基本概念 1.1 **什么是库** 在软件开发领域,库是指预先编写并编译好的一组功能集合,以二进制的形式存在,可供其他程序调用。这些库通常包含了各种预定义的功能...

    linux下静态库及动态库的创建与使用.pdf

    动态库在编译时仅作为引用,而在运行时通过动态链接器(如ld-linux.so*)找到并加载相应的库。动态库的命名遵循libxxxx.so.major.minor的格式,其中xxxx是库名,major是主版本号,minor是次版本号。 在Linux中,...

Global site tag (gtag.js) - Google Analytics