1. 程式人生 > 其它 >從零開始編譯一個gcc的交叉編譯工具鏈

從零開始編譯一個gcc的交叉編譯工具鏈

為了避免和host的編譯系統耦合,很多sdk和需要和客戶聯編的軟體都會提供自己的工具鏈或者要求客戶的系統滿足某種工具鏈要求。

大概梳理下來獨立的ToolChain 有如下一些好處:

1.不用關心host作業系統,只要下載toolchain,隨時隨地(前提是x86的Linux作業系統,Windows和arm的Linux上也可以做,不過需要單獨做,每增加一套,會有更多的維護成本)可以編譯。
2.部分編譯器補丁,作業系統不一定會發,這時需要對toolchain單獨打補丁,如果用host的編譯工具,要考慮補丁之後和OS本身的相容性。
3.arm當前本身效能比x86還是差不少,而且公司內部arm的伺服器數量有限,這都要求在x86伺服器上進行交叉編譯。

1 安裝包下載

apt-get installlibgmp-devlibmpfr-devlibmpc-devg++ make gawk

下載編譯需要的原始碼包(上面用apt命令下載的gmp,mpfr和mpc也可以下載原始碼包編譯,上面為了省事直接下載了安裝源上的包)

wgethttp://ftp.wayne.edu/gnu/mpfr/mpfr-4.1.0.tar.xz

wgethttp://ftp.wayne.edu/gnu/gmp/gmp-6.2.1.tar.xz

wgethttp://ftp.wayne.edu/gnu/mpc/mpc-1.2.1.tar.gz

如果這3個組建是原始碼編譯的話,記得在gcc編譯目錄下面建立軟連線,方便編譯器能自動搜尋到,否則需要單獨指定程式碼目錄

ln -s ../mpfr-4.1.0 mpfr
ln -s ../gmp-6.2.1 gmp
ln -s ../mpc-1.2.1 mpc

binutils也可以取最新版本的,這個無所謂:

wgethttp://ftpmirror.gnu.org/binutils/binutils-2.28.1.tar.xz

gcc取了ubuntu18和ubuntu20上的7.x和9.x系列的最新版本,也可以取11.x,一般來說版本越新,功能越強大:
wgethttp://mirror.team-cymru.com/gnu/gcc/gcc-7.5.0/gcc-7.5.0.tar.xz
wgethttp://mirror.team-cymru.com/gnu/gcc/gcc-9.4.0/gcc-9.4.0.tar.xz

wgethttp://mirror.team-cymru.com/gnu/gcc/gcc-5.4.0/gcc-5.4.0.tar.bz2

核心版本之前下載這個只是為了和host上的儘量保持一致,這樣核心版本和glibc的版本匹配關係不用自己摸索了,實際上其他匹配的組合也可以:
wgethttps://www.kernel.org/pub/linux/kernel/v4.x/linux-4.15.1.tar.xz

glibc的版本很重要,決定了編譯出來的版本能執行的最小支援版本,如果目標機器上的glibc版本比這個更老,則無法執行,2.23是ubuntu 16上的glibc版本:
wgethttp://ftpmirror.gnu.org/glibc/glibc-2.23.tar.xz

為什麼都下載xz版本的?因為比其他版本小啊

2 程式的交叉編譯和執行過程


在host伺服器上安裝了c和c++的交叉編譯工具鏈(假定目標系統是aarch64的系統),編譯過程中會將c或者c++程式先編譯成彙編臨時檔案,然後依賴本地的彙編器as編譯成目標檔案,再用連結器ld連結生成可執行檔案,但這個可執行檔案的格式是按目標系統來構建的,所以在host伺服器上無法執行。

編譯完的可執行檔案通過版本釋出或者拷貝到方式下載的目標系統上,例如最簡單的a.out小程式,如果該程式依賴C++的庫,則在目標系統上載入的過程中會先對C++的標準庫進行動態連結,然後連結底層的C標準庫(基本上所有程式語言底層都是基於C標準庫),載入完之後核心的排程器會將該程式從入口執行起來。

3 編譯過程

3.0 編譯準備

解壓縮上面下載的包。

建立編譯目標目錄。

燧原很多產品都是xxx開頭,雖然我還不知道這3個字母是什麼都縮寫,但我還是沿用了,其實用其他路徑名也可以的。

