GCC和g++是如何进行自举的?

201

这个问题困扰着我一段时间了。GCC和g++是如何编译自己的?

我猜每个修订版本都是用之前构建的版本编译的。这是真的吗?如果是这样,是否意味着最旧的g++和GCC版本是用汇编语言编写的?


13
每个修订版最终都可以自己编译。 :) - Martin Hennings
4
如果你想了解第一批编译器是如何产生的,阅读这篇文章会很有趣。 - parkovski
1
@parkovski 这个链接失效了吗? - Nubcake
1
上次链接出现在2016年6月4日:https://web.archive.org/web/20160604035203/homepage.ntlworld.com/edmund.grimley-evans/bcompiler.html - akraf
2个回答

188

最早的GCC版本是使用另一个C编译器进行编译的,因为在它被编写时还有其他编译器可用。第一款C编译器(约1973年左右)可能是用PDP-11汇编语言或其前身B语言实现的,但无论哪种方式,B编译器都是用汇编语言编写的。同样地,第一款C++编译器(CPre/Cfront,1979-1983)可能首先是用C实现的,然后用C++重写。

当你编译GCC或任何自托管编译器时,完整的构建顺序如下:

  1. 使用现有的C编译器构建新版本的GCC
  2. 使用刚刚构建的版本重新构建新版本的GCC
  3. (可选)为了验证目的,重复步骤2。

这个过程称为引导。它测试编译器编译自身的能力,并确保生成的编译器使用了它本身实现的所有优化。

编辑:在评论中,Drew Dormann指向了Bjarne Stroustrup关于C++最早实现的描述。它是用C++实现的,但由Stroustrup称为“预处理器”从C++翻译成C;按他的定义不是一个完整的编译器,但仍然使用C ++引导了C ++。


21
引导式编译的3步版本确实是用于验证的:编译器本身被用作自测试案例。使用[其他工具]编译的GCC应该产生相同的结果(相同的二进制文件, 不包括像 __DATE____TIME__ 这样即使在同一编译器调用之间也会变化的宏),与使用[GCC compiled with [other]]编译的GCC相同 - 如果不是这样,那就是一个错误,而3阶段引导构建就是为了捕捉这种情况。 - pmdj
21
如果不是这样,那就是一个漏洞,或者更不可能的情况是正在引入的一个阴险后门(“对于信任的反思”)。 - Steve Jessop
13
@sleske说的不对。步骤2的二进制输出必须与步骤3的二进制输出完全相同,否则就会出现错误。原因就像pmjordan所说的那样:NewCompiler1和NewCompiler2是具有相同源代码(即NewCompiler的源代码)的程序。它们都接受相同的输入(即NewCompiler的源代码)。因此,无论它们自己编译器是什么(在这种情况下,NewCompiler1是用OldCompiler编译的,而NewCompiler2是用NewCompiler1编译的),它们将产生相同的输出。也就是说,NewCompiler2和NewCompiler3是二进制上完全相同的。 - Steve Jessop
14
你是否曾经想过:如果我们失去了所有C编译器二进制文件,该怎么办?并且必须从头开始引导? 这是我的解决方案:有一个称为Tiny C Compiler的编译器(实际上可以编译Linux内核,因此功能非常完整)。其所有C源文件总共只有30,000行代码,包括注释。虽然这需要相当大的努力,但熟悉C语言的人可以通过阅读源代码学习如何生成二进制输出,并且手动“编译”TCC源代码(我在这里想到了打孔卡片)。然后使用重新编译的TCC来引导GCC或类似的编译器。 - datenwolf
12
@datenwolf说的差不多就是这样。如果我们假设失去了所有C编译器二进制文件,但仍然有汇编器,那么我们可以编写一个汇编程序TinyTinyC。它将比TinyC少一些功能: 我们不需要它能够编译GCC或Linux内核,我们只需要它能够编译TinyC。然后在TinyC源代码上运行它,这样就会得到一个能够编译Linux(希望能编译glibc和GCC)的C编译器,这样我们就可以开展业务了。如果我们甚至没有汇编器,那么我们首先要启动一个汇编器,这比启动一个C编译器更容易。 - Steve Jessop
显示剩余20条评论

