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


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

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

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

1 安裝包下載

apt-get install libgmp-dev libmpfr-dev libmpc-dev  g++ make gawk

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

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

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

wget http://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也可以取最新版本的,這個無所謂:

wget http://ftpmirror.gnu.org/binutils/binutils-2.28.1.tar.xz

 gcc取了ubuntu18和ubuntu20上的7.x和9.x系列的最新版本,也可以取11.x,一般來說版本越新,功能越強大:

wget http://mirror.team-cymru.com/gnu/gcc/gcc-7.5.0/gcc-7.5.0.tar.xz
wget http://mirror.team-cymru.com/gnu/gcc/gcc-9.4.0/gcc-9.4.0.tar.xz
wget http://mirror.team-cymru.com/gnu/gcc/gcc-5.4.0/gcc-5.4.0.tar.bz2 

 內核版本之前下載這個只是為了和host上的盡量保持一致,這樣內核版本和glibc的版本匹配關系不用自己摸索了,實際上其他匹配的組合也可以:

wget https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.15.1.tar.xz

 glibc的版本很重要,決定了編譯出來的版本能運行的最小支持版本,如果目標機器上的glibc版本比這個更老,則無法運行,2.23是ubuntu 16上的glibc版本:

wget http://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包。

cd binutils-2.28.1

mkdir xxx_aarch64_gcc9.4.0_glibc2.23linux_build

cd xxx_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

 先把編譯器編譯出來。

cd gcc-9.4.0
mkdir xxx_aarch64_gcc9.4.0_glibc2.23linux_build
cd xxx_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

cd glibc-2.23
mkdir xxx_aarch64_gcc9.4.0_glibc2.23linux_build
cd xxx_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所需要的庫

cd gcc-9.4.0/xxx_aarch64_gcc9.4.0_glibc2.23linux_build
make -j20 all-target-libgcc
make install-target-libgcc
cd ../../

3.6 編譯glibc庫

cd glibc-2.23/xxx_aarch64_gcc9.4.0_glibc2.23linux_build
make -j20
make install
cd ../../

 

3.7 編譯c++庫

cd gcc-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....

  

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/ or https://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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM