摘自http://gotowqj.iteye.com/blog/1926734css
摘自http://www.360doc.com/content/14/0313/13/12747488_360246417.shtmlhtml
linux 下有動態庫和靜態庫,動態庫以.so爲擴展名,靜態庫以.a爲擴展名。兩者都使用普遍。本文主要講動態庫方面知識。node
這麼多so,是的。使用ldd顯示的so,並非全部so都是須要使用的,下面舉個例子linux
看看,雖然沒有用到,可是同樣有連接進來,那看看程序啓動時候有沒有去加載它們呢ios
咱們知道linux連接so有兩種途徑:顯示和隱式。所謂顯示就是程序主動調用dlopen打開相關so;這裏須要補充的是,若是使用顯示連接,上篇文章討論的那些問題都不存在。首先,dlopen的so使用ldd是查看不到的。其次,使用dlopen打開的so並非在進程啓動時候加載映射的,而是當進程運行到調用dlopen代碼地方纔加載該so,也就是說,若是每一個進程顯示連接a.so;可是若是發佈該程序時候忘記附帶發佈該a.so,程序仍然可以正常啓動,甚至若是運行邏輯沒有觸發運行到調用dlopen函數代碼地方。該程序還能正常運行,即便沒有a.so.c++
既然顯示加載這麼多優勢,那麼爲何實際生產中不多碼農使用它呢, 主要緣由仍是起使用不是很方便,須要開發人員多寫很多代碼。因此不被大多數碼農使用,還有一個重要緣由應該是能提早發現錯誤,在部署的時候就能發現缺乏哪些so,而不是等到實際上限運行的時候才發現缺東少西。shell
下面舉個工做中最常碰到的問題,來引伸出本篇內容吧。ubuntu
寫一個最簡單的so, tmp.cppbash
1. int test()app
2. {
3. return 20;
4. }
編譯=>連接=》運行, 下面main.cpp 內容請參見上一篇文章。
[stevenrao]$ g++ -fPIC -c tmp.cpp
[stevenrao]$ g++ -shared -o libtmp.so tmp.o
[stevenrao]$ mv libtmp.so /tmp/
[stevenrao]$ g++ -o demo -L/tmp -ltmp main.cpp
[stevenrao]$ ./demo
./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory
[stevenrao]$ g++ -fPIC -c tmp.cpp
[stevenrao]$ g++ -shared -o libtmp.so tmp.o
[stevenrao]$ mv libtmp.so /tmp/
[stevenrao]$ g++ -o demo -L/tmp -ltmp main.cpp
[stevenrao]$ ./demo
./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory
[stevenrao]$ ldd demo
linux-vdso.so.1 => (0x00007fff7fdc1000)
libtmp.so => not found
[stevenrao]$ ldd demo
linux-vdso.so.1 => (0x00007fff7fdc1000)
libtmp.so => not found
這個錯誤是最多見的錯誤了。運行程序的時候找不到依賴的so。通常人使用方法是修改LD_LIBRARY_PATH這個環境變量
export LD_LIBRARY_PATH=/tmp
[stevenrao]$ ./demo
test
這樣就OK了, 不過這樣export 只對當前shell有效,當另開一個shell時候,又要從新設置。能夠把export LD_LIBRARY_PATH=/tmp 語句寫到 ~/.bashrc中,這樣就對當前用戶有效了,寫到/etc/bashrc中就對全部用戶有效了。
前面連接時候使用 -L/tmp/ -ltmp 是一種設置相對路徑方法,還有一種絕對路徑連接方法。
[stevenrao]$ g++ -o demo /tmp/libtmp.so main.cpp
[stevenrao]$ ./demo
test
[stevenrao]$ ldd demo
linux-vdso.so.1 => (0x00007fff083ff000)
/tmp/libtmp.so (0x00007f53ed30f000)
絕對路徑雖然申請設置環境變量步驟,可是缺陷也是致命的,這個so必須放在絕對路徑下,不能放到其餘地方,這樣給部署帶來很大麻煩。因此應該禁止使用絕對路徑連接so。
搜索路徑分兩種,一種是連接時候的搜索路徑,一種是運行時期的搜索路徑。像前面提到的 -L/tmp/ 是屬於連接時期的搜索路徑,即給ld程序提供的編譯連接時候尋找動態庫路徑;而 LD_LIBRARY_PATH則既屬於連接期搜索路徑,又屬於運行時期的搜索路徑。
這裏須要介紹鏈-rpath連接選項,它是指定運行時候都使用的搜索路徑。聰明的同窗立刻就想到,運行時搜索路徑,那它記錄在哪兒呢。也像. LD_LIBRARY_PATH那樣,每部署一臺機器就須要配一下嗎。呵呵,不須要..,由於它已經被硬編碼到可執行文件內部了。看看下面演示
[stevenrao] $ g++ -o demo -L /tmp/ -ltmp main.cpp
[stevenrao] $ ./demo
./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory
[stevenrao] $ g++ -o demo -Wl,-rpath /tmp/ -L/tmp/ -ltmp main.cpp
[stevenrao] $ ./demo
test
[stevenrao] $ readelf -d demo
Dynamic section at offset 0xc58 contains 26 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libtmp.so]
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000f (RPATH) Library rpath: [/tmp/]
0x000000000000001d (RUNPATH) Library runpath: [/tmp/]
看看是吧,編譯到elf文件內部了,路徑和程序深深的耦合到一塊兒
繼續上一篇《 linux下so動態庫一些鮮爲人知的祕密(中) 》介紹so搜索路徑,還有一個相似於-path,叫LD_RUN_PATH環境變量, 它也是把路徑編譯進可執行文件內,不一樣的是它只設置RPATH。
如何程序在鏈接時使用了共享庫,就必須在運行的時候可以找到共享庫的位置。linux的可執行程序在執行的時候默認是先搜索/lib和/usr/lib這兩個目錄,而後按照/etc/ld.so.conf裏面的配置搜索絕對路徑。同時,Linux也提供了環境變量LDLIBRARYPATH供用戶選擇使用,用戶能夠經過設定它來查找除默認路徑以外的其餘路徑,如查找/work/lib路徑,你能夠在/etc/rc.d/rc.local或其餘系統啓動後便可執行到的腳本添加以下語句:LDLIBRARYPATH =/work/lib:$(LDLIBRARYPATH)。而且LDLIBRARYPATH路徑優先於系統默認路徑以前查找(詳細參考《使用LDLIBRARYPATH》)。
不過LDLIBRARYPATH的設定做用是全局的,過多的使用可能會影響到其餘應用程序的運行,因此多用在調試。(LDLIBRARYPATH的缺陷和使用準則,能夠參考《Why LDLIBRARYPATH is bad》 )。一般狀況下推薦仍是使用gcc的-R或-rpath選項來在編譯時就指定庫的查找路徑,而且該庫的路徑信息保存在可執行文件中,運行時它會直接到該路徑查找庫,避免了使用LDLIBRARYPATH環境變量查找。
現代鏈接器在處理動態庫時將連接時路徑(Link-time path)和運行時路徑(Run-time path)分開,用戶能夠經過-L指定鏈接時庫的路徑,經過-R(或-rpath)指定程序運行時庫的路徑,大大提升了庫應用的靈活性。好比咱們作嵌入式移植時#arm-linux-gcc $(CFLAGS) –o target –L/work/lib/zlib/ -llibz-1.2.3 (work/lib/zlib下是交叉編譯好的zlib庫),將target編譯好後咱們只要把zlib庫拷貝到開發板的系統默認路徑下便可。或者經過-rpath(或-R )、LDLIBRARYPATH指定查找路徑。
連接器ld的選項有 -L,-rpath 和 -rpath-link,看了下 man ld,大體是這個意思:
-L: 「連接」的時候,去找的目錄,也就是全部的 -lFOO 選項裏的庫,都會先從 -L 指定的目錄去找,而後是默認的地方。編譯時的-L選項並不影響環境變量LDLIBRARYPATH,-L只是指定了程序編譯鏈接時庫的路徑,並不影響程序執行時庫的路徑,系統仍是會到默認路徑下查找該程序所須要的庫,若是找不到,仍是會報錯,相似cannot open shared object file。
-rpath-link:這個也是用於「連接」的時候的,例如你顯示指定的須要 FOO.so,可是 FOO.so 自己是須要 BAR.so 的,後者你並無指定,而是 FOO.so 引用到它,這個時候,會先從 -rpath-link 給的路徑裏找。
-rpath: 「運行」的時候,去找的目錄。運行的時候,要找 .so 文件,會從這個選項裏指定的地方去找。對於交叉編譯,交叉編譯連接器需已經配置 --with-sysroot 選項才能起做用。也就是說,-rpath指定的路徑會被記錄在生成的可執行程序中,用於運行時查找須要加載的動態庫。-rpath-link 則只用於連接時查找。
直接man ld。The linker uses the following search paths to locate required shared libraries:
1. Any directories specified by -rpath-link options. 2. Any directories specified by -rpath options. The difference between -rpath and -rpath-link is that directories specified by -rpath options are included in the executable and used at runtime, whereas the -rpath-link option is only effective at link time. Searching -rpath in this way is only supported by native linkers and cross linkers which have been configured with the --with-sysroot option. 3. On an ELF system, for native linkers, if the -rpath and -rpath-link options were not used, search the contents of the environment variable "LD_RUN_PATH". 4. On SunOS, if the -rpath option was not used, search any directories specified using -L options. 5. For a native linker, the search the contents of the environment variable "LD_LIBRARY_PATH". 6. For a native ELF linker, the directories in "DT_RUNPATH" or "DT_RPATH" of a shared library are searched for shared libraries needed by it. The "DT_RPATH" entries are ignored if "DT_RUNPATH" entries exist. 7. The default directories, normally /lib and /usr/lib. 8. For a native linker on an ELF system, if the file /etc/ld.so.conf exists, the list of directories found in that file. If the required shared library is not found, the linker will issue a warning and continue with the link.
1 |
gcc -Wl,--start-group foo.o bar.o -Wl,--end-group |
This is important, because otherwise the compiler driver program may silently drop the linker options, resulting in a bad link.
二進制 |
對應源碼 |
|
有一個程序 |
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; }
$ gcc -shared -fPIC libb2.c -o libB2.so $ sudo mv libB2.so /usr/lib/libB.so $ gcc -shared -fPIC libb.c -o libB.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,這是咱們須要的麼?)
$ 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麼?
$ 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次編譯的程序的信息:
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 ...
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 ....
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
用它解釋第一個程序:
用它解釋第二個程序:
用它解釋第三個程序:
有意思的就是這個程序了,可執行程序的RUNPATH是一個重要的判斷條件,卻並不被作爲這兒搜索路徑!!
本文是在kubuntu 11.10下編寫測試的。爲了儘量簡單,例子也都是認爲製造的。並且咱們看到,在使用RPATH的時候是正常的,RUNPATH通常來講,被推薦使用,但這兒它卻不能正常工做。
因此,當使用RUNPATH時,咱們須要明白:某些狀況下可能須要設置環境變量 LD_LIBRARY_PATH