交叉编译工具链构建原理

交叉编译工具链构建原理

这是与弗朗西斯科·图尔科(Francesco Turco)讨论的结果

弗朗西斯科为初学者提供了一个很好的教程(死链,Wayback机器没有存档版本),以及一个示例,从x86_64 Debian主机为ARM目标构建工具链的分步过程。

谢谢弗朗西斯科发起这个活动!

我想要一个交叉编译器!你说的这个工具链是什么?

交叉编译器实际上是不同工具的集合,这些工具被设置为紧密地协同工作。这些工具以某种级联方式链接在一起,其中一个工具的输出成为另一个工具的输入,最终产生在机器上运行的实际二进制代码。因此,我们称这种安排为“工具链”。当工具链要为运行它的机器之外的机器生成代码时,这被称为交叉工具链。

那么,工具链中的组件是什么?

在工具链中起作用的组件首先是编译器本身。编译器将源代码(用C、C++等语言)转换成汇编代码。选择的编译器是GNU编译器集合,众所周知的gcc

汇编程序解释汇编代码以生成目标代码。这是由二进制实用程序完成的,如GNU binutils。

一旦生成了不同的目标代码文件,它们就会聚集在一起形成最终的可执行二进制文件。这称为链接,通过使用链接器来实现。GNU binutils还附带了一个链接器。

到目前为止,我们得到了一个完整的工具链,能够将源代码转化为实际的可执行代码。根据目标上运行的操作系统,我们还需要C库。C库提供了一个执行基本任务的标准抽象层(例如分配内存、在终端上打印输出、管理文件访问……)。有许多C库,每个都针对不同的系统。对于Linux桌面,有glibceglibc甚至uClibc,对于嵌入式Linux,您可以选择eglibcuClibc,而对于没有操作系统的系统,您可以使用newlibdietlibc,甚至根本不使用。还有一些其他的C库,但它们没有被广泛使用,并且/或者是针对非常特殊的需求(例如,klibc是C库的一个非常小的子集,旨在构建受约束的初始ramdisks)。

在Linux下,C库需要知道内核的API,以决定存在什么特性,如果需要的话,为缺失的特性包含什么模拟。该API由内核头文件提供。注意:这是特定于Linux的(可能还有极少数其他操作系统),其他操作系统上的C库不需要内核头文件。

现在,所有这些组件是如何连接在一起的?

到目前为止,已经涵盖了所有主要组件,但它们需要按照特定的顺序构建。从我们最终要使用的编译器开始,我们可以看到依赖关系是什么。我们称该编译器为最终编译器

  • 最终的编译器需要C库来知道如何使用它,但是:
  • 构建C库需要编译器

A需要B,B需要A .这是典型的先有鸡还是先有蛋的问题……这可以通过构建一个精简的编译器来解决该编译器不需要C库,但能够构建C库。我们称之为引导初始核心编译器。这是新的依赖列表:

  • 最终的编译器需要C库来知道如何使用它
  • 构建C库需要核心编译器,但是:
  • 核心编译器需要C库头文件和启动文件来知道如何使用C库

B需要C,C需要B .又是先有鸡还是先有蛋。为了解决这个问题,我们需要构建一个只安装头文件和启动文件的C库启动文件(也称为C运行时CRT)是gcc需要在NPTL系统上启用线程本地存储(TLS)的极少数文件。所以现在我们有:

  • 最终的编译器需要C库来知道如何使用它
  • 构建C库需要一个核心编译器
  • 核心编译器需要C库头文件和启动文件来了解如何使用C库,但是:
  • 构建启动文件需要编译器

天啊… C需要D,D又需要C。因此我们需要构建一个更简单的编译器,它不需要头文件,但需要启动文件。这个编译器也是一个引导、初始或核心编译器。为了区分两个核心编译器,我们称之为一个核心通道1(core pass 1),而前者为一个核心通道2(core pass 2)。依赖性列表变成:

  • 最终的编译器需要C库来知道如何使用它
  • 构建C库需要编译器
  • 核心通道2编译器需要C库头文件和启动文件来了解如何使用C库
  • 构建启动文件需要编译器
  • 我们需要一个核心通道1编译器

正如我们前面所说的,C库也需要内核头文件。对内核头没有要求,所以在这种情况下故事结束了:

  • 最终的编译器需要C库来知道如何使用它
  • 构建C库需要一个核心编译器
  • 核心通道2编译器需要C库头文件和启动文件来了解如何使用C库
  • 构建启动文件需要编译器和内核头文件
  • 我们需要一个核心通道1编译器

