導讀:
未顯示需要 JavaScript 的文檔選項
級别: 初級
2005 年 9 月 01 日
在進行嵌入式開發之前,首先要建立一個交叉編譯環境,這是一套編譯器、連接配接器和libc庫等組成的開發環境。文章通過一個具體的例子說明了這些嵌入式交叉編譯開發工具的制作過程。
随着消費類電子産品的大量開發和應用和Linux作業系統的不斷健壯和強大,嵌入式系統越來越多的進入人們的生活之中,應用範圍越來越廣。
在裁減和定制Linux,運用于你的嵌入式系統之前,由于一般嵌入式開發系統存儲大小有限,通常你都要在你的強大的pc機上建立一個用于目标機的交叉編譯環境。這是一個由編譯器、連接配接器和解釋器組成的綜合開發環境。交叉編譯工具主要由binutils、gcc 和 glibc 幾個部分組成。有時出于減小 libc庫大小的考慮,你也可以用别的 c 庫來代替 glibc,例如uClibc、dietlibc 和newlib。建立一個交叉編譯工具鍊是一個相當複雜的過程,如果你不想自己經曆複雜的編譯過程,網上有一些編譯好的可用的交叉編譯工具鍊可以下載下傳。
下面我們将以建立針對arm的交叉編譯開發環境為例來解說整個過程,其他的體系結構與這個相類似,隻要作一些對應的改動。我的開發環境是,主控端i386-redhat-7.2,目标機 arm。
這個過程如下
1. 下載下傳源檔案、更新檔和建立編譯的目錄
2. 建立核心頭檔案
3. 建立二進制工具(binutils)
4. 建立初始編譯器(bootstrap gcc)
5. 建立c庫(glibc)
6. 建立全套編譯器(full gcc)
下載下傳源檔案、更新檔和建立編譯的目錄
1. 標明軟體版本号
選擇軟體版本号時,先看看glibc源代碼中的INSTALL檔案。那裡列舉了該版本的glibc編譯時所需的binutils和gcc的版本号。例如在 glibc-2.2.3/INSTALL 檔案中推薦 gcc 用2.95以上,binutils 用 2.10.1 以上版本。
我選的各個軟體的版本是:
linux-2.4.21+rmk2
binutils-2.10.1
gcc-2.95.3
glibc-2.2.3
glibc-linuxthreads-2.2.3
如果你選的glibc的版本号低于2.2,你還要下載下傳一個叫glibc-crypt的檔案,例如glibc-crypt-2.1.tar.gz。Linux 核心你可以從www.kernel.org 或它的鏡像下載下傳。
Binutils、gcc和glibc你可以從FSF的FTP站點ftp://ftp.gun.org/gnu/或它的鏡像去下載下傳。在編譯glibc時,要用到 Linux 核心中的 include目錄的核心頭檔案。如果你發現有變量沒有定義而導緻編譯失敗,你就改變你的核心版本号。例如我開始用linux-2.4.25+vrs2,編譯glibc-2.2.3時報 BUS_ISA 沒定義,後來發現在 2.4.23 開始它的名字被改為CTL_BUS_ISA。如果你沒有完全的把握保證你改的核心改完全了,就不要動核心,而是把你的Linux 核心的版本号降低或升高,來适應 glibc。
Gcc 的版本号,推薦用 gcc-2.95以上的。太老的版本編譯可能會出問題。Gcc-2.95.3是一個比較穩定的版本,也是核心開發人員推薦用的一個 gcc 版本。
如果你發現無法編譯過去,有可能是你選用的軟體中有的加入了一些新的特性而其他所選軟體不支援的原因,就相應降低該軟體的版本号。例如我開始用gcc-3.3.2,發現編譯不過,報 as、ld 等版本太老,我就把 gcc 降為2.95.3。太新的版本大多沒經過大量的測試,建議不要選用。
2. 建立工作目錄
首先,我們建立幾個用來工作的目錄:
在你的使用者目錄,我用的是使用者liang,是以使用者目錄為/home/liang,先建立一個項目目錄embedded。
$pwd
/home/liang
$mkdir embedded
再在這個項目目錄 embedded 下建立三個目錄 build-tools、kernel 和tools。
build-tools-用來存放你下載下傳的 binutils、gcc 和 glibc的源代碼和用來編譯這些源代碼的目錄。
kernel-用來存放你的核心源代碼和核心更新檔。
tools-用來存放編譯好的交叉編譯工具和庫檔案。
$cd embedded
$mkdir build-tools kernel tools
執行完後目錄結構如下:
$ls embedded
build-tools kernel tools
3. 輸出和環境變量
我們輸出如下的環境變量友善我們編譯。
$export PRJROOT=/home/liang/embedded
$export TARGET=arm-linux
$export PREFIX=$PRJROOT/tools
$export TARGET_PREFIX=$PREFIX/$TARGET
$export PATH=$PREFIX/bin:$PATH
如果你不慣用環境變量的,你可以直接用絕對或相對路徑。我如果不用環境變量,一般都用絕對路徑,相對路徑有時會失敗。環境變量也可以定義在.bashrc檔案中,這樣當你logout或換了控制台時,就不用老是export這些變量了。
體系結構和你的TAEGET變量的對應如下表
你可以在通過glibc下的config.sub腳本來知道,你的TARGET變量是否被支援,例如:
$./config.sub arm-linux
arm-unknown-linux-gnu
在我的環境中,config.sub 在 glibc-2.2.3/scripts 目錄下。
網上還有一些 HOWTO 可以參考,ARM 體系結構的《The GNU Toolchainfor ARM Target HOWTO》,PowerPC 體系結構的《Linux for PowerPCEmbedded Systems HOWTO》等。對TARGET的選取可能有幫助。
4. 建立編譯目錄
為了把源碼和編譯時生成的檔案分開,一般的編譯工作不在的源碼目錄中,要另建一個目錄來專門用于編譯。用以下的指令來建立編譯你下載下傳的binutils、gcc和glibc的源代碼的目錄。
$cd $PRJROOT/build-tools
$mkdir build-binutils build-boot-gcc build-gcc build-glibcgcc-patch
build-binutils-編譯binutils的目錄
build-boot-gcc-編譯gcc 啟動部分的目錄
build-glibc-編譯glibc的目錄
build-gcc-編譯gcc 全部的目錄
gcc-patch-放gcc的更新檔的目錄
gcc-2.95.3 的更新檔有gcc-2.95.3-2.patch、gcc-2.95.3-no-fixinc.patch和gcc-2.95.3-returntype-fix.patch,可以從http://www.linuxfromscratch.org/下載下傳到這些更新檔。
再将你下載下傳的 binutils-2.10.1、gcc-2.95.3、glibc-2.2.3 和glibc-linuxthreads-2.2.3 的源代碼放入 build-tools 目錄中
看一下你的 build-tools 目錄,有以下内容:
$ls
binutils-2.10.1.tar.bz2 build-gcc gcc-patch
build-binutls build-glibc glibc-2.2.3.tar.gz
build-boot-gcc gcc-2.95.3.tar.gzglibc-linuxthreads-2.2.3.tar.gz
建立核心頭檔案
“涯愦 www.kernel.org下載下傳的核心源代碼放入 $PRJROOT /kernel目錄
進入你的 kernel 目錄:
$cd $PRJROOT /kernel
解開核心源代碼
$tar -xzvf linux-2.4.21.tar.gz
或
$tar -xjvf linux-2.4.21.tar.bz2
小于 2.4.19 的核心版本解開會生成一個 linux目錄,沒帶版本号,就将其改名。
$mv linux linux-2.4.x
給 Linux 核心打上你的更新檔
$cd linux-2.4.21
$patch -p1 <../patch-2.4.21-rmk2
”嘁肽诤松成頭檔案
$make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
你也可以用 config 和 xconfig 來代替menuconfig,但這樣用可能會沒有設定某些配置檔案選項和沒有生成下面編譯所需的頭檔案。推薦大家用makemenuconfig,這也是核心開發人員用的最多的配置方法。配置完退出并儲存,檢查一下的核心目錄中的include/linux/version.h 和 include/linux/autoconf.h檔案是不是生成了,這是編譯 glibc 是要用到的,version.h 和autoconf.h 檔案的存在,也說明了你生成了正确的頭檔案。
還要建立幾個正确的連結
$cd include
$ln -s asm-arm asm
$cd asm
$ln -s arch-epxa arch
$ln -s proc-armv proc
接下來為你的交叉編譯環境建立你的核心頭檔案的連結
$mkdir -p $TARGET_PREFIX/include
$ln -s $PRJROOT/kernel/linux-2.4.21/include/linux$TARGET_PREFIX/include/linux
$in -s $PRJROOT/kernel/linux-2.4.21/include/asm-arm$TARGET_PREFIX/include/asm
也可以把 Linux 核心頭檔案拷貝過來用
$cp -r $PRJROOT/kernel/linux-2.4.21/include/linux$TARGET_PREFIX/include
$cp -r $PRJROOT/kernel/linux-2.4.21/include/asm-arm$TARGET_PREFIX/include
建立二進制工具(binutils)
binutils是一些二進制工具的集合,其中包含了我們常用到的as和ld。
首先,我們解壓我們下載下傳的binutils源檔案。
$tar -xvjf binutils-2.10.1.tar.bz2
然後進入build-binutils目錄配置和編譯binutils。
$cd build-binutils
$../binutils-2.10.1/configure --target=$TARGET--prefix=$PREFIX
--target 選項是指出我們生成的是 arm-linux 的工具,--prefix是指出我們可執行檔案安裝的位置。
會出現很多 check,最後産生 Makefile 檔案。
有了 Makefile 後,我們來編譯并安裝 binutils,指令很簡單。
$make
$make install
看一下我們 $PREFIX/bin 下的生成的檔案
$ls $PREFIX/bin
arm-linux-addr2line arm-linux-gasp arm-linux-objdumparm-linux-strings
arm-linux-ar arm-linux-ld arm-linux-ranlib arm-linux-strip
arm-linux-as arm-linux-nm arm-linux-readelf
arm-linux-c++filt arm-linux-objcopy arm-linux-size
我們來解釋一下上面生成的可執行檔案都是用來幹什麼的
add2line - 将你要找的位址轉成檔案和行号,它要使用 debug資訊。
Ar-産生、修改和解開一個存檔檔案
As-gnu 的彙編器
C++filt-C++ 和 java中有一種重載函數,所用的重載函數最後會被編譯轉化成彙編的标号,c++filt就是實作這種反向的轉化,根據标号得到函數名。
Gasp-gnu 彙編器預編譯器。
Ld-gnu 的連接配接器
Nm-列出目标檔案的符号和對應的位址
Objcopy-将某種格式的目标檔案轉化成另外格式的目标檔案
Objdump-顯示目标檔案的資訊
Ranlib-為一個存檔檔案産生一個索引,并将這個索引存入存檔檔案中
Readelf-顯示 elf 格式的目标檔案的資訊
Size-顯示目标檔案各個節的大小和目标檔案的大小
Strings-列印出目标檔案中可以列印的字元串,有個預設的長度,為4
Strip-剝掉目标檔案的所有的符号資訊
建立初始編譯器(bootstrap gcc)
首先進入 build-tools 目錄,将下載下傳 gcc 源代碼解壓
$tar -xvzf gcc-2.95.3.tar.gz
然後進入 gcc-2.95.3 目錄給 gcc 打上更新檔
$cd gcc-2.95.3
$patch -p1< ../gcc-patch/gcc-2.95.3.-2.patch
$patch -p1< ../gcc-patch/gcc-2.95.3.-no-fixinc.patch
$patch -p1< ../gcc-patch/gcc-2.95.3-returntype-fix.patch
echo timestamp >gcc/cstamp-h.in
在我們編譯并安裝 gcc 前,我們先要改一個檔案$PRJROOT/gcc/config/arm/t-linux,把
TARGET_LIBGCC2-CFLAGS = -fomit-frame-pointer -fPIC
這一行改為
TARGET_LIBGCC2-CFLAGS = -fomit-frame-pointer -fPIC-Dinhibit_libc -D__gthr_posix_h
你如果沒定義 -Dinhibit,編譯時将會報如下的錯誤
../../gcc-2.95.3/gcc/libgcc2.c:41: stdlib.h: No such file ordirectory
../../gcc-2.95.3/gcc/libgcc2.c:42: unistd.h: No such file ordirectory
make[3]: *** [libgcc2.a] Error 1
make[2]: *** [stmp-multilib-sub] Error 2
make[1]: *** [stmp-multilib] Error 1
make: *** [all-gcc] Error 2
如果沒有定義 -D__gthr_posix_h,編譯時會報如下的錯誤
In file included from gthr-default.h:1,
from ../../gcc-2.95.3/gcc/gthr.h:98,
from ../../gcc-2.95.3/gcc/libgcc2.c:3034:
../../gcc-2.95.3/gcc/gthr-posix.h:37: pthread.h: No such file ordirectory
還有一種與-Dinhibit同等效果的方法,那就是在你配置configure時多加一個參數-with-newlib,這個選項不會迫使我們必須使用newlib。我們編譯了bootstrap-gcc後,仍然可以選擇任何c庫。
接着就是配置boostrap gcc, 後面要用bootstrap gcc 來編譯 glibc庫。
$cd ..; cd build-boot-gcc
$../gcc-2.95.3/configure --target=$TARGET --prefix=$PREFIX \
>--without-headers --enable-languages=c --disable-threads
這條指令中的 -target、--prefix 和配置 binutils的含義是相同的,--without-headers就是指不需要頭檔案,因為是交叉編譯工具,不需要本機上的頭檔案。-enable-languages=c是指我們的boot-gcc 隻支援 c 語言。--disable-threads 是去掉 thread功能,這個功能需要 glibc 的支援。
接着我們編譯并安裝 boot-gcc
$make all-gcc
$make install-gcc
我們來看看 $PREFIX/bin 裡面多了哪些東西
你會發現多了 arm-linux-gcc 、arm-linux-unprotoize、cpp 和 gcov幾個檔案。
Gcc-gnu 的 C 語言編譯器
Unprotoize-将 ANSI C 的源碼轉化為 K&R C的形式,去掉函數原型中的參數類型。
Cpp-gnu的 C 的預編譯器
Gcov-gcc 的輔助測試工具,可以用它來分析和優程式。
使用 gcc3.2 以及 gcc3.2 以上版本時,配置 boot-gcc 不能使用--without-headers 選項,而需要使用 glibc 的頭檔案。
建立 c 庫(glibc)
首先解壓 glibc-2.2.3.tar.gz 和 glibc-linuxthreads-2.2.3.tar.gz源代碼
$tar -xvzf glibc-2.2.3.tar.gz
$tar -xzvf glibc-linuxthreads-2.2.3.tar.gz--directory=glibc-2.2.3
然後進入 build-glibc 目錄配置 glibc
$cd build-glibc
$CC=arm-linux-gcc ../glibc-2.2.3/configure --host=$TARGET--prefix="/usr"
--enable-add-ons --with-headers=$TARGET_PREFIX/include
CC=arm-linux-gcc 是把 CC 變量設成你剛編譯完的boostrapgcc,用它來編譯你的glibc。--enable-add-ons是告訴glibc用linuxthreads 包,在上面我們已經将它放入了 glibc源碼目錄中,這個選項等價于-enable-add-ons=linuxthreads。--with-headers 告訴 glibc 我們的linux核心頭檔案的目錄位置。
配置完後就可以編譯和安裝 glibc
$make install_root=$TARGET_PREFIX prefix="" install
然後你還要修改 libc.so 檔案
将
GROUP ( /lib/libc.so.6 /lib/libc_nonshared.a)
改為
GROUP ( libc.so.6 libc_nonshared.a)
這樣連接配接程式 ld 就會在 libc.so所在的目錄查找它需要的庫,因為你的機子的/lib目錄可能已經裝了一個相同名字的庫,一個為編譯可以在你的主控端上運作的程式的庫,而不是用于交叉編譯的。
建立全套編譯器(full 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++
--enable-languages=c,c++ 告訴 full gcc 支援 c 和 c++語言。
然後編譯和安裝你的 full gcc
$make all
我們再來看看 $PREFIX/bin 裡面多了哪些東西
你會發現多了 arm-linux-g++ 、arm-linux-protoize 和arm-linux-c++ 幾個檔案。
G++-gnu的 c++ 編譯器。
Protoize-與Unprotoize相反,将K&R C的源碼轉化為ANSIC的形式,函數原型中加入參數類型。
C++-gnu 的 c++ 編譯器。
到這裡你的交叉編譯工具就算做完了,簡單驗證一下你的交叉編譯工具。
用它來編譯一個很簡單的程式 helloworld.c
#include
int main(void)
{
printf("hello world\n");
return 0;
}
$arm-linux-gcc helloworld.c -o helloworld
$file helloworld
helloworld: ELF 32-bit LSB executable, ARM, version 1,
dynamically linked (uses shared libs), not stripped
上面的輸出說明你編譯了一個能在 arm 體系結構下運作的helloworld,證明你的編譯工具做成功了。
參考資料
Wookey ,Chris Rutter, Jeff Sutherland, Paul Webb ,《The GNUToolchain for ARM Target HOWTO》
Karim Yaghmour,《Building Embedded LinuxSystems》,USA:O'Reilly,2003
本文轉自feisky部落格園部落格,原文連結:http://www.cnblogs.com/feisky/archive/2008/04/11/1586605.html,如需轉載請自行聯系原作者