本节综合地解释构建方法中的逻辑和技术细节。 您现在并不需要立刻理解本节的所有内容,在实际进行构建的过程中, 可以更清晰地理解本节的信息。在整个构建过程中,您随时可以回来翻阅本节。
第 5 章 的总目标是构造一个临时环境,它包含一组可靠的, 能够与宿主系统完全分离的工具。这样,通过使用 chroot 命令,其余各章中执行的命令就被限制在这个临时环境中。 这确保我们能够干净、顺利地构建 LFS 系统。整个构建过程被精心设计, 以尽量降低新读者可能面临的风险,同时提供尽可能多的教育价值。
在继续阅读之前,请注意往往被称为目标三元组的工作平台名称。 获得它的一种简单方法是运行许多软件包附带的 config.guess 脚本。解压缩 Binutils 源码,
然后运行脚本:./config.guess
,观察输出。 例如,对于 32 位
Intel 处理器,输出应该是 i686-pc-linux-gnu,而对于 64 位系统输出应该是
x86_64-pc-linux-gnu。
另外注意平台的动态链接器的名称,它又被称为动态加载器 (不要和 Binutils 中的普通链接器 ld 混淆)。 动态链接器由 Glibc
提供,它寻找并加载程序所需的共享库, 为程序运行做好准备,然后运行程序。 在 32 位 Intel 机器上动态链接器的名称是
ld-linux.so.2
(在 64 位系统上是
ld-linux-x86-64.so.2
)。
一个确定动态链接器名称的准确方法是从宿主系统找一个二进制可执行文件, 然后执行: readelf -l <二进制文件名> | grep
interpreter
并观察输出。包含所有平台的权威参考可以在 Glibc 源码树根目录的
shlib-versions
文件中找到。
第 5 章 中构建方法的技术重点在于:
微调工作平台的名称,将目标三元组中的 “供应商” (vendor) 字段修改,得到 LFS_TGT
中的三元组。 这保证我们第一次构建 Binutils 和 GCC 时,
能够产生与宿主系统兼容的交叉链接器和交叉编译器。 它们不为其他架构生成二进制代码, 而是生成与当前硬件兼容的二进制代码。
交叉编译临时环境中的库。 由于交叉编译器从本质上不可能依赖于宿主环境中的任何东西, 我们可以降低宿主环境中头文件或库被嵌入新编译的工具的概率, 从而防止新编译的系统被污染。另外,交叉编译使得我们可能在 64 位硬件上同时编译出 32 位和 64 位的库。
小心地修改 GCC 源代码, 从而使得编译器使用我们指定的目标系统动态链接器。
我们首先安装 Binutils 。这是由于 GCC 和 Glibc 的 configure 脚本首先测试汇编器和链接器的一些特性, 以决定启用或禁用一些软件特性。初看起来这并不重要, 但没有正确配置的 GCC 或者 Glibc 可以导致工具链中潜伏的故障。 这些故障可能到整个构建过程快要结束时才突然爆发, 不过在花费大量无用功之前,测试套件的失败可以将这类错误凸显出来。
Binutils 将汇编器和链接器安装在两个位置,一个是 /tools/bin
, 另一个是 /tools/$LFS_TGT/bin
。
这两个位置中的工具互为硬链接。链接器的一个重要方面是它搜索库的顺序, 通过向 ld 命令加入 --verbose
参数,可以得到关于搜索路径的详细信息。例如,
ld --verbose | grep
SEARCH
会输出当前的搜索路径及其顺序。此外,通过编译一个样品 (dummy) 程序并向链接器传递
--verbose
参数, 可以知道哪些文件被链接。例如,
gcc dummy.c -Wl,--verbose 2>&1
| grep succeeded
将显示所有在链接过程中被成功打开的文件。
下一步安装 GCC。在执行它的 configure 脚本时, 您会看到类似下面这样的输出:
checking what assembler to use... /tools/i686-lfs-linux-gnu/bin/as
checking what linker to use... /tools/i686-lfs-linux-gnu/bin/ld
基于我们上面论述的原因,这些输出非常重要。 这说明 GCC 的配置脚本没有在 PATH 变量指定的目录中搜索工具。 然而,在
gcc 的实际运行中,
未必会使用同样的搜索路径。为了查询 gcc
会使用哪个链接器,需要执行以下命令: gcc
-print-prog-name=ld
。
通过向 gcc 传递 -v
参数,可以知道在编译样品程序时发生的细节。例如,
gcc -v dummy.c
会输出预处理、编译和汇编阶段中的详细信息,包括 gcc 的包含文件搜索路径和顺序。
下一步安装“净化的” (sanitized) Linux API 头文件。 这允许 C 标准库 (Glibc) 与 Linux 内核提供的各种特性交互。
下一步安装 Glibc 。在构建 Glibc 时最重要的考虑是编译器、 二进制工具和内核头文件。编译器一般不成问题, Glibc
总是使用传递给配置脚本的 --host
参数相关的编译器。
例如,在我们的例子中,使用的编译器是 i686-lfs-linux-gnu-gcc。
但二进制工具和内核头文件的问题比较复杂,安全起见, 我们使用配置脚本提供的开关,保证正确的选择。 在 configure 脚本运行完成后,可以检查 glibc-build
目录中的 config.make
文件,了解全部重要的细节。 注意参数 CC=i686-lfs-linux-gnu-gcc
控制构建系统使用正确的二进制工具,而参数 -nostdinc
和 -isystem
控制编译器的头文件搜索路径。 这些事项凸显了 Glibc
软件包的一个重要性质 —— 它的构建机制是相当自给自足的,不依赖于工具链默认值。
在第二次构建 Binutils 时,我们在配置时使用开关 --with-lib-path
来控制 ld 的库文件搜索路径。
在第二次构建 GCC 时,需要修改其源码,使 GCC 使用新的动态链接器。 如果不这么做,GCC 程序中就会嵌入宿主
/lib
目录中的动态链接器名称,
从而破坏我们脱离宿主环境的目标。之后,核心工具链就是自包含、 不依赖宿主的。第 5 章
中的其他软件包都将在 /tools
中新的 Glibc 基础上构建。
在进入 第 6 章 中的 chroot
环境后,由于我们之前提到的自给性质,在全部重要软件包中首先安装 Glibc。 一旦 Glibc 被安装在 /usr
目录中,我们将会简便地修改工具链默认值,然后继续构建目标 LFS 系统的剩余部分。