蔣立兵,申 秋,韓隆隆
(中國電子科技集團公司第三十研究所,四川 成都 610041)
在某些特殊網(wǎng)絡(luò)內(nèi)進行內(nèi)網(wǎng)漫游的時候,能夠觸及甚至登錄大量的設(shè)備或板卡,這些板卡通常運行的是嵌入式Linux系統(tǒng),能夠通過telnet或ssh方式登錄。當(dāng)需要在這些設(shè)備上運行一些常用的工具時,會面臨一些比較棘手的問題,大致可分為如下幾類。
(1)目標(biāo)CPU架構(gòu)為ARM、PowerPC、MIPS等嵌入式平臺時,x86架構(gòu)下編譯的程序不能在目標(biāo)上運行,而代碼在目標(biāo)平臺也不能進行編譯。
(2)目標(biāo)機器采用uClibc時,通過常規(guī)ARM、MIPS等交叉編譯工具編譯出的程序,在目標(biāo)機器上依舊不能運行。
(3)目標(biāo)機器的內(nèi)核版本很低,高版本內(nèi)核下編譯的程序不能在目標(biāo)程序上運行。
本文從交叉編譯工具生成、交叉編譯和跨版本運行等方面進行闡述,能夠解決大部分上述問題。
交叉編譯是在嵌入式領(lǐng)域常用的一種編譯方式,通過選擇不同的交叉編譯工具鏈,能夠在當(dāng)前平臺下,編譯出可運行于不同體系架構(gòu)平臺的程序[1]。例如在x86平臺電腦上,使用Android Studio進行Android JNI開發(fā)時,會調(diào)用ARM的交叉編譯工具鏈進行編譯,編譯好的軟件能夠在Android手機上運行。
使用交叉編譯的主要原因如下:
(1)目標(biāo)平臺一般為低功耗設(shè)計,性能較低,運行速度較慢;
(2)編譯過程需要消耗大量資源,嵌入式系統(tǒng)往往沒有足夠的內(nèi)存和硬盤;
(3)完整的編譯環(huán)境需要很多支持包,很多嵌入式系統(tǒng)是精簡版Linux,難以滿足需求。
Linux編譯過程包括了預(yù)處理、編譯、匯編、鏈接等過程[2-3],如圖1所示。交叉編譯工具鏈(Toolchain)就是為了編譯跨平臺體系結(jié)構(gòu)的程序代碼而形成的一套完整工具集。它隱藏了預(yù)處理、編譯、匯編、鏈接等細(xì)節(jié)。當(dāng)指定了源文件(.c)和編譯工具(GCC)時,它會按照編譯流程調(diào)用不同的子工具,生成最終的可執(zhí)行文件(ELF)。
嵌入式目標(biāo)平臺類型眾多,包括ARM、MIPS、MIPS64、MIPS64el、PowerPC、PowerPC64 等。常用平臺的交叉編譯工具可以通過網(wǎng)絡(luò)下載,如ARM、MIPS等。但是當(dāng)目標(biāo)平臺是使用的libc版本不匹配時或目標(biāo)內(nèi)核版本過低時,都可能導(dǎo)致編譯出的程序不能正常運行,因此需要能夠自行編譯所需要平臺交叉編譯工具鏈。目前構(gòu)建交叉編譯工具鏈可通過crosstool-NG、Buildroot等工具進行制作。對比了crosstool-NG和Buildroot兩款工具,其中crosstool-NG能夠支持更老的內(nèi)核和libc版本,對于內(nèi)網(wǎng)漫游過程中遇到的目標(biāo)機器適應(yīng)性更強,作為使用首選。Buildroot與crosstool-NG的使用類似,可以根據(jù)實際情況靈活選擇。本文使用crosstool-NG作為示例。
1.3.1 crosstool-NG獲取
目前,crosstool-NG的最新版為1.24,本文示例選用1.10[4]版本,可以支持較老的內(nèi)核和libc版本。
想要編譯老版本內(nèi)核的工具鏈,編譯環(huán)境的選擇也很重要,應(yīng)盡量選用低版本操作系統(tǒng),同時有相應(yīng)的更新源支持,本文示例選用的操作系統(tǒng)為Ubuntu 12.04。
crosstool-NG源碼下載后需要進行編譯,編譯時需要安裝一些Linux組件,可以提前執(zhí)行:
sudo apt-get install libncurses5-dev bison flex texinfo automake libtool patch gcj cvs cvsd gawk
然后按照常規(guī)的Linux編譯方法,可以編譯得到可執(zhí)行文件ct-ng,如下所示。
root@ubuntu:~# ct-ng -v
GNU Make 3.81
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHAN TABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
1.3.2 crosstool-NG使用
crosstool-NG內(nèi)部集成了很多默認(rèn)配置,執(zhí)行ct-ng list-samples命令,可以查看支持的默認(rèn)配置。如果配置剛好符合需求,可以直接使用,否則需要進行定制化修改。默認(rèn)部分配置如下:
選擇和配置參數(shù)需要根據(jù)目標(biāo)CPU架構(gòu)、系統(tǒng)、libc等信息進行判斷。
CPU架構(gòu)可以通過cat /proc/cpuinfo指令查看,根據(jù)CPU型號,判斷CPU的架構(gòu)類型,如ARM、MIPS64、MIPS64(el為小端模式)等,部分CPU信息如下:
系統(tǒng)信息可通過uname-a指令查看。根據(jù)目標(biāo)系統(tǒng)的系統(tǒng)版本信息,選擇相近的內(nèi)核版本。系統(tǒng)信息如下:
$ uname -a
Linux localhost 4.19.81-perf-gb5c27fd #1 SMP PREEMPT Thu Aug 6 04:10:30 CST 2020 aarch64 Android
目標(biāo)平臺使用的是glibc還是uClibc,通過在目標(biāo)平臺查看/lib目錄下的libc庫文件進行判斷,如果為libc.so.6,則使用glibc;如果為libuClibc-0.9.xx.xx.so,則使用uClibc。配置ct-ng時需要選用與目標(biāo)系統(tǒng)uClibc版本相同的版本,圖2中的uClibc版本為0.9.33.2。
本文通過PowerPC平臺,展示參數(shù)配置過程。
(1)執(zhí)行ct-ng powerpc-unknown-linux-gnu,配置成默認(rèn)配置。
(2)執(zhí)行ct-ng menuconfig進入配置界面,如圖3所示。
(3)進入Target options頁面,配置CPU參數(shù),主要參數(shù)為Target Architecture(CPU架構(gòu))、Bitness(32位或64位)、Endianness(大小端),其他參數(shù)根據(jù)CPU單獨進行配置,如圖4所示。
(4)進入Option System頁面,配置系統(tǒng)參數(shù),主要參數(shù)為Linux Kernel Version,如圖5所示。
(5)進入C Compiler頁面,配置編譯器選項,主要參數(shù)為gcc version,根據(jù)目標(biāo)系統(tǒng)選擇合適的GCC版本,如果需要編譯C++代碼,需要勾選C++選項,如圖6所示。
(6)進入C library頁面,配置libc選項,主要參數(shù)為C library(選擇glibc或uClibc),*libc version(選擇glibc或uClibc的版本),如圖7所示。
配置完成后,可以執(zhí)行ct-ng build,進行編譯。編譯過程中,需要下載很多源碼包,但是由于選擇的crosstool-NG版本太老,默認(rèn)下載鏈接已經(jīng)失效,需要手動下載。開始編譯后,會在當(dāng)前目錄生成target文件夾,進入./target/tarballs/目錄下,查看有tmp-dl結(jié)尾的文件,下載相應(yīng)的包,然后拷貝到tarballs目錄。大部分的包都可以在清華開源軟件鏡像站[5]進行下載。
完成所有的包下載后,開始進行編譯,編譯過程時間較長,編譯過程中也可能出現(xiàn)各種各樣的錯誤,一般是由于缺少庫,或者配置錯誤,或者是操作系統(tǒng)和GCC版本不匹配等問題,可以通過搜索具體問題進行解決。由于編譯過程的自由度頗高,可能出現(xiàn)的問題也較多,具體如何解決問題不在本文贅述。
編譯完成后會在/home/(user)/x-tools/目錄下生成相應(yīng)的工具鏈,其中/bin目錄下包含了gcc、g++、as等交叉編譯工具,如圖8所示。
1.3.3 交叉編譯工具鏈?zhǔn)褂?/p>
將生成的交叉編譯工具鏈所在的bin文件目錄加入系統(tǒng)環(huán)境變量,可以直接調(diào)用相應(yīng)的編譯工具進行編譯,也可在使用Makefile時直接使用。如未加入環(huán)境變量,則需要指定絕對路徑,例如,/home/test/x-tools/powerpc-unknown-linux-gnu/bin/powerpc-unknown-linux-gnu-gcc。
編譯完成ELF文件可以通過file指令查看文件的基本信息,同時可以將工具上傳到目標(biāo)機器上進行使用驗證,如果仍然不能正常運行,則需要檢查libc版本是否正確、Linux內(nèi)核是否支持等問題,調(diào)整參數(shù)后重新編譯工具鏈。
在內(nèi)網(wǎng)漫游過程中,還存在一種較為特殊的情況。目標(biāo)系統(tǒng)是x86或x64平臺,但系統(tǒng)內(nèi)核比較老,libc庫所支持的版本也比較老。在較高版本編譯環(huán)境下編譯出的ELF文件,在目標(biāo)平臺運行是,可能出現(xiàn)如圖9所示類似的錯誤。
圖9中出錯的原因在于,源碼中使用了高版本libc包含的庫函數(shù),而目標(biāo)平臺的libc版本較低,不支持這些庫函數(shù),從而導(dǎo)致沒有辦法正常運行。解決方案有兩種,一是修改程序源碼,找到使用高版本庫函數(shù)的地方,就行修改和替換;二是將高版本的libc庫文件上傳到目標(biāo),通過修改可執(zhí)行文件的lib庫依賴路徑,使得能夠在目標(biāo)平臺上正常運行。修改源碼的方式對較為簡單的代碼比較實用,但對于較為復(fù)雜的程序需要選用第二種方式,下面介紹如何實現(xiàn)。
首先需要安裝patchelf工具,Ubuntu可以通過apt-get install patchelf指令進行安裝。
patchelf 是一個用來修改ELF程序的小工具,可以修改動態(tài)鏈接庫的默認(rèn)搜索路徑rpath。
在編譯平臺上,建立新文件夾mylib,然后使用ldd指令查看編譯好的程序所需的庫文件,然后將所有依賴的庫文件拷貝到mylib文件夾,如下:
root@m(xù)yuser: ldd dropbear
linux-vdso.so.1 => (0x00007fffd25d2000)
libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1(0x00007fd429d6b000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1(0x00007fd429b51000)
libcrypt.so.1 => /lib/x86_64-linux-gnu/libcrypt.so.1 (0x00007fd429919000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6(0x00007fd42954f000)
./mylib/ld-2.15.so => /lib64/ld-linux-x86-64.so.2(0x00007fd42a1af000)
通過patchelf修改編譯好的ELF文件的rpath,可以將文件的默認(rèn)鏈接庫文件地址指向mylib文件夾。首先執(zhí)行patchelf --set-rpath ./mylib dropbear,同時需要修改指定與libc配套的ld.so文件,執(zhí)行patchelf --set-interpreter ./mylib/ld-2.15.so。執(zhí)行成功后,其次通過ldd指令查看動態(tài)庫鏈接情況,程序所需的庫已指向當(dāng)前目錄下的mylib文件夾,如下:
root@m(xù)yuser: ldd dropbear
linux-vdso.so.1 => (0x00007ffcd7bef000)
libutil.so.1 => ./mylib/libutil.so.1 (0x00007 ff046340000)
libz.so.1 => ./mylib/libz.so.1 (0x00007ff046129000)
libcrypt.so.1 => ./mylib/libcrypt.so.1 (0x00007 ff045ef0000)
libc.so.6 => ./mylib/libc.so.6 (0x00007 ff045b30000)
./mylib/ld-2.15.so => /lib64/ld-linux-x86-64.so.2(0x00007ff046784000)
將編譯好的ELF文件以及mylib文件夾上傳到目標(biāo)平臺上,放在同一目錄下,即可成功運行。需要注意的是,ELF文件有最低內(nèi)核版本的限制,目標(biāo)平臺的內(nèi)核版本過低的話,也會導(dǎo)致運行不成功。
本文針對在特殊網(wǎng)絡(luò)內(nèi)進行內(nèi)網(wǎng)漫游時,對多種平臺的板卡、終端設(shè)備空有權(quán)限而無法運行自己的常用工具的情況,提供了兩種解決方案:針對較老的內(nèi)核和較為少用的CPU平臺,使用crosstool-NG工具,制作相應(yīng)的交叉編譯工具鏈;針對內(nèi)核版本過低的x86-64平臺,使用修改動態(tài)鏈接庫的方式,讓編譯好的ELF文件連接高版本的lib庫,從而能夠正常運行。