Linux支持共享庫已經(jīng)有悠久的歷史了,不再是什么新概念了。大家都知道如何編譯、連接以及動態(tài)加載(dlopen/dlsym/dlclose)共享庫。但是,可能很多人,甚至包括一些高手,對共享庫相關(guān)的一些環(huán)境變量認(rèn)識模糊。當(dāng)然,不知道這些環(huán)境變量,也可以用共享庫,但是,若知道它們,可能就會用得更好。下面介紹一些常用的環(huán)境變量,希望對家有所幫助:
LD_LIBRARY_PATH這個環(huán)境變量是大家最為熟悉的,它告訴loader:在哪些目錄中可以找到共享庫??梢栽O(shè)置多個搜索目錄,這些目錄之間用冒號分隔開。在linux下,還提供了另外一種方式來完成同樣的功能,你可以把這些目錄加到/etc/ld.so.conf中,或則在/etc/ld.so.conf.d里創(chuàng)建一個文件,把目錄加到這個文件里。當(dāng)然,這是系統(tǒng)范圍內(nèi)全局有效的,而環(huán)境變量只對當(dāng)前shell有效。按照慣例,除非你用上述方式指明,loader是不會在當(dāng)前目錄下去找共享庫的,正如shell不會在當(dāng)前目前找可執(zhí)行文件一樣。
LD_PRELOAD這個環(huán)境變量對于程序員來說,也是特別有用的。它告訴loader:在解析函數(shù)地址時,優(yōu)先使用LD_PRELOAD里指定的共享庫中的函數(shù)。這為調(diào)試提供了方便,比如,對于C/C++程序來說,內(nèi)存錯誤最難解決了。常見的做法就是重載malloc系列函數(shù),但那樣做要求重新編譯程序,比較麻煩。使用LD_PRELOAD機(jī)制,就不用重新編譯了,把包裝函數(shù)庫編譯成共享庫,并在LD_PRELOAD加入該共享庫的名稱,這些包裝函數(shù)就會自動被調(diào)用了。在linux下,還提供了另外一種方式來完成同樣的功能,你可以把要優(yōu)先加載的共享庫的文件名寫在/etc/ld.so.preload里。當(dāng)然,這是系統(tǒng)范圍內(nèi)全局有效的,而環(huán)境變量只對當(dāng)前shell有效。
LD_ DEBUG 這個環(huán)境變量比較好玩,有時使用它,可以幫助你查找出一些共享庫的疑難雜癥(比如同名函數(shù)引起的問題)。同時,利用它,你也可以學(xué)到一些共享庫加載過程的知識。它的參數(shù)如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
BIND_NOW 這個環(huán)境變量與dlopen中的flag的意義是一致,只是dlopen中的flag適用于顯示加載的情況,而BIND_NOW/BIND_NOT適用于隱式加載。
LD_PROFILE/LD_PROFILE_OUTPUT:為指定的共享庫產(chǎn)生profile數(shù)據(jù),LD_PROFILE指定共享庫的名稱,LD_PROFILE_OUTPUT指定輸出profile文件的位置,是一個目錄,且必須存在,默認(rèn)的目錄為/var/tmp/或/var/profile。通過profile數(shù)據(jù),你可以得到一些該共享庫中函數(shù)的使用統(tǒng)計信息。
------------------------------------------------------------------------------------------
調(diào)用一些別人編寫的動態(tài)鏈接庫,但是一般都不提供源文件或.lib文件(有些比較摳門的第三方廠商就如此),就無法進(jìn)行隱式連接
隱式連接(默認(rèn),無須聲明)
通過在某些目錄搜集共享目標(biāo)的信息,讓系統(tǒng)在程序啟動時自動載入和連接共享庫
顯式鏈接
讓程序自己通過庫服務(wù)(libdl.so)來載入和連接到共享庫
making the program load and link the shared objects thanks to some
用3個C++程序來演示這些服務(wù),因為不嚴(yán)格地,C可以認(rèn)為是C++的子集,出現(xiàn)的問題都是類似的
共享的庫 定義了一個全局的C++類,在載入庫時必須進(jìn)行初始化,在卸載事必須銷毀,這套子程序就叫做靜態(tài)構(gòu)造和析構(gòu)器。共享庫還提供了在C語言中的入口(在主程序中被調(diào)用), 因為C沒有mangle(名稱修飾)
試驗1: 用ld建立共享庫
1)在哪里尋找共享庫,雖然 LD_LIBRAR_PATH據(jù)說是不被推薦使用的
$ export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:`pwd`
2)建立共享庫
$ g++ -c base_shared.cc
$ ld -shared base_shared.o -o libbase_shared.so
3)建立主程序
$ g++ base.cc -L`pwd` -lbase_shared
/usr/bin/ld: a.out: hidden symbol `__dso_handle’ in /usr/lib/gcc/i486-linux-gnu/4.3.2/crtbegin.o is referenced by DSO
/usr/bin/ld: final link failed: Nonrepresentable section on output
collect2: ld returned 1 exit status
因為引用了一個隱藏的符號__dso_handle,所以報錯了
我們只好改用下面的共享庫創(chuàng)建方式
$ g++ -shared base_shared.o -o libbase_shared.so
而不是像上面那樣用 ld, 這個很重要
然后用下面的選項 編譯 主程序
$ g++ base.cc -L`pwd` -lbase_shared
現(xiàn)在可以正常通過了,運(yùn)行一下
$ ./a.oout
Constructor of toto
Main entry point
Shared lib entry point, toto’s var = 18
Destructor of toto
我們可以看到,主程序動態(tài)連接到共享庫上了,并且 共享庫的 靜態(tài) 構(gòu)造和解構(gòu)器 會被自動調(diào)用
顯示鏈接共享庫
所謂顯示,就是在主程序里面必須聲明,Linux下是通過dlopen()來主動打開共享庫
$ g++ -c base_shared.cc
$ ld -shared base_shared.o -o libbase_shared.so
$ g++ base1.cc -ldl
$ ./a.out
Main entry point
Loading shared lib…
./libbase_shared.so: undefined symbol: __dso_handle
運(yùn)行時報錯, 因為無法解析 __dso_handle
我們只好再次選擇用gcc生成共享庫,并修改編譯選項
$ g++ -shared base_shared.o -o libbase_shared.so
$ g++ base1.cc -l dl
再試,就好了
實際上,我們之需要編譯共享庫就好了,不需要重新編譯主程序,因為主程序的編譯是沒有問題的,是運(yùn)行的時候報錯
可以看到,在調(diào)用dlopen的時候,共享庫被自動載入了構(gòu)造過程
結(jié)論:
無論我們是顯式還是隱式調(diào)用 共享庫,要用g++ -shared來生成,而不是用ld -shared. 因為靜態(tài)構(gòu)造器 可能會 用到
Ifyour shared library contains global or static objects withconstructors, then make sure to use gcc -shared, not ld, to create theshared library. This will make sure that any processor-specific magicneeded to execute the constructors is included.
gcc/g++鏈接注意事項
-l參數(shù)就是用來指定程序要鏈接的庫,-l參數(shù)緊接著就是庫名
那么庫名跟真正的庫文件名有什么關(guān)系呢?就拿數(shù)學(xué)庫來說,他的庫名是m,他的庫文件名是libm.so,很容易看出,把庫文件名的頭lib和尾.so去掉就是庫名了
(用IDA反匯編出,也可以看到庫名)
-L參數(shù)跟著的是庫文件所在的目錄名。不然,鏈接時會找不到庫文件,除非你放到標(biāo)準(zhǔn)庫目錄(/lib,/usr/lib等)
PKG_CONFIG_PATH, 默認(rèn)是 默認(rèn)是/usr/lib/pkgconf
pkg-config base-shared –libs –cflags
生成共享庫的另外一種方式
gcc -shared test.c -o libtest.so
-fpic 使輸出的對象模塊是按照可重定位地址方式生成的。(GOT)
LIBRART_PATH
g++-3.4 char.cpp -L. -lsystem-linux-golden -llanguage-linux-golden
1)函數(shù)要聲明為extern
extern C {
}
反而不行
2) LD_LIBRARY_PATH要設(shè)置正確
export LD_LIBRARY_PATH=.
--------------------------------------------------------------------------------------------
類似Windows系統(tǒng)中的動態(tài)鏈接庫,Linux中也有相應(yīng)的共享庫用以支持代碼的復(fù)用。Windows中為*.dll,而Linux中為*.so,我來詳細(xì)的告訴你如何在linux下編寫動態(tài)庫,以及如何使用它.
在linux下編寫動態(tài)鏈接庫的步驟:
1. 編寫庫的頭文件和源文件.
2. 把所有涉及到的源文件用如下方式編譯為目標(biāo)文件:
g++/gcc -g -c -fPIC -o library1.o library1.cpp
g++/gcc -g -c -fPIC -o library2.o library2.cpp
......
......
(注釋:-fPIC指通過這個選項來生成與位置無關(guān)的代碼,可以在任何地址被連接和裝載,-c指只編譯而不連接原程序)
3. 把所有的目標(biāo)文件鏈接為動態(tài)庫:
g++/gcc -g -shared -Wl,-soname,lib***.so -o lib***.so.1.0.0 library1.o library2.o .... -lc
(注釋:-lc選項,表示使用c語言庫,一般都要用到)
4. 建立一個庫名鏈接
ln -s lib***.so.1.0.0 lib***.so
現(xiàn)在你就可以引用庫了.下面我分別給出簡單例子告訴你如何動態(tài)和靜態(tài)使用動態(tài)庫:
假如你的應(yīng)用程序源代碼叫testlib.cpp
采用\如下方式編譯:
g++ -g -o testlib testlib.cpp -ldl
(注釋:-ldl選項,表示生成的對象模塊需要使用共享庫)
////////這個例子告訴你如何動態(tài)的調(diào)用.so庫
testlib.cpp
#include <dlfcn.h>
#include <iostream.h>
#include ...
int main()
{
void *handle=NULL;
//define a pointer which will point to the function in the lib you want to use.
YourFuntionType (*pFunc)(YourFunctionPerameterList........);
//open the lib you want to use.
handle=dlopen("/../../../yourlib.so",RTLD_LAZY);
if(handle==NULL)
{
cout<<"failed loading library!"<<endl;
return -1;
}
dlerror();
//try to load the function in lib
pFunc=(YourFuntionType(*)(YourFunctionPerameterList))dlsym(handle,"YourFuntionName");
if(dlerror()!=NULL)
{
cout<<"Loading function in lib error!"<<endl;
return -1;
}
//now you can use the funtion like this
(*pFunc)(YourFuntionPerameterList);
return 0;
}
(注釋:dlopen()
第一個參數(shù):指定共享庫的名稱,將會在下面位置查找指定的共享庫。
-環(huán)境變量LD_LIBRARY_PATH列出的用分號間隔的所有目錄。
-文件/etc/ld.so.cache中找到的庫的列表,用ldconfig維護(hù)。
-目錄usr/lib。
-目錄/lib。
-當(dāng)前目錄。(這里就是這種情況)
第二個參數(shù):指定如何打開共享庫。
-RTLD_NOW:將共享庫中的所有函數(shù)加載到內(nèi)存
-RTLD_LAZY: 會推后共享庫中的函數(shù)的加載操作,直到調(diào)用dlsym()時方加載某函數(shù)
dlsym()
調(diào)用dlsym時,利用dlopen()返回的共享庫的phandle以及函數(shù)名稱作為參數(shù),返回要加載函數(shù)的入口地址。
dlerror()
該函數(shù)用于檢查調(diào)用共享庫的相關(guān)函數(shù)出現(xiàn)的錯誤。
)
特別需要注意的幾點(diǎn)問題:
1. 當(dāng)你想用c++寫動態(tài)庫的時候,記住千萬別忘了在頭文件里面加上如下內(nèi)容,否則生成的庫在動態(tài)調(diào)用的時候會出問題!!!!!!!
#ifdef __cplusplus
extern "C" {
#endif
....
....
#ifdef __cplusplus
}
#endif
2. 當(dāng)你的庫中包括與omniORB3相關(guān)的東西的時候,一定要在makefile中加上 -D__x86__ -D__OSVERSION=4
/////////////這個例子告訴你如何靜態(tài)調(diào)用.so庫
首先你得確保你的應(yīng)用程序能夠找到你的.so庫,這可以有幾種方法來實現(xiàn).
方法一:
1.你可以把YourLib.so.1.0.0 和YourLib.so放到/usr/lib中,然后執(zhí)行命令:ldconfig,這樣你就可以在你的應(yīng)用程序中直接調(diào)用你庫中的函數(shù)了,當(dāng)然你 得把庫的頭文件包含到你的應(yīng)用程序中
2.編譯你的應(yīng)用程序
g++/gcc -g -o yourapp yourapp.cpp –lYourLib
方法二:
1.你也可以采用在系統(tǒng)中設(shè)置環(huán)境變量的辦法來實現(xiàn). 在root目錄下:
vi .bash_profile
然后添加LD_LIBRARY=/../YourDirIncludingYourLib
然后注消一次,環(huán)境變量就生效了,這樣你就可以在你的應(yīng)用程序中直接調(diào)用庫中的函數(shù)了,同樣你得有頭文件.
2.編譯你的應(yīng)用程序
g++/gcc -g -o yourapp yourapp.cpp –lYourLib
方法三:
你可以直接采用在編譯鏈接的時候告訴系統(tǒng)你的庫在什么地方
g++/gcc -g -o yourapp yourapp.cpp -L/YourDirIncludingYourLib –lYourLib
/////////////////////////////////
假如你的庫中有個函數(shù):int eat(.....)
那么采用如下方式調(diào)用它
yourapp.cpp
#include "YourLib.h"
int main()
{
eat();
return 0;
}
是不是很easy?對了在靜態(tài)調(diào)用的時候好像不存在上面的"注意1"的問題,不過鑒于保險起見,最好還是按照標(biāo)準(zhǔn)的方式寫c++頭文件吧,這絕對是個好習(xí)慣.
----------------------------------------------------------------------------------------------------
如果你想添加共享庫支持到一個原來不包含共享庫支持的 port 或是其它軟件, 共享庫的版本號應(yīng)該遵循如下規(guī)則。通常來說,由此得出的數(shù)字與軟件的發(fā)行版本無關(guān)。
建立共享庫的三個原則是:
從1.0開始
如果改動與以前版本相兼容,增加副版本號(注意,ELF系統(tǒng)忽略副版本號)。
如果是個不兼容的改動,增加主版本號。
例如,添加函數(shù)和修正錯誤導(dǎo)致副版本號增加, 而刪除函數(shù)、函數(shù)調(diào)用語法改變等,會迫使主版本號改變。
保持這種形式的版本號:主版本號.副版本號 (x.y)。 我們的 a.out 動態(tài)鏈接器不能很好的處理 x.y.z形式的版本號。在比較共享庫版本號以決定跟哪個庫文件鏈接的時候, 任何y以后的版本號(那是指第三個數(shù)字) 總是會被忽略。如果給定的兩個共享庫的不同在于“細(xì)微”版本 (“micro” revision)的話,ld.so將會與較高修訂版本的鏈接。即,如果你要與libfoo.so.3.3.3鏈接,鏈接器只在(ELF文件的)頭部記錄 3.3, 并且在連接時,與文件名以 libfoo.so.3.(任何數(shù)字>= 3).(現(xiàn)有的最高數(shù)字) 開頭的任何文件鏈接。
注意: ld.so 總是會使用 “副”版本號最高的。例如,即使一個程序最初是(被設(shè)定)與 libc.so.2.0鏈接的, ld.so也會優(yōu)先選擇使用libc.so.2.2,而不是 libc.so.2.0。
另外,我們的 ELF 動態(tài)鏈接器完全不處理副版本號。 可我們還是應(yīng)該指定一個主版本號和副版本號,因為我們的 Makefile 會按系統(tǒng)類型“做正確的事”。
對于不屬于某個 port 的庫文件,我們的原則是在各個 FreeBSD 正式發(fā)行版(RELEASE)之間只改變一次共享庫版本號(譯者注:一般只是副版本號)。 并且,在 FreeBSD 正式發(fā)行版 (RELEASE)主版本之間(那是指像從 3.x 到 4.x), 也應(yīng)該僅改變一次共享庫主版本號。 當(dāng)你需要對系統(tǒng)庫做一些改變并要增加版本號時, 請查看Makefile的提交日志。 這是 committer 的責(zé)任:確保自(最近的)正式發(fā)行版 (RELEASE) 之后只有第一次這樣的改動會讓在Makefile 里的共享庫版本號更新, 而隨后的(在下一個 RELEASE 之前的)改動不會使共享庫版本號更新。