5.Compiling a cross toolchain
5.1 Introduction
本章展示如何构建交叉编译器和相关工具。尽管本书中的交叉编译是伪装的,但其原理和构建真实的交叉工具链是一致的。
本章中编译的程序会被安装在 $LFS/tools 目录中,以将它们和后续章节中安装的文件分开。但是,本章中编译的库会被安装到它们的最终位置,因为这些库在我们最终要构建的系统中也存在。
5.2 Binutils-2.40 - Pass 1
Binutils 包含汇编器、链接器以及其他用于处理目标文件的工具。
估计构建时间: 1 SBU
需要硬盘空间: 639 MB
5.2.1 安装交叉工具链中的 Binutils
注意
返回并重新阅读编译过程的一般说明一节。仔细理解那些标为“重要”的说明,以防止之后出现问题。
首先构建 Binutils 相当重要,因为 Glibc 和 GCC 都会对可用的链接器和汇编器进行测试,以决定可以启用它们自带的哪些特性。
Binutils 文档推荐创建一个新的目录,以在其中构建 Binutils:
mkdir -v build
cd build
注意
为了衡量本书其余部分使用的 SBU 值,需要测量本软件包从配置开始直到第一次安装花费的时间。为了容易地完成测量,可以将命令包装在 time 命令中,就像这样:time { ../configure … && make && make install; }。
现在,准备编译 Binutils:
../configure --prefix=$LFS/tools \
--with-sysroot=$LFS \
--target=$LFS_TGT \
--disable-nls \
--enable-gprofng=no \
--disable-werror
配置选项的含义:
–prefix=$LFS/tools
这告诉配置脚本准备将 Binutils 程序安装在 $LFS/tools 目录中。
–with-sysroot=$LFS
该选项告诉构建系统,交叉编译时在 $LFS 中寻找目标系统的库。
–target=$LFS_TGT
由于 LFS_TGT 变量中的机器描述和 config.guess 脚本的输出略有不同, 这个开关使得 configure 脚本调整 Binutils 的构建系统,以构建交叉链接器。
–disable-nls
该选项禁用临时工具不需要的国际化功能。
–enable-gprofng=no
该选项禁用临时工具不需要的 gprofng 工具。
–disable-werror
该选项防止宿主系统编译器警告导致构建失败。
然后编译该软件包:
make
安装该软件包:
make install
该软件包的更多信息可以在第 8.18.2 节 “Binutils 的内容”中找到。
5.3 GCC-12.2.0 - Pass 1
GCC 软件包包含 GNU 编译器集合,其中有 C 和 C++ 编译器。
估计构建时间: 3.3 SBU
需要硬盘空间: 3.8 GB
5.3.1 安装交叉工具链中的 GCC
GCC 依赖于 GMP、MPFR 和 MPC 这三个包。由于宿主发行版未必包含它们,我们将它们和 GCC 一同构建。将它们都解压到 GCC 源码目录中,并重命名解压出的目录,这样 GCC 构建过程就能自动使用它们:
注意
对于本章内容有一些很常见的误解。该软件包的构建过程就像之前 (软件包构建说明) 解释的那样,首先,解压 gcc-12.2.0 压缩包,然后切换到解压出的目录中。之后才能执行后续的命令。
tar -xf ../mpfr-4.2.0.tar.xz
mv -v mpfr-4.2.0 mpfr
tar -xf ../gmp-6.2.1.tar.xz
mv -v gmp-6.2.1 gmp
tar -xf ../mpc-1.3.1.tar.gz
mv -v mpc-1.3.1 mpc
对于 x86_64 平台,还要设置存放 64 位库的默认目录为 “lib”:
case $(uname -m) in
x86_64)
sed -e '/m64=/s/lib64/lib/' \
-i.orig gcc/config/i386/t-linux64
;;
esac
GCC 文档建议在一个新建的目录中构建 GCC:
mkdir -v build
cd build
准备编译 GCC:
../configure \
--target=$LFS_TGT \
--prefix=$LFS/tools \
--with-glibc-version=2.37 \
--with-sysroot=$LFS \
--with-newlib \
--without-headers \
--enable-default-pie \
--enable-default-ssp \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-threads \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
--enable-languages=c,c++
配置选项的含义:
–with-glibc-version=2.37
该选项指定目标系统将要使用的 Glibc 版本。这与宿主系统的 libc 没有关系,因为第一遍的 GCC 产生的所有代码都会在与宿主系统的 libc 完全隔离的 chroot 环境中运行。
–with-newlib
由于现在没有可用的 C 运行库,使用该选项保证构建 libgcc 时 inhibit_libc 常量被定义,以防止编译任何需要 libc 支持的代码。
–without-headers
在创建完整的交叉编译器时,GCC 需要与目标系统兼容的标准头文件。由于我们的特殊目的,这些头文件并不必要。这个开关防止 GCC 查找它们。
–enable-default-pie 和 –enable-default-ssp
它们使得 GCC 在编译程序时默认启用一些增强安全性的特性 (详见第 8 章中的关于 PIE 和 SSP 的说明)。在本阶段并没有使用它们的必要性,但是尽早使用它们能够使得临时安装和最终安装的软件包更相近,这样构建过程更加稳定。
–disable-shared
这个开关强制 GCC 静态链接它的内部库。我们必须这样做,因为动态库需要目标系统中尚未安装的 Glibc。
–disable-multilib
在 x86_64 平台上,LFS 不支持 multilib 配置。这个开关对于 x86 来说可有可无。
–disable-threads, –disable-libatomic, –disable-libgomp, –disable-libquadmath, –disable-libssp, –disable-libvtv, –disable-libstdcxx
这些开关禁用对于线程、libatomic、libgomp、libquadmath、libssp、libvtv,以及 C++ 标准库的支持。在构建交叉编译器时它们可能编译失败,而且在交叉编译临时 libc 时并不需要它们。
–enable-languages=c,c++
这个选项保证只构建 C 和 C++ 编译器。目前只需要这两个语言。
执行以下命令编译 GCC:
make
安装该软件包:
make install
刚刚构建的 GCC 安装了若干内部系统头文件。其中的 limits.h 一般来说,应该包含对应的系统头文件 limits.h,在我们的 LFS 环境中,就是 $LFS/usr/include/limits.h。然而,在构建 GCC 的时候,$LFS/usr/include/limits.h 还不存在,因此 GCC 安装的内部头文件是一个不完整的、自给自足的文件,不包含系统头文件提供的扩展特性。这对于构建临时的 Glibc 已经足够了,但后续工作将需要完整的内部头文件。使用以下命令创建一个完整版本的内部头文件,该命令与 GCC 构建系统在一般情况下生成该头文件的命令是一致的:
注意
下列命令作为实例,展示了命令行代换操作的两种不同写法:反引号和 $() 结构。可以将该命令改写为使用一种写法完成两次代换,但我们这里特意展示如何混用两种写法。一般来说 $() 这种写法更常用。
cd ..
cat gcc/limitx.h gcc/glimits.h gcc/limity.h > \
`dirname $($LFS_TGT-gcc -print-libgcc-file-name)`/install-tools/include/limits.h
该软件包的详细信息在第 8.26.2 节 “GCC 的内容”可以找到。
5.4 Linux-6.1.11 API Headers
Linux API 头文件 (在 linux-6.1.11.tar.xz 中) 导出内核 API 供 Glibc 使用。
估计构建时间: 不到 0.1 SBU
需要硬盘空间: 1.5 GB
5.4.1 安装 Linux API 头文件
Linux 内核需要导出一个应用程序编程接口 (API) 供系统的 C 运行库 (例如 LFS 中的 Glibc) 使用。这通过净化内核源码包中提供的若干 C 头文件完成。
确保软件包中没有遗留陈旧的文件:
make mrproper
下面从源代码中提取用户可见的头文件。我们不能使用推荐的 make 目标“headers_install”,因为它需要 rsync,这个程序在宿主系统中未必可用。头文件会先被放置在 ./usr 目录中,之后再将它们复制到最终的位置。
make headers
find usr/include -type f ! -name '*.h' -delete
cp -rv usr/include $LFS/usr
5.4.2 Linux API 头文件的内容
安装的头文件: /usr/include/asm/.h, /usr/include/asm-generic/.h, /usr/include/drm/.h, /usr/include/linux/.h, /usr/include/misc/.h, /usr/include/mtd/.h, /usr/include/rdma/.h, /usr/include/scsi/.h, /usr/include/sound/.h, /usr/include/video/.h, 以及 /usr/include/xen/*.h
安装的目录: /usr/include/asm, /usr/include/asm-generic, /usr/include/drm, /usr/include/linux, /usr/include/misc, /usr/include/mtd, /usr/include/rdma, /usr/include/scsi, /usr/include/sound, /usr/include/video, 以及 /usr/include/xen
简要描述
/usr/include/asm/*.h Linux API 汇编头文件
/usr/include/asm-generic/*.h Linux API 通用汇编头文件
/usr/include/drm/*.h Linux API DRM 头文件
/usr/include/linux/*.h Linux API Linux 头文件
/usr/include/misc/*.h Linux API 杂项头文件
/usr/include/mtd/*.h Linux API MTD 头文件
/usr/include/rdma/*.h Linux API RDMA 头文件
/usr/include/scsi/*.h Linux API SCSI 头文件
/usr/include/sound/*.h Linux API 音频头文件
/usr/include/video/*.h Linux API 视频头文件
/usr/include/xen/*.h Linux API Xen 头文件
5.5 Glibc-2.37
Glibc 软件包包含主要的 C 语言库。它提供用于分配内存、检索目录、打开和关闭文件、读写文件、字符串处理、模式匹配、算术等用途的基本子程序。
估计构建时间: 1.5 SBU
需要硬盘空间: 822 MB
5.5.1 安装 Glibc
首先,创建一个 LSB 兼容性符号链接。另外,对于 x86_64,创建一个动态链接器正常工作所必须的符号链接:
case $(uname -m) in
i?86) ln -sfv ld-linux.so.2 $LFS/lib/ld-lsb.so.3
;;
x86_64) ln -sfv ../lib/ld-linux-x86-64.so.2 $LFS/lib64
ln -sfv ../lib/ld-linux-x86-64.so.2 $LFS/lib64/ld-lsb-x86-64.so.3
;;
esac
注意: 以上命令是正确的。ln 命令有多种语法变式,因此在报告看似错误的命令之前,请先阅读 info coreutils ln 和 ln(1)。
一些 Glibc 程序使用与 FHS 不兼容的 /var/db 目录存放它们的运行时数据。应用一个补丁,使得这些程序在 FHS 兼容的位置存放运行时数据:
patch -Np1 -i ../glibc-2.37-fhs-1.patch
Glibc 文档推荐在一个新建的目录中构建 Glibc:
mkdir -v build
cd build
确保将 ldconfig 和 sln 工具安装到 /usr/sbin 目录中:
echo "rootsbindir=/usr/sbin" > configparms
下面,准备编译 Glibc:
../configure \
--prefix=/usr \
--host=$LFS_TGT \
--build=$(../scripts/config.guess) \
--enable-kernel=3.2 \
--with-headers=$LFS/usr/include \
libc_cv_slibdir=/usr/lib
配置选项的含义:
–host=$LFS_TGT, –build=$(../scripts/config.guess)
在它们的共同作用下,Glibc 的构建系统将自身配置为使用 $LFS/tools 中的交叉链接器和交叉编译器,进行交叉编译。
–enable-kernel=3.2
该选项告诉 Glibc 编译出支持 3.2 版或者更新的 Linux 内核,这样就不会使用那些为更老内核准备的替代方案。
–with-headers=$LFS/usr/include
该选项告诉 Glibc 在编译过程中,使用 $LFS/usr/include 目录中的头文件,这样它就知道内核拥有哪些特性,并据此对自身进行优化。
libc_cv_slibdir=/usr/lib
在 64 位机器上,这保证将库安装到 /usr/lib,而不是默认的 /lib64。
在当前阶段,可能出现下列警告:
configure: WARNING:
*** These auxiliary programs are missing or
*** incompatible versions: msgfmt
*** some features will be disabled.
*** Check the INSTALL file for required versions.
msgfmt 程序的缺失或不兼容一般是无害的。msgfmt 程序是 Gettext 软件包的一部分,宿主发行版应该提供它。
注意: 有报告称该软件包在并行构建时可能失败,如果发生了这种情况,加上 “-j1” 选项重新执行 make 命令。
编译该软件包:
make
安装该软件包:
警告
如果 LFS 没有正确设定,而且您不顾本书的建议,以 root 用户的身份进行构建,下面的命令会将新构建的 Glibc 安装到您的宿主系统中,这几乎必然导致宿主系统完全无法使用。因此,在运行下面的命令前,请再次检查该环境变量是否已经正确设定,并确认您并非以 root 身份操作。
make DESTDIR=$LFS install
make install 选项的含义:
DESTDIR=$LFS
多数软件包使用 DESTDIR 变量指定软件包应该安装的位置。如果不设定它,默认值为根 (/) 目录。这里我们指定将软件包安装到 $LFS,它在第 7.4 节 “进入 Chroot 环境”之后将成为根目录。
改正 ldd 脚本中硬编码的可执行文件加载器路径:
sed '/RTLDLIST=/s@/usr@@g' -i $LFS/usr/bin/ldd
小心
现在我们不可避免地要停下确认新工具链的各基本功能 (编译和链接) 能如我们所预期的那样工作。执行以下命令进行完整性检查:
echo 'int main(){}' | $LFS_TGT-gcc -xc -
readelf -l a.out | grep ld-linux
如果一切正常,那么应该没有错误消息,而且最后一行命令应该输出下列格式的内容:
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
注意,对于 32 位机器,解释器的名字将会是 /lib/ld-linux.so.2。
如果输出不像上面描述的那样,或者根本没有输出,就说明出了问题。检查并重新跟踪各个步骤,找到出问题的地方并修正它。在继续构建之前,必须解决这个问题。
检验步骤顺利完成后,清理测试文件:
rm -v a.out
注意
在下一章中,构建各软件包的过程可以作为对工具链是否正常构建的额外检查。如果 一些软件包,特别是第二遍的 Binutils 或者 GCC 不能构建,说明在之前安装 Binutils,GCC,或者 Glibc 时出了问题。
现在我们的交叉工具链已经构建完成,可以完成 limits.h 头文件的安装。为此,运行 GCC 开发者提供的一个工具:
$LFS/tools/libexec/gcc/$LFS_TGT/12.2.0/install-tools/mkheaders
该软件包的详细信息可以在第 8.5.3 节 “Glibc 的内容”中找到。
5.6 Libstdc++ from GCC-12.2.0
Libstdc++ 是 C++ 标准库。我们需要它才能编译 C++ 代码 (GCC 的一部分用 C++ 编写)。但在构建第一遍的 GCC时我们不得不暂缓安装它,因为 Libstdc++ 依赖于当时还没有安装到目标目录的 Glibc。
估计构建时间: 0.2 SBU
需要硬盘空间: 1.1 GB
5.6.1 安装目标系统的 Libstdc++
注意
Libstdc++ 是 GCC 源代码的一部分。您应该先解压 GCC 源码包并切换到解压出来的 gcc-12.2.0 目录。
为 Libstdc++ 创建一个单独的构建目录,并进入它:
mkdir -v build
cd build
准备编译 Libstdc++:
../libstdc++-v3/configure \
--host=$LFS_TGT \
--build=$(../config.guess) \
--prefix=/usr \
--disable-multilib \
--disable-nls \
--disable-libstdcxx-pch \
--with-gxx-include-dir=/tools/$LFS_TGT/include/c++/12.2.0
配置选项的含义:
–host=…
指定使用我们刚刚构建的交叉编译器,而不是 /usr/bin 中编译器。
–disable-libstdcxx-pch
这个开关防止安装预编译头文件,在这个阶段不需要它们。
–with-gxx-include-dir=/tools/$LFS_TGT/include/c++/12.2.0
该选项指定包含文件的安装路径。因为 Libstdc++ 是 LFS 的 C++ 标准库,这个安装路径应该与 C++ 编译器 ($LFS_TGT-g++) 搜索 C++ 标准头的位置一致。在正常的构建过程中,这项信息被构建系统由顶层目录自动传递给 Libstdc++ configure 脚本。但我们没有使用顶层目录构建系统,因此需要明确指定该选项。C++ 编译器会将 sysroot 路径 $LFS (我们在构建第一遍的 GCC 时指定了它) 附加到包含文件搜索目录之前,因此它实际上会搜索 $LFS/tools/$LFS_TGT/include/c++/12.2.0。该选项和后续使用的 DESTDIR 变量 (在 make install 命令中) 一起,确保将头文件安装到这一路径。
运行以下命令编译 Libstdc++:
make
安装这个库:
make DESTDIR=$LFS install
移除对交叉编译有害的 libtool 档案文件:
rm -v $LFS/usr/lib/lib{stdc++,stdc++fs,supc++}.la
该软件包的详细信息在第 8.26.2 节 “GCC 的内容”可以找到。