从零开始编译一个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)