建立 gnu tool chain

之前都是用建好的 tool chain, 完全不知道如何去建立一個 tool chain. 所以此文章就紀錄如何去建立特定平台的 tool chain. 補充一下:

  1. 這邊使用的 tool chain 也相當於 cross-compiler.
  2. 這邊以 linux, glibc, GNU GCC 和 GNU Binutils 作為 tool chain 的要件
  3. 參考 reference 與自己的想法所寫.

1. 概述

根據[1], tool chain 主要可分成compiler, assembler 與 linker.

  • compiler: 編譯程式並產生 assemble code. 如 GNU compiler collection(gcc)
  • assembler: 把 assemble code 轉換為特定平台上的 object code. 如 GNU binutils
  • linker: 把 object code files 合併起來以及加入所需的library links

基於上述便產生出一個執行檔. 然而對於執行在 target Operating System 的程式來說還需要 C library, 如 glibc, newlib, etc. 而對 C Library 來說則又需要 kernel header files. 來確認 kernel 有提供那些功能.

了解構成要件後, 接著看一個 tool chain 要件之間需求關聯. 其由結果往前推導 [1] :

  1. 對於最後的編譯器(cross-compiler) 需要 C library, 並知道如何使用 APIs 與 crt(c runtime) files, 但是
  2. 編譯 C library 是需要一個 compiler

由於 1 & 2 彼此互相需要. 為了解決此問題. 就先編譯一個不需要 C library 的精簡編譯器 (stripped-down compiler). 相依關係又變為

  1. 編譯 C library 是需要一個 compiler, 但是
  2. stripped-down compiler 需要 C library headers 和 start files. 才知道如何使用 C library,

發覺 2 & 3 也是彼此需要. 為了解決這個問題, 需先安裝 C library header files 與 start files. 這邊 start files 其實就是 C runtime, 可參考[4]的 Core runtime files 部分. 所以關聯又變為:

  1. stripped-down compiler 需要 C library headers 和 start files. 才知道如何使用 C library, 但是
  2. 編譯 start files 需要一個 compiler

又發覺 3 & 4 彼此需要. 為解決這個問題, 必須編譯一個不須要 header files 與 start files 的簡易編譯器 (simple compiler). 關係又變為:

  1. 編譯 start files 需要一個 compiler,
  2. 編譯一個 simple compiler

再繼續往下之前, 前面有提到 C library 是需要 kernel haders. 所以先更改 4

  1. 編譯 start files 需要一個 compiler 以及 kernel headers

然而 tool chain 不單單只有 compiler 而已, 其餘的 assemble, linker, 等等部分都要加, 所以全部的關連可以在整理為:

  1. 對於最後的編譯器(cross-compiler) 需要 C library, 並知道如何使用 APIs 與 crt(c runtime) files. 而且還需要 binutils
  2. 編譯 C library 是需要一個 compiler 和 binutils
  3. stripped-down compiler 需要 C library headers 和 start files. 才知道如何使用 C library, 而且還需要 binutils
  4. 編譯 start files 需要一個 compiler 以及 kernel headers, 而且還需要 binutils
  5. 編譯一個 simple compiler, 而且還需要 binutils

基於上述的推導, 建立一個 tool chain 的編譯順序可為:

  1. binutils
  2. simple compiler
  3. Linux kernel headers
  4. C library 的 header files 和 start files
  5. stripped-down compiler
  6. 整個 C library
  7. cross-compiler(final compiler)

最後再補上 GNU GCC 的外部套件選項, 最終順序變為:

  1. binutils
  2. GMP, ISL, MPC, MPFR (可透過 gcc 裡的 script 下載)
  3. simple compiler
  4. Linux kernel headers
  5. C library 的 header files 和 start files
  6. stripped-down compiler
  7. 整個 C library
  8. cross-compiler(final compiler)

補充: [1]針對步驟還分得更細, 有興趣請自行觀看.

2. 建立 tool chain

編譯流程參考[2][3]. 兩者之間的流程大致相同. 作法也與上方的概述相同. 前者套件版本新上許多, 之外與[3][4]最大的不同就是編譯 gcc 的時候不需要重新 configure.
以下編譯以 arm64 平台為目標. 並把 tool chain 安裝於 /opt/toolchain 底下.

基本套件安裝

安裝編譯時所需的相關套件

root@:~# apt-get install g++ make gawk flex colormake

下載所需的 source codes

