[转载] 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表示文件所在的目录