Learn from Linux from Scratch (LFS) (Part 2)

Part 1中,涉及的知识点主要集中在宿主机上准备构建环境。本篇将记录对应于书中第5章”Constructing a Temporary System”一章的从“Introduction”到“GCC-9.2.0 – Pass 2”有关内容。

为了方便阅读,几个主要的变量说明如下

LFS=/mnt/lfs
LC_ALL=POSIX
LFS_TGT=$(uname -m)-lfs-linux-gnu
PATH=/tools/bin:/bin:/usr/bin

/tools为指向$LFS/tools的符号链接。

简易的编译用时参考:Standard Build Unit(SBU)

LFS定义编译Binutils的时长为1个SBU。显然SBU并不是一个学术上很严谨的计量方法,但却很方便的给出了编译用时的估计方法。

我的系统的SBU大约是3m12.622s。按照这个估计,我完成10 SBU的”GCC-9.2.0 – Pass 1″大约需要32分。为了稍微快一点我在构建GCC-9.2.0 – Pass 1时为make添加了-j2的选项,最后make完用时18m55.776s。

为了交叉编译而设置的configure脚本的选项

--prefix=/tools:make install到自定义位置时最常见的选项。因为我们希望所有中间工具链全部生成到/tools所以所有的configure都会有这个选项。

--with-sysroot=$LFS:告知编译系统从$LFS中查找所需要的库

--target=$LFS_TGT:设置目标的架构。虽然在个人PC上,$LFS_TGT无非就是x86或者x86_64这两种情况,但是LFS明显是考虑到了PowerPC、ARM这些奇奇怪怪的架构。

--with-lib-path=/tools/lib(Binutils pass 1),--with-headers=/tools/include(Glibc pass 1),--with-local-prefix=/tools--with-native-system-header-dir=/tools/include(GCC pass 1)

用readelf确定interpreter

现在我们要接触一些二进制工具,以及一些关于Linux可执行文件方面的事情。目前使用的readelf工具(来自Binutils)尚取自宿主机。但是这不妨碍我们讨论这个问题。

首先观察宿主机上的GCC编译一个“空壳”C程序:

echo 'int main(){}' > dummy.c
gcc dummy.c
readelf -l a.out | grep interpreter
# 以下为回显
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

readelf -l的作用是读取程序头(Program Header),根据Computer Science from the Bottom Up网站Dynamic Linking一章对interpreter的说明

ELF allows executables to specify an interpreter, which is a program that should be used to run the executable. The compiler and static linker set the interpreter of executables that rely on dynamic libraries to be the dynamic linker.

一言以蔽之,一个程序要运行所必须的interpreter的路径是以某种方式硬编码在这个程序里面的。而且这个路径还是个绝对路径,就是说你的系统必须提供这个interpreter,这个程序才能运行得起来。

如果可执行程序中的interpreter是/lib64/ld-linux-x86-64.so.2,说明那这个可执行程序仍然依赖宿主系统。这不符合构建一个独立的Linux操作系统的目标。

现在,或许可以对Part 1中提出的“创建中间工具链的目的”做一个进一步说明。也就是说,我们构建中间工具链的部分原因是,中间工具链中所有可执行程序的interpreter都是(比如说)/mylib64/ld-linux-x86-64.so.2这样引用非宿主系统上动态库的形式,从而在工具链上摆脱宿主系统的影响。

那么硬编码interpreter路径的某种方式到底究竟是什么呢?答案是GCC的源码。在5.5节”GCC-9.2.0 – Pass 1″(以及5.10节”GCC-9.2.0 – Pass 2″)中,我们按书中给出的脚本修改了gcc/config/{linux,i386/linux{,64}}.h。这几个头文件中硬编码/lib64/ld...的地方,都被替换成了/tools/lib64/ld...。脚本还替换了一些宏,虽然我暂时不太明白为什么是那些宏,但肯定是为了避免引用宿主系统上的东西。

在构建完成中间工具链的Glibc后,我们可以用中间工具链的GCC编译一个dummy.c了,看看它的interpreter:

$LFS_TGT-gcc dummy.c  # $LFS_TGT-gcc即/tools/bin/x86_64-lfs-linux-gnu-gcc
readelf -l a.out | grep interpreter
# 以下为回显
      [Requesting program interpreter: /tools/lib64/ld-linux-x86-64.so.2]

现在已经是/tools/lib64/ld-linux-x86-64.so.2,这个interpreter已经不是宿主系统提供的了,是我们最开始编译Binutils得到的。

发表评论

电子邮件地址不会被公开。