如果是直接在host上編譯的話,儘量不要用root使用者來操作,免得把host作業系統搞掛了痛不欲生。如果是在容器裡面編譯就隨意了,容器的root檔案系統弄壞大不了刪掉當前容器重新啟動一個。

目錄裡面最好包含gcc版本號、glibc版本號和目標硬體架構名,免得進去了之後猜:

mkdir -p /opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux

更新好PATH全域性變數,確保後面編譯過程中使用的工具都是新編譯出來的,而不是host上的:

export PATH=/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/bin:$PATH

3.1 編譯binutils

正式編譯gcc之前,需要先編譯一個編譯gcc的工具,也就是binutils包。

cdbinutils-2.28.1

mkdirxxx_aarch64_gcc9.4.0_glibc2.23linux_build

cdxxx_aarch64_gcc9.4.0_glibc2.23linux_build

--disable-multilib的含義是不需要考慮同一個系列硬體架構下面的相容性,例如aarch64目標機能執行,是否需要做aarch32上執行?x86_64的程式是否需要做i586上執行等等,一般應該沒這種需求。

../configure --prefix=/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux --target=aarch64-linux-gnu--disable-multilib

下面這個數字根據伺服器的核數來,對應make程式最多執行的程序個數,如果執行的伺服器核數非常多,完全可以配置更大,實際上編譯過程中由於檔案依賴關係,大多數時候執行不了這麼多程序:

make -j20

make install

cd ../../

3.2 編譯Linux Kernel Headers

如果目標機的linux核心版本(軟體和執行的硬體)和host完全一樣的話,可以直接用apt命令下載,如果不是完全一樣的話就需要重新編譯一下標頭檔案。

cd linux-4.15.1/

make ARCH=arm64 INSTALL_HDR_PATH=/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu/ headers_install
cd ..

核心裡面的硬體體系名和gcc不一樣,例如這裡的aarch64在linux核心裡面還是叫arm64,另外一個引數指向要安裝標頭檔案的目錄。

3.3 編譯C/C++ Compilers

cdgcc-9.4.0

mkdirxxx_aarch64_gcc9.4.0_glibc2.23linux_build

cdxxx_aarch64_gcc9.4.0_glibc2.23linux_build

../configure --prefix=/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux --target=aarch64-linux-gnu --with-glibc-version=2.23 --enable-languages=c,c++ --disable-multilib--with-protoc

make -j20 all-gcc
make install-gcc
cd ..

先把編譯器編譯出來。

3.4 編譯Standard C Library Headers and Startup Files

cdglibc-2.23

mkdirxxx_aarch64_gcc9.4.0_glibc2.23linux_build

cdxxx_aarch64_gcc9.4.0_glibc2.23linux_build

../configure --prefix=/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu--build=$MACHTYPE --host=aarch64-linux-gnu--target=aarch64-linux-gnu--disable-multilib

make install-bootstrap-headers=yes install-headers
make -j20 csu/subdir_lib
install csu/crt1.o csu/crti.o csu/crtn.o/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu/lib
aarch64-linux-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu/lib/libc.so
touch/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu/include/gnu/stubs.h
cd ..

csu/crt1.o csu/crti.o csu/crtn.o這幾個庫檔案後面編譯是需要的,但沒有自動安裝。libc.so和stubs.h後面第3.5步需要,但第3.6步會重新生成。

3.5 編譯gcc所需要的庫

cdgcc-9.4.0/xxx_aarch64_gcc9.4.0_glibc2.23linux_build

make -j20 all-target-libgcc
make install-target-libgcc

cd ../../

3.6 編譯glibc庫

cdglibc-2.23/xxx_aarch64_gcc9.4.0_glibc2.23linux_build

make -j20
make install

cd ../../

3.6 編譯c++庫

cdgcc-9.4.0/xxx_aarch64_gcc9.4.0_glibc2.23linux_build

make -j20
make install

cd ../../

4 遇到的編譯問題

9.4的gcc原始碼有個錯誤,報PATH_MAX未定義,搜了一下標頭檔案中的定義,最大是4096,手工改成4096之後編譯通過:

