原文地址: 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表示檔案所在的目錄