23

如果您想在现代环境(x86 Linux)中复制GCC的引导过程,可以使用由bootstrappable项目开发的工具:

  • 我们可以从hex0汇编器(在x86上是357字节二进制文件)开始,它大致执行以下两个命令所做的操作:

sed 's/[;#].*$//g' hex0_x86.hex0 | xxd -r -p > hex0
chmod +x hex0

即将二进制程序的ASCII等效项翻译成二进制代码,但是它本身是用hex0编写的。

基本上,hex0有与其二进制代码一一对应的等效源代码。

  • hex0可用于构建稍微更强大一些的hex1汇编程序,支持一些其他功能(一个字符标签和计算偏移量)。 hex1是用hex0汇编语言编写的。

  • hex1可用于构建hex2(支持多字符标签的更先进的汇编程序)。

  • 然后可以使用hex2来构建宏汇编程序(使用宏而不是十六进制操作码的程序)。

  • 然后可以使用该宏汇编程序构建在汇编语言中编写的“C编译器”cc_x86。cc_x86仅支持C的一小部分,但这是一个令人印象深刻的开端。

  • 您可以使用cc_x86构建在C中编写的C编译器M2-Planet(宏平台中性转换器)。M2-Planet是自主主机并且可以构建它自己。

  • 然后可以使用M2-Planet构建小型方案解释器GNU Mes

  • mes可用于运行在方案中编写的C编译器mescc,并与mes位于同一存储库中。

  • mescc可用于重新构建mes并构建mes C库。

  • 然后可以使用mescc构建稍微修复的Tiny C编译器

  • 然后可以使用它来构建更新版本的TCC 0.9.27。

  • 使用旧版GCC可以构建GCC 4.0.4和musl C库。

  • 然后可以使用旧版GCC构建较新版本的GCC。 例如,GCC 4.0.4-> GCC 4.7.4->现代GCC。

  • 简而言之:

    hex0 -> hex1 -> hex2 -> M0 -> M2-Planet -> Mes -> Mescc -> TCC -> GCC。


    你忘了需要重新编译现代GCC才能获得所有C++优化(毕竟现在的GCC是用C++编写的),而不仅仅是4.7.4版本的。 - Валерий Заподовников
    这只是一个简短的摘要。当您构建现代GCC时,可以使用其构建系统自动重新编译它。还有许多缺少的步骤与编译器无关,您需要构建shell、binutils和许多其他工具。例如,您可以查看live-bootstrap https://github.com/fosslinux/live-bootstrap/blob/master/parts.rst,尽管目前仅支持g++ 4.7.4。 - Andrius Štikonas
    1. 你是指x86_64还是x86 32位?
    2. M2 Planet自托管有什么关系吗?
    3. 为什么需要重建mes并构建mes C库?
    4. 如果GNU Mes已经是一个C编译器,为什么我们还需要构建TCC?也就是说,为什么不能用mes来构建GCC?
    5. 为什么不能使用修补过的TCC来构建GCC和musl,而是用它来构建未修补过的TCC?
    - einpoklum
    这是32位版本。64位支持即将完成,但尚未完成。2. 嗯,在这个引导过程中,它是第一个自举程序,也就是说,到那时你可以基本上忘记汇编语言,专注于更高级的编译器。3. 因为用mescc构建的mes比用M2-Planet构建的mes更强大一些(而且速度更快)。4. 因为mescc无法构建更复杂的软件,如果我没记错的话,它甚至没有浮点数。此外,mescc非常慢,构建tcc需要10分钟,而GCC则更大。5. 这是一个修补过的TCC 0.9.26版本,它要旧得多。TCC 0.9.27更强大。 - Andrius Štikonas

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接