读书频道 > 系统 > linux > 深度探索Linux操作系统系统构建和原理解析
2.2.5 编译freestanding的交叉编译器
2013-09-28 14:31:02     我来说两句 
收藏    我要投稿   
全书一共8章:第1章介绍了如何准备工作环境。在第2章中构建了编译工具链,这是后面构建操作系统各个组件的基础。在这一章中,不仅详细讲解了工具链的构建过程,而且还通过对编译链接过程的探讨,深入讨论了工具链  立即去当当网订购

正如我们前面讨论的,因为编译器和C库之间循环依赖的问题,我们需要找到一个办法解决这个鸡和蛋的问题。幸运的是,C编译器提供了一个freestanding的实现,即一个不依赖C库的编译器。那么如何编译一个freestanding的编译器呢?

GCC提供了一个编译选项--with-newlib。这是一个让人困惑的C库参数,因为newlib本身就是一套C库的实现,所以容易让人误解为工具链中使用的C库是newlib,而不是其他的C库。事实上,在构建交叉编译器时,其有着特殊的意义,文件configure.ac中的注释解释得很清楚。
gcc-4.7.2/gcc/configure.ac
# If this is a cross-compiler that does not
# have its own set of headers then define
# inhibit_libc

# If this is using newlib, without having the headers available now,
# then define inhibit_libc in LIBGCC2_CFLAGS.
# This prevents libgcc2 from containing any code which requires libc
# support.
: ${inhibit_libc=false}
if { { test x$host != x$target && test "x$with_sysroot" = x ; } ||
       test x$with_newlib = xyes ; } &&
     { test "x$with_headers" = x || test "x$with_headers" = xno ; } ;
then
       inhibit_libc=true
fi

注释中说明,在构建交叉编译器且尚未安装C库头文件的情况下,需要定义变量inhibit_libc。一旦定义了该变量,将去掉libgcc库中对C库的一切依赖,转而使用GCC内部的实现。如下面的代码片段:
gcc-4.7.2/libgcc/crtstuff.c:
#if defined(OBJECT_FORMAT_ELF) \
    && !defined(OBJECT_FORMAT_FLAT) \
    && defined(HAVE_LD_EH_FRAME_HDR) \
    && !defined(inhibit_libc) && !defined(CRTSTUFFT_O) \
    && defined(__FreeBSD__) && __FreeBSD__ >= 7
#include <link.h>
# define USE_PT_GNU_EH_FRAME
#endif

我们看到,如果没有定义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编译器,首先解开源码包:
vita@baisheng:/vita/build$ tar xvf ../source/gcc-4.7.2.tar.bz2

GCC依赖包括浮点计算、复数计算的几个数学库GMP、MPFR和MPC。可以先单独编译这些库,然后通过GCC的配置选项如--with-mpc、--with-mpfr、--with-gmp告知GCC这几个库的位置。也可以将这几个库的源码解压到GCC的源码目录下,在编译时,GCC会自动探测并编译。这里我们采用后者;
vita@baisheng:/vita/build/gcc-4.7.2$ tar xvf \
    ../../source/gmp-5.0.5.tar.bz2
vita@baisheng:/vita/build/gcc-4.7.2$ mv gmp-5.0.5/ gmp
vita@baisheng:/vita/build/gcc-4.7.2$ tar xvf \
   ../../source/mpfr-3.1.1.tar.bz2
vita@baisheng:/vita/build/gcc-4.7.2$ mv mpfr-3.1.1/ mpfr
vita@baisheng:/vita/build/gcc-4.7.2$ tar xvf \
    ../../source/mpc-1.0.1.tar.gz
vita@baisheng:/vita/build/gcc-4.7.2$ mv mpc-1.0.1/ mpc

GCC要求在单独的目录编译,因此我们创建编译目录gcc-build,配置如下:
vita@baisheng:/vita/build$ mkdir gcc-build
vita@baisheng:/vita/build$ cd gcc-build
vita@baisheng:/vita/build/gcc-build$ ../gcc-4.7.2/configure \
    --prefix=$CROSS_GCC_TMP --target=$TARGET \
    --with-sysroot=$SYSROOT \
    --with-newlib --enable-languages=c \
    --with-mpfr-include=/vita/build/gcc-4.7.2/mpfr/src \
    --with-mpfr-lib=/vita/build/gcc-build/mpfr/src/.libs \
    --disable-shared --disable-threads \
    --disable-decimal-float --disable-libquadmath \
    --disable-libmudflap --disable-libgomp \
    --disable-nls --disable-libssp

下面介绍各个配置参数的意义。

--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编译器不支持不必要的特性,也不编译不必要的库。

编译完成后,使用如下命令进行安装:
vita@baisheng:/vita/build/gcc-build$ make
vita@baisheng:/vita/build/gcc-build$ make install

在使用--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所包含的全部内容。我们采用后者来解决这个问题。
vita@baisheng:/vita/cross-gcc-tmp$ ln -s libgcc.a \
   lib/gcc/i686-none-linux-gnu/4.7.2/libgcc_eh.a

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:2.2.4 构建二进制工具
下一篇:2.2.6 安装内核头文件
相关文章
图文推荐
3.3.6 GNOME的软件管
3.3.5 GNOME的文件管
3.3.4 GNOME的窗口管
3.3.3 收藏夹和快捷
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站