Linux下动态库的创建与更新
Linux下動態庫(libname.x.y.z)的創建與更新
由于主程序和它依賴的共享庫是由不同的開發者開發的。共享庫的開發者會不停地更新共享庫的版本,以修正bug,增加功能或改進性能。版本多了之后,就可能會引起不兼容問題。在Windows上,這叫做“DLL HELL"。Linux下同樣也會遇到相似問題,Linux對動態庫采用如“libname.so.x.y.z”的命名規則來解決兼容問題。其中x代表主版本號,y代表次版本號,z代表發行號。根據約定,只要主版本號x相同,就不會引起兼容問題,即libname.so.x,Linux稱之為SO-NAME。
程序會把它依賴的共享庫的名字和主版本號(即SO-NAME)記錄在dynamic段中。程序加載時,動態加載器會去dynamic段中尋找這些SONAME,以加載適當版本的依賴庫,從而解決兼容問題。可以通過readelf -d programname 或者 ldd programname查看。
1. 創建共享庫
共享庫代碼: /* * filename:hello.c * version:1.0.0 */ #include <stdio.h>void hello(void) {printf("HelloWorld\n"); }/* * filename:hello.h * version:1.0.0 */ #ifndef HELLO_H_ #define HELLO_H_void hello(void);#endif 在Ubuntu15中,使用如下命令把hello.c編譯成動態庫,版本1.0.0。要注意的是逗號的后面沒有空格,有時我們會習慣性地加上一個空格,導致編譯出錯。 $ gcc -shared -fPIC -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0 hello.c
- -shared表示輸出結果是動態共享庫
- -fPIC表示生成位置無關碼
- -Wl表示把后面的兩個參數(soname 和 libhello.so.1)傳遞給鏈接器。注意這里的l是love的首字母,且是小寫
此時通過readelf查看新生成的目標文件,如下圖所示,可以發現SONAME已經被保存到了目標文件中。
$ readelf -d libhello.so.1.0.0 Dynamic section at offset 0xf04 contains 25 entries:Tag Type Name/Value0x00000001 (NEEDED) Shared library: [libc.so.6]0x0000000e (SONAME) Library soname: [libhello.so.1] ......此時當前文件夾下的文件為
$ tree . ├── hello.c ├── hello.h └── libhello.so.1.0.00 directories, 3 files共享庫創建完成后,我們需將它安裝到系統中,以便其他程序能夠使用它。通常有兩種方法
- 在root權限下,將目標文件拷貝到/lib 或者/usr/lib目錄下,然后再運行一下ldconfig命令,生成一個符號鏈接。這是最簡單的方法,但需要管理員權限
- 在非root權限下,運行ldconfig -n shared_library_directory。其中-n表示在當前目錄下創建符號鏈接。當然后續還需將當前目錄導出
我們這里使用的是方法2。得到如下結果。可以發現生成了一個符號鏈接,指向我們的共享庫。動態加載時(注意不是鏈接時)依靠的就是這個SONAME。
$ tree . ├── hello.c ├── hello.h ├── libhello.so.1 -> libhello.so.1.0.0 └── libhello.so.1.0.00 directories, 4 files2. 使用共享庫
現在我們再寫一個簡單的代碼,使用一下我們剛剛創建的共享庫。
/* * filename:test.c * version:1.0.0 */ #include "hello.h" int main(void) {hello();return 0; }然后編譯鏈接該測試程序
$ gcc test.c -L. -lhello -o test /usr/bin/ld: cannot find -lhello collect2: error: ld returned 1 exit status其中“-L. -lhello"表示在當前目錄下查找libhello.so。當前文件夾下確實沒有libhello.so,所以出錯。這時我們需要通過ln -s 命令創建一個符號鏈接,讓它指向我們的共享庫。
$ ln -s libhello.so.1.0.0 libhello.so現在我們再次編譯(gcc test.c -L. -lhello -o test)就通過了。這時通過ldd命令查看可執行文件test的依賴關系。 $ ldd testlinux-gate.so.1 => (0xb7795000)libhello.so.1 => not foundlibc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75be000)/lib/ld-linux.so.2 (0x80040000)
可以發現libhello.so.1狀態是not found,所以此時如果運行程序,一定會出錯。
$ ./test ./test: error while loading shared libraries: libhello.so.1: cannot open shared object file: No such file or directory出現了運行時出錯,這是因為動態加載器不知道到哪里加載libhello.so.1。
解決方法
- 編輯 /etc/ld.so.conf, 將本次測試用目錄加入文件中,然后執行ldconf命令
- 執行 export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH,臨時方法。
我們這只是測試,所以使用的是臨時方法。此時,我們運行測試程序,就會輸出正確結果了。
3. 模擬發布動態庫
我們將libhello.so.1.0.0和test.c 拷貝到另一個文件夾中。因為程序加載動態庫時會尋找符號鏈接libhello.so.1,所以我們需要執行一下 "ldconfig -n .",在新文件夾下生成一個符號鏈接。此時如果運行報錯,有可能就是你把終端關了,重新開一個新終端,那么我們就需要再次執行一下剛才那個導出LD_LIBRARY_PATH命令,然后再運行程序。
4. 更新共享庫,升級次版本號
此時回到我們先前的目錄,不是我們測試發布動態庫那個目錄了。修改如下代碼。 /* * filename:hello.c * version:1.1.0 */ #include <stdio.h>void hello(void) {printf("Minor Version Number was modified!\n"); }然后執行
$ gcc -shared -fPIC -Wl,-soname,libhello.so.1 -o libhello.so.1.1.0 hello.c注意:我們這里把libhello.so.1.0.0升級到了libhello.so.1.1.0。前面我們曾經提到,只要libname.x,y.z中,x不變,用戶程序就不必修改,即我們這里的test.c無需修改。
再把新生成的libhello.so.1.1.0拷貝到那個模擬發布目錄下。把終端切換到模擬發布目錄下,執行“ldconfig -n .",重新生成符號鏈接。此時的該目錄下的文件結構如下:
執行程序,輸出修改后的結果。
5. 更新版本庫 升級主版本號
再次修改hello.c
/* * filename:hello.c * version:2.0.0 */ #include <stdio.h>void hello(void) {printf("Major Version Number was modified!\n"); }
再次編譯共享庫
然后把libhello.so.2.0.0拷貝到那個模擬發行目錄,執行ldconfig -n .,然后再查看此時的文件結構
可以發現生成了兩個符號鏈接,但此時執行程序的話,仍然輸出“Minor Version Number was modified!",這是因為我們并沒有重新編譯test.c,它保存的SONAME仍然是”libhello.so.1",而不是“libhello.so.2"。
本博客參考了以下兩個資源,在此致敬
- 程序員的自我修養----鏈接,裝載與庫(俞甲子 石凡 潘愛明)
- http://littlewhite.us/archives/301?utm_source=tuicool&utm_medium=referral
總結
以上是生活随笔為你收集整理的Linux下动态库的创建与更新的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于IE无法打开flex程序的问题
- 下一篇: [导入]Linux下载工具利器ProZi