如何构建一个GCC交叉编译工具链
GCC不仅是一个编译器,它是一个开源工程,可以让你建立各种编译器。一些编译器支持多线程,一些支持共享库,一些支持Multilib(典型的应用是在64位机上运行32位应用程序),这些都取决于在编译 编译器 时的配置。
本文档将说明怎么建立一个交叉编译器。你需要一个已经安装gcc的Unix-like环境。
一、需要的包
Debian系统,首先需要安装一些包
$ sudo apt-get install g++ make gawk
其他的包将使用源代码来编译。在根文件系统的某个地方新建一个文件夹,下载下面的包。本文是2014年写的,你看到时,可能有更新的包可以使用,所以你可以使用更新的包。
$ wget http://ftpmirror.gnu.org/binutils/binutils-2.24.tar.gz
$ wget http://ftpmirror.gnu.org/gcc/gcc-4.9.2/gcc-4.9.2.tar.gz
$ wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.17.2.tar.xz
$ wget http://ftpmirror.gnu.org/glibc/glibc-2.20.tar.xz
$ wget http://ftpmirror.gnu.org/mpfr/mpfr-3.1.2.tar.xz
$ wget http://ftpmirror.gnu.org/gmp/gmp-6.0.0a.tar.xz
$ wget http://ftpmirror.gnu.org/mpc/mpc-1.0.2.tar.gz
$ wget ftp://gcc.gnu.org/pub/gcc/infrastructure/isl-0.12.2.tar.bz2
$ wget ftp://gcc.gnu.org/pub/gcc/infrastructure/cloog-0.18.1.tar.gz
最开始的四个包:binutils、gcc、Linux kernel和glibc是主要要用的包。后面三个mpfr、gmp、mpc你可以使用系统自带的package manager安装,但是可能比较旧。最后两个包ISL和CLooG是可选的,它们支持一些优化。
二、它们是怎么有机结合在一起的呢?
当我们完成时,我们会构建如下的程序和库。首先,构建左边的工具,然后使用这些工具来构建右边的程序和库。我们将不构建目标系统的Linux kernel,但是我们需要kernel header来构建目标系统标准C库。
左边的编译器将调用汇编器和链接器。其他的包MPFR、GMP和MPC将连接到编译器中。
右边图中的a.out,运行在目标OS上,使用交叉编译器,连接目标系统标准C库和C++库。C++标准库调用标准C库,C库直接调用Linux kernel。
注意,除了使用glibc外,还可以使用其他替代的C库实现,比如Newlibc,uclibc等,其他替代的C库用在嵌入式中较多,比glibc库要小,功能没有glibc全面。
三、构建步骤
解压所有的包。
$ for f in *.tar*; do tar xf $f; done
创建一些其他目录的符号连接,这5个包依附于gcc,如果存在符号连接,gcc脚本将自动build这些包。
$ cd gcc-4.9.2
$ ln -s ../mpfr-3.1.2 mpfr
$ ln -s ../gmp-6.0.0 gmp
$ ln -s ../mpc-1.0.2 mpc
$ ln -s ../isl-0.12.2 isl
$ ln -s ../cloog-0.18.1 cloog
$ cd ..
选择一个安装路径,确保有写的权限。下面步骤中,我将安装新的工具链到/opt/cross
$ sudo mkdir -p /opt/cross
$ sudo chown jeff /opt/cross
整个构建过程中,确保安装的bin子目录在你的PATH环境变量中。后续你可以从PATH中移除该目录,但是大部分构建步骤将会默认通过PATH来查找aarch64-linux-gcc和其他host工具。
$ export PATH=/opt/cross/bin:$PATH
注意/opt/cross/aarch64-linux/目录下的文件。该目录被认为是虚拟的aarch64 linux目标系统的根目录。理论上,可以使用里面所有的头文件和库。
1. Binutils
构建binutils、安装交叉汇编器、链接器和其他工具的步骤:
$ mkdir build-binutils
$ cd build-binutils
$ ../binutils-2.24/configure --prefix=/opt/cross --target=aarch64-linux --disable-multilib
$ make -j4
$ make install
$ cd ..
我们制定aarch64-linux作为目标系统类型,binutils的配置脚本将识别到它与正在进行编译的主机系统不一样,配置一个交叉汇编器和交叉链接器。这些工具将安装到/opt/cross/bin,名字以arm-linux-开头。
--disable-multilib意味着我们只希望我们的程序和库使用aarch64指令集,而不使用aarch32的指令集。
2. Linux Kernel Headers
将Linux kernel 头文件安装到/opt/cross/aarch64-linux/include, 使用新工具链构建的程序会调用这些目标环境中的aarch64 kernel。
$ cd linux-3.17.2
$ make ARCH=arm64 INSTALL_HDR_PATH=/opt/cross/aarch64-linux headers_install
$ cd ..
我们也可以在构建binutils之前做这件事。
尽管第4步,configure脚本期望linux kernel header依据安装,但是实际上在步骤6之前,当我们编译标准C库时,linux kernel headers不会被用到。
因为Linux kernel和其他开源工程不一样,它有一个不同的方式来识别目标CPU架构: ARCH=arm
剩下的步骤涉及构建GCC和Glibc。这里有个道道,就是部分gcc需要部分glibc被构建,而部分glibc又需要gcc被构建。我们不能一步搞定这些编译,而是要分成几步。我们要这几个包之间来往几次。
3. C/C++ Compilers
该步将构建gcc的C和C++编译器,并安装到/opt/cross/bin,目前还不能引用这些编译器来构建库。
$ mkdir -p build-gcc
$ cd build-gcc
$ ../gcc-4.9.2/configure --prefix=/opt/cross --target=aarch64-linux --enable-languages=c,c++ --disable-multilib
$ make -j4 all-gcc
$ make install-gcc
$ cd ..
因为我们指定了--target=aarch64-linux,构建脚本会依据aarch64-linux-前缀查找第一步安装的binutils工具。同样,C/C++编译器的名字也会带上aarch64-linux-前缀。
--enable-languages=c,c++避免了在GCC套件中出现其他的编译器,比如Fortran、Java等。
4. Standard C Library Headers and Startup Files
安装标准C库头文件到/opt/cross/aarch64-linux/include。我们会使用第三步构建的C编译器来编译库的startup files并安装到/opt/cross/aarch64-linux/lib。最后我们创建几个傀儡文件,libc.so和stubs.h,在第5步会用到,但是第6步会替换为真的。
$ mkdir -p build-glibc
$ cd build-glibc
$ ../glibc-2.20/configure --prefix=/opt/cross/aarch64-linux --build=$MACHTYPE --host=aarch64-linux --target=aarch64-linux --with-headers=/opt/cross/aarch64-linux/include --disable-multilib libc_cv_forced_unwind=yes
$ make install-bootstrap-headers=yes install-headers
$ make -j4 csu/subdir_lib
$ install csu/crt1.o csu/crti.o csu/crtn.o /opt/cross/aarch64-linux/lib
$ aarch64-linux-gcc -nostdlib -nostartfiles -shared -x c /dev/null -o /opt/cross/aarch64-linux/lib/libc.so
$ touch /opt/cross/aarch64-linux/include/gnu/stubs.h
$ cd ..
--prefix=/opt/cross/aarch64-linux告诉configure脚本它要安装头文件和库到这里。注意这个和普通的--prefix。
Glibc的configure需要我们制定所有的--build,--host和--target系统类型。
$MACHTYPE是一个预定义的环境变量,表示正在运行build脚本的机器。--build=$MACHTYPE是必须的,因为在第六步中,该build脚本将编译一些额外的工具,这些工具是build进程的一部分。
--host,在glibc的configure中,--host和--target选项都指glibc库最终运行的系统。
我们手动安装C库startup文件,ctr1.o ctri.o和ctrn.o。其他的方法好像都有副作用。
5. Compiler Support Library
使用第三步的交叉编译器构建compiler support library,编译器支持的库包含一些C++异常处理样板代码等等。该库依赖第四步安装的startup file。第六步需要该库。不像其他的指导手册,我们不需要重新运行gcc configure。只需要在相同配置下构建额外的target即可。
$ cd build-gcc
$ make -j4 all-target-libgcc
$ make install-target-libgcc
$ cd ..
两个静态库,libgcc.a和libgcc_eh.a,安装到/opt/cross/lib/gcc/aarch64-linux/4.9.2.
共享库,lingcc_s.so,安装到/opt/cross/aarch64-linux/lib64.
6. Standard C Library
这步完成glibc的安装。安装标准C库到/opt/cross/aarch64-linux/lib中。静态库名字为libc.a,动态库为libc.so
$ cd build-glibc
$ make -j4
$ make install
$ cd ..
7. Standard C++ Library
最后完成gcc的安装,构建标准C++库,安装到/opt/cross/aarch64-linux/lib64。它依赖第六步的C库。目标静态库名字为libstdc++.a,动态库为libstdc++.so。
$ cd build-gcc
$ make -j4
$ make install
$ cd ..
How to Build a GCC Cross-Compiler
http://preshing.com/20141119/how-to-build-a-gcc-cross-compiler/