如何构建GCC交叉编译器

Posted chocolate2018

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何构建GCC交叉编译器相关的知识,希望对你有一定的参考价值。

如何构建GCC交叉编译器

GCC不仅仅是一个编译器。它是一个开源项目,可以让你构建各种编译器。有些编译器支持多线程;一些支持共享库;一些支持multilib。这完全取决于在构建编译器之前如何配置它。
本指南将演示如何构建交叉编译器,交叉编译器是为另一台机器构建程序的编译器。您所需要的只是一个类unix环境,其中已经安装了GCC的最新版本。

在本指南中,我将使用Debian Linux为AGARC64构建一个完整的C++交叉编译器,这是一个64位的指令集,可在最新的ARM处理器中使用。我实际上没有AArch64设备——我只是想要一个AArch64编译器来验证这个bug。
必需的程序包
从一个干净的Debian系统开始,您必须首先安装几个软件包:

$ sudo apt-get install g++ make gawk

其他一切都将从源头构建。在某处创建一个新目录,并下载以下源包。(如果您以后遵循这个指南,每个包都会有更最新的版本。通过将每个URL粘贴到浏览器中而不带文件名来检查更新版本。例如: http://ftpmirror.gnu.org/binutils/)

$ 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内核和Glibc)是主要的。我们本可以使用系统的包管理器以二进制形式安装接下来的三个包,但这往往会提供较旧的版本。最后两个包,ISL 和CLooG,是可选的,但是它们可以在我们将要构建的编译器中进行更多的优化。
各部分如何组合在一起
当我们完成的时候,我们已经建立了下面的每个程序和库。首先,我们将构建左侧的工具,然后使用这些工具构建右侧的程序和库。我们实际上不会构建目标系统的Linux内核,但是我们确实需要内核头文件来构建目标系统的标准C库。

左边的编译器将调用汇编器和链接器作为其工作的一部分。我们下载的所有其他包,如MPFR、GMP和MPC,都将链接到编译器本身。
右边的图表代表了在目标操作系统上运行的示例程序a.out,它使用交叉编译器构建,并与目标系统的标准C和c++库相链接。标准c++库调用标准C库,C库直接对AArch64 Linux内核进行系统调用。
注意,与使用Glibc作为标准C库实现不同,我们可以使用Newlib,这是另一种实现。Newlib是嵌入式设备的一个流行的C库实现。与Glibc不同,Newlib不需要目标系统上有一个完整的操作系统——只需要一个名为Libgloss的瘦硬件抽象层。Newlib没有定期发布;相反,您应该直接从Newlib CVS存储库中提取源代码。Newlib的一个限制是,目前它似乎不支持为AArch64构建多线程程序。这就是为什么我在这里不使用它的原因。
构建步骤
提取所有源程序包。

$ for f in *.tar*; do tar xf $f; done

创建从GCC目录到其他一些目录的符号链接。这五个包是GCC的依赖项,当符号链接出现时,GCC的构建脚本将自动构建它们。

$ 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找到aarch64-linux-gcc和其他主机工具。

$ export PATH=/opt/cross/bin:$PATH

请特别注意安装在/opt/cross/aarch64-linux/下的内容。此目录被认为是虚构的AArch64 Linux目标系统的系统根。理论上,自托管的AArch64 Linux编译器可以使用此处放置的所有头文件和库。显然,为主机系统构建的程序(如交叉编译器本身)都不会安装到此目录中。

  1. 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的 configure 脚本会认识到这个目标与我们所构建的机器不同,并因此配置一个交叉汇编器和交叉链接器。这些工具将被安装到/opt/cross/bin目录下,它们的名字前缀为aarch64-linux-。
• --disable-multilib 意味着我们只希望我们的Binutils安装使用AArch64指令集的程序和库,而不是任何相关指令集,如AArch32。
2. Linux内核头文件
这个步骤将Linux内核头文件安装到/opt/cross/aarch64-linux/include,这将最终允许使用我们的新工具链构建的程序在目标环境中对AArch64内核进行系统调用。

$ cd linux-3.17.2
$ make ARCH=arm64 INSTALL_HDR_PATH=/opt/cross/aarch64-linux headers_install
$ cd ..

•我们甚至可以在安装Binutils之前完成此操作。
•Linux内核头文件直到第6步(我们构建标准C库时)才会真正使用,尽管第4步中的configure脚本预计它们已经安装。
•因为Linux内核是一个不同于其他内核的开源项目,所以它有一种不同的方法来识别目标CPU体系结构:ARCH=arm64
剩下的所有步骤都涉及到构建GCC和Glibc。关键在于GCC的某些部分依赖于Glibc的某些部分,反之亦然。我们不能在一个步骤中构建任何一个包;我们需要在两个包之间来回切换,以满足它们的依赖关系的方式构建它们的组件。

