構建交叉編譯工具鏈
部分摘自《Building Embedded Linux Systems
》作者: Karim Yaghmour
劉建文略譯並整理(http://blog.csdn.net/keminlau
)
KEY: 交叉編譯 嵌入式 Linux C庫 glibc
Buildroot自動構建交叉編譯工具鏈
在過去很長的一段時間裡,構建一套交叉編譯工具鏈對於嵌入式開發人員來說簡直是一場惡夢,因為他們得手動跟蹤各種源碼包(及其更新包)之間的依賴關係。buildroot,和有名的微型C庫——uclibc一起發布的小工具改變這一事實。
為啥複雜
Configuring and building an appropriate GNU toolchain is a complex and delicate operation that requires a good understanding of the dependencies between the different software packages and their respective roles. This knowledge is required, because the GNU toolchain components are developed and released independently from one another.
Buildroot是像Linux核心構建系統
類似的基於GNU make的軟體構建系統。不過,Buildroot只包含構建所需的Makefiles和一些patches,沒有待構建軟體的源碼,源碼必須從網上動態下載。Buildroot主要是就用來構建[使用uClibc的交叉編譯工具鏈
]和根檔案系統。
什麼叫使用uClibc的交叉編譯工具鏈?
首先要理解什麼是編譯工具鏈。編譯工具鏈可簡單理解為編譯工具集,包括編譯器、彙編器、連結器和C標準庫。編譯器負責將原始碼轉換為二進位機器碼(或彙編代碼),像gcc;彙編器和連結器等則負責【可執行檔】的構建,像binutils,中文為二進位工具集;C標準庫是通用的機器碼庫,供連結器用,像 glibc。 從參與編譯構建任務的角色看,前三者是【器具】,有操作的;最後者C庫是【資料材料】。
接著理解什麼是交叉編譯。交叉的前提主機(HOST)與目標機(TARGET)使用不同的CPU體系,編譯在主機上進行,產生目標機的機器碼 。交叉編譯工具鏈與本地編譯工具鏈的區別,第一,交叉編譯工具鏈的【編譯器具】具有產生目標機機器碼的功能;第二,交叉編譯工具鏈的【C庫】是目標機機器碼庫。
最後理解何為【使用uClibc的交叉編譯工具鏈】變得很顯然了。
此步引申出幾個問題,第一,【編譯器具】具有交叉編譯性質需要做些什嗎?第二,目標機機器碼C庫如何產生?第三,uClibc與glibc的不同體現在哪裡?
使用Buildroot
- 建立你自己的載板支援(board support)
- 離線構建
- 源碼樹外(out of tree)構建
- 環境變數
- 目標機的根檔案系統的定製
- Busybox定製
- uClibc定製
- 建立基於單一buildroot源碼樹的多重專案
- 於buildroot外使用uClicb工具鏈
- 源碼包的下載位置
- 使用外部工具鏈
- 擴充buildroot(工具軟體部分)
使用詳細請查看buildroot文檔
。
手動構建交叉編譯工具鏈
既然有了buildroot,還有必要學習如何構建工具鏈嗎?答案當然有必要。我們是不建議重複發明輪子的,但如果我們不自己發明一次,我們永遠不知道輪子是怎麼來了。從職業的角度看,應該說不建議對外行的事物重新發明。
[嵌入式Linux開發
]的第一步是構建(能編譯運行在目標機平台的核心和應用程式的)交叉編譯工具鏈,而[嵌入式Linux開發]第一步的第一步,是已經有一個產生這個交叉編譯工具鏈的[本地工具鏈
](native toolchain);第二步,是組織項目的工作目錄。
本地工具鏈一般隨發行版安裝,如果沒有選擇安裝,或者不小心損壞,可以從發行版的CD或網上取得安裝包進行重要安裝即可。
工作目錄
在為你的嵌入式系統開發或定製各種軟體的工作過程中,你需要用到很多的源碼包和工具集。為這些源碼包、工具集和其它[項目相關組件]定立一個清晰易理解的目錄結構——項目工作空間(project workspace)是工作的第一步。下表是一個示範,你可以按需求適當調整,調整以直觀為原則。另外,把你寫的代碼與從網上下載的源碼分隔開,這樣既可以減少原始碼歸屬的迷惑,又可劃清授權問題。
Table 4-1. Suggested project directory layout
| Directory |
Content |
| bootldr |
存放為嵌入式系統構建的不同版本的bootloader |
| build-tools |
存放構建跨平台開發工具鏈的軟體包 |
| debug |
調試工具及相關軟體包 |
| doc |
嵌入式系統項目文檔 |
| images |
存放待用的bootloader鏡像、核心鏡像和根檔案系統鏡像 |
| kernel |
存放為嵌入式系統構建的不同版本的核心 |
| project |
你編寫的項目代碼 |
| rootfs |
存放嵌入式系統核心運行時的根檔案系統 |
| sysapps |
存放為嵌入式系統構建的不同版本的系統工具 |
| tmp |
臨時檔案 |
| tools |
完整的跨平台開發工具鏈和C庫 |
以上的目錄均隸屬於你的project workspace內的子目錄(當然以上目錄還會有子目錄),而project workspace的位置隨你自己定。不過我建議你不要使用全域性質(system-wide )的目錄,像/usr或/usr/local。可以使用/home或/home內的目錄,這些目錄可共用給使用者組內所有使用者。如果一定要用全域目錄,你可以用/opt目錄。
以下是~/control-project目錄情況:
$ ls -l ~/control-project
total 4
drwxr-xr-x 13 karim karim 1024 Mar 28 22:38 control-module
drwxr-xr-x 13 karim karim 1024 Mar 28 22:38 daq-module
drwxr-xr-x 13 karim karim 1024 Mar 28 22:38 sysmgnt-module
drwxr-xr-x 13 karim karim 1024 Mar 28 22:38 user-interface
以上的情況特別一點,有四個嵌入式項目,每個項目都有一個工作目錄,而每個工作目錄都有上表的功能不同的子目錄,比如:
$ ls -l ~/control-project/daq-module
total 11
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 bootldr
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 build-tools
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 debug
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 doc
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 images
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 kernel
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 project
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 rootfs
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 sysapps
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 tmp
drwxr-xr-x 2 karim karim 1024 Mar 28 22:38 tools
由於一些構建工具需要路徑資訊,你可以編寫一小段的指令碼完成這個任務,比如以下是daq-module項目的指令碼develdaq :
export PROJECT=daq-module
export PRJROOT=/home/karim/control-project/${PROJECT}
cd $PRJROOT
執行它:$ . develdaq
構建工具鏈步驟
- 確定目標(Target)的名字
- 確定可用的Kernel/GCC/Glibc/Binutils版本號碼組合
- 確定工具目錄
- 準備核心標頭檔
- 編譯binutils
- 編譯最小的自舉式(Bootstrap)GCC
- 編譯Glibc
- 編譯全功能的GCC
確定目標(Target)的名字
target不是工具鏈的名字,而是工具鏈的性質,類型標識,決定工具鏈輸出何種平台機器碼。target的定義與本地HOST無關,只與目標機TARGET相關,由目標機的硬體平台(處理器體系)與軟體平台(作業系統)組合定義。
以下是四常見的target:
- arm-linux: Support for ARM processors such as armV4, armv5t, and so on.
- mips-linux: Support for various MIPS core such as r3000, r4000, and so on.
- ppc-linux: Linux/PowerPC combination with support for various PPC chips.
- m68k-linux: This targets Linux running on the Motorola 68k processor.
完整的列表在: http://www.gnu.org
確定可用的Kernel/GCC/Glibc/Binutils版本號碼組合
這是構建工具鏈最麻煩的一步。安裝文檔、郵件清單是擷取資訊的地方。以下是對arm-linux可用的已知版本號碼組合:
- ARM/Kernel 2.6/GCC 2.95.1,GCC 3.3/BINUTILS 2.10.x or
- ARM/Kernel 2.4/GCC 2.95.1/BINUTILS 2.9.5
確定是否有更新包可用
確定好版本號碼後,確保該版本的Kernel/GCC/Glibc/Binutils是否有相應的更新包。
確定工具目錄
如前我們定義的工作目錄布局所得,工具鏈會在${PRJROOT}/build-tools 內編譯構建,安裝到{PRJROOT}/tools。為了方便我們編譯,我們定義一些環境變數,並將其輸出。以下是指令碼develdaq 的內容:
export PROJECT=projectXX
export PRJROOT=/home/nakeman/${PROJECT}
export TARGET=i386-linux
export PREFIX=${PRJROOT}/tools
export TARGET_PREFIX=${PREFIX}/${TARGET}
export PATH=${PREFIX}/bin:${PATH}
cd $PRJROOT
TARGET:目標名字
PREFIX: prefix是首碼,語義不足,指路徑首碼,而且這裡預設指[工具鏈的安裝目錄] 的路徑首碼。比如,本地工具鏈的prefix一般都是/usr,意思是說,你可以在BINDIR=$PREFIX/bin找到gcc,在 INCLUDEDIR=$PREFIX/include找到標頭檔。為了避免與本地工具鏈混淆,交叉工具鏈使用非/usr作為prefix。
安裝工具鏈到/usr/local的一個問題
Some people prefer to set PREFIX to /usr/local. This results in the tools and libraries being installed within the /usr/local directory where they can be accessed by any user. I find this approach not to be useful for most situations, however, because even projects using the same target architecture may require different toolchain configurations.
為整個Team Dev建置工具鏈
If you need to set up a toolchain for an entire development team, instead of sharing tools and libraries via the /usr/local directory, I suggest that a developer build the toolchain within an entry shared by all project members in the /home directory, as I said earlier. In a case in which no entry in the /home directory is shared among group members, a developer may build the toolchain within an entry in her workstation's /opt directory and then share her resulting ${PRJROOT}/tools directory with her colleagues. This may be done using any of the traditional sharing mechanisms available, such as NFS, or using a tar-gzipped archive available on an FTP server. Each developer using the package will have to place it in a filesystem hierarchy identical to the one used to build the toolchain for the tools to operate adequately. In a case in which the toolchain was built within the /opt directory, this means placing the toolchain in the /opt directory.
環境變數也能定義在.bashrc檔案中,這樣當你logout或換了控制台時,就不用老是export這些變數了。
有了以上的準備,現在可以開始構建工具鏈了。
編譯 Binutils
1. 下載源碼包(ftp://ftp.gnu.org/gnu/binutils/)並解包:
$ cd $PRJROOT/build-tools/src
$ tar -xzf binutils-2.10.1.tar.gz
另:如果有更新包,最好更新一下。
2. Configure :
$./configure --target=$TARGET --prefix=$PREFIX
配置指令碼一般會檢查編譯環境,比如主機的一些資源,然後根據options(比如這裡的目標target和安裝位置prefix) 產生編譯所需要makefile。
3. 編譯:
$ make
4. 安裝:
$ make install
binutils的組成及其使用
| Utility |
Use |
| as |
gnu 的彙編器 |
| ld |
The GNU linker |
| gasp |
gnu 彙編器前置處理器 |
| ar |
產生、修改和解開一個archive 檔案 |
| nm |
列出目標檔案的符號和對應的地址 |
| objcopy |
將某種格式的目標檔案轉化成另外格式的目標檔案 |
| objdump |
顯示目標檔案內容的資訊 |
| ranlib |
為一個archive檔案產生索引,並將這個索引存入archive檔案中 |
| readelf |
顯示 elf 格式的目標檔案的資訊 |
| size |
列出目標檔案各個節的大小和目標檔案的大小 |
| strings |
列印出目標檔案中能列印的字串,有個預設的長度,為4 |
| strip |
剝掉目標檔案的所有的符號資訊 |
| c++filt |
C++ 和 java 中有一種重載函數,所用的重載函數最後會被編譯轉化成彙編的標號,c++filt 就是實現這種反向的轉化,根據標號得到函數名 |
| addr2line |
將你要找的地址轉成檔案和行號,他要使用 debug 資訊 |
準備核心標頭檔
編譯GCC的第一步是準備好核心標頭檔(KEMIN:聽起來怪怪的,用GCC編譯GCC,編譯器本身也一支程式,關鍵是理解編譯一個編譯需要些什麼),交叉編譯一工具鏈需要核心標頭檔。
1. 下載並解包到${PRJROOT}/kernel;
2. 如果有更新包,更新它;
3. 更改核心的ARCH屬性,通過更改源碼樹頂層的makefie裡的ARCH變數;
4. 配置核心:
make menuconfig
配置兩個選項: System and processor type, and select a system consistent with the tools you’re building.
設定完退出並儲存,檢查一下的核心目錄中的 include/linux/version.h 和 include/linux/autoconf.h 檔案是不是產生了,這是編譯 glibc是要用到的,version.h 和 autoconf.h 檔案的存在,也說明了你產生了正確的標頭檔。
5. 拷貝
$ mkdir -p ${TARGET_PREFIX}/include
$ cp -r include/linux/ ${TARGET_PREFIX}/include
$ cp -r include/asm-i386/ ${TARGET_PREFIX}/include/asm
$ cp -r include/asm-generic/ ${TARGET_PREFIX}/include
注意,除非你的工具鏈更改了System OR processor type,否則核心配置是一次性的。
編譯最小的自舉式(Bootstrap)GCC
我們先編譯一個最小的交叉C編譯器,然後用它編譯目標平台的glibc,最後編譯全功能的GCC。這個過程前者的編譯條件依賴後者,比如編譯C++編譯器需要glibc,有點像電腦自舉啟動。
1. 下載並解包,更新包如有必要:
$ cd ${PRJROOT}/build-tools
$ tar xvzf gcc-2.95.3.tar.gz
2. Con?gure:
$ cd build-boot-gcc
$ ../gcc-2.95.3/configure --target=$TARGET --prefix=${PREFIX} /
> --without-headers --with-newlib --enable-languages=c
配置選項除了target和prefix外,有三個與編譯binuilts不同的選項:
- --without-headers:因為這是一個交叉編譯器,而目標機的系統庫還沒編譯,glibc還沒有編譯;
- --with-newlib:這個選項只指為了讓編譯順利通過,暫時不指定任何庫,因為glibc還沒有編譯;
- --enable-languages=c :這個選項指定最小的GCC使用C語言。
3. Build:
$ make
4. Install:
$ make install
編譯Glibc
瞭解glibc的人應該知道,glibc遠不只是一個簡單的通用語言程式碼程式庫,它一個現代作業系統程式碼程式庫;它除了實現標準C庫(其中的libc)外,還封裝了作業系統功能介面(POSIX標準),網路介面和線程操作介面等;絕大部分的應用程式都是通過glibc(連結glibc代碼)使用作業系統功能的。另外,要區別glibc與核心的C庫,核心開發是不用glibc的,核心內有一個小的C庫實現。
1.下載(ftp.gnu.org/gnu/glibc)並解包:
$ cd ${PRJROOT}/build-tools
$ tar xvzf glibc-2.2.3.tar.gz
由於種種原因,glibc被分割成以glibc核心與add-ons的形式發布。在我們配置glibc時,如果開啟add-ons,那麼這個add-ons必需已經就緒(下載並解包在適當位置)。對嵌入式系統一般需要Linux threads add-on。
$ tar -xvzf glibc-linuxthreads-2.2.3.tar.gz --directory=glibc-2.2.3
2. Con?gure:
$ cd build-glibc
$ CC=$TARGET-linux-gcc ../glibc-2.2.3/configure --host=$TARGET /
> --prefix="/usr" --enable-add-ons=linuxthreads /
> --with-headers=${TARGET_PREFIX}/include
- CC:與之前配置不同,編譯glibc前修改了CC變數,這是因為這個glibc是編譯給目標機用的,所以要用剛編譯好的$TARGET-linux-gcc進行交叉編譯。
- --host:配置項使用--host而不是--target也說明了,這個庫是運行在目標機,而不是本機。
- --prefix:同樣這個也是glibc在目標機的安裝位置,而不是本機,本地不安裝這個交叉編譯出來的程式碼程式庫。這個值用來提供給編譯器把glibc的安裝位置寫入程式碼入 glibc,然後在原生編譯安裝時我們修改安裝位置(請看下面安裝一步),安裝入目標機只需檔案拷貝產生的檔案。
- --enable-add-ons:詳細請查看配置文檔;
- --with-headers:glibc對核心標頭檔是有依賴的,如果這個選項不提供,那編譯會先用/usr/include ,本機核心的標頭檔,這是與編譯目標機的glibc是不符的。
一些其它可能用到配置的選項:
- --disable-profile/--disable-shared :預設的編譯會產生三組庫檔案:共用庫、靜態庫和分析版(profiling information)的靜態庫。如果你的目標機沒有很多的應用程式,可以選擇只產生靜態庫。
- --enable-static-nss:略;
- --without-fp:略。
3. Build:
$ make
注意,由於glibc比較龐大,編譯冗長耗時,如果編譯過程發生錯誤,最先用make clean 清理掉中間結果再make。
4. Install:
$ make install_root=${TARGET_PREFIX} prefix="" install
如前所說,這個glibc這是安裝給原生應用程式執行用的,所安裝位置特定,注意變數prefix先清空。
5. 最後一步是修改 libc.so 檔案
將
GROUP ( /lib/libc.so.6 /lib/libc_nonshared.a)
改為
GROUP ( libc.so.6 libc_nonshared.a)
這樣連接器 ld 就會在 libc.so 所在的目錄尋找他需要的庫,因為你的機子的/lib目錄可能已裝了一個相同名字的庫,這個是為編譯本地應用程式的庫,而不是用於交叉編譯的庫。
編譯全功能的GCC
在建立boot-gcc 的時候,我們只支援了C。到這裡,我們就要建立全套編譯器,來支援C和C++。
$cd $PRJROOT/build-tools/build-gcc
$../gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX --enable-languages=c,c++
$make all
$make install
參考
- 怎麼為linux嵌入式研發建立交叉編譯環境(2.4核心):http://www.sudu.cn/info/html/edu/20070101/294486.html
- original:http://blog.csdn.net/keminlau/archive/2009/12/05/4945951.aspx
- ARM 體繫結構的《The GNU Toolchain for ARM Target HOWTO》
- PowerPC 體繫結構的《Linux for PowerPC Embedded Systems HOWTO》
- The Scott Howard CrossGCC FAQ is available at http://www.sthoward.com/CrossGCC/.
- The Bill Gatliff CrossGCC FAQ is available at http://crossgcc.billgatliff.com/.
- crosgcc mailing list hosted by Red Hat at http://sources.redhat.com/ml/crossgcc/.