1. 面試 | linux 下的動態鏈接庫問題
在 Linux 開發時,我們經常會看到一些形如 xxx.so 的名稱出現,其中 so 是 Shared Object 的縮寫,即可以共享的目標文件,也就是我們所稱為的動態鏈接庫,和在 Windows 下大家玩 游戲 時遇到的 xxx.dll 錯誤中的文件是一個類型的。
面試中經常會問到以下問題:
庫是寫好的現有的,成熟的,可以復用的代碼。現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常。本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。
庫有兩種:
在一個程序的編譯過程中,分為以下幾個步驟: 預處理 , 編譯 , 匯編 , 鏈接 。本文中討論的鏈接庫就是針對最後一個步驟「鏈接」而言的。
動態庫和靜態庫的區別
左圖為靜態鏈接庫,右圖為動態鏈接庫
對於靜態鏈接庫而言在鏈接階段,會將匯編生成的「目標文件.o」與引用到的庫一起鏈接打包到可執行文件中。因此對應的鏈接方式稱為靜態鏈接:
靜態鏈接可以理解為最後生成了一個「單文件免安裝綠色版」的程序,優點在於移植的時候只需要移動這一個文件,缺點在於文件體積非常大,為了解決這樣的問題,就有了動態鏈接庫。動態鏈接庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行時才被載入。
動態庫連接到系統空間,如果多個程序連接了同一個庫,那麼只需要一份,優點在於編譯程序的時候不會將對應的庫文件全部打包在生成的程序中,而是保留了到對應庫的鏈接,缺點就是移植的時候如果只移動了對應的程序沒有安裝相關的庫的話,就會看到類似以下喜聞樂見的結果了。
在 Linux 下一個動態庫有y三個不同名字的文件組成:
當程序在內部列出所需要的鏈接庫時,僅僅使用 soname。當你創建一個鏈接庫時,使用 real name。安裝一個新的鏈接庫時,把它復制到一個DLL文件夾里,然後運行程序 ldconfig。ldconfig 檢查存在的 real name 文件,並且創建指向它符號鏈接 soname 文件。可能大家比較常見到的有 libsodium 等。
有了上面關於庫的一些基礎知識之後,我們可以開始嘗試創建一個動態庫來供程序使用了。
比如我們有一個求最大值的函數 max(int a,int b,int c) ,放在文件 max.c 中文件內容如下:
可以通過:
將其編譯為共享庫,-fPIC是編譯選項,PIC是 Position Independent Code 的縮寫,表示要生成位置無關的代碼,這是動態庫需要的特性; -shared是鏈接選項,告訴 gcc 生成動態庫而不是可執行文件。為了讓用戶知道我們的動態庫中有哪些介面可用,我們需要編寫對應的頭文件,比如可以寫一個 max.h :
設置一個驅動函數來測試我們編寫的動態庫:
通過 gcc test.c -L. -lmax來生成 a.out,其中-lmax表示要鏈接 libmax.so,-L.表示搜索要鏈接的庫文件時包含當前路徑。
但是這樣直接運行的話,會出現一個錯誤:
由於 Linux 是通過/etc/ld.so.cache文件搜尋要鏈接的動態庫的,而 /etc/ld.so.cache 是 ldconfig 程序讀取 /etc/ld.so.conf 文件生成的,本次使用的動態庫 libmax.so 並不在對應的目錄下,就會導致程序無法找到對應的動態鏈接庫,這樣我們的解決方法有二:
小結
動態鏈接庫是各個系統中的一個重要的組成部分且在 Linux 開發相關領域中尤為重要,也是一個面試的高頻考點,除了動態鏈接庫以外,還有以下相關知識也是高頻考點,在面試前一定要准備好:
本文作者:Nova Kwok
2. gcc 生成動態庫時-fpic選項是什麼意思。
fpic:產生位置無關碼
解釋一下,位置無關碼就是可以在進程的任意內存位置執行的目標碼,動態鏈接庫必須使用。
3. 編譯時找不到動態庫
報錯:
分析:
應該是動態庫鏈接的庫位置不對,默認在目錄usr/lib/ 下查找動態庫。
方式一 用ln -s建立創建軟連接,確保/usr/lib下存在庫。
ln -s /絕對路徑/源 /絕對路徑/目的
方式二鍵譽州 編譯時使用-rpath 或者-rpath-link,例虛畢如如下gcc編譯時設置
mips64el-redhat-linux-gcc -o test *.c -Wall -L./lib/mips64le/ -lpthread -lm -Wl,-rpath=/usr/稿蔽local/gcc-4.8.3-d197-n64-loongson/usr/mips64el-redhat-linux/lib64/
掛載命令的順序
4. gcc編譯錯誤!
提示說需要-fPIC編譯,然後在鏈接動態庫的地方加上-fPIC的參數編譯結果還是報錯,需要把共享庫所用到的所有靜態庫都採用-fPIC編譯一邊才可以成功的在64位環境下編譯出動態庫。
這里的-fPIC指的是地址無關代碼
你看看這里的這篇文章吧:http://www.cnblogs.com/lightsalt/archive/2011/10/19/2217628.html
5. makefile 生成動態庫和靜態庫的區別
生成動態庫的時候要注意,編譯生成目標文件的時候加上-fPIC參數,生成位置無關的可重定位代碼,然後鏈接的時候加上-shared生成動態共享庫。比如一個hello.c,生成靜態庫:
gcc-ohello.o-chello.c
arrcslibhello.ahello.o
生成動態庫的命令:
gcc-fPIChello.o-chello.c
gcc-shared-olibhelllo.sohello.o
還有一個區別是:靜態庫參與鏈接過程,而動態庫不鏈接到可執行文件中,可執行程序在運行的時候,對應的動態庫也要載入到內存中,否則可執行程序運行不了。
更多詳細細節,可以網路搜索視頻教程:Makefile工程實踐
6. 動態庫編譯詳解
當前類介紹:upper.c ( upper) 依賴於 bottom.c(play)
說明:當執行可執行程序的時候,需要去/lib. /user/lib下和LD_LIBRARY_PATH下尋找so.並不會在當前目錄下尋找.
所以執行./main.out會報錯.如下:
解決方案:指定.so運行搜尋路徑
1.-Wl,-rpath ./mypath 加入參數,並且將libplay.so 到./mypath目錄下.
2.設置LD_LIBRARY_PATH,指定目錄.
說明:指定了-Wl,-rpath, 設置LD_LIBRARY_PATH也是可以生效的.並不是說只會去-Wl,-rpath下尋找.
首先生成一個bottom.so,然後用upper.so去依賴bottom.so, 然後main.c 再去依賴upper.so.
說明:這里編譯的時候直接出錯,是因為沒有指定搜尋路徑,所以無法通過編譯.
解決編譯問題方案.
1.我們依然採用LD_LIBRARY_PATH的方式可以解決編譯和運行的問題.
2.生成libplay的時候,直接指定-Wl,-rpath 給libbottom.可以解決編譯不通過的問題.
3.依賴所有庫
依賴所有庫只能解決編譯問題,無法處理運行的路徑.
另一種思路:我們在執行main.out的時候 執行-Wl,-rpath.並不在生成libplay的時候指定,看下是否正常.
由此可見,-Wl,-rpath 只能針對直接依賴的libplay.so指定了路徑,但是libbottom還是無法查找到 .但是LD_LIBRARY是可以的.
rpath只能對直接依賴的so設置搜尋目錄,並且可以設置所有依賴的編譯路徑.
總結: 解決編譯問題,在生成libplay的時候指定-Wl,-rpath運行路徑,或者設置LD_LIBRARAY_PATH,都可以解決這個問題.
當我們現在擁有的so包含一個直接依賴的so和很多間接依賴的so,但是沒有設置rpath.所以是不能直接依賴主so進行編譯和運行的.
為了通過編譯:
1.在只鏈接主so的情況下可以去設置rpath或者LD_LIBRARY_PATH.
2.或者鏈接所有so.
為了通過運行:
為了正常運行可以設置LD_LIBRARY_PATH.
--disable-new-dtags,---dt-needed-entries
結論概述:
1.我們在生成間接依賴的庫的時候,為了保證其他庫可以直接依賴,需要加入-Wl,-rpath.保證編譯通過.
2.LD_LIBRARY_PATH可以解決一切編譯運行問題.
7. 調用動態庫的時候有幾個問題會經常碰到
通常這樣做就可以解決庫無法鏈接的問題了。 makefile裡面怎麼正確的編譯和連接生成.so庫文件,然後又是在其他程序的makefile裡面如何編譯和連接才能調用這個逗搏庫文件的函數?答:你需要告訴動態鏈接器、載入器ld.so在哪裡才能找到這個共享庫,可以設置環境變數把庫的路徑添加到庫目錄/lib和/usr/lib,LD_LIBRARY_PATH=$(pwd),這種方法採用命令行方法不太方便,一種替代方法 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注釋^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LD_LIBRARY_PATH可以在/etc/profile還是 ~/.profile還是 ./bash_profile里設置,或者.bashrc里, 改完後運行source /etc/profile或 . /etc/profile 更好的辦法是添入/etc/ld.so.conf, 然後執行 /sbin/ldconfig ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注釋^^^^^^^^^^^^^^^^^^^^^^^^^^^^是把庫路徑添加到/etc/ld.so.conf,然後以root身份運行ldconfig 也可以在連接的時候指定文件路徑和名稱 -I -L. GCC=gccCFLAGS=-Wall -ggdb -fPIC#CFLAGS=all: libfunc test libfunc:func.o func1.o $(GCC) -shared -Wl,-soname,libfunc.so.1 -o libfunc.so.1.1 $ 編譯目標文件時使用gcc的姿野-fPIC選項,產生與位置無關的代碼並能被載入到任何地址: gcc ?fPIC ?g ?c liberr.c ?o liberr.o 使用gcc的-shared和-soname選項; 使用gcc的-Wl選項把參數傳遞給連接器ld; 使用gcc的-l選項顯示的連接C庫,以保證可以得到所需的啟動(startup)代碼,從而避免程序在使用不同的,可能不兼容版本的C庫的系統上不能啟動執行。 gcc ?g ?shared ?Wl,-soname,liberr.so ?o liberr.so.1.0.0 liberr.o ?lc 建立相應的符號連接: ln ?s liberr.so.1.0.0 liberr.so.1; ln ?s liberr.so.1.0.0 liberr.so; 在MAKEFILE中:$@表示規則中的目標文件集。在模式規則中,如果有多個目標,那麼,"$@"就是匹配於目標中模式定義的集合。$%僅跡指喊當目標是函數庫文件中,表示規則中的目標成員名。例如,如果一個目標是"foo.a(bar.o)",那麼,"$%"就是"bar.o","$@"就是 "foo.a"。如果目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那麼,其值為空。