-
考慮翻譯Qt官方blog中的RPATH and RUNPATH這篇文章,在繼續之前,我需要先驗證自己的理解是正確的,至少能自圓其說,能說服自己。
用例子說話
二進制 |
對應源碼 |
|
有一個程序 |
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