在發(fā)布linux-fpga系列文章 和PowerPC平臺Linux的移植系列文章后,很多朋友發(fā)mail來問我一些執(zhí)行軟件上面的問題,大部分都是和軟件所使用的鏈接庫相關(guān)的。Novell的官方網(wǎng)站上有一篇文章 對相關(guān)問題介紹得比較詳細(xì),我就參照該文章寫出中文版,希望能對遇到此類問題的朋友有所幫助。
介紹
很多時(shí)候,諸如glibc這種基本庫,由于有著不同的版本存在,如果正好你手上只有適用于某個(gè)版本的packages,就會遇到各種各樣的問題。所以,如果你打算在不同版本上使用這些軟件包,就需要尋找一個(gè)比較好的移植方法。而這篇文章正是介紹相應(yīng)方法的。在往下看之前,請確保你有能力安裝和配置相應(yīng)的文件(因?yàn)槲臋n中也會提供一些提示)。下面所有的示例都基于X86硬件平臺,如果你使用的是其他體系,那么可能示例代碼和相應(yīng)的技巧都需要改變。本文是在實(shí)際工程中所使用步驟的總結(jié)性描述,歡迎提出意見和指正。
背景
一個(gè)Linux程序通常都包含有執(zhí)行特定功能的機(jī)器代碼,而很多功能通常都由庫文件提供。程序員朋友都知道,庫文件又分為靜態(tài)版本和動態(tài)共享版本,當(dāng)一個(gè)程序被創(chuàng)建時(shí),開發(fā)者就會決定是使用動態(tài)庫還是靜態(tài)庫。在使用靜態(tài)庫的程序中,可能出現(xiàn)不同版本或者在靜態(tài)庫的基礎(chǔ)架構(gòu)上產(chǎn)生變化而引起的二進(jìn)制不兼容。由于C++的成熟但是未通用標(biāo)準(zhǔn)化,經(jīng)常會出現(xiàn)后者的情況。詳細(xì)點(diǎn)說,就是一些具體實(shí)現(xiàn)的細(xì)節(jié),比如,如果用于創(chuàng)建庫的編譯器版本不同,派生繼承格(derived inheritance lattices)中的虛擬函數(shù)進(jìn)行地址映射的機(jī)制,就會使相同庫擁有不同且互不兼容的版本。
共享庫倒是有很多好處,機(jī)器代碼只需要加載到內(nèi)存一次;使用這個(gè)庫的不同程序都在共享它,而且只有程序具體所需要的數(shù)據(jù)才會在內(nèi)存中分配空間;另外,系統(tǒng)管理員可以很輕松得升級共享庫,而無需接觸那些使用這個(gè)庫的程序,這一點(diǎn)對于安全特性來說特別重要。
說了半天,上面沒有一個(gè)特點(diǎn)是Linux特性,因?yàn)檫@些都是各種現(xiàn)代操作系統(tǒng)所使用或者支持庫的常規(guī)描述,所以就不廢話了,具體的細(xì)節(jié)建議Google。
劇本
終于進(jìn)入正題了。你遇沒遇到過這種問題,辛辛苦苦down下來一個(gè)新版本程序,正準(zhǔn)備在你新版本,最新推出的Linux系統(tǒng)上使用,卻發(fā)現(xiàn)不能運(yùn)行?恩,這種問題通常發(fā)生于libc4到libc5的轉(zhuǎn)換或者libc5到libc6的轉(zhuǎn)換,后者就是通常所說的glibc (the GNU C library)。
C的庫基本上是所有UNIX/Linux系統(tǒng)中最重要的庫文件,因?yàn)樗鼈儼缪萘怂袘?yīng)用程序和內(nèi)核之間的接口角色。如果這種核心庫文件被改變,而這種改變又不向后兼容,那么,就可能導(dǎo)致整個(gè)系統(tǒng)都無法使用。
當(dāng)然還有另外一種情況,一個(gè)程序已經(jīng)在一個(gè)使用了較新版本的庫的系統(tǒng)中被構(gòu)建,而你如今想將它安裝到你的當(dāng)前系統(tǒng)中,當(dāng)然,這種程序是不可能正常運(yùn)行的,原因就是程序很可能會使用到只有較新版本的庫文件中才會擁有的新的或者改動過的特性。正如所有人都知道的那樣,包含有舊版本庫所沒有的功能的新版本庫是不會被加載以及執(zhí)行的。下面以glibc舉例子:
GLIBC 版本 文件名稱
libc5 /lib/libc.so.5
libc6 /lib/libc.so.6
有時(shí)版本號還有附加后綴,比如libc.so.6.3.3.
正如在背景中所描述的那樣,由版本號大于3.3的GNU C++所編譯的C++庫的二進(jìn)制不兼容性,會導(dǎo)致額外的復(fù)雜度,就算是在當(dāng)前包括了一致的API(Application Programming Interface)和ABI(Application Binary Interface)定義的標(biāo)準(zhǔn)化環(huán)境中也同樣如此。而且,未來C++ compilers和庫的版本是否會保持完全兼容,也尚未得知。針對這個(gè)內(nèi)容,LSB(Linux Standards Base)推薦所有的軟件開發(fā)商用C++開發(fā)軟件的時(shí)候,C++部分都使用靜態(tài)鏈接,不幸的是沒幾個(gè)開發(fā)商是遵循了這個(gè)推薦的。更糟糕的是,Linux的發(fā)布廠商通常都會創(chuàng)建自己版本的C++編譯器,卻沒有讓它們的共享庫和標(biāo)準(zhǔn)C++編譯器的一致。
總的來說,如果你想運(yùn)行一個(gè)程序,卻失敗了,錯(cuò)誤信息和下面這條信息類似,那么這個(gè)問題可能能夠通過本文“解決方案”部分的建議來解決。
運(yùn)行程序時(shí)的錯(cuò)誤信息
./a.out: relocation error: ./a.out: symbol errno, version GLIBC_2.0 not defined in file libc.so.6 with link time reference
如果你恰好遇到像上面這樣的關(guān)于symbol errno的錯(cuò)誤信息,就說明你的程序被鏈接到了某個(gè)版本號低于2.3的glibc上。為了保證其線程安全的特性(每個(gè)線程盡量只訪問別的線程不訪問的變量或內(nèi)存,如果硬是要訪問同一變量或內(nèi)存的話,就要采用適當(dāng)?shù)幕コ鈾C(jī)制來避免由于線程切換而導(dǎo)致的不確定性),更新版本的glibc不再將errno作為全局變量來提供。在這種情況下,可能你不得不安裝擁有較老版本(比如2.2.5版本)的兼容環(huán)境。
但是,如果你并沒有看見上述信息,而程序還是沒有按照你所想象那樣的正常運(yùn)行,那你可能就是遇到了上述介紹的C++相關(guān)問題。我們知道,運(yùn)行時(shí)鏈接器(runtime linker)用于將你的程序加載到內(nèi)存中,并負(fù)責(zé)解決任何和共享庫相關(guān)的依賴,它還能夠在你的系統(tǒng)中定位所需要的共享庫,并將它們加載到內(nèi)存中,但是,這個(gè)共享庫不一定適合目前你希望運(yùn)行的程序。這就是問題所在。
解決方案
等你確定了問題的源頭后,就可以創(chuàng)建一個(gè)兼容環(huán)境,來提供正常執(zhí)行程序所需要的功能特性。在這里強(qiáng)烈推薦一種方法,就是在標(biāo)準(zhǔn)系統(tǒng)文件路徑,例如/lib和 /usr/lib外安裝附加的庫文件,如下所示 :
想移植的程序名 zoo
用于安裝兼容文件的目錄地址的前綴 /opt/compat-env/zoo
需要的庫 glibc-2.3.2-95.27
libgcc-3.2.3-42 libstdc++-3.2.3-42
所需要文件最初的源目錄 /root/zoo-src
第一步
在自行規(guī)定的目錄下創(chuàng)建路徑/bin和/lib:
% prefix=/opt/compat-env/zoo
% mkdir -p $prefix/bin $prefix/lib
第二步
將所需要的文件拷貝到相應(yīng)的位置:
% srcdir=/root/zoo-src
% cd $prefix/bin
% cp -p $srcdir/zoo .
% cd $prefix/lib
% rpm2cpio $srcdir/glibc-2.3.2-95.27*.rpm | cpio -idvm ‘*libc*.so*’
% find . -name ‘*libc*.so*’ -exec mv -v ‘{}’ . \;
% rpm2cpio $srcdir/glibc-2.3.2-95.27*.rpm | cpio -idvm ‘*ld*.so*’
% find . -name ‘*ld*.so*’ -exec mv -v ‘{}’ . \;
% rpm2cpio $srcdir/libgcc-3.2.3-42*.rpm | cpio -idvm ‘*libgcc_so*.so*’
% find . -name ‘*libgcc_so*.so*’ -exec mv -v ‘{}’ . \;
% rpm2cpio $srcdir/libstdc++-3.2.3-42*.rpm | cpio -idvm ‘*libstdc*.so*’
% find . -name ‘*libstdc*.so*’ -exec mv -v ‘{}’ . \;
% rm -rf lib usr
第三步
創(chuàng)建一個(gè)腳本,用于建立合適的環(huán)境變量和啟動程序:
% cd $prefix/bin
% mv zoo zoo.exec
% cat > zoo libcwait.c zoo