How to Build a GCC Cross-Compiler
Introduction
因為最近常用一些cross-compiling的東西,參考到很多文件,為了加深印象,所以自己實作一次。
GCC (GNU Compiler Collection),是一套程式語言編譯器,以GPL和LGPL許可證所發行的自由軟體,也是GNU tool-chain的主要組成元件之一[2]。它並不只是一個編譯器。也是一個可以讓你建立很多種編譯器的Open Source的專案。在編譯之前你可以設定好多東西,像是可以支持Multithreading, shared libraries, 或是multilib等等。
這個文章就來嘗試建立一個RA_PI2的Cross-Compiler。並且使用自己build出來的Cross-Compiler去編譯一個hello_world放到PI2上去跑。
下圖是一個簡單的示意圖:
Preparation
Ubuntu
這邊使用的Host OS是Ubuntu。
Docker
使用一個完全乾淨的Container來做這件事[3],
事情會比較單純化,也會加深很多印象。
詳細Docker內容請參考網站。
安裝
$ sudo apt-get update
$ sudo apt-get install docker.io
測試一下是否成功
$ sudo docker info
註冊及登入Docker Hub
$ sudo docker login
開啟Docker
sudo docker run -t -i ubuntu bash
接下來所有內容都是在Docker裡面執行的。 理論上就算你一直複製貼上,應該不會錯的XD
建立建置資料夾
mkdir /tmp/pi_cross
cd /tmp/pi_cross
下載套件
sudo apt-get update
sudo apt-get install g++ make gawk -y
sudo apt-get install vim wget xz-utils -y
將以下幾個網址存在檔案wget-list裡,有需要的話可以到網站去找最新版的。 ex. http://mirror.pregi.net/gnu/binutils/ 底下會有很多檔案。
可從distrowatch.com找到最新的「gcc」, 「glibc」, 「header」相依性。雖說目前Raspbian的官方版本比我們實作的還要舊。
http://ftpmirror.gnu.org/binutils/binutils-2.24.tar.gz
http://ftpmirror.gnu.org/gcc/gcc-4.9.2/gcc-4.9.2.tar.gz
https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.17.2.tar.xz
http://ftpmirror.gnu.org/glibc/glibc-2.20.tar.xz
http://ftpmirror.gnu.org/mpfr/mpfr-3.1.2.tar.xz
http://ftpmirror.gnu.org/gmp/gmp-6.0.0a.tar.xz
http://ftpmirror.gnu.org/mpc/mpc-1.0.2.tar.gz
ftp://gcc.gnu.org/pub/gcc/infrastructure/isl-0.12.2.tar.bz2
ftp://gcc.gnu.org/pub/gcc/infrastructure/cloog-0.18.1.tar.gz
然後用wget一次下載所有套件
wget --input-file=wget-list --continue --directory-prefix=/tmp/pi_cross
套件說明:
binutils : 包含了連結器(Linker),組譯器(assembler),和其他處理物件檔案的工具。
gcc : 內容是GNU compiler collection, 其中包含了C 和 C++編譯器。
linux-4.3 : 由linux kernel API所匯出的表頭檔,是要給Glibc用的。
Glibc : 內容是C的函式庫。 這個函式庫包含了一些記憶體配置, 檔案搜尋,開關讀寫
檔案,字串處理,數值運數等等基本函數。
mpfr : 內容是多精度數值運算的函數。
gmp : 這個套件包含了一些多精度數值運算的函式庫,建立GCC需要用到。
mpc : 也是包含一些多精度運算的函式庫,建立GCC需要用到。
ISL (opt) :
cloog (opt) : 使用ISL和cloog這兩個套件可以允許一些編譯的最佳化,但是也可以不用。
套件相依性:
理論上以我的理解是這樣,只是這東西博大精深,可能在挖下去會更複雜,所以先暫時這麼理解,就誰用誰大概知道就好。
套件解壓縮
for f in *.tar*; do tar xf $f; done
取得target平臺資訊
因為待回build tool chain需要有target的名字,所以先在數莓派裡下以下指令:
gcc -dumpmachine
得到 arm-linux-gnueabihf
如果想要知道到底一個套件底下支援多少個平臺,有多少的target名稱, 可以爬一下任何一個套件底下的config.sub檔。
設定環境變數
export TARGET=arm-linux-gnueabihf
建立symbolic link
這步驟將build gcc會用到的5個套件,都建立link進去。
cd gcc-4.9.2
ln -s ../mpfr-3.1.2 mpfr
ln -s ../gmp-6.0.0 gmp
ln -s ../mpc-1.0.2 mpc
ln -s ../isl-0.12.2 isl
ln -s ../cloog-0.18.1 cloog
cd ..
建立tool-chain路徑
這邊建立我們要放tool-chain的資料夾
sudo mkdir -p /opt/cross
因為在build的過程中會一直參考到/opt/cross/bin這個路徑,所以記得export到環境中,有需要的話可以寫到~/.bashrc裡面。
export PATH=/opt/cross/bin:$PATH
Build Cross-Compiler
1. Binutils
mkdir build-binutils
cd build-binutils
../binutils-2.24/configure --prefix=/opt/cross --target=$TARGET
make -j32
make install
cd ..
我們將target系統類別設定為arm-linux-gnueabihf,Binutils的組態腳本會去辨識如果target和Host是不一樣的話,會編譯成cross-assembler和cross-linker。這部份會將工具安裝到/opt/cross/bin/底下。
2. Linux Kernel Headers
這步驟會安裝linux kernel header,用這個tool-chian build出來的程式就可以在目標裝置上,進行系統呼叫。
cd linux-3.17.2
make ARCH=arm INSTALL_HDR_PATH=/opt/cross/$TARGET headers_install
cd ..
接下來的幾個步驟就是GCC和Glibc的安裝,但是這兩個套件都互相有一些相依性,所以不能一次就build完,會一部份一部份的跳著處理,如下圖所示。
3. C/C++ Compilers
這步驟會建置並且安裝GCC的 C 和 C++ Cross-compiler,並且安裝到 /opt/cross/bin 底下。
mkdir -p build-gcc
cd build-gcc
../gcc-4.9.2/configure --prefix=/opt/cross --target=$TARGET --enable-languages=c,c++
make -j32 all-gcc
make install-gcc
cd ..
這步驟因為我們有--target=arm-linux-gnueabihf,所以腳本就會去尋找我們step 1 所建立的Binutils cross-tools,以arm-linux-gnueabihf為前置字串(prefix)的部份(ex. arm-linux-gnueabihf-as)。而這個部份所建立出來的C/C++也會以arm-linux-gnueabihf為prefix(還是講prefix比較不會怪怪的XD)。
這邊使用--enable-languages=c,c++ ,主要是限定,因為這樣就不會去compile其他的語言的套件,像是Fortran, Java...
4. Standard C Library Headers and Startup Files
這步驟有以下重點:
- a. 安裝Glibc的標準C函式庫標頭檔到/opt/cross/arm-linux-gnueabihf/include。
- b. 使用C編譯器(from step 3)去編譯startup file並且安裝到/opt/cross/arm-linux-gnueabihf/lib。
- c. 產生libc.so和stubs.h,這己的檔案都會在step 5用到,並且再step 6被取代掉。
mkdir -p build-glibc
cd build-glibc
../glibc-2.20/configure --prefix=/opt/cross/$TARGET --build=$MACHTYPE --host=$TARGET --target=$TARGET --with-headers=/opt/cross/$TARGET/include libc_cv_forced_unwind=yes
make install-bootstrap-headers=yes install-headers
make -j32 csu/subdir_lib
install csu/crt1.o csu/crti.o csu/crtn.o /opt/cross/$TARGET/lib
$TARGET-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o /opt/cross/$TARGET/lib/libc.so
touch /opt/cross/$TARGET/include/gnu/stubs.h
cd ..
- --prefix=/opt/cross/arm-linux-gnueabihf 選項告訴組態檔應該到哪裡安裝標頭檔和函式庫。
- 這邊我們定義了--build, --host和--target,其實有點怪,但是資料說是官方定義的,所以照用...
- $MACHTYPE是一個預先定義的環境變數,主要描述目前跑這個腳本的機器。
- --build=$MACHTYPE,這個選項宣告主要是step 6會用到。
- 這邊的--host也是個怪定義。 再Glibc的組態,--host和--targer都是描述Glibc函式庫將會再哪個系統上跑。
- 我們手動安裝了crtl.o, crti.o和crtn.o。
5. Compiler Support Library
- a. 這邊使用step 3的cross-compilers去編譯一些函式庫。 裡麵包含了一些C++的例外處理。
- b. 這些函式庫依賴於step 4 所建立的startup 檔案。
- c. 這步驟所建立出來的函式庫會在step 6用到。
- d. 這個步驟不需要重新run GCC的組態,只要直接以相同組態加上一些其他的target就好了。
cd build-gcc
make -j32 all-target-libgcc
make install-target-libgcc
cd ..
- 兩個靜態函式庫 libgcc.a和 libgcc_eh.a會被安裝到/opt/cross/lib/arm-linux-gnueabihf/4.9.2/底下
- 一個動態函式庫libgcc_s.so會被安裝到/opt/cross/arm-linux-gnueabihf/lib底下。
6. Standard C Library
在這步驟,就會完成Glibc的建置,安裝標準C函式庫到/opt/cross/lib
輸出靜態函式庫為libc.a,動態函式庫為libc.so。
cd build-glibc
make -j32
make install
cd ..
7. Standard C++ Library
終於到最後一步了,完成GCC的建置。安裝標準C++函式庫到/opt/cross/arm-linux-gnueabihf/lib/底下。這步驟也是依賴於step 6的C函式庫。 輸出靜態函式庫為libstdc++.a,動態函式庫為libstdc++.so
cd build-gcc
make -j32
make install
cd ..
Dealing with Build Errors
如果中間有一些errors的話,有可能是底下幾個原因:
1. 有缺少某個套件。
2. build的順序錯誤,別忘記這個的相依性很重要!
3. 有時候套件之間的版本組合也很重要,並不是全部拿最新的就可以用(尤其是kernel的版本!!)。
在build的時候要用log,像是
make 2>&1 | tee build.log
這樣有error才可以參考。
GCC支援很多的組態,但是有些比較冷門的可能也還沒有人去弄過,所以就有可能有問題。
Testing the Cross-Compiler 如果一切都成功了,接下來我們就可以來測試一下。
簡單來個hello world,編譯完以後丟到raspberry pi 2上跑,並且反組看看。