我们需要增加一些新的要求。当我们为目标编译代码时,我们需要汇编程序和链接程序。当然,这样的代码是从C库构建的,所以我们需要在C库启动文件之前构建binutils,以及完整的C库本身。此外,gcc中的一些代码也将转而在目标上运行。幸运的是,对binutils没有要求。因此,我们的依赖链如下:

  • 最终的编译器需要C库来知道如何使用它,还需要binutils
  • 构建C库需要核心通道2编译器和binutils
  • 核心通道2编译器需要C库头文件和启动文件,以了解如何使用C库和binutils
  • 构建启动文件需要编译器、内核头文件和binutils
  • 核心通道1编译器需要binutils

依次构建组件:

  1. binutils
  2. 核心通道1编译器
  3. 内核头文件
  4. c库头文件和启动文件
  5. 核心通道2编译器
  6. 完整的C库
  7. 最终编译器

是啊!:-)但是我们结束了吗?

事实上,不是的,仍然有缺失的依赖项。就工具本身而言,我们不需要任何其他东西。

但是gcc有一些先决条件。它依靠一些外部库来执行一些重要的任务(比如处理常数中的复数……)。构建这些库有几种选择。首先,人们可能会认为依靠Linux发行版来提供这些库。唉,直到最近,它们才被广泛使用。因此,如果发行版不是太新的话,我们很有可能必须构建这些库(我们将在下面进行构建)。受影响的库包括:

  • GNU多精度算术库——GMP(GNU Multiple Precision Arithmetic Library);
  • 具有正确舍入的多精度浮点计算的C库——MPFR(Multiple-Precision Floating-point-computations with correct Rounding);
  • 复数算术的C语言库——MPC。

这些库的依赖关系如下:

  1. MPC需要GMP和MPFR
  2. MPFR需要GMP
  3. GMP没有先决条件

因此,构建顺序变为:

  1. GMP
  2. MPFR
  3. MPC
  4. binutils
  5. 核心通道1编译器
  6. 内核头文件
  7. C库头文件和启动文件
  8. 核心通道2编译器
  9. 完整的C库
  10. 最终编译器

是啊!或者更多?

这足以构建一个功能工具链。所以如果你现在已经受够了,你可以到此为止。或者如果你很好奇,你可以继续阅读。

gcc还可以利用其他一些外部库。这些额外的可选库用于启用gcc中的高级功能,如循环优化(GRAPHITE)链路时间优化(LTO, Link Time Optimisation)。如果要使用这些库,您需要另外三个库:

要启用GRAPHITE,根据GCC版本,可能需要以下一项或多项:

  1. PPL, Parma多面体库;
  2. ISL, 整数集库;
  3. CLooG/PPL, 使用PPL后端的Chunky循环生成器;
  4. CLooG, 使用ISL后端的Chunky循环生成器。

要启用LTO:-ELF对象文件访问库,libelf

这些库的依赖关系如下:

  1. PPL要求GMP
  2. CLooG/PPL需要GMP和PPL或ISL之一;
  3. ISL没有先决条件;
  4. libelf没有先决条件。

列表现在看起来像这样:

  1. GMP
  2. MPFR
  3. MPC
  4. CLooG/PPL(如果需要)
  5. ISL(如果需要)
  6. libelf(如果需要)
  7. binutils
  8. 核心通道1编译器
  9. 内核头文件
  10. C库头文件和启动文件
  11. 核心通道2编译器
  12. 完整的C库
  13. 最终编译器

这个列表现在已经完成了!哇哦!或者是?

但是为什么crosstool-NG的步骤更多呢?

从理论的角度来看,已经制定的十三个步骤是必要的步骤。然而在现实中,还是有一些小的不同。crosstool-NG中的额外步骤有三个不同的原因。

第一,GNU binutils不支持某些类型的输出。用binutils生成平面二进制文件是不可能的,所以我们必须使用另一个添加了这种支持的组件: elf2flt。elf2flt还需要zlib压缩库-如果我们正在构建加拿大的或跨本地的工具链,我们可能无法使用主机的zlib。

第二,工具链的本地化需要一些主机操作系统上的附加库: gettextlibiconv

第三,crosstool-NG还可以构建一些额外的调试实用程序在目标上运行。这是我们构建的地方,例如,cross-gdbgdbserver和原生gdb(最后两个在目标上运行,第一个在工具链所在的机器上运行)。其他工具(straceltraceDUMAdmalloc)与工具链完全无关,但在开发时非常有用,因此作为好东西包含在内(它们很容易构建,所以没问题;更复杂的东西不值得包含在crosstool-NG中)。

posted @ 2024-03-14 15:19  汪淼焱  阅读(222)  评论(0编辑  收藏  举报