[转载] RPATH/LD_LIBRARY_PATH/RUNPATH

原文地址: https://zhuanlan.zhihu.com/p/534778561

rpath和runpath都是用来指定搜索动态链接库的目录的,如果不清楚动态链接库是什么,可见静态库vs动态库。

动态链接库(shared libraries)作为库并不像静态库一样和可执行文件绑定,而是在运行时加载。但,可执行文件在运行时怎么知道库的位置呢,实际上ld会按照一定的目录顺序来搜索动态库路径。

先从一个小实验开始,通过编译一个动态链接库,然后试着和可执行文件链接:

// test_shared.h
#include <stdio.h>
void printHelloWorld();

// test_shared.c
#include "test_shared.h"
void printHelloWorld()
{
        printf("Hello World!\n");
}

编译得动态链接库:

gcc -c test_shared.c -o test_shared.o
gcc --shared test_shared.o -o libtest_shared.so

然后在可执行文件里使用这个动态链接库:

// use_shared.c
#include "test_shared.h"

void main()
{
        printHelloWorld();
}

编译可执行文件,链接该动态链接库:

gcc -o use_shared use_shared.c -L./ -ltest_shared
./use_shared 
./use_shared: error while loading shared libraries: libtest_shared.so: cannot open shared object file: No such file or directory

问题来了,为什么可执行文件找不到这个动态链接库呢,我们debug一下看看ld是怎么寻找动态链接库的:

LD_DEBUG=libs ./use_shared 
     10781: find library=libtest_shared.so [0]; searching
     10781:  search cache=/etc/ld.so.cache
     10781:  search path=/usr/lib/x86_64:/usr/lib  (system search path)
     10781:   trying file=/usr/lib/x86_64/libtest_shared.so
     10781:   trying file=/usr/lib/libtest_shared.so
     10781: 
./use_shared: error while loading shared libraries: libtest_shared.so: cannot open shared object file: No such file or directory

LD_LIBRARY_PATH

先试试LD_LIBRARY_PATH来指定库搜索的路径,看下debug结果:

LD_DEBUG=libs LD_LIBRARY_PATH=./ ./use_shared 
     10853: find library=libtest_shared.so [0]; searching
     10853:  search path=./x86_64:.  (LD_LIBRARY_PATH)
     10853:   trying file=./x86_64/libtest_shared.so
     10853:   trying file=./libtest_shared.so
     10853: 
     10853: find library=libc.so.6 [0]; searching
     10853:  search path=./x86_64:.  (LD_LIBRARY_PATH)
     10853:   trying file=./x86_64/libc.so.6
     10853:   trying file=./libc.so.6
     10853:  search cache=/etc/ld.so.cache
     10853:   trying file=/lib/x86_64-linux-gnu/libc.so.6
     10853: 
     10853: 
     10853: calling init: /lib/x86_64-linux-gnu/libc.so.6
     10853: 
     10853: 
     10853: calling init: ./libtest_shared.so
     10853: 
     10853: 
     10853: initialize program: ./use_shared
     10853: 
     10853: 
     10853: transferring control: ./use_shared
     10853: 
Hello World!
     10853: 
     10853: calling fini: ./use_shared [0]
     10853: 
     10853: 
     10853: calling fini: ./libtest_shared.so [0]
     10853:

可以看出当前路径已经加到了搜索路径中,可执行文件成功运行。删掉库会怎样呢:

rm libuse_shared.so
LD_DEBUG=libs LD_LIBRARY_PATH=./ ./use_shared 
     11330: find library=libtest_shared.so [0]; searching
     11330:  search path=./x86_64:.  (LD_LIBRARY_PATH)
     11330:   trying file=./x86_64/libtest_shared.so
     11330:   trying file=./libtest_shared.so
     11330:  search cache=/etc/ld.so.cache
     11330:  search path=/usr/lib/x86_64:/usr/lib  (system search path)
     11330:   trying file=/usr/lib/x86_64/libtest_shared.so
     11330:   trying file=/usr/lib/libtest_shared.so
     11330: 
./use_shared: error while loading shared libraries: libtest_shared.so: cannot open shared object file: No such file or directory

可以看到先使用了LD_LIBRARY_PATH,后使用了system path都没有找到该库。

到这应该能大概理解运行时库的搜索路径,我们使用了LD_LIBRARY_PATh来指定runtime path,rpath和runpath也类似,区别是搜索时的顺序。

RPATH

用之前一样的程序

// test_share.h
#include <stdio.h>
void printHelloWorld();

// test_shared.c
#include "test_shared.h"

void printHelloWorld()
{
        printf("Hello World!\n");
}

编译链接得到:

gcc -c test_shared.c -o test_shared.o
gcc --shared test_shared.o -o libtest_shared.so

源文件

//use_shared.c
#include "test_shared.h"

void main()
{
        printHelloWorld();
}

编译链接可执行文件

gcc -o use_shared use_shared.c -L./ -ltest_shared  -Wl,-rpath,./,--disable-new-dtags

注意,--disable-new-dtags 表示使用的是rpath,去掉后编译器默认使用runpath。

用readelf来确认路径,输出如预想的一样:

readelf -d use_shared | grep PATH
 0x000000000000000f (RPATH)              Library rpath: [./]

接下来创建一个输出不同但名字一样的B库,放在别的目录下:

mkdir overrided_library
cd overrided_library

源文件为:

// test_share.h
#include <stdio.h>
void printHelloWorld();

// test_shared.c
#include "test_shared.h"

void printHelloWorld()
{
        printf("Hello World from overrided library!\n");
}

编译链接如上

gcc -c test_shared.c -o test_shared.o
gcc --shared test_shared.o -o libtest_shared.so

我们设置一下LD_LIBRARY_PATH,在这个路径下放上B库,看看可执行文件到底选择哪个呢?

LD_DEBUG=libs LD_LIBRARY_PATH=./overriding_library/ ./use_shared 
      9168: find library=libtest_shared.so [0]; searching
      9168:  search path=./tls/i686/sse2/cmov:./tls/i686/sse2:./tls/i686/cmov:./tls/i686:./tls/sse2/cmov:./tls/sse2:./tls/cmov:./tls:./i686/sse2/cmov:./i686/sse2:./i686/cmov:./i686:./sse2/cmov:./sse2:./cmov:.  (RPATH from file ./use_shared)
      9168:   trying file=./tls/i686/sse2/cmov/libtest_shared.so
      9168:   trying file=./tls/i686/sse2/libtest_shared.so
      ...
      9168:   trying file=./sse2/libtest_shared.so
      9168:   trying file=./cmov/libtest_shared.so
      9168:   trying file=./libtest_shared.so
      9168: 
      9168: 
      9168: calling init: /lib/i386-linux-gnu/libc.so.6
      9168: 
      9168: 
      9168: calling init: ./libtest_shared.so
      9168: 
      9168: 
      9168: initialize program: ./use_shared
      9168: 
      9168: 
      9168: transferring control: ./use_shared
      9168: 
Hello World!
      9168: 
      9168: calling fini: ./use_shared [0]
      9168: 
      9168: 
      9168: calling fini: ./libtest_shared.so [0]
      9168:

发现rpath中找到了,就不继续在LD_LIBRARY_PATH中找了,如果把当前路径下的库删掉,就会用B库了,如下:

rm libtest_shared.so
LD_LIBRARY_PATH=./overrided_library/ ./use_shared
Hello World from overrided library!

调试信息也可以证明:

LD_DEBUG=libs LD_LIBRARY_PATH=./overrided_library/ ./use_shared 
     11512: find library=libtest_shared.so [0]; searching
     11512:  search path=./tls/i686/sse2/cmov:./tls/i686/sse2:./tls/i686/cmov:./tls/i686:./tls/sse2/cmov:./tls/sse2:./tls/cmov:./tls:./i686/sse2/cmov:./i686/sse2:./i686/cmov:./i686:./sse2/cmov:./sse2:./cmov:.  (RPATH from file ./use_shared)
     11512:   trying file=./tls/i686/sse2/cmov/libtest_shared.so
     11512:   trying file=./tls/i686/sse2/libtest_shared.so
     ...
     11512:   trying file=./cmov/libtest_shared.so
     11512:   trying file=./libtest_shared.so
     11512:  search path=./overrided_library/tls/i686/sse2/cmov:./overrided_library/tls/i686/sse2:./overrided_library/tls/i686/cmov:./overrided_library/tls/i686:./overrided_library/tls/sse2/cmov:./overrided_library/tls/sse2:./overrided_library/tls/cmov:./overrided_library/tls:./overrided_library/i686/sse2/cmov:./over/i686/sse2:./overrided_library/i686/cmov:./overrided_library/i686:./overrided_library/sse2/cmov:./overrided_library/sse2:./over/cmov:./overrided_library  (LD_LIBRARY_PATH)
     11512:   trying file=./overrided_library/tls/i686/sse2/cmov/libtest_shared.so
     11512:   trying file=./overrided_library/tls/i686/sse2/libtest_shared.so
     ...
     11512:   trying file=./overrided_library/sse2/libtest_shared.so
     11512:   trying file=./overrided_library/cmov/libtest_shared.so
     11512:   trying file=./overrided_library/libtest_shared.so
     11512: 
     11512: calling init: ./overrided_library/libtest_shared.so
     11512: 
     11512: 
     11512: initialize program: ./use_shared
     11512: 
     11512: 
     11512: transferring control: ./use_shared
     11512: 
