解決Linux程序編譯鏈接動(dòng)態(tài)庫(kù)版本的相關(guān)問(wèn)題
前言
不同版本的動(dòng)態(tài)庫(kù)可能會(huì)不兼容,如果程序在編譯時(shí)指定動(dòng)態(tài)庫(kù)是某個(gè)低版本,運(yùn)行是用的一個(gè)高版本,可能會(huì)導(dǎo)致無(wú)法運(yùn)行。Linux上對(duì)動(dòng)態(tài)庫(kù)的命名采用libxxx.so.a.b.c的格式,其中a代表大版本號(hào),b代表小版本號(hào),c代表更小的版本號(hào),我們以Linux自帶的cp程序?yàn)槔?,通過(guò)ldd查看其依賴的動(dòng)態(tài)庫(kù)
$ ldd /bin/cp linux-vdso.so.1 => (0x00007ffff59df000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fb3357e0000) librt.so.1 => /lib64/librt.so.1 (0x00007fb3355d7000) libacl.so.1 => /lib64/libacl.so.1 (0x00007fb3353cf000) libattr.so.1 => /lib64/libattr.so.1 (0x00007fb3351ca000) libc.so.6 => /lib64/libc.so.6 (0x00007fb334e35000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fb334c31000) /lib64/ld-linux-x86-64.so.2 (0x00007fb335a0d000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb334a14000)
左邊是依賴的動(dòng)態(tài)庫(kù)名字,右邊是鏈接指向的文件,再查看libacl.so相關(guān)的動(dòng)態(tài)庫(kù)
$ ll /lib64/libacl.so* lrwxrwxrwx. 1 root root 15 1月 7 2015 /lib64/libacl.so.1 -> libacl.so.1.1.0 -rwxr-xr-x. 1 root root 31280 12月 8 2011 /lib64/libacl.so.1.1.0
我們發(fā)現(xiàn)libacl.so.1實(shí)際上是一個(gè)軟鏈接,它指向的文件是libacl.so.1.1.0,命名方式符合我們上面的描述。也有不按這種方式命名的,比如
$ ll /lib64/libc.so* lrwxrwxrwx 1 root root 12 8月 12 14:18 /lib64/libc.so.6 -> libc-2.12.so
不管怎樣命名,只要按照規(guī)定的方式來(lái)生成和使用動(dòng)態(tài)庫(kù),就不會(huì)有問(wèn)題。而且我們往往是在機(jī)器A上編譯程序,在機(jī)器B上運(yùn)行程序,編譯和運(yùn)行的環(huán)境其實(shí)是有略微不同的。下面就說(shuō)說(shuō)動(dòng)態(tài)庫(kù)在生成和使用過(guò)程中的一些問(wèn)題
動(dòng)態(tài)庫(kù)的編譯
我們以一個(gè)簡(jiǎn)單的程序作為例子
// filename:hello.c #include <stdio.h> void hello(const char* name) { printf("hello %s!\n", name); } // filename:hello.h void hello(const char* name);
采用如下命令進(jìn)行編譯
gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.1
需要注意的參數(shù)是-Wl,soname
(中間沒(méi)有空格),-Wl選項(xiàng)告訴編譯器將后面的參數(shù)傳遞給鏈接器,
-soname
則指定了動(dòng)態(tài)庫(kù)的soname(簡(jiǎn)單共享名,Short for shared object name)
現(xiàn)在我們生成了libhello.so.0.0.1,當(dāng)我們運(yùn)行ldconfig -n .
命令時(shí),當(dāng)前目錄會(huì)多一個(gè)軟連接
$ ll libhello.so.0 lrwxrwxrwx 1 handy handy 17 8月 17 14:18 libhello.so.0 -> libhello.so.0.0.1
這個(gè)軟鏈接是如何生成的呢,并不是截取libhello.so.0.0.1名字的前面部分,而是根據(jù)libhello.so.0.0.1編譯時(shí)指定的-soname生成的。也就是說(shuō)我們?cè)诰幾g動(dòng)態(tài)庫(kù)時(shí)通過(guò)-soname指定的名字,已經(jīng)記載到了動(dòng)態(tài)庫(kù)的二進(jìn)制數(shù)據(jù)里面。不管程序是否按libxxx.so.a.b.c格式命名,但Linux上幾乎所有動(dòng)態(tài)庫(kù)在編譯時(shí)都指定了-soname,我們可以通過(guò)readelf工具查看soname,比如文章開(kāi)頭列舉的兩個(gè)動(dòng)態(tài)庫(kù)
$ readelf -d /lib64/libacl.so.1.1.0 Dynamic section at offset 0x6de8 contains 24 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libattr.so.1] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000e (SONAME) Library soname: [libacl.so.1]
這里省略了一部分,可以看到最后一行SONAME為libacl.so.1,所以/lib64才會(huì)有一個(gè)這樣的軟連接
再看libc-2.12.so文件,該文件并沒(méi)有采用我們說(shuō)的命名方式
$ readelf -d /lib64/libc-2.12.so Dynamic section at offset 0x18db40 contains 27 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2] 0x000000000000000e (SONAME) Library soname: [libc.so.6]
同樣可以看到最后一行SONAME為libc.so.6,即便該動(dòng)態(tài)庫(kù)沒(méi)有按版本號(hào)的方式命名,但仍舊有一個(gè)軟鏈指向該動(dòng)態(tài)庫(kù),而該軟鏈的名字就是soname指定的名字
所以關(guān)鍵就是這個(gè)soname,它相當(dāng)于一個(gè)中間者,當(dāng)我們的動(dòng)態(tài)庫(kù)只是升級(jí)一個(gè)小版本時(shí),我們可以讓它的soname相同,而可執(zhí)行程序只認(rèn)soname指定的動(dòng)態(tài)庫(kù),這樣依賴這個(gè)動(dòng)態(tài)庫(kù)的可執(zhí)行程序不需重新編譯就能使用新版動(dòng)態(tài)庫(kù)的特性
可執(zhí)行程序的編譯
還是以hello動(dòng)態(tài)庫(kù)為例,我們寫一個(gè)簡(jiǎn)單的程序
// filename:main.c #include "hello.h" int main() { hello("handy"); return 0; }
現(xiàn)在目錄下是如下結(jié)構(gòu)
├── hello.c ├── hello.h ├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 └── main.c
libhello.so.0.0.1是我們編譯生成的動(dòng)態(tài)庫(kù),libhello.so.0是通過(guò)ldconfig生成的鏈接,采用如下命令編譯main.c
$ gcc main.c -L. -lhello -o main /usr/bin/ld: cannot find -lhello
報(bào)錯(cuò)找不到hello動(dòng)態(tài)庫(kù),在Linux下,編譯時(shí)指定-lhello,鏈接器會(huì)去尋找libhello.so這樣的文件,當(dāng)前目錄下沒(méi)有這個(gè)文件,所以報(bào)錯(cuò)。建立這樣一個(gè)軟鏈,目錄結(jié)構(gòu)如下
├── hello.c ├── hello.h ├── libhello.so -> libhello.so.0.0.1 ├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 └── main.c
讓libhello.so鏈接指向?qū)嶋H的動(dòng)態(tài)庫(kù)文件libhello.so.0.0.1,再編譯main程序
gcc main.c -L. -lhello -o main
這樣可執(zhí)行文件就生成了。通過(guò)以上測(cè)試我們發(fā)現(xiàn),在編譯可執(zhí)行程序時(shí),鏈接器會(huì)去找它依賴的libxxx.so這樣的文件,因此必須保證libxxx.so的存在
用ldd查看其依賴的動(dòng)態(tài)庫(kù)
$ ldd main linux-vdso.so.1 => (0x00007fffe23f2000) libhello.so.0 => not found libc.so.6 => /lib64/libc.so.6 (0x00007fb6cd084000) /lib64/ld-linux-x86-64.so.2 (0x00007fb6cd427000)
我們發(fā)現(xiàn)main程序依賴的動(dòng)態(tài)庫(kù)名字是libhello.so.0,既不是libhello.so也不是libhello.so.0.0.1。其實(shí)在生成main程序的過(guò)程有如下幾步
- 鏈接器通過(guò)編譯命令-L. -lhello在當(dāng)前目錄查找libhello.so文件
- 讀取libhello.so鏈接指向的實(shí)際文件,這里是libhello.so.0.0.1
- 讀取libhello.so.0.0.1中的SONAME,這里是libhello.so.0
- 將libhello.so.0記錄到main程序的二進(jìn)制數(shù)據(jù)里
也就是說(shuō)libhello.so.0是已經(jīng)存儲(chǔ)到main程序的二進(jìn)制數(shù)據(jù)里的,不管這個(gè)程序在哪里,通過(guò)ldd查看它依賴的動(dòng)態(tài)庫(kù)都是libhello.so.0
而為什么這里ldd查看main顯示libhello.so.0為not found呢,因?yàn)閘dd是從環(huán)境變量$LD_LIBRARY_PATH指定的路徑里來(lái)查找文件的,我們指定環(huán)境變量再運(yùn)行如下
$ export LD_LIBRARY_PATH=. && ldd main linux-vdso.so.1 => (0x00007fff7bb63000) libhello.so.0 => ./libhello.so.0 (0x00007f2a3fd39000) libc.so.6 => /lib64/libc.so.6 (0x00007f2a3f997000) /lib64/ld-linux-x86-64.so.2 (0x00007f2a3ff3b000)
可執(zhí)行程序的運(yùn)行
現(xiàn)在測(cè)試目錄結(jié)果如下
├── hello.c ├── hello.h ├── libhello.so -> libhello.so.0.0.1 ├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 ├── main └── main.c
這里我們把編譯環(huán)境和運(yùn)行環(huán)境混在一起了,不過(guò)沒(méi)關(guān)系,只要我們知道其中原理,就可以將其理清楚
前面我們已經(jīng)通過(guò)ldd查看了main程序依賴的動(dòng)態(tài)庫(kù),并且指定了LD_LIBRARY_PATH變量,現(xiàn)在就可以直接運(yùn)行了
$ ./main hello Handy!
看起來(lái)很順利。那么如果我們要部署運(yùn)行環(huán)境,該怎么部署呢。顯然,源代碼是不需要的,我們只需要?jiǎng)討B(tài)庫(kù)和可執(zhí)行程序。這里新建一個(gè)運(yùn)行目錄,并拷貝相關(guān)文件,目錄結(jié)構(gòu)如下
├── libhello.so.0.0.1 └── main
這時(shí)運(yùn)行會(huì)main會(huì)發(fā)現(xiàn)
$ ./main ./main: error while loading shared libraries: libhello.so.0: cannot open shared object file: No such file or directory
報(bào)錯(cuò)說(shuō)libhello.so.0文件找不到,也就是說(shuō)程序運(yùn)行時(shí)需要尋找的動(dòng)態(tài)庫(kù)文件名其實(shí)是動(dòng)態(tài)庫(kù)編譯時(shí)指定的SONAME,這也和我們用ldd查看的一致。通過(guò)ldconfig -n .
建立鏈接,如下
├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 └── main
再運(yùn)行程序,結(jié)果就會(huì)符合預(yù)期了
從上面的測(cè)試看出,程序在運(yùn)行時(shí)并不需要知道libxxx.so,而是需要程序本身記載的該動(dòng)態(tài)庫(kù)的SONAME,所以main程序的運(yùn)行環(huán)境只需要以上三個(gè)文件即可
動(dòng)態(tài)庫(kù)版本更新
假設(shè)動(dòng)態(tài)庫(kù)需要做一個(gè)小小的改動(dòng),如下
// filename:hello.c #include <stdio.h> void hello(const char* name) { printf("hello %s, welcom to our world!\n", name); }
由于改動(dòng)較小,我們編譯動(dòng)態(tài)庫(kù)時(shí)仍然指定相同的soname
gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.2
將新的動(dòng)態(tài)庫(kù)拷貝到運(yùn)行目錄,此時(shí)運(yùn)行目錄結(jié)構(gòu)如下
├── libhello.so.0 -> libhello.so.0.0.1 ├── libhello.so.0.0.1 ├── libhello.so.0.0.2 └── main
此時(shí)目錄下有兩個(gè)版本的動(dòng)態(tài)庫(kù),但libhello.so.0指向的是老本版,運(yùn)行ldconfig -n .
后我們發(fā)現(xiàn),鏈接指向了新版本,如下
├── libhello.so.0 -> libhello.so.0.0.2 ├── libhello.so.0.0.1 ├── libhello.so.0.0.2 └── main
再運(yùn)行程序
$ ./main hello Handy, welcom to our world!
沒(méi)有重新編譯就使用上了新的動(dòng)態(tài)庫(kù), wonderful!
同樣,假如我們的動(dòng)態(tài)庫(kù)有大的改動(dòng),編譯動(dòng)態(tài)庫(kù)時(shí)指定了新的soname,如下
gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0.0
將動(dòng)態(tài)庫(kù)文件拷貝到運(yùn)行目錄,并執(zhí)行ldconfig -n .
,目錄結(jié)構(gòu)如下
├── libhello.so.0 -> libhello.so.0.0.2 ├── libhello.so.0.0.1 ├── libhello.so.0.0.2 ├── libhello.so.1 -> libhello.so.1.0.0 ├── libhello.so.1.0.0 └── main
這時(shí)候發(fā)現(xiàn),生成了新的鏈接libhello.so.1,而main程序還是使用的libhello.so.0,所以無(wú)法使用新版動(dòng)態(tài)庫(kù)的功能,需要重新編譯才行
總結(jié)
在實(shí)際生產(chǎn)環(huán)境中,程序的編譯和運(yùn)行往往是分開(kāi)的,但只要搞清楚這一系列過(guò)程中的原理,就不怕被動(dòng)態(tài)庫(kù)的版本搞暈。簡(jiǎn)單來(lái)說(shuō),按如下方式來(lái)做
- 編譯動(dòng)態(tài)庫(kù)時(shí)指定
-Wl
,-soname
,libxxx.so.a
,設(shè)置soname為libxxx.so.a,生成實(shí)際的動(dòng)態(tài)庫(kù)文件libxxx.so.a.b.c, - 編譯可執(zhí)行程序時(shí)保證libxx.so存在,如果是軟鏈,必須指向?qū)嶋H的動(dòng)態(tài)庫(kù)文件libxxx.so.a.b.c
- 運(yùn)行可執(zhí)行文件時(shí)保證libxxx.so.a.b.c文件存在,通過(guò)ldconfig生成libxxx.so.a鏈接指向libxxx.so.a.b.c
- 設(shè)置環(huán)境變量LD_LIBRARY_PATH,運(yùn)行可執(zhí)行程序
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。
- linux下編譯boost.python簡(jiǎn)單方法
- 深入探討Linux靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)的詳解(一看就懂)
- linux生成(加載)動(dòng)態(tài)庫(kù)靜態(tài)庫(kù)和加載示例方法
- Linux下g++編譯與使用靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的方法
- 分析Windows和Linux動(dòng)態(tài)庫(kù)
- linux 程序、動(dòng)態(tài)庫(kù)、靜態(tài)庫(kù)內(nèi)部添加版本號(hào)和編譯時(shí)間詳解
- Linux動(dòng)態(tài)庫(kù)函數(shù)的詳解
- Linux靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)實(shí)例詳解
- 淺談Linux C語(yǔ)言動(dòng)態(tài)庫(kù)及靜態(tài)庫(kù)
- linux中使用boost.python調(diào)用c++動(dòng)態(tài)庫(kù)的方法
相關(guān)文章
linux系統(tǒng)中rsync+inotify實(shí)現(xiàn)服務(wù)器之間文件實(shí)時(shí)同步
這篇文章主要介紹了rsync+inotify實(shí)現(xiàn)服務(wù)器之間文件實(shí)時(shí)同步,需要的朋友可以參考下2014-11-11Centos 7.4服務(wù)器時(shí)間同步配置方法【基于NTP服務(wù)】
這篇文章主要介紹了Centos 7.4服務(wù)器時(shí)間同步配置方法,結(jié)合實(shí)例形式分析了NTP服務(wù)器安裝、啟動(dòng)、設(shè)置時(shí)間同步等相關(guān)命令及問(wèn)題解決方法,需要的朋友可以參考下2019-03-03Linux系統(tǒng)如何修改遠(yuǎn)程連接22端口
這篇文章主要介紹了Linux系統(tǒng)如何修改遠(yuǎn)程連接22端口問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12