3.C / c++编译器
这一步将只构建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,构建脚本会查找我们在步骤1中构建的以aarch64-linux-前缀命名的Binutils交叉工具。同样,C/C++编译器的名称将以aarch64-linux-作为前缀。
•–enable-languages=c,c++阻止GCC套件中的其他编译器,如Fortran, Go或Java被构建。
4. 标准C库头文件和启动文件
在这一步中,我们将Glibc的标准C库头文件安装到/opt/cross/aarch64-linux/include。我们还使用步骤3中内置的C编译器来编译库的启动文件,并将它们安装到/opt/cross/aarch64-linux/lib。最后,我们创建两个dummy文件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 告诉Glibc的configure脚本,它应该在哪里安装头和库。注意,它与通常的–prefix不同。
•尽管有一些矛盾的信息,Glibc的configure 脚本目前要求我们指定所有三种类型–build, --host 和 --target系统类型。
M A C H T Y P E 是 一 个 预 定 义 的 环 境 变 量 , 它 描 述 运 行 构 建 脚 本 的 计 算 机 − − b u i l d = MACHTYPE是一个预定义的环境变量,它描述运行构建脚本的计算机 --build= MACHTYPEbuild=MACHTYPE是必需的,因为在步骤6中,构建脚本将编译一些作为构建过程本身一部分运行的其他工具。
•–host在这里的意义与我们迄今为止使用的不同。在Glibc的配置中,–host和–target选项都用来描述Glibc库最终运行的系统。
•我们手动将C库的启动文件crt1.o、crti.o和crtn.o安装到安装目录。似乎没有一个规则在没有其他副作用的情况下做到这一点。
5.编译器支持库
此步骤使用步骤3中构建的交叉编译器来构建编译器支持库。编译器支持库包含一些C++异常处理样板代码,等等。此库取决于步骤4中安装的启动文件。步骤6中需要库本身。与其他指南不同,我们不需要重新运行GCC的配置。我们只是在同一配置中构建额外的目标。

$ cd build-gcc
$ make -j4 all-target-libgcc
$ make install-target-libgcc
$ cd ..

•在/opt/cross/lib/gcc/aarch64-linux/4.9.2/中安装了两个静态库libgcc.a 和 libgcc_eh.a。
•共享库libgcc_s.so安装到/opt/cross/aarch64-linux/lib64。
6.标准C库
在这一步中,我们完成了Glibc包,它构建了标准C库并将其文件安装到 /opt/cross/aarch64-linux/lib/。静态库名为libc.a,共享库名为libc.so。

$ cd build-glibc
$ make -j4
$ make install
$ cd ..

7、标准C++库
最后,我们完成了GCC包,它构建了标准的C++库并安装到/opt/cross/aarch64-linux/lib64/。它取决于步骤6中构建的C库。生成的静态库名为libstdc++.a,共享库为 libstdc++.so。

$ cd build-gcc
$ make -j4
$ make install
$ cd ..

处理构建错误
如果在构建过程中遇到任何错误,有三种可能:

  1. 您在构建系统中缺少一个必需的包或工具。
  2. 您试图以不正确的顺序执行构建步骤。
  3. 您所做的一切都是正确的,但是在您试图构建的配置中有一些东西被破坏了。
    您必须检查构建日志以确定哪种情况适用。GCC支持很多配置,其中一些可能不会立即构建。一个配置越不受欢迎,它被破坏的可能性就越大。作为一个开源项目,GCC依赖于其用户的贡献来保持每个配置工作。
    自动化上述步骤
    我已经编写了一个名为build_cross_gcc的小型bash脚本来执行上述所有步骤。你可以在GitHub上找到它。在我的Core 2 Quad Q9550 Debian机器上,从开始到结束需要13分钟。在跑步前根据自己的喜好定制。

build_cross_gcc也支持Newlib配置。当您构建基于newlib的交叉编译器时,上面的步骤4、5和6可以合并为一个单独的步骤。(事实上,许多现有指南都是这样做的。)为了支持Newlib,编辑脚本选项如下:

TARGET=aarch64-elf
USE_NEWLIB=1
CONFIGURATION_OPTIONS="--disable-multilib --disable-threads"

构建GCC交叉编译器的另一种方法是使用组合树,其中Binutils, GCC和Newlib的源代码被合并到单个目录中。只有当GCC和Binutils捆绑的intl和lilibty库相同时,组合树才能工作,而本文使用的版本不是这样的。组合树也不支持Glibc,所以它不适合这种配置。
有一些流行的构建脚本,即crosstool-NG 和 EmbToolkit,它们可以自动构建交叉编译器的整个过程。我使用crosstool-NG得到了混合的结果,但是它帮助我在编写本指南时理解构建过程。
测试交叉编译器
如果一切构建成功,让我们检查交叉编译器是否有拨码音:

$ aarch64-linux-g++ -v
Using built-in specs.
COLLECT_GCC=aarch64-linux-g++
COLLECT_LTO_WRAPPER=/opt/cross/libexec/gcc/aarch64-linux/4.9.2/lto-wrapper
Target: aarch64-linux
Configured with: ../gcc-4.9.2/configure --prefix=/opt/cross --target=aarch64-linux --enable-languages=c,c++ --disable-multilib
Thread model: posix
gcc version 4.9.2 (GCC)

我们可以从前面的帖子编译C++ 14程序,然后分解它:

$ aarch64-linux-g++ -std=c++14 test.cpp
$ aarch64-linux-objdump -d a.out
...
0000000000400830 <main>:
  400830:       a9be7bfd        stp     x29, x30, [sp,#-32]!
  400834:       910003fd        mov     x29, sp
  400838:       910063a2        add     x2, x29, #0x18
  40083c:       90000000        adrp    x0, 400000 <_init-0x618>
  ...

这是我第一次尝试构建交叉编译器。我写这个指南基本上是为了记住我学到的东西。我认为上面的步骤可以作为构建其他配置的一个很好的模板;我也使用build_cross_gcc来构建TARGET=powerpc-eabi。您可以从任何包中浏览 config.sub ,以查看支持哪些其他目标环境。

以上是关于如何构建GCC交叉编译器的主要内容,如果未能解决你的问题,请参考以下文章

交叉编译时如何使用外部库?

构建GCC作为Raspberry-Pi的交叉编译器

构建GCC作为Raspberry-Pi的交叉编译器

构建用于操作系统开发的GCC交叉编译器

如何构建MIPS交叉编译工具链

如何在 ARM 交叉编译时选择要链接的静态库?