建立目錄 pkgs 與 sources, 把下列的 packages 下載到 pkgs

root@:~/cross_compiler# mkdir pkgs sources 
  1. linux-4.14.14.tar.xz
  2. gcc-7.2.0.tar.xz
  3. binutils-2.29.1.tar.xz
  4. glibc-2.26.tar.xz

解壓縮下載的檔案到 sources 目錄

root@:~/cross_compiler/pkgs# for f in *.tar*; do tar xf $f -C ../sources/; done

進入 gcc-7.2.0 目錄, 執行裡面的 script. 下載所需要的 external packages [5]

root@:~/cross_compiler/pkgs# cd ../sources/gcc-7.2.0/
root@:~/cross_compiler/sources/gcc-7.2.0# ./contrib/download_prerequisites

建立 objs 目錄, 並在底下建立 binutils-2.29.1 gcc-7.2.0 glibc-2.26 目錄

root@:~/cross_compiler# mkdir objs
root@:~/cross_compiler/objs# mkdir binutils-2.29.1  gcc-7.2.0  glibc-2.26

在 /opt 底下建立 toolchain 目錄, 作為存放 toolchain 的地方

root@:~/cross_compiler# mkdir /opt/toolchain

編譯步驟

編譯 binultis

root@:~/cross_compiler/objs/binutils-2.29.1# ../../sources/binutils-2.29.1/configure --prefix=/opt/toolchain --target=aarch64-linux
root@:~/cross_compiler/objs/binutils-2.29.1# colormake `-j$(nproc)`
root@:~/cross_compiler/objs/binutils-2.29.1# colormake install

接著第一次編譯 gcc

root@:~/cross_compiler/objs/gcc-7.2.0# ../../sources/gcc-7.2.0/configure --prefix=/opt/toolchain --target=aarch64-linux --enable-languages=c,c++
root@:~/cross_compiler/objs/gcc-7.2.0# colormake `-j$(nproc)` all-gcc
root@:~/cross_compiler/objs/gcc-7.2.0# colormake install-gcc

然後把 /opt/toolchain/bin 路徑加入環境變數 PATH

root@:~/cross_compiler/objs/gcc-7.2.0# export PATH=/opt/toolchain/bin/:${PATH}

而為了編譯 glibc, 必須先安裝對應平台的 linux header

root@:~/cross_compiler/sources/linux-4.14.14# make ARCH=arm64 INSTALL_HDR_PATH=/opt/toolchain/aarch64-linux headers_install

為了第二次編譯 gcc, 需要安裝 glibc headers 與 start files (c runtime files). 這邊分四個步驟.
步驟(1): 首先安裝 glibc header files

root@:~/cross_compiler/objs/glibc-2.26#../../sources/glibc-2.26/configure --prefix=/opt/toolchain/aarch64-linux --build=$MACHTYPE --host=aarch64-linux --target=aarch64-linux --with-headers=/opt/toolchain/aarch64-linux/include
root@:~/cross_compiler/objs/glibc-2.26# colormake install-headers

錯誤: 如果沒有先安裝 linux header files, 在 configure 就會發生錯誤

if no header installed 
checking installed Linux kernel header files... missing or too old!
configure: error: GNU libc requires kernel header files from
Linux 3.2.0 or later to be installed before configuring.
The kernel header files are found usually in /usr/include/asm and
/usr/include/linux; make sure these directories use files from
Linux 3.2.0 or later.  This check uses <linux/version.h>, so
make sure that file was built correctly when installing the kernel header
files.  To use kernel headers not from /usr/include/linux, use the
configure option --with-headers.

步驟(2): 編譯 glibc 的 runtime libraries 並複製到 /opt/toolchain/aarch64-linux/lib/

root@:~/cross_compiler/objs/glibc-2.26# colormake `-j$(nproc)` csu/subdir_lib
root@:~/cross_compiler/objs/glibc-2.26# install csu/crt*.o /opt/toolchain/aarch64-linux/lib/

步驟(3): 由於 stubs.h 不存在, 編譯時又需要. 就建一個空的 /opt/toolchain/aarch64-linux/include/gnu/stubs.h

root@:~/cross_compiler/objs/glibc-2.26# touch /opt/toolchain/aarch64-linux/include/gnu/stubs.h

步驟(4): 編譯 gcc 時候需要 glibc.so. 但是 glibc.so 根本沒有被編譯, 所以產生一個空的 so 檔案. 原因可參考下列 [3]

