[轉載] RPATH/LD_LIBRARY_PATH/RUNPATH

sinpo828發表於2024-04-29

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

相關文章