前兩天寫了一個動態庫,然后試圖編譯到程序里面去運行,結果發現編譯的時候通過gcc的-L參數來指定路徑僅僅能讓編譯通過,運行時還是會出問題的。
比如下面這個例子:
main.c是主程序,sum.c中間含有一個函數add,用來執行加法,代碼如下:
1 /* 2 * main.c 3 */ 4 #include <stdio.h> 5 6 int add(int a, int b); 7 8 int main(int argc,char *argv[]) 9 { 10 printf("sum = %d\n", add(3,5)); 11 return 0; 12 }
1 /* 2 * sum.c 3 */ 4 int add(int a, int b) 5 { 6 return a + b; 7 }
出錯結果如下圖所示:
我在編譯的時候通過-L指定了查找動態庫的位置,結果運行的時候還是找不到我自己寫的那個libsum.so這個動態庫,后來去查了一下,才明白其中原委。
程序在鏈接動態庫的時候分為2步,編譯時鏈接和運行時鏈接。
1. 編譯時鏈接
這個過程是由ld程序來執行的,所以編譯時找不到動態庫的位置的話,經常就會看到這種錯誤:
這個過程嚴格意義上來說並不能說是鏈接,因為在這里ld程序並沒有真正的把庫里面的函數的執行代碼寫到可執行文件里面,只是把一些符號還有其他的必要信息寫道了可執行文件里面,供可執行文件運行時查找。
總的來說,ld在這一步里面就是做了兩個事情:
1. 查找動態庫中是否含有我們需要的符號(函數和全局變量),如果都能找到,則鏈接允許通過,生成了可執行文件。
2. 在可執行文件中寫入了符號和其他必要的信息(例如符號的地址),供可執行文件運行時查找。
2. 運行時鏈接
這個過程是由ld-linux.so程序來執行,這個才是真正的鏈接。它所做的工作就是將動態庫的代碼映射到進程(可執行文件運行起來就是進程啦...)的虛擬地址空間中,供進程來調用。
關於鏈接,加載,運行的更多信息可以參看<參考文章1>(詳細地址見本文最后)。
OK,明白了上面兩個鏈接之后,我們再來看這兩個鏈接查找動態庫的目錄位置,如下:
運行時,ld-linux.so查找共享庫的順序
(1)ld-linux.so.6在可執行的目標文件中被指定,可用readelf命令查看
(2)ld-linux.so.6缺省在/usr/lib和lib中搜索;當glibc安裝到/usr/local下時,它查找/usr/local/lib
(3)LD_LIBRARY_PATH環境變量中所設定的路徑
(4)/etc/ld.so.conf(或/usr/local/etc/ld.so.conf)中所指定的路徑,由ldconfig生成二進制的ld.so.cache中
編譯時,ld-linux.so查找共享庫的順序
(1)ld-linux.so.6由gcc的spec文件中所設定
(2)gcc --print-search-dirs所打印出的路徑,主要是libgcc_s.so等庫。可以通過GCC_EXEC_PREFIX來設定
(3)LIBRARY_PATH環境變量中所設定的路徑,或編譯的命令行中指定的-L/usr/local/lib
(4)binutils中的ld所設定的缺省搜索路徑順序,編譯binutils時指定。(可以通過“ld --verbose | grep SEARCH”來查看)
(5)二進制程序的搜索路徑順序為PATH環境變量中所設定。一般/usr/local/bin高於/usr/bin
(6)編譯時的頭文件的搜索路徑順序,與library的查找順序類似。一般/usr/local/include高於/usr/include
大家注意編譯時查找的路徑可以通過gcc -L參數或者LIBRARY_PATH來指定,但是運行時的查找路徑卻不包含gcc -L和LIBRARY_PATH環境變量指定的路徑,所以這樣就會出現我們剛開始所說的那個問題,編譯時通過-L指定了動態庫的搜索路徑,編譯也通過了,但是運行時卻會報錯,這是因為運行時查找動態庫的路徑還沒指定,所以我們自己寫的動態庫就找不到了,而要解決這個問題,通過設置環境變量LD_LIBRARY_PATH或者修改/etc/ld.so.conf(記得修改完了運行ldconfi來生成ld.so.cache)就可以了。如下圖所示:
參考文章:
3. readelf命令