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