Hello World from overrided library!
     11512: 
     11512: calling fini: ./use_shared [0]
     11512: 
     11512: 
     11512: calling fini:./overrided_library/libtest_shared.so [0]
     11512:

所以结论是,在优先级上,rpath > LD_LIBRARY_PATH,且是第一个搜索的路径。
RUNPATH

用rpath来编译可执行文件(保险的话可以用--enable-new-dtags,但个人用的gcc-7版本的默认是带的):

gcc -o use_shared use_shared.c -L./ -ltest_shared -Wl,-rpath,./
readelf -d use_shared | grep PATH 
0x0000001d (RUNPATH)                    Library runpath: [./]
LD_LIBRARY_PATH=./overrided_library/ ./use_shared 
Hello World from overrided library!

debug后发现LD_LIBRARY_PATH在runpath之前搜索,搜索到库后就不再继续在runpath中搜索了,如果删掉LD_LIBRARY_PATH下的库被删掉,用的就是RUNPATH下的库了,debug信息也可以证明:

LD_DEBUG=libs LD_LIBRARY_PATH=./overrided_library/ ./use_shared 
     26712: find library=libtest_shared.so [0]; searching
     26712:  search path=./overrided_library/tls/i686/sse2/cmov:./overrided_library/tls/i686/sse2:./overrided_library/tls/i686/cmov:./overrided_library/tls/i686:./overrided_library/tls/sse2/cmov:./overrided_library/tls/sse2:./overrided_library/tls/cmov:./overrided_library/tls:./overrided_library/i686/sse2/cmov:./overrided_library/i686/sse2:./overrided_library/i686/cmov:./overrided_library/i686:./overrided_library/sse2/cmov:./overrided_library/sse2:./overrided_library/cmov:./overrided_library  (LD_LIBRARY_PATH)
     26712:   trying file=./overrided_library/tls/i686/sse2/cmov/libtest_shared.so
     26712:   trying file=./overrided_library/tls/i686/sse2/libtest_shared.so
     ...
     26712:   trying file=./overrided_library/sse2/libtest_shared.so
     26712:   trying file=./overrided_library/cmov/libtest_shared.so
     26712:   trying file=./overrided_library/libtest_shared.so
     26712:  search path=./tls/i686/sse2/cmov:./tls/i686/sse2:./tls/i686/cmov:./tls/i686:./tls/sse2/cmov:./tls/sse2:./tls/cmov:./tls:./i686/sse2/cmov:./i686/sse2:./i686/cmov:./i686:./sse2/cmov:./sse2:./cmov:.  (RUNPATH from file ./use_shared)
     26712:   trying file=./tls/i686/sse2/cmov/libtest_shared.so
     26712:   trying file=./tls/i686/sse2/libtest_shared.so
     ...
     26712:   trying file=./cmov/libtest_shared.so
     26712:   trying file=./libtest_shared.so
     26712: 
     26712: calling init: /lib/i386-linux-gnu/libc.so.6
     26712: 
     26712: 
     26712: calling init: ./libtest_shared.so
     26712: 
     26712: 
     26712: initialize program: ./use_shared
     26712: 
     26712: 
     26712: transferring control: ./use_shared
     26712: 
Hello World!
     26712: 
     26712: calling fini: ./use_shared [0]
     26712: 
     26712: 
     26712: calling fini: ./libtest_shared.so [0]
     26712:

Conclusion

综上,动态库的搜索路径优先级是:

    rpath
    LD_LIBRARY_PATH
    runpath

从历史的角度来说,一开始是只有rpath的,问题是rpath在编译时一旦设了就不能靠LD_LIBRARY_PATH来自定义加载的路径了,每次要测不同的库的时候(放的位置可能不同)就得重新build可执行文件,这样很烦。因此才引入了runpath,编译后在运行时还可以用LD_LIBRARY_PATH来覆盖掉,这样就不用每次重新编译了,只需要NEEDED里的Value一致即可。

Extra

有时候RUNPATH为$ORIGIN,如图:

$ORIGIN表示文件所在的目录

posted @ 2024-04-29 18:06  sinpo828  阅读(89)  评论(0编辑  收藏  举报