2.2.5 编译freestanding的交叉编译器
正如我们前面讨论的,因为编译器和C库之间循环依赖的问题,我们需要找到一个办法解决这个鸡和蛋的问题。幸运的是,C编译器提供了一个freestanding的实现,即一个不依赖C库的编译器。那么如何编译一个freestanding的编译器呢?
GCC提供了一个编译选项—with-newlib。这是一个让人困惑的C库参数,因为newlib本身就是一套C库的实现,所以容易让人误解为工具链中使用的C库是newlib,而不是其他的C库。事实上,在构建交叉编译器时,其有着特殊的意义,文件configure.ac中的注释解释得很清楚。
注释中说明,在构建交叉编译器且尚未安装C库头文件的情况下,需要定义变量inhibit_libc。一旦定义了该变量,将去掉libgcc库中对C库的一切依赖,转而使用GCC内部的实现。如下面的代码片段:
我们看到,如果没有定义inhibit_libc,则libgcc库中可能会包含link.h,而这恰恰是glibc提供的头文件。
换句话说,我们可以通过将变量inhibit_libc赋值为true,告诉GCC编译为freestanding实现。但是,遗憾的是,GCC并没有暴露一个直观的配置选项供配置时设置这个变量,相反需要通过另外相关的变量来控制变量inhibit_libc的值。
再次回顾文件gcc-4.7.2/gcc/configure.ac中的关于定义inhibit_libc的条件语句部分,if中的条件如下:
1)如果是交叉编译且未设置—with-sysroot,或者设置了—with-newlib。
2)没有设置—with-headers。
对于条件1),因为我们使用了sysroot的方式,所以要满足第一个条件,就需要设置—with-newlib。对于条件2),因为我们没有指定头文件,所以自然成立。
看了前面的讨论,相信读者就比较清楚—with-newlib的意义了,使用—with-newlib并不是强行指定GCC使用newlib实现的C库。我们无从考究参数—with-newlib的出处,但是因为newlib的初衷就是作为freestanding环境中的C库,或许这个参数的名称来源于此。
下面,我们开始编译用于freestanding环境的gcc编译器,首先解开源码包:
GCC依赖包括浮点计算、复数计算的几个数学库GMP、MPFR和MPC。可以先单独编译这些库,然后通过GCC的配置选项如—with-mpc、—with-mpfr、—with-gmp告知GCC这几个库的位置。也可以将这几个库的源码解压到GCC的源码目录下,在编译时,GCC会自动探测并编译。这里我们采用后者;
GCC要求在单独的目录编译,因此我们创建编译目录gcc-build,配置如下:
下面介绍各个配置参数的意义。
❑—prefix=$CROSS_GCC_TMP:freestanding的GCC与最终的hosted的GCC还是有些差别的,这里的freestanding的GCC只是一个临时的GCC,并不会用作最终的交叉编译器。所以,为了避免污染最后的工具链,这里将freestanding的GCC安装在一个临时的目录$CROSS_GCC_TMP中。
❑—target=$TARGET:与在Binutils中指定参数—target同样的道理,告诉编译脚本构建的预处理器、编译器等是运行在本机上的,但是最后编译的程序或库是运行在目标体系结构$TARGET上的,即构建交叉编译器。
❑—with-sysroot=$SYSROOT:配置参数—with-sysroot告诉GCC目标系统的根文件系统存放在$SYSROOT目录下,编译时到$SYSROOT目录下查找目标系统的头文件以及库。
❑—enable-languages=c:编译C库只需要C编译器,所以这个临时的freestanding编译器只支持C编译器。而且像C++编译器,即使想编译也是有心无力,因为其依赖目标系统的C库,所以目前也没有条件进行编译。
❑—disable-shared:除了编译器外,软件包GCC中也包含有一个运行时库libgcc。该库主要包括一些目标处理器不支持的数学运算、异常处理,以及一些小的比较复杂的便利函数。在默认情况下,会既编译libgcc的静态库版本,也编译动态库版本。但是动态库与静态库不同,加载器在加载动态库后需要进行一些初始化,比如初始化变量,而这些相关的代码是在C库的启动文件中实现的,包括crt1.o、crti.o等,因此,编译libgcc的动态版本时将会链接启动文件。但是此时目标机器的C库尚未编译,链接将发生类似“找不到crt1.o文件”的错误。因此,这里通过配置选项—disable-shared告诉编译脚本不要编译libgcc的动态库,仅编译静态库。
❑—with-mpfr-include和—with-mpfr-lib:对于MPFR这个库,其目录结构与GCC的默认设定有一些差异,因此我们需要明确指定,否则编译时会报找不到libmpfr的错误。这就是配置时指定配置选项—with-mpfr-include和—with-mpfr-lib的原因。
另外我们还通过形如—disable-xxx这样的参数禁止了一些库的编译,也关闭了编译器的一些特性,因为目前这个freestanding的交叉编译器根本不需要这些特性,我们只需要一个基本的能够将C库中的代码翻译为目标机器的指令这样一个基本的编译器即可。而且,最重要的是,某些库和特性中可能会依赖C库,因此,临时的freestanding编译器不支持不必要的特性,也不编译不必要的库。
编译完成后,使用如下命令进行安装:
在使用—disable-shared禁止编译libgcc的动态库后,GCC的编译脚本将不再编译库libgcc_eh.a。但是后面编译Glibc时,Glibc将链接libgcc_eh.a,Glibc的Thread cancellation使用了GCC中的异常处理部分的实现,这里eh就是exception handling的缩写。我们可以直接修改Glibc中的Makeconfig文件,或者通过建一个指向libgcc.a的符号链接libgcc_eh.a来解决这个问题。因为libgcc.a中包含libgcc_eh.a所包含的全部内容。我们采用后者来解决这个问题。