2.2 构建工具链

虽然构建的目标系统是运行在IA32体系架构上的,但是我们不能使用宿主系统的工具链,否则可能会导致目标系统依赖宿主系统。在编译程序时,如果使用了宿主系统的链接器,那么链接器将在宿主系统的文件系统中寻找依赖的动态库,这势必会导致目标系统中的程序链接宿主系统的某些库,从而导致目标系统依赖宿主系统。其直观表现就是程序在编译时可能会顺利通过,但是当在目标系统中运行时,却可能出现未定义符号的错误。

除了上述的依赖问题外,目标系统使用的工具链的各个组件的版本,通常不同于宿主系统,因此,这也要求为目标系统构建一套新的工具链。

但是工具链在软件开发中占据极其重要的位置,包括编译、汇编、链接等多个组件在内的任一组件的问题都可能导致程序执行时出现问题,如执行效率低下,甚至带来安全问题。因此,在实际应用中,很多时候我们都是直接使用已经构建好的工具链,这类工具链一般都被广泛使用,所以在某种意义上其正确性是被实践检验过的,但是也有缺点,就是没有针对具体的硬件平台进行优化。因此,有时我们也会借助某些辅助工具,针对我们的特定硬件,进行配置优化,“半自动”地为目标系统构建编译工具链。

在现实中,完全手工构建工具链的机会并不多,很多时候我们可能都是使用别人已经构建好的。但是,工具链中包含的组件可以说是除了操作系统内核之外的最底层的系统软件,无论是对理解操作系统,还是对开发程序来说,都有着重要的意义。即使永远不需自己手工编译工具链,但是了解工具链的构建过程,也可以帮助更高效灵活地运用已有的工具链,可以在多个现成的工具链中进行更好的选择,也有助于进行“半自动”地构建工具链。

2.2.1 GNU工具链组成

编译过程分为4个阶段,分别是:编译预处理、编译、汇编以及链接。每个阶段都涉及了若干工具,GNU将这些工具分别包含在3个软件包中:Binutils、GCC、Glibc。

❑Binutils:GNU将凡是与二进制文件相关的工具,都包括在软件包Binutils中。Binutils就是Binary utilities的简写,其中主要包括生成目标文件的汇编器(as),链接目标文件的链接器(ld)以及若干处理二进制文件的工具,如objdump、strip等。但是也不是Binutils中的所有的工具都是处理二进制文件的,比如处理文本文件的预编器(cpp)也包含在其中。

❑GCC:GNU将编译器包含在GCC中,包括C编译器、C++编译器、Fortran编译器、Ada编译器等。为简单起见,在本章中我们只构建C/C++编译器。GCC中还提供了C++的启动文件。

❑Glibc:C库包含在Glibc中。除了C库外,动态链接器(dynamic loder/linker)也包含在这个包中。另外这个包中还提供C的启动文件。事实上,有很多C库的实现,比如适用于Linux桌面系统的Glibc、EGlibc、uClibc;在嵌入式系统上,可以使用EGlibc或者uClibc;对于没有操作系统的系统,也就是所说的freestanding enviroment,可以选择newlib、dietlibc,或者根本就不用C库。

除了这三个软件包外,工具链中还需要包括内核头文件。用户空间中的很多操作需要借助内核来完成,但是通常用户程序不必直接和内核打交道,而是通过更易用的C库。C库中的很大一部分函数是对内核服务的封装。在某种意义上,内核头文件可以看作是内核与C库之间的协议。因此,构建C库之前,需要首先在工具链中安装内核头文件。