root@:~/cross_compiler/objs/glibc-2.26# aarch64-linux-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o /opt/toolchain/aarch64-linux/lib/libc.so
Finally, 'libgcc_s.so' requires a 'libc.so' to link against.  However, since we will never actually execute its code, it doesn't matter what it contains.  So, treating '/dev/null' as a C source file, we produce a dummy 'libc.so' in one step:

第二次編譯 gcc

root@:~/cross_compiler/objs/gcc-7.2.0# colormake `-j$(nproc)` all-target-libgcc
root@:~/cross_compiler/objs/gcc-7.2.0# colormake install-target-libgcc

錯誤: 沒有安裝 glibc header files

./gthr-default.h:35:10: fatal error: pthread.h: No such file or directory
 #include <pthread.h>
          ^~~~~~~~~~~
compilation terminated.
Makefile:915: recipe for target '_gcov_dump.o' failed

錯誤: 沒有建立空的 stub.h

/opt/toolchain/aarch64-linux/include/features.h:447:10: fatal error: gnu/stubs.h: No such file or directory
 #include <gnu/stubs.h>
          ^~~~~~~~~~~~~

錯誤: 沒有安裝 runtime files

/opt/toolchain/aarch64-linux/bin/ld: cannot find crti.o: No such file or directory
/opt/toolchain/aarch64-linux/bin/ld: cannot find -lc
/opt/toolchain/aarch64-linux/bin/ld: cannot find crtn.o: No such file or directory
collect2: error: ld returned 1 exit status
Makefile:977: recipe for target 'libgcc_s.so' failed
make[1]: *** [libgcc_s.so] Error 1
make[1]: Leaving directory '/root/cross_compiler/objs/gcc-7.2.0/aarch64-linux/libgcc'
Makefile:14161: recipe for target 'all-target-libgcc' failed

至此可以開始編譯整個 glibc

root@:~/cross_compiler/objs/glibc-2.26# colormake `-j$(nproc)`
root@:~/cross_compiler/objs/glibc-2.26# colormake install

第三次, 也是最後一次編譯 gcc

root@:~/cross_compiler/objs/gcc-7.2.0# colormake `-j$(nproc)`
root@:~/cross_compiler/objs/gcc-7.2.0# colormake install

編譯完成的 tool chain 便放置在 /opt/toolchain 底下, 而底下目錄的說明可以參考[6]

3. 驗證 tool chain

下方為一個簡單的 hello 程式, 配合工具來檢驗編譯出來的程式是否為 arm64 格式

#include <stdio.h>
int main (int argc, char **argv)
{
    puts ("Hello, world!");
    return 0;
}

編譯 test.c 程式

root@:~/tmp_code# aarch64-linux-gcc test.c -o c_test

使用 aarch64-linux-readelf 來檢查執行檔格式

root@:~/tmp_code# aarch64-linux-readelf -hl c_test
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  ....
  ....
  Type:                              EXEC (Executable file)
  Machine:                           AArch64
  ....
  ....

使用 qemu 來執行 arm64 執行檔

root@:~/tmp_code# apt-get install qemu-user
root@:~/tmp_code# ln -sf /opt/toolchain/aarch64-linux/lib/ld-2.26.so /lib/ld-linux-aarch64.so.1
root@:~/tmp_code# qemu-aarch64 c_test
Hello, world!

根據[3]檢查 libgcc_s.so. 原因請參考下方.

root@:~/tmp_code# aarch64-linux-readelf -d /opt/toolchain/aarch64-linux/lib64/libgcc_s.so

 Dynamic section at offset 0x11dc8 contains 27 entries:
   Tag        Type                         Name/Value
  0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
  0x000000000000000e (SONAME)             Library soname: [libgcc_s.so.1]
         ...
Looking at the dynamic section of the installed 'libgcc_s.so', we see that the 'NEEDED' entry for the C library does include the '.6' suffix, indicating that was linked against our fully build EGLIBC, and
not our dummy 'libc.so':
  1. crosstool-NG: How a toolchain is constructed
  2. How to Build a GCC Cross-Compiler
  3. EGLIBC: cross-building
  4. Gentoo Linux: Creating a cross-compiler
  5. Installing GCC
  6. GNU Cross-Platform Development Toolchain

留言

這個網誌中的熱門文章

yocto recipe : (1) 撰寫 recipe

yocto recipe : (2) 撰寫 bbappend

yocto recipe : (3) 使用 External Source 來編譯軟體