linux下的so、o、lo、a、la檔案
o: 編譯的目標檔案
a: 靜態庫,其實就是把若干o檔案打了個包
so: 動態連結庫(共享庫)
lo: 使用libtool編譯出的目標檔案,其實就是在o檔案中添加了一些資訊
la: 使用libtool編譯出的庫檔案,其實是個文字檔案,記錄同名動態庫和靜態庫的相關資訊
1 libtool的工作原理
libtool 是一個通用庫支援指令碼,將使用動態庫的複雜性隱藏在統一、可移植的介面中;使用libtool的標準方法,可以在不同平臺上建立並呼叫動態庫。可以認為libtool是gcc的一個抽象,其包裝了gcc(或者其他的編譯器),使用者無需知道細節,只要告訴libtool需要編譯哪些庫即可,libtool將處理庫的依賴等細節。libtool只與字尾名為lo、la為的libtool檔案打交道。
libtool主要的一個作用是在編譯大型軟體的過程中解決了庫的依賴問題;將繁重的庫依賴關係的維護工作承擔下來,從而釋放了程式設計師的人力資源。libtool提供統一的介面,隱藏了不同平臺間庫的名稱的差異等細節,生成一個抽象的字尾名為la高層庫libxx.la(其實是個文字檔案),並將該庫對其它庫的依賴關係,都寫在該la的檔案中。該檔案中的dependency_libs記錄該庫依賴的所有庫(其中有些是以.la檔案的形式加入的);libdir則指出了庫的安裝位置;library_names記錄了共享庫的名字;old_library記錄了靜態庫的名字。
當編譯過程到link階段的時候,如果有下面的命令:
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
libtool會到/usr/lib路徑下去尋找liba.la,然後從中讀取實際的共享庫的名字(library_names中記錄了該名字,比如liba.so)和路徑(lib_dir中記錄了,比如libdir=’/usr/lib’),返回諸如/usr/lib/liba.so的引數給激發出的gcc命令列。
如果liba.so依賴於庫/usr/lib/libb.so,則在liba.la中將會有dependency_libs=’-L/usr/lib -lb’或者dependency_libs=’/usr/lib/libb.la’的行,如果是前者,其將直接把“-L/usr/lib –lb”當作引數傳給gcc命令列;如果是後者,libtool將從/usr/lib/libb.la中讀取實際的libb.so的庫名稱和路徑,然後組合成引數“/usr/lib/libb.so”傳遞給gcc命令列。
當要生成的檔案是諸如libmylib.la的時候,比如:
$libtool --mode=link gcc -o libmylib.la -rpath /usr/lib –L/usr/lib –la
其依賴的庫的搜尋基本類似,只是在這個時候會根據相應的規則生成相應的共享庫和靜態庫。
注意:libtool在連結的時候只會涉及到字尾名為la的libtool檔案;實際的庫檔名稱和庫安裝路徑以及依賴關係是從該檔案中讀取的。
2 為何使用 -Wl,--rpath-link -Wl,DIR?
使用libtool解決編譯問題看上去沒什麼問題:庫的名稱、路徑、依賴都得到了很好的解決。但下結論不要那麼著急,一個顯而易見的問題就是:並不是所有的庫都是用libtool編譯的。
比如上面那個例子,
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
如果liba.so不是使用libtool工具生成的,則libtool此時根本找不到liba.la檔案(不存在該檔案)。這種情況下,libtool只會把“–L/usr/lib –la”當作引數傳遞給gcc命令列。
考慮以下情況:要從myprog.o檔案編譯生成myprog,其依賴於庫liba.so(使用libtool生成),liba.so又依賴於libb.so(libb.so的生成不使用libtool),而且由於某種原因,a對b的依賴並沒有寫入到liba.la中,那麼如果用以下命令編譯:
$libtool --mode=link gcc -o myprog -rpath /usr/lib –L/usr/lib –la
激發出的gcc命令列類似於下面:
gcc –o myprog /usr/lib/liba.so
由於liba.so依賴於libb.so(這種依賴可以用readelf讀liba.so的ELF檔案看到),而上面的命令列中,並沒有出現libb.so,於是,可能會出現問題。
說“可能”,是因為如果在本地編譯的情況下,gcc在命令列中找不到一個庫(比如上面的liba.so)依賴的其它庫(比如libb.so),連結器會按照某種策略到某些路徑下面去尋找需要的共享庫:
1. 所有由'-rpath-link'選項指定的搜尋路徑.
2. 所有由'-rpath'指定的搜尋路徑. '-rpath'跟'-rpath_link'的不同之處在於,由'-rpath'指定的路徑被包含在可執行檔案中,並在執行時使用, 而'-rpath-link'選項僅僅在連線時起作用.
3. 在一個ELF系統中, 如果'-rpath'和'rpath-link'選項沒有被使用, 會搜尋環境變數'LD_RUN_PATH'的內容.它也只對本地聯結器起作用.
4. 在SunOS上, '-rpath'選項不使用, 只搜尋所有由'-L'指定的目錄.
5. 對於一個本地聯結器,環境變數'LD_LIBRARY_PATH'的內容被搜尋.
6. 對於一個本地ELF聯結器,共享庫中的`DT_RUNPATH'和`DT_RPATH'操作符會被需要它的共享庫搜尋. 如果'DT_RUNPATH'存在了, 那'DT_RPATH'就會被忽略.
7. 預設目錄, 常規的,如'/lib'和'/usr/lib'.
8. 對於ELF系統上的本地聯結器, 如果檔案'/etc/ld.so.conf'存在, 這個檔案中有的目錄會被搜尋.
從以上可以看出,在使用本地工具鏈進行本地編譯情況下,只要庫存在於某個位置,gcc總能通過如上策略找到需要的共享庫。但在交叉編譯下,上述八種策略,可以使用的僅僅有兩個:-rpath-link,-rpath。這兩個選項在上述八種策略當中優先順序最高,當指定這兩個選項時,如果連結需要的共享庫找不到,連結器會優先到這兩個選項指定的路徑下去搜索需要的共享庫。通過上面的描述可以看到:-rpath指定的路徑將被寫到可執行檔案中;-rpath-link則不會;我們當然不希望交叉編譯情況下使用的路徑資訊被寫進最終的可執行檔案,所以我們選擇使用選項-rpath-link。
gcc的選項“-Wl,--rpath-link –Wl,DIR”會把-rpath-link選項及路徑資訊傳遞給連結器。回到上面那個例子,如果命令列中沒有出現libb.so,但gcc指定了“-Wl,--rpath-link –Wl,DIR”,則連結器找不到libb.so的時候,會首先到後面-rpath-link指定的路徑去尋找其依賴的庫。此處我們使用的編譯命令的示例是使用unicore平臺的工具鏈。
$ unicore32-linux-gcc –o myprog /usr/lib/liba.so \
-Wl,--rpath-link -Wl,/home/UNITY_float/install/usr/lib
這樣,編譯器會首先到“/home/UNITY_float/install/usr/lib”下面去搜索libb.so
libtool如何把選項“-Wl,--rpath-link –Wl,DIR”傳遞給gcc?libtool中有一個變數“hardcode_libdir_flag_spec”,該變數本來是傳遞“-rpath”選項的,但我們可以修改它,新增我們需要的路徑,傳遞給unicore32-linux-gcc。
“hardcode_libdir_flag_spec”原來的定義如下:
hardcode_libdir_flag_spec="\${wl}--rpath \${wl}\$libdir"
我們修改後的定義如下:
hardcode_libdir_flag_spec="\${wl}—rpath-link \${wl}\$libdir \
-Wl,--rpath-link -Wl,/home/UNITY_float/install/usr/lib \
-Wl,--rpath-link -Wl,/home/UNITY_float/install/usr/X11R6/lib "
這樣,當libtool在“--mode=link”的模式下,就會把選項“-Wl,--rpath-link –Wl,DIR”傳遞給gcc編譯器了。