1、目標(biāo)庫(kù)
編譯程序的一種簡(jiǎn)單方式是簡(jiǎn)單地將每一個(gè)源文件編譯成目標(biāo)文件,然后將這些目標(biāo)鏈接在一起組成可執(zhí)行程序:
?
gcc -g prog.c mod1.c mod2.c mod3.c gcc -g prog_nolib prog.o mod1.o mod2.o mod3.o
?
鏈接實(shí)際上是用鏈接器 ld 來(lái)完成的,使用 gcc 命令鏈接一個(gè)程序時(shí),編譯器會(huì)再幕后調(diào)用 ld。
為了解決共享的問(wèn)題,可以將一組目標(biāo)文件組織成一個(gè)對(duì)象庫(kù),對(duì)象庫(kù)分為兩種:
靜態(tài)庫(kù)
共享庫(kù),比靜態(tài)庫(kù)更具優(yōu)勢(shì)
2、靜態(tài)庫(kù)
靜態(tài)庫(kù)也稱為歸檔文件,有點(diǎn):
可以將一組經(jīng)常被調(diào)用到的目標(biāo)文件組織成單個(gè)庫(kù)文件,這樣避免構(gòu)建多個(gè)程序時(shí)反復(fù)編譯原來(lái)代碼的問(wèn)題
鏈接命令變得更加簡(jiǎn)單,鏈接時(shí)只需要指定靜態(tài)庫(kù)的名稱即可,不需要列出一個(gè)個(gè)目標(biāo)文件
2.1、創(chuàng)建和維護(hù)靜態(tài)庫(kù)
使用 ar 命令能夠創(chuàng)建和維護(hù)靜態(tài)庫(kù),靜態(tài)庫(kù)的名稱一般為 libname.a:
?
ar options archive object-filr...
?
options 參數(shù)由一系列的字母構(gòu)成,其中一個(gè)是操作碼,其他是可能影響操作的執(zhí)行修飾符:
r : 替換,將一個(gè)目標(biāo)文件插入到歸檔文件中并取代同名的目標(biāo)文件
t :目錄表,顯示歸檔中的目錄表,默認(rèn)情況下只會(huì)列出歸檔文件中目標(biāo)文件的名稱,添加 v 修飾之后可以看到記錄在歸檔文件中的各個(gè)目標(biāo)文件的其他特性
d : 從歸檔文件中刪除一個(gè)模塊
2.2、使用靜態(tài)庫(kù)
程序和靜態(tài)庫(kù)鏈接起來(lái)存在的兩種方式:
在連接命令中指定靜態(tài)庫(kù)的名稱
?
gcc -g -c prog.c gcc -g -o prog prog.o libdemo.a
?
或者將靜態(tài)庫(kù)放在鏈接器搜索的其中一個(gè)標(biāo)準(zhǔn)目錄中 ,例如/usr/lib,然后使用 -l 選項(xiàng)指定庫(kù)名,庫(kù)名需要去除 lib ?前綴和 .a 后綴:
?
gcc -g -o prog prog.o -ldemo
?
如果庫(kù)不在鏈接器的搜索目錄中,則需要使用 -L 指定額外的目錄:
?
gcc -g -o prog prog.o -Llibdir -ldemo
?
3、共享庫(kù)概述
多個(gè)程序使用同一個(gè)靜態(tài)庫(kù)時(shí),每個(gè)程序都有自己的目標(biāo)模塊的副本,這種代碼冗余存在的缺點(diǎn):
存儲(chǔ)同一個(gè)目標(biāo)模塊的多個(gè)副本會(huì)浪費(fèi)磁盤(pán)空間,并且所浪費(fèi)的空間是比較大的
如果使用了同一模塊的程序在同一時(shí)刻執(zhí)行,每個(gè)程序都有自己獨(dú)立的虛擬地址空間,其中保存了一份目標(biāo)代碼副本,這將會(huì)提供系統(tǒng)中虛擬內(nèi)存的整體使用量
如果需要修改一個(gè)靜態(tài)庫(kù)的一個(gè)目標(biāo)模塊,那么所有使用這個(gè)模塊的可執(zhí)行程序都必須重新進(jìn)行鏈接合并這個(gè)變更
共享庫(kù)就是為了克服靜態(tài)庫(kù)的諸多缺點(diǎn),它還具有的優(yōu)勢(shì)有:
由于整個(gè)程序的大小變得更小了,一些情況下,程序可以完全被加載到內(nèi)存中,從而能夠更快的啟動(dòng)程序,這一點(diǎn)只有在大型共享庫(kù)正被其他程序使用時(shí)才成立,第一個(gè)加載共享庫(kù)的程序?qū)嶋H上會(huì)在啟動(dòng)時(shí)花費(fèi)更長(zhǎng)的時(shí)間,因?yàn)楸仨氁日业焦蚕韼?kù)并將其加載到內(nèi)存中
目標(biāo)模塊修改是 ,不需要重新編譯程序就可以看到變更,甚至當(dāng)運(yùn)行著的程序正在使用共享庫(kù)的現(xiàn)有版本時(shí)也能夠進(jìn)行這樣的變更
共享庫(kù)的額外開(kāi)銷:
創(chuàng)建共享庫(kù)和構(gòu)建使用共享庫(kù)的程序比靜態(tài)庫(kù)更復(fù)雜
共享庫(kù)在編譯時(shí)需要使用位置獨(dú)立代碼,這在大多數(shù)架構(gòu)上會(huì)帶來(lái)性能開(kāi)銷,因?yàn)樗枰褂靡粋€(gè)額外的寄存器
在運(yùn)行時(shí)必須要執(zhí)行符號(hào)重定位,符號(hào)重定位期間,需要對(duì)共享庫(kù)中的每個(gè)符號(hào)(變量或函數(shù))的引用修改成符合在虛擬內(nèi)存中的實(shí)際運(yùn)行時(shí)位置,這個(gè)重定位的過(guò)程需要花費(fèi)一定的時(shí)間
雖然共享庫(kù)的代碼是多個(gè)進(jìn)程共享的,但是其中的變量不是,每個(gè)使用庫(kù)的進(jìn)程會(huì)擁有自己的在庫(kù)中定義的全局和靜態(tài)變量的副本。
3.1、創(chuàng)建和使用共享庫(kù)
創(chuàng)建一個(gè)共享庫(kù)
?
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c gcc -g -shared libfoo.so mod1.o mod2.o mod3.o
?
貢獻(xiàn)庫(kù)的命名方式一般為 libname.so
可以向之前構(gòu)建的共享庫(kù)中添加單個(gè)目標(biāo)模塊,也可以從中刪除單個(gè)目標(biāo)模塊
也可以將編譯共享庫(kù)的命令放在一行:
?
gcc -g -fPIC -Wall mod1.c mod2.c mod3.c -shared -o libfoo.so
?
位置獨(dú)立的代碼
-fPIC ?選項(xiàng)指定編譯器應(yīng)該生成位置無(wú)關(guān)代碼,這會(huì)改變編譯器生成執(zhí)行文件特定操作的代碼方式,包括訪問(wèn)全局、靜態(tài)和外部變量,訪問(wèn)字符串常量,以及獲取函數(shù)的地址,這些改變使得代碼可以在運(yùn)行時(shí)放在任意虛擬地址處,這一點(diǎn)對(duì)于共享庫(kù)來(lái)說(shuō)是必須的,因?yàn)殒溄拥臅r(shí)候是無(wú)法知道共享庫(kù)代碼位于內(nèi)存何處。
在 linux/x86-32 上,可以使用不加 -fPIC 選項(xiàng)編譯的模塊來(lái)創(chuàng)建共享庫(kù),為了確定一個(gè)既有目標(biāo)文件在編譯時(shí)是否使用了 -fPIC 選項(xiàng),可以使用檢查目標(biāo)文件符號(hào)表中是否存在 _GLOBAL_OFFSET_TABLE:
?
nm mod1.o | grep _GLOBAL_OFFSET_TABLE readelf -s mod1.o | grep _GLOBAL_OFFSET_TABLE
?
下面的命令中如果產(chǎn)生了輸出,則說(shuō)明指定的共享庫(kù)中至少存在一個(gè)目標(biāo)模塊在編譯時(shí)沒(méi)有指定 -fPIC:
?
objdump --all-headers libfoo.so | grep TEXTREL objdump --all-headers libfoo.so | grep TEXTREL
?
TEXTREL 表示存在一個(gè)目標(biāo)模塊,其文本段中包含需要運(yùn)行時(shí)重定位的引用。
使用一個(gè)共享庫(kù)
為了使用共享庫(kù)需要做兩件事情,而使用靜態(tài)庫(kù)則無(wú)需完成這兩件事情:
可執(zhí)行文件中不包含它所需要的目標(biāo)文件的副本,因此需要通過(guò)某種機(jī)制找到運(yùn)行時(shí)所需要的共享庫(kù):鏈接階段將共享庫(kù)名稱嵌入可執(zhí)行文件中,一個(gè)程序所依賴的所有共享庫(kù)列表稱為程序的動(dòng)態(tài)依賴表
在程序運(yùn)行時(shí)需要某種機(jī)制解析嵌入的庫(kù)名,即找出與在可執(zhí)行文件中指定的名稱對(duì)應(yīng)的共享庫(kù)文件,如果庫(kù)不再內(nèi)存中的話就將庫(kù)加載進(jìn)內(nèi)存
運(yùn)行時(shí)解析內(nèi)嵌庫(kù)名的工作由動(dòng)態(tài)鏈接器(也稱為動(dòng)態(tài)鏈接加載器或運(yùn)行時(shí)加載器)來(lái)完成,動(dòng)態(tài)鏈接器本身也是一個(gè)共享庫(kù),其名稱為 lib/ld-linux.so.2,所有使用共享庫(kù)的 ELF 可執(zhí)行文件都會(huì)用到這個(gè)共享庫(kù)。
lib/ld-linux.so.2 通常是指向動(dòng)態(tài)鏈接器可執(zhí)行文件的符號(hào)鏈接,這個(gè)文件的名稱為 ld-version.so。
動(dòng)態(tài)鏈接器會(huì)檢查程序所需的共享庫(kù)清單并使用一組預(yù)先定義好的規(guī)則來(lái)在文件系統(tǒng)上找出相關(guān)的庫(kù)文件。其中一些規(guī)則指定了一組存放共享庫(kù)的標(biāo)準(zhǔn)目錄,如 /lib,/usr/lib。
LD_LIBRARY_PATH 環(huán)境變量
通知?jiǎng)討B(tài)鏈接器一個(gè)共享庫(kù)位于一個(gè)非標(biāo)準(zhǔn)目錄中的一種方法是將該目錄添加到 LD_LIBRARY_PATH ?環(huán)境變量中以分號(hào)分隔的目錄列表中,如果定義了 LD_LIBRARY_PATH,那么動(dòng)態(tài)鏈接器在查找標(biāo)準(zhǔn)庫(kù)目錄前會(huì)先查找該環(huán)境變量列出的目錄中的共享庫(kù)。
?
LD_LIBRARY_PATH=. ./prog # 告知?jiǎng)討B(tài)鏈接器在當(dāng)前工作目錄中搜索共享庫(kù)
?
靜態(tài)鏈接和動(dòng)態(tài)鏈接的比較
術(shù)語(yǔ)鏈接通常指的是使用鏈接器 ld 將一個(gè)或多個(gè)編譯過(guò)的目標(biāo)文件合成一個(gè)可執(zhí)行程序,也稱為靜態(tài)鏈接。
在運(yùn)行時(shí)使用共享庫(kù)的可執(zhí)行程序需要經(jīng)歷額外的動(dòng)態(tài)鏈接階段。
3.2、共享庫(kù) soname
嵌入到可執(zhí)行程序以及動(dòng)態(tài)鏈接器在運(yùn)行時(shí)搜索的名稱除了使用真實(shí)名稱,還經(jīng)常使用別名來(lái)創(chuàng)建共享庫(kù),這種別名稱為 soname,ELF 中的 ?DT_SONAME ?標(biāo)簽。
如果共享庫(kù)擁有一個(gè) soname,那么在靜態(tài)鏈接階段會(huì)將 soname 嵌入到可執(zhí)行文件中,而不會(huì)使用真實(shí)名稱,同時(shí)后面的動(dòng)態(tài)鏈接器在運(yùn)行時(shí)也會(huì)使用這個(gè) soname 來(lái)搜索庫(kù),引入 soname 的目的是為了提供一個(gè)間接層,使得可執(zhí)行程序能夠在運(yùn)行時(shí)使用與鏈接時(shí)使用不同的但兼容的共享庫(kù)。
?
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c gcc -g -shared -Wl,-soname,libbar.so -o libfoo.so mod1.o mod2.o mod3.o
?
-Wl,-soname,libbar.so 等選項(xiàng)都是傳給鏈接器的指令,以將共享庫(kù) libfoo.so 的 soname 設(shè)置為 libbar.so
使用一下命令中的任意一個(gè),可以確定既有共享庫(kù)的 soname:
?
objdump -p libz.so | grep SONAME SONAME ? ? ? ? ? ? ? libz.so.1 ? readelf -d libz.so | grep SONAME 0x000000000000000e (SONAME) ? ? ? ? ? ? Library soname: [libz.so.1]
?
使用 soname ?創(chuàng)建程序:
?
gcc -g -Wall -o prog prog.c libfoo.so
?
鏈接器檢查到庫(kù) libfoo.so 包含了 soname : libbar.so,就將這個(gè) soname 嵌入到可執(zhí)行文件中。
使用共享庫(kù)時(shí)必須要?jiǎng)?chuàng)建一個(gè)符號(hào)鏈接,將 soname 指向庫(kù)的真實(shí)名稱,并且必須要將這個(gè)符號(hào)鏈接放在動(dòng)態(tài)鏈接器搜索的其中一個(gè)目錄中。
程序載入內(nèi)存以設(shè)備執(zhí)行時(shí)發(fā)生的事情:
要找出一個(gè)進(jìn)程當(dāng)前使用的共享庫(kù)則可以列出相應(yīng)的 Linux 特有的 /proc/PID/maps 文件中的內(nèi)容。 ?
4、使用共享庫(kù)的有用工具
ldd
ldd ?: 列出動(dòng)態(tài)依賴,顯示一個(gè)程序運(yùn)行時(shí)所需要的共享庫(kù)。
ldd ?會(huì)解析出每個(gè)庫(kù)引用,使用的搜索方式與動(dòng)態(tài)鏈接器一樣,并以下面的形式顯示結(jié)果:
?
library-name => resolves-to-path
?
對(duì)于多數(shù)的 ELF 文件,ldd 至少會(huì)列出與 ld-linux.so.2、動(dòng)態(tài)鏈接器以及標(biāo)準(zhǔn) C 庫(kù) lib.so.6 相關(guān)的條目。
objdump 和 readelf
objdump ?:用來(lái)從可執(zhí)行文件、編譯過(guò)的目標(biāo)、以及共享庫(kù)中獲取各類信息,包括反匯編的二進(jìn)制機(jī)器碼,還可以用來(lái)顯示這些文件各個(gè) ELF 節(jié)的頭部信息,當(dāng)這樣使用時(shí),它就類似于 readelf,readelf 能顯示類似的信息,但是格式不同。
nm
nm 命令會(huì)列出目標(biāo)庫(kù)或可執(zhí)行文件中定義的一組符號(hào),常用來(lái)尋找哪些庫(kù)定義了一個(gè)符號(hào)。
5、共享庫(kù)版本和命名規(guī)則
命名形式:
?
libname.so major-id.minor-id
?
major-id :主要版本標(biāo)識(shí)符,區(qū)分兩個(gè)不兼容的版本
minor-id :次要版本可以是任意字符串,但是通常的形式是兩個(gè)由點(diǎn)分隔的數(shù)字,第一個(gè)數(shù)字表示次要版本,第二個(gè)數(shù)字表示該次要版本的補(bǔ)丁號(hào)或者修訂號(hào)
?
libdemo.so.1.0.1 libdemo.so.1.0.2 libdemo.so.2.0.0
?
共享庫(kù)的 soname 包括相應(yīng)的主版本標(biāo)識(shí)符,但是不包含次要版本標(biāo)識(shí)符,因此 ?soname 的命名形式為:
?
libname.so.major-id
?
通常,會(huì)將 soname ?創(chuàng)建為包含真實(shí)名稱的目錄的一個(gè)相對(duì)符號(hào)鏈接:
?
libdemo.so.1 ? ? -> libdemo.so.1.0.2 libdemo.so.2 ? ? -> libdemo.so.2.0.0
?
共享庫(kù)的一個(gè)主要版本可能有多個(gè)不同的次要版本,通常每個(gè)庫(kù)的主要版本的 ?soname ?會(huì)指向主要版本中最新的次要版本,由于靜態(tài)鏈接階段會(huì)將 soname ?的副本(獨(dú)立于次要版本)嵌入到可執(zhí)行文件中,并且 soname 符號(hào)鏈接后面可能會(huì)被修改為指向更新的次要版本,從而確保在執(zhí)行期間能夠加載庫(kù)的最新的次要版本。
同一個(gè)庫(kù)的不同主要版本也能夠同時(shí)存在,被需要它們的程序分別訪問(wèn)。
除了真實(shí)名稱和 soname 之外,通常還會(huì)為每個(gè)共享庫(kù)定義第三個(gè)名稱:鏈接器名稱,將可執(zhí)行文件與共享庫(kù)鏈接起來(lái)時(shí)會(huì)使用這個(gè)名稱,鏈接器名稱是一個(gè)只包含庫(kù)名稱不包含主要版本和次要版本標(biāo)識(shí)符的符號(hào)鏈接,其形式為 libname.so,有了鏈接器名稱之后就可以構(gòu)建能夠自動(dòng)使用共享庫(kù)最新版本的鏈接命令。
鏈接器名稱一般與它所引用的文件位于同一個(gè)目錄中,它既可以鏈接到真實(shí)名稱,也可以鏈接到庫(kù)的最新主要版本的 soname。通常最好使用指向 soname ?的鏈接,從而對(duì) soname 的修改會(huì)自動(dòng)反應(yīng)到鏈接器名稱。
如果需要使用一個(gè)舊版本的共享庫(kù),就不能使用鏈接器名稱,要使用真實(shí)名稱或者 soname 來(lái)指示出需要的版本。
5.1、使用標(biāo)準(zhǔn)規(guī)范創(chuàng)建一個(gè)共享庫(kù)
創(chuàng)建目標(biāo)文件
?
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
?
創(chuàng)建共享庫(kù),指定真實(shí)名稱和 soname
?
gcc -g -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 mod1.o mod2.o mod3.o
?
為 soname 和鏈接器名稱創(chuàng)建恰當(dāng)?shù)姆?hào)鏈接
?
ln -s libdemo.so.1.0.1 libdemo.so.1 ln -s libdemo.so.1 libdemo.so
?
使用鏈接器名稱構(gòu)建可執(zhí)行文件
?
gcc -g -Wall -o prog prog.c -L. -ldemo LD_LIBRARY_PATH=. ./prog
?
5.2、安裝共享庫(kù)
共享庫(kù)及其關(guān)聯(lián)的符號(hào)鏈接一般會(huì)被安裝到一個(gè)標(biāo)準(zhǔn)目錄中,標(biāo)準(zhǔn)目錄包括:
/usr/lib :是大多數(shù)標(biāo)準(zhǔn)庫(kù)安裝的目錄
/lib :應(yīng)該將系統(tǒng)啟動(dòng)時(shí)用到的庫(kù)安裝到這個(gè)目錄,因?yàn)橄到y(tǒng)啟動(dòng)時(shí)可能還沒(méi)有掛載 /usr/lib
/usr/local/lib :應(yīng)該將非標(biāo)準(zhǔn)或者實(shí)驗(yàn)性質(zhì)的庫(kù)安裝到這個(gè)目錄
/etc/ld.so.conf 中列出的目錄
安裝完成后一般需要?jiǎng)?chuàng)建 soname ?和鏈接器名稱的符號(hào)鏈接:
?
mv libdemo.so.1.0.1 /usr/lib cd /usr/lib ln -s libdemo.so.1.0.1 libdemo.so.1 # 創(chuàng)建 soname ln -s libdemo.so.1 libdemo.so# 創(chuàng)建鏈接器名稱
?
ldconfig
ldconfig 解決了共享庫(kù)的兩個(gè)潛在問(wèn)題:
共享庫(kù)可以位于各種目錄中,如果動(dòng)態(tài)鏈接器需要通過(guò)搜索這些目錄來(lái)找一個(gè)庫(kù)并加載這個(gè)庫(kù),整個(gè)過(guò)程將非常慢
當(dāng)安裝了新版本的庫(kù)或者刪除了舊版本的庫(kù),那么 ?soname 符號(hào)鏈接就不是最新的
ldconfig 可以通過(guò)執(zhí)行兩個(gè)任務(wù)來(lái)解決這些問(wèn)題:
它搜索一組標(biāo)準(zhǔn)的目錄并創(chuàng)建或更新一個(gè)緩存文件 /etc/ld.so.cache 使之包含在所有這些目錄中的主要版本(每個(gè)庫(kù)的主要版本的最新的次要版本)列表,動(dòng)態(tài)鏈接器在運(yùn)行時(shí)解析庫(kù)名稱時(shí)會(huì)輪流使用這個(gè)緩存文件:
為了構(gòu)建這個(gè)緩存,ldconfig 會(huì)搜索在 /etc/ld.so.conf 中指定的目錄,然后搜索 /lib 和 /usr/lib
`/etc/ld.so.conf 文件由一個(gè)目錄路徑名(應(yīng)該是絕對(duì)路徑名)列表構(gòu)成,其中路徑名之間用換行、空格、制表符、逗號(hào)或冒號(hào)分隔
在一些發(fā)行版中, /usr/local/lib 目錄也位于這個(gè)列表中
ldconfig -p 會(huì)顯示 /etc/ls.so.cache 的當(dāng)前內(nèi)容
它檢查每個(gè)庫(kù)的各個(gè)主要版本的最新次要版本以找出嵌入的 soname,然后在同一目錄中為每個(gè) soname 創(chuàng)建(或更新)相對(duì)符號(hào)鏈接:
為了能夠正確執(zhí)行這些動(dòng)作,ldconfig 要求庫(kù)的名稱要根據(jù)前面介紹的規(guī)范來(lái)命名:庫(kù)的真實(shí)名稱包含主要和次要標(biāo)識(shí)符,它們隨著庫(kù)的版本的更新而恰當(dāng)?shù)脑鲩L(zhǎng)
可以使用命令行選項(xiàng)來(lái)指定它執(zhí)行其中一個(gè)動(dòng)作: -N 選項(xiàng)會(huì)防止緩存的重建,-X 選項(xiàng)會(huì)阻止 soname 符號(hào)鏈接的創(chuàng)建。 此外, -v(verbose)選項(xiàng)會(huì)使得 ldconfig 輸出描述其所執(zhí)行的動(dòng)作的信息
每當(dāng)安裝了一個(gè)新的庫(kù),更新或者刪除一個(gè)既有庫(kù),以及 /etc/ld.so.conf 中的目錄列表被修改后,都運(yùn)行相應(yīng)的 ldconfig。
安裝一個(gè)庫(kù)的兩個(gè)不同主要版本:
?
mv libdemo.so.1.0.1 libdemo.so.2.0.0 /usr/lib
?
為鏈接器名稱創(chuàng)建符號(hào)鏈接:
?
ln -s libdemo.so.2 libdemo.so
?
如果更新庫(kù)的一個(gè)次要版本,由于鏈接器名稱指向了最新的 soname,因此 ldconfig 還能取得保持鏈接器名稱的最新效果:
?
mv libdemo.so.2.0.1 /usr/lib
?
如果使用的是私有庫(kù),即沒(méi)有安裝在上述的標(biāo)準(zhǔn)目錄中的庫(kù),那么可以使用 -n 選項(xiàng)讓 ldconfig 創(chuàng)建 soname ,這個(gè)選項(xiàng)指定了只處理在命令行中列出的目錄的庫(kù),無(wú)需更新緩存文件。
使用 ldconfig 來(lái)處理當(dāng)前工作目錄中的庫(kù):
?
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c gcc -g -c -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 mod1.o mod2.o mod3.o /sbin/ldconfig -nv .
?
5.3、兼容與不兼容庫(kù)比較
滿足下列條件時(shí)表示修改過(guò)的庫(kù)與既有版本兼容:
庫(kù)中所有公共方法和變量的語(yǔ)義保持不變
沒(méi)有刪除庫(kù)的公共 API 中的函數(shù)和變量,但向公共 API 中添加新函數(shù)和變量
每個(gè)函數(shù)分配的結(jié)構(gòu)以及沒(méi)和函數(shù)的返回結(jié)構(gòu)保持不變
如果這些條件都滿足,更新庫(kù)時(shí)只需要調(diào)增既有庫(kù)的次要版本號(hào),否則就要?jiǎng)?chuàng)建新的主版本。
5.4、升級(jí)共享庫(kù)
共享庫(kù)的優(yōu)點(diǎn)之一就是當(dāng)一個(gè)運(yùn)行著的程序正在使用共享庫(kù)的一個(gè)既有版本時(shí),也能夠安裝新的主要版本或者次要版本的庫(kù),需要做的是:
創(chuàng)建新的庫(kù)版本,將其安裝到恰當(dāng)?shù)哪夸?/p>
根據(jù)需要更新 soname 和鏈接器名稱符號(hào)鏈接,或者使用 ldconfig 完成這部分工作
更新次要版本:
?
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c gcc -g -c -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.2 mod1.o mod2.o mod3.o mv libdemo.so.1.0.2 /usr/lib ldconfig -v | grep libdemo
?
更新主要版本:
?
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c gcc -g -c -shared -Wl,-soname,libdemo.so.2 -o libdemo.so.2.0.0 mod1.o mod2.o mod3.o mv libdemo.so.2.0.0 /usr/lib ldconfig -v | grep libdemo cd /usr/lib ln -sf libdemo.so.2 libdemo.so
?
6、在目標(biāo)文件中指定庫(kù)搜索目錄
通知?jiǎng)討B(tài)鏈接器共享庫(kù)的位置的方式:
LD_LIBRARY_PATH 環(huán)境變量中指定
將共享庫(kù)安裝到標(biāo)準(zhǔn)庫(kù)目錄:/lib,/usr/lib,/etc/ls.so.conf 中列出的其中一個(gè)目錄
第三種方式:在靜態(tài)編輯階段可以在執(zhí)行文件中插入一個(gè)在運(yùn)行時(shí)搜索共享庫(kù)的目錄列表,這種方式對(duì)于庫(kù)位于一個(gè)固定的但不屬于動(dòng)態(tài)鏈接器搜索標(biāo)準(zhǔn)位置時(shí)非常有用,在創(chuàng)建文件時(shí)需要增加 -rpath 鏈接器選項(xiàng)。
?
gcc -g -Wall,-rpath,/home/mtk/pdir -o prog prog.c libdemo.so
?
將字符串 /home/mtk/pdir 復(fù)制到可執(zhí)行 文件 prog 的運(yùn)行時(shí)庫(kù)路徑列表中,在程序運(yùn)行時(shí),動(dòng)態(tài)鏈接器在解析共享庫(kù)引用時(shí)還會(huì)搜索這個(gè)路徑。
指定多個(gè)目錄:
多次指定 -rpath 選項(xiàng),所有這些列出的目錄會(huì)被鏈接成一個(gè)放到可執(zhí)行文件中的有序 -rpath ?列表
在一個(gè) -rpath 選項(xiàng)中可以指定多個(gè)由分號(hào)分隔開(kāi)的目錄列表,運(yùn)行時(shí),動(dòng)態(tài)鏈接器會(huì)按照 -rpath 選項(xiàng)中指定的目錄順序來(lái)搜索目錄
-rpath 的一個(gè)可替代方案就是使用 LD_RUN_PARH 環(huán)境變量,可以將由一個(gè)逗號(hào)分隔開(kāi)的目錄的字符串賦值給該變量,只有當(dāng)構(gòu)建可執(zhí)行文件時(shí)沒(méi)有指定 -rpath 選項(xiàng)才會(huì)使用 LD_RUN_PATH
6.1、在構(gòu)建貢獻(xiàn)庫(kù)時(shí)使用 -rpath 鏈接器選項(xiàng)
假設(shè)有一個(gè)庫(kù) lib1.so 依賴于另一個(gè)共享庫(kù) lib2.so ,另外再假設(shè)這些庫(kù)分別位于非標(biāo)準(zhǔn)目錄 d1 和 d2中:
首先在 pdir/d2 目錄中構(gòu)建 libx2.so,這里省略了庫(kù)的版本號(hào)和 soanme:
?
cd /home/mtk/pdir/d2 gcc -g -c -fPIC -Wall modx2.c gcc -g -shared -o libx2.so modx2.o
?
接著在 pdir/d1 目錄中構(gòu)建 libx1.so。由于 libx1.so 依賴于 lib2.so,并且 libx2.so 位于一個(gè)非標(biāo)準(zhǔn)目錄中,因此在指定 libx2.so 的運(yùn)行時(shí)位置時(shí)需要使用 -rpath 鏈接器選項(xiàng)。這個(gè)選項(xiàng)的取值與庫(kù)的鏈接時(shí)位置可以不同:
?
cd /home/mtk/pdir/d1 gcc -g -c -Wall modx1.c gcc -g -shared -o libx1.so modx1.o -Wl,-rpath,/home/mtk/pdir/d2 -L/home/mtk/pdir/d2 -lx2
?
pdir 目錄中構(gòu)建主程序,由于主程序使用了 ?libx1.so 并且這個(gè)庫(kù)位于一個(gè)非標(biāo)準(zhǔn)目錄中,因此還需要使用 -rpath 鏈接器選項(xiàng):
?
cd /home/mtk/pdir gcc -g -Wall -o prog prog.c -Wl,-rpath,/home/mtk/pdir/d1 -L/home/mtk/pdir/d1 -lx1
?
在鏈接主程序是,無(wú)需指定 libx2.so,由于鏈接器能夠分析 libx1.so 中的 rpath 列表,因此它能夠找到 libx2.so,同時(shí)在靜態(tài)鏈接階段解析出所有的符號(hào)。
可以通過(guò) readelf --dynamic 或者等價(jià)的 readelf -d 命令的輸出來(lái)查看 rpath 列表。
6.2、ELF DT_RPATH 和 DT_RUNPATH 條目
第一版 ELF 規(guī)范中,只有一種 rpath 列表能夠被嵌入到可執(zhí)行文件或共享庫(kù)中,對(duì)應(yīng) ?ELF ?文件中的 DT_RPATH 標(biāo)簽。后續(xù)的 ELF 舍棄了 DT_RPATH ,同時(shí)引入 DT_RUNPATH 來(lái)表示 rpath 列表,兩者的差別在于動(dòng)態(tài)鏈接器在運(yùn)行時(shí)搜索共享庫(kù)時(shí)它們相對(duì)于 LD_LIBRARY_PATH 環(huán)境變量的優(yōu)先級(jí),DT_RPATH 的優(yōu)先級(jí)更高,DT_RUNPATH 的優(yōu)先級(jí)更低。
默認(rèn)情況下,鏈接器將 rpath 列表創(chuàng)建為 DT_RPATH 標(biāo)簽。為了讓鏈接器將 rpath 列表創(chuàng)建為 DT_RUNPATH 條目必須使用 --enable-new-dtags。
6.3、在 rpath 中使用 $ORIGIN
應(yīng)用程序中使用了自身的共享庫(kù),但同時(shí)不希望強(qiáng)制要求將這些庫(kù)安裝在其中一個(gè)標(biāo)準(zhǔn)目錄中,可以在構(gòu)建鏈接器的時(shí)候,增加 $ORIGIN,動(dòng)態(tài)鏈接器將這個(gè)字符串解釋成 "包含應(yīng)用程序的目錄":
?
gcc -Wl,-rpath,'ORIGIN'/lib ...
?
7、在運(yùn)行時(shí)找出共享庫(kù)
在解析庫(kù)依賴時(shí),動(dòng)態(tài)鏈接器首先會(huì)檢查各個(gè)依賴字符串以確定它是否包含 /,如果找到了一個(gè)斜線,那么依賴字符串就會(huì)被解釋成一個(gè)路徑名,并且會(huì)使用該路徑名加載庫(kù),否則動(dòng)態(tài)鏈接器會(huì)使用下面的規(guī)則來(lái)搜索共享庫(kù):
如果可執(zhí)行文件的 DT_RPATH 運(yùn)行時(shí)庫(kù)路徑列表中包含目錄并且不包含 DT_RUNPATH 列表,那么就搜索這些目錄,按照鏈接程序時(shí)指定的目錄順序
如果定義了 LD_LIBRARY_PATH 環(huán)境變量,那么就會(huì)輪流搜索該變量值中以冒號(hào)分隔的各個(gè)目錄,如果可執(zhí)行文件是一個(gè) set-user-ID 或者 set-group-ID 程序,那么就會(huì)忽略 LD_LIBRARY_PATH 變量。這項(xiàng)安全措施就是為了防止用戶欺騙動(dòng)態(tài)鏈接器讓其加載一個(gè)與可執(zhí)行文件所屬的庫(kù)的名稱一樣的私有庫(kù)
如果可執(zhí)行文件 DT_RUNPATH 運(yùn)行時(shí)庫(kù)路徑列表中包含目錄,那么就搜索這些目錄,按照鏈接程序時(shí)指定的目錄順序
檢查 /etc/ld.so.cache 文件以確認(rèn)它是否包含了與庫(kù)相關(guān)的條目
搜索 /lib 和 /usr/lib 目錄
運(yùn)行時(shí)符號(hào)解析
假設(shè)現(xiàn)在有一個(gè)主程序和一個(gè)共享庫(kù),它們兩個(gè)都定義了一個(gè)全局函數(shù) xyz(),并且共享庫(kù)中的另一個(gè)函數(shù)調(diào)用了 xyz():
?
gcc -g -c -fPIC -Wall -c foo.c gcc -g -shared -o libfoo.so foo.o gcc -g -o prog prog.c libfoo.so LD_LIBRARY_PATH=. ./prog
?
主程序中的 xyz() 定義將覆蓋共享庫(kù)中的定義
如果一個(gè)全局符號(hào)在多個(gè)庫(kù)中進(jìn)行了定義,那么對(duì)該符號(hào)的引用會(huì)綁定在掃描庫(kù)時(shí)找到的第一個(gè)定義,掃描順序是按照這些庫(kù)在靜態(tài)鏈接命令行中列出時(shí)從左到右的順序
如果想使用共享庫(kù)中的 ?xyz() 調(diào)用,需要指定 -Bsymbolic ?鏈接器選項(xiàng):
?
gcc -g -c -fPIC -Wall -c foo.c gcc -g -shared -Wl,-Bsymbolic -o libfoo.so foo.o gcc -g -o prog prog.c libfoo.so LD_LIBRARY_PATH=. ./prog
?
-Bsymbolic ?鏈接器選項(xiàng)指定了共享庫(kù)中對(duì)全局符號(hào)的應(yīng)用應(yīng)該優(yōu)先綁定到庫(kù)中的相應(yīng)定義(如果存在)。
使用靜態(tài)庫(kù)取代共享庫(kù)
默認(rèn)情況下,當(dāng)鏈接器能夠選擇名稱一樣的共享庫(kù)和靜態(tài)庫(kù)時(shí),會(huì)優(yōu)先使用共享庫(kù):
?
-Lsomedir -ldemo
?
并且 libdemo.so 和 libdemo.a 都存在的話,會(huì)優(yōu)先使用 libdemo.a。
如果要強(qiáng)制使用靜態(tài)庫(kù):
在 gcc 命令中指定靜態(tài)庫(kù)的路徑名,包括 ?.a ?擴(kuò)展
在 gcc 命令行中指定 -static 選項(xiàng)
使用 -Wl,-Bstatic 和 -Wl,Bdynamic gcc 選項(xiàng)來(lái)顯示指定鏈接到靜態(tài)庫(kù)還是動(dòng)態(tài)庫(kù)
評(píng)論
查看更多