本节阐述了整个构建方法的一些基本原理和技术细节,您不必马上就理解本节中的所有内容。在您实际操作之后,就会了解大多数的东西,您可以在任何时候回顾本节。
第五章的总体目标是提供一个临时环境,您可以通过 chroot 进入到这个环境,在里面构建一个第六章的干净、没有问题的目标 LFS 系统。为了尽量的与宿主系统分开,我们一步步构建了一个自包含、自依赖的工具链。这个构建过程对于新手而言犯错误的风险已被降到最低,同时它也提供了尽量大的学习价值。
在继续之前,要先知道工作平台的名称,就是所谓的目标三元组(“target
triplet”)。通常情况下,目标三元组可能是 i686-pc-linux-gnu。确定该名字的简单办法就是运行许多源码包中都含有的
config.guess脚本,解压
Binutils 包运行 ./config.guess
并注意输出结果。
另外,注意工作平台的动态连接器名称,通常指的是动态加载器(不要与 Binutils 里面的标准连接器 ld 相混淆)。动态连接器由 Glibc
提供,用来找到并加载一个程序运行时所需的共享库,在做好程序运行的准备之后,运行这个程序。动态连接器的名称通常是
ld-linux.so.2
。在不怎么流行的平台上则可能是
ld.so.1
,而在新的 64
位平台上更可能是别的完全不同的名称。通过查看宿主系统的 /lib
目录可以确定动态连接器的名字。确定这个名称还有一个必杀技,就是在宿主系统上随便找一个二进制文件,运行 readelf -l <name of binary> | grep
interpreter
并查看输出的内容。涵盖所有平台的权威参考请查看 Glibc 源码根目录里的
shlib-versions
文件。
以下是关于第五章中构建方法的一些技术要点:
这个过程在原理上与交叉编译类似,通过安装在同一个目录的工具的协同工作,它还利用了一点 GNU 的“魔术”。
小心处理标准连接器的库文件搜索路径,确保程序仅连接到指定的库上。
小心处理 gcc 的
specs
文件,告诉编译器要使用哪个动态连接器。
首先安装的是 Binutils ,因为 GCC 和 Glibc 的 configure 脚本要在汇编器和连接器上执行各种各样的特性测试,以确定软件的哪些功能要启用或禁用。这样做比你想像的还要重要,配置不正确的 GCC 或者 Glibc 会导致工具链出现微妙的错误,这样的错误造成的影响可能直到整个系统快要编译完成的时候才显现出来。测试程序通常会在其它的许多工作进行之前给出错误警告 (以避免其后的无效劳动)。
Binutils 的汇编器和连接器安装在两个位置: /tools/bin
和
/tools/$TARGET_TRIPLET/bin
。其中一个程序是另外一个的硬连接。连接器的一个重要方面是它的库搜索顺序,将
--verbose
选项传递给 ld 可以获得详细的信息。比如:ld --verbose | grep SEARCH
将显示当前搜索路径和顺序。要显示 ld
连接的是哪个文件,可以编译一个伪(dummy)程序并把 --verbose
传递给连接器。例如:gcc dummy.c -Wl,--verbose 2>&1 | grep
succeeded
将显示所有连接成功的文件。
接下来安装的是 GCC 。下面是运行 GCC 的 configure 脚本时,输出内容的一个实例:
checking what assembler to use...
/tools/i686-pc-linux-gnu/bin/as
checking what linker to use... /tools/i686-pc-linux-gnu/bin/ld
基于上面提到过的原因,这是重要的步骤,它同时证明了 GCC 的配置脚本并不是搜索 PATH 里的目录来寻找要使用哪个工具的,而且在
gcc
的实际操作中也不一定会使用相同的搜索路径。想要知道 gcc 将会使用哪个标准连接器,请运行 gcc -print-prog-name=ld
。
通过在编译一个伪程序传递参数 -v
给
gcc
可以获得详细的信息。举例来说,gcc -v
dummy.c将会给出在预处理、编译和汇编各个阶段的详细信息,包括 gcc 包含的搜索路径及其顺序。
下一个安装的包是 Glibc。编译 Glibc 的时候,最需要注意的地方是编译器、Binutils 和内核头文件。编译器一般不是问题,因为
Glibc 总是使用在 PATH
目录里找到的 gcc;Binutils
和内核头文件要复杂点。因此,为慎重起见,明确使用配置开关(选项)来强制进行正确的选择。在运行 configure 脚本之后,检查在 glibc-build
目录下的 config.make
文件,注意所有重要的细节信息。CC="gcc -B/tools/bin/"
控制着使用哪一个
Binutils,而用选项 -nostdinc
和
-isystem
用来控制编译器的库文件的搜索路径。这些说明了 Glibc 的一个重要方面——在编译方法上它是高度自给自足的,通常不依赖于工具链的默认值。
在安装完 Glibc 之后,需要做一些调整来保证 /tools
是唯一的搜索和连接路径。安装一个调整好的 ld
它的硬链接搜索路径限制在 /tools/lib
。接下来修正
gcc 的 specs 文件,使它指向新的位于
/tools/lib
的动态连接器。最后一步对于整个过程至关重要,正如先前提及的,指向动态连接器的硬链接路径被嵌入到每个共享 ELF 可执行文件里。这可以通过
readelf -l <name of binary> |
grep interpreter
来查看。修正 gcc 的 specs
文件确保了现在开始到本章结束编译的所有程序都会使用位于 /tools/lib
下的新动态连接器。
在第二遍编译 GCC 时,也需要修改其源码使 GCC 使用新生成的动态连接器。不这样做的结果是 GCC 会把宿主系统
/lib
目录下动态连接器的名字嵌入进来,这样有悖于与宿主系统隔离的目标。
在第二遍编译 Binutils 时,我们可以利用参数 --with-lib-path
来控制 ld
的库文件搜索路径。如前面所指出的,核心工具链是自给自足的,第五章剩余部分的所有软件包在编译时都会使用位于
/tools
目录下的新 Glibc。
基于上述 Glibc 自给自足的特性,当我们进入 chroot 虚拟环境之后,在第六章中首先安装就是 Glibc。一旦安装好 Glibc 到
/usr
,马上更改工具链的默认值,然后继续构建目标 LFS 系统的剩余部分。