libtool: compile: /home/ronghua.zhou/zrh/gcc-9.4.0/xxx_aarch64_gcc9.4.0_glibc2.23linux_build/./gcc/xgcc -shared-libgcc -B/home/ronghua.zhou/zrh/gcc-9.4.0/xxx_aarch64_gcc9.4.0_glibc2.23linux_build/./gcc -nostdinc++ -L/home/ronghua.zhou/zrh/gcc-9.4.0/xxx_aarch64_gcc9.4.0_glibc2.23linux_build/aarch64-linux-gnu/libstdc++-v3/src -L/home/ronghua.zhou/zrh/gcc-9.4.0/xxx_aarch64_gcc9.4.0_glibc2.23linux_build/aarch64-linux-gnu/libstdc++-v3/src/.libs -L/home/ronghua.zhou/zrh/gcc-9.4.0/xxx_aarch64_gcc9.4.0_glibc2.23linux_build/aarch64-linux-gnu/libstdc++-v3/libsupc++/.libs -B/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu/bin/ -B/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu/lib/ -isystem /opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu/include -isystem /opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/aarch64-linux-gnu/sys-include -D_GNU_SOURCE -D_DEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -DASAN_HAS_EXCEPTIONS=1 -DASAN_NEEDS_SEGV=1 -DCAN_SANITIZE_UB=0 -I. -I../../../../libsanitizer/asan -I.. -I ../../../../libsanitizer/include -I ../../../../libsanitizer -Wall -W -Wno-unused-parameter -Wwrite-strings -pedantic -Wno-long-long -fPIC -fno-builtin -fno-exceptions -fno-rtti -fomit-frame-pointer -funwind-tables -fvisibility=hidden -Wno-variadic-macros -fno-ipa-icf -I../../libstdc++-v3/include -I../../libstdc++-v3/include/aarch64-linux-gnu -I../../../../libsanitizer/../libstdc++-v3/libsupc++ -std=gnu++11 -g -O2 -D_GNU_SOURCE -MT asan_poisoning.lo -MD -MP -MF .deps/asan_poisoning.Tpo -c ../../../../libsanitizer/asan/asan_poisoning.cc-fPIC -DPIC -o .libs/asan_poisoning.o
../../../../libsanitizer/asan/asan_linux.cc: In function 'void __asan::AsanCheckIncompatibleRT()':
../../../../libsanitizer/asan/asan_linux.cc:216:21: error: 'PATH_MAX' was not declared in this scope
216 | char filename[PATH_MAX];
| ^~~~~~~~
../../../../libsanitizer/asan/asan_linux.cc:217:35: error: 'filename' was not declared in this scope; did you mean 'fileno'?
217 | MemoryMappedSegment segment(filename, sizeof(filename));
| ^~~~~~~~
| fileno
Makefile:599: recipe for target 'asan_linux.lo' failed
make[4]: *** [asan_linux.lo] Error 1
make[4]: *** Waiting for unfinished jobs....

vi gcc-9.4.0/libsanitizer/asan/asan_linux.cc

5 怎麼用?

將編譯出來的結果 /opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/ 拷貝到任意的x86_64都linux編譯機上(環境的glibc版本必須要高於2.23版本),將編譯命令的的gcc/g++等程式換成/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/bin裡面對應程式,並增加連結庫的搜尋路徑/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/lib,增加標頭檔案搜尋路徑/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/include就可以正常編譯了。例如:

/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/bin/xxx_aarch64_gcc9.4.0_glibc2.23linuxg++ -I/opt/xxx/xxx_aarch64_gcc9.4.0_glibc2.23linux/include ~/zrh/test.cpp

編譯出來的結果a.out可以拷貝到飛騰伺服器上執行:

PS C:\Users\ronghua.zhou> ssh zzz@xxx

Authorized users only. All activities may be monitored and reported.
zzz@xxxs password:

Authorized users only. All activities may be monitored and reported.
Web console:https://localhost:9090/orhttps://10.12.110.184:9090/

Last login: Wed Jul 21 16:12:49 2021 from 10.12.60.98
[iqd@localhost ~]$ uname -a
Linux localhost.localdomain 4.19.90-17.5.ky10.aarch64 #1 SMP Fri Aug 7 13:35:33 CST 2020 aarch64 aarch64 aarch64 GNU/Linux
[iqd@localhost ~]$ ./a.out
sz=3, sz1=8

6 相關程式碼

支援一鍵式從外網下載原始碼並編譯、安裝交叉工具鏈

zhouronghua/CCC: the Compiler of the Cross Compiler (github.com)