linux下動態庫和靜態庫生成

原文鏈接:https://www.bbsmax.com/A/lk5aNpRZd1/

有時候需要把一組代碼編譯成一個庫,這個庫在很多項目中都要用到,例如libc就是這樣一個庫, 我們在不同的程序中都會用到libc中的庫函數(例如printf),也會用到libc中的變量(例如以後 要講到的environ變量)。本文將介紹怎麼創建這樣一個庫。
這些文件的目錄結構是:

$ tree
.
|-- main.c
`-- stack
|-- is_empty.c
|-- pop.c
|-- push.c
|-- stack.c
`-- stack.h
1 directory, 6 files

我們把stack.c、push.c、pop.c、is_empty.c編譯成目標文件:

$ gcc -c stack/stack.c stack/push.c stack/pop.c stack/is_empty.c

然後打包成一個靜態庫libstack.a:

$ ar rs libstack.a stack.o push.o pop.o is_empty.o
ar: creating libstack.a

庫文件名都是以lib開頭的,靜態庫以.a作爲後綴,表示Archive。ar命令類似於tar命令,起一個打包的作用,但是把目標文件打包成靜態庫只能用ar命令而不能用tar命令。選項r表示將後面的文件列表添加到文件包,如果文件包不存在就創建它,如果文件包中已有同名文件就替換成新的。s是專用於生成靜態庫的,表示爲靜態庫創建索引,這個索引被鏈接器使用。ranlib命令也可以爲靜態庫創建索引,以上命令等價於:

然後我們把libstack.a和main.c編譯鏈接在一起:

$ gcc main.c -L. -lstack -Istack -o main

-L選項告訴編譯器去哪裏找需要的庫文件,-L.表示在當前目錄找。-lstack告訴編譯器要鏈 接libstack庫,-I選項告訴編譯器去哪裏找頭文件。注意,即使庫文件就在當前目錄,編譯器默認 也不會去找的,所以-L.選項不能少。編譯器默認會找的目錄可以用-print-search-dirs選項查看。編譯器會在這些搜索路徑以及-L選項指定的路徑中查找用-l選項指定的庫,比如-lstack,編譯器會首先找有沒有共享庫libstack.so,如果有就鏈接它,如果沒有就找有沒有靜態庫libstack.a,如果有就鏈接它。所以編譯器是優先考慮共享庫的,如果希望編譯器只鏈接靜態庫,可以指定-static選項。

動態庫(共享庫)

組成共享庫的目標文件和一般的目標文件有所不同,在編譯時要加-fPIC選項,例如:

$ gcc -c -fPIC stack/stack.c stack/push.c stack/pop.c stack/is_empty.c

-f後面跟一些編譯選項,PIC是其中一種,表示生成位置無關代碼(Position Independent Code)。
現在把main.c和共享庫編譯鏈接在一起,然後運行:

$ gcc main.c -g -L. -lstack -Istack -o main
$ ./main
./main: error while loading shared libraries: libstack.so: cannot open shared object file: No such file or directory

結果出乎意料,編譯的時候沒問題,由於指定了-L.選項,編譯器可以在當前目錄下找到libstack.so,而運行時卻說找不到libstack.so。那麼運行時在哪些路徑下找共享庫呢?我們先用ldd命令查看可執行文件依賴於哪些共享庫:

$ ldd main
linux-gate.so.1 => (0xb7f5c000)
libstack.so => not found
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7dcf000)
/lib/ld-linux.so.2 (0xb7f42000)

ldd模擬運行一遍main,在運行過程中做動態鏈接,從而得知這個可執行文件依賴於哪些共享庫,每個共享庫都在什麼路徑下,加載到進程地址空間的什麼地址。/lib/ld-linux.so.2是動態鏈接器,它的路徑是在編譯鏈接時指定的,gcc在做鏈接時用dynamic-linker指定動態鏈接器的路徑,它也像其它共享庫一樣加載到進程的地址空間中。libc.so.6的路徑/lib/tls/i686/cmov/libc.so.6是由動態鏈接器ld-linux.so.2在做動態鏈接時搜索到的,而libstack.so的路徑沒有找到。linux-gate.so.1這個共享庫其實並不存在於文件系統中,它是由內核虛擬出來的共享庫,所以它沒有對應的路徑,它負責處理系統調用。總之,共享庫的搜索路徑由動態鏈接器決定,從ld.so(8)的Man Page可以查到共享庫路徑的搜索順序:

首先在環境變量LD_LIBRARY_PATH所記錄的路徑中查找。
然後從緩存文件/etc/ld.so.cache中查找。這個緩存文件由ldconfig命令讀取配置文 件/etc/ld.so.conf之後生成,稍後詳細解釋。
如果上述步驟都找不到,則到默認的系統路徑中查找,先是/usr/lib然後是/lib。

先試試第一種方法,在運行main時通過環境變量LD_LIBRARY_PATH把當前目錄添加到共享庫的搜索路徑:

$ LD_LIBRARY_PATH=. ./main

這種方法只適合在開發中臨時用一下,通常LD_LIBRARY_PATH是不推薦使用的,儘量不要設置這個環境變量,理由可以參考Why LD_LIBRARY_PATH is bad

再試試第二種方法,這是最常用的方法。把libstack.so所在目錄的絕對路徑(比如/home/akaedu/somedir)添加到/etc/ld.so.conf中(該文件中每個路徑佔一行),然後運行ldconfig

$ sudo ldconfig -v

ldconfig命令除了處理/etc/ld.so.conf中配置的目錄之外,還處理一些默認目錄,如/lib、/usr/lib等,處理之後生成/etc/ld.so.cache緩存文件,動態鏈接器就從這個緩存中搜索共享庫。現在再用ldd命令查看,libstack.so就能找到了:

$ ldd main
linux-gate.so.1 => (0xb809c000)
libstack.so => /home/akaedu/somedir/libstack.so (0xb806a000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7f0c000)
/lib/ld-linux.so.2 (0xb8082000)

第三種方法就是把libstack.so拷到/usr/lib或/lib目錄,這樣可以確保動態鏈接器能找到這個共享庫。
其實還有第四種方法,在編譯可執行文件main的時候就把libstack.so的路徑寫死在可執行文件中:

$ gcc main.c -g -L. -lstack -Istack -o main -Wl,rpath,/home/akaedu/somedir

-Wl,-rpath,/home/akaedu/somedir表示-rpath /home/akaedu/somedir是由gcc傳遞給鏈接器的選項。可以看到readelf的結果多了一條rpath記錄:

$ readelf -a main
...
Dynamic section at offset 0xf10 contains 23 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library:
[libstack.so]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000f (RPATH) Library rpath: [/home/akaedu/somedir]
...

還可以看出,可執行文件運行時需要哪些共享庫也都記錄在.dynamic段中。當然rpath這種辦法也是不推薦的,把共享庫的路徑定死了,失去了靈活性。
甚至還可以這樣寫:

$ gcc -o main main.c -g -L. -lstack -Istack ./stack/libstack.so

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章