建立 gnu tool chain
之前都是用建好的 tool chain, 完全不知道如何去建立一個 tool chain. 所以此文章就紀錄如何去建立特定平台的 tool chain. 補充一下:
- 這邊使用的 tool chain 也相當於 cross-compiler.
- 這邊以 linux, glibc, GNU GCC 和 GNU Binutils 作為 tool chain 的要件
- 參考 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] :
- 對於最後的編譯器(cross-compiler) 需要 C library, 並知道如何使用 APIs 與 crt(c runtime) files, 但是
- 編譯 C library 是需要一個 compiler
由於 1 & 2 彼此互相需要. 為了解決此問題. 就先編譯一個不需要 C library 的精簡編譯器 (stripped-down compiler). 相依關係又變為
- 編譯 C library 是需要一個 compiler, 但是
- 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 部分. 所以關聯又變為:
- stripped-down compiler 需要 C library headers 和 start files. 才知道如何使用 C library, 但是
- 編譯 start files 需要一個 compiler
又發覺 3 & 4 彼此需要. 為解決這個問題, 必須編譯一個不須要 header files 與 start files 的簡易編譯器 (simple compiler). 關係又變為:
- 編譯 start files 需要一個 compiler,
- 編譯一個 simple compiler
再繼續往下之前, 前面有提到 C library 是需要 kernel haders. 所以先更改 4
- 編譯 start files 需要一個 compiler 以及 kernel headers
然而 tool chain 不單單只有 compiler 而已, 其餘的 assemble, linker, 等等部分都要加, 所以全部的關連可以在整理為:
- 對於最後的編譯器(cross-compiler) 需要 C library, 並知道如何使用 APIs 與 crt(c runtime) files. 而且還需要 binutils
- 編譯 C library 是需要一個 compiler 和 binutils
- stripped-down compiler 需要 C library headers 和 start files. 才知道如何使用 C library, 而且還需要 binutils
- 編譯 start files 需要一個 compiler 以及 kernel headers, 而且還需要 binutils
- 編譯一個 simple compiler, 而且還需要 binutils
基於上述的推導, 建立一個 tool chain 的編譯順序可為:
- binutils
- simple compiler
- Linux kernel headers
- C library 的 header files 和 start files
- stripped-down compiler
- 整個 C library
- cross-compiler(final compiler)
最後再補上 GNU GCC 的外部套件選項, 最終順序變為:
- binutils
- GMP, ISL, MPC, MPFR (可透過 gcc 裡的 script 下載)
- simple compiler
- Linux kernel headers
- C library 的 header files 和 start files
- stripped-down compiler
- 整個 C library
- 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
解壓縮下載的檔案到 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':
留言
張貼留言