https://miyuki.github.io/2017/10/04/gcc-archaeology-1.html
在这篇文章中,我将分享我在现代系统上构建和使用 GNU C 编译器的最早版本之一——GCC 1.27(1988 年发布)的经验。
环境
在我的实验中,我使用了一个基于 Debian 8 的 LXC 容器(为什么不是 9?因为我在 Debian 9 发布之前就开始写这篇文章了)。我决定使用 i386 容器(而不是 amd64 容器)来节省工作量;我敢肯定,在与路径和符号链接共舞之后,amd64 的一切也会好起来的)。
Debian 8 将 GCC 4.9.2 作为主机编译器、Binutils 2.25 和 Glibc 2.19 发布。
获取源码
GCC 的旧版本可从 gcc.gnu.org 的官方服务器获得。第一个可用版本是 0.9 版。不幸的是,这个版本对我们来说不是很有趣,因为它不支持 i386 架构(或任何兼容的架构)。
GNU C 编译器的 1.27 版(请注意,当时它被称为 GNU C 编译器而不是 GNU 编译器集合)是第一个支持 i386 架构的可用版本。它于 1988 年 9 月 5 日发布(当时 Linux 还不存在)。
构建和测试
与现代版本不同,GCC 1.27 不包含任何庞大的配置脚本,并且配置是手动完成的。尽管如此,它非常简单且有据可查。事实上,它只涉及创建 4 个符号链接。
令人惊讶的是,C 标准的兼容性在 GNU 工具链中得到了很好的维护。此外,基本的 Glibc 标头也向后兼容旧编译器。由于这一事实,在修补了十几行(~92000 行)代码后,可以使用现代编译器编译 GCC 1.27。它们中的大多数都与 C 库中的更改有关,还有一些是由于现代 C 编译器中实现的更严格的 C 语法规则(参见 gcc-1.27.patch)。
另一个问题是缺少一个名为 syms.h
的标头,它显然定义了一些用于以 SDB 格式生成调试信息的常量。缺少标头也就不足为奇了:在 Linux 上,SDB 很久以前就被 DWARF 格式所取代。我设法在麻省理工学院网站上找到了这些标题:syms.h。该 URL 表明这些文件与 IBM AIX OS 有关。
标头和规格
在现代系统上使源代码可编译并不足以获得一个有效的编译器。您可能知道,GNU 工具链包括其他工具,例如汇编程序和编译器与之交互的链接器。幸运的是,生成的汇编代码的语法与现代版本的 GNU 汇编程序完全兼容(调试信息除外)。至于链接器,需要对所谓的链接器规范(即 GCC 驱动程序使用的命令行选项)进行一些调整。
应用这些修复后,我们现在有一个能够生成有效 elf
二进制文件的编译器。
引导
快速提醒:引导编译器意味着使用自身编译它。有关更多详细信息,请参阅“Bootstrapping (compilers)”维基百科文章。
bootstrap 的第一个小问题来自 glibc 标头:现代版本的 glibc 假定编译器支持 64 位整数类型,而 GCC 1.27 则不然。罪魁祸首是 /usr/include/bits/byteswap.h
.通过向编译器传递标志 -D_BITS_BYTESWAP_H
,可以很容易地禁用它的包含。
现在,尝试引导编译器会导致在编译与 C 预处理器相关的文件 cccp.c
时失败:编译器崩溃并出现分段错误。在深入研究了这次失败的原因之后,我设法使用 C-Reduce 生成了一个最小的失败测试用例:
struct file_buf { } fn1(), a;
fn2() { a = fn1(); }
编译器在尝试取消引用空指针时崩溃,同时将表达 a = fn1()
式从 AST 转换为其中间表示形式 (RTL)。显然,i386 后端在代码中存在一个错误,该错误处理对按值返回结构的函数的调用。
事实证明,修复这个错误相对简单。添加 GCC 1.31 中存在的单个检查可以修复错误,并且引导程序现在成功。
顺便说一句,这可能意味着以前没有人尝试过在 x86 上引导 GCC 1.27。
此外,我设法执行了引导比较,即构建:
- 阶段 1 编译器,即由主机编译器编译的 GCC 1.27 (GCC 4.9.2)
- 第 2 阶段编译器,即由第 1 阶段编译器编译的 GCC 1.27
- 第 3 阶段编译器,即由 2 阶段编译器编译的 GCC 1.27
正如我所料,第 2 阶段和第 3 阶段是相同的。
四处游玩 (Playing around)
我试图找到一些代码(除了 GCC 本身)来编译和使用。请记住:我们需要用所谓的 K&R C 编写代码(因为当时还不存在 ANSI C89 标准)。
例如,我使用了一个程序来生成 Mandelbrot 集的 ASCII 图像(不幸的是,我未能找出此代码的作者是谁)。
以下是代码,格式化后可读性更好:
main (n)
{
float r, i, R, I, b;
for (i = -1; i < 1; i += .06, puts (""))
for (r = -2; I = i, (R = r) < 1; r += .03, putchar (n + 31))
for (n = 0; b = I * I, 26 > n++ && R * R + b < 4;
I = 2 * R * I + i, R = R * R - b + r);
}
如您所见,它涉及相当复杂的控制流和浮点运算。GCC 1.27 编译它没有错误,并且输出与现代版本的 GCC 编译的相同代码的输出相匹配。
GCC 1.27 包含许多现代编译器的典型功能,例如:
- Compiler warnings 编译器警告
- Optimizations 优化
- Debug information output 调试信息输出
- Instrumentation for code profiling 代码分析检测
- Command-line flags controlling all of the above 控制上述所有内容的命令行标志
探索 GCC 源代码
另一件令我惊讶的事情是,这些旧版本的 GCC 的许多想法甚至大部分代码至今仍在使用。
编译器通过可切换的头文件支持多个后端(即, config-i386v.h
用于 i386 System V, config-sparc.h
用于 Sparc Sun)。计算机描述 .md
文件描述 CPU 指令模式。在构建阶段,它们被解析并转换为 C 源文件,稍后与编译器链接。当然,在过去的几十年里,DSL .md
已经发展,但原理保持不变。此外,LLVM 编译器基础结构也使用类似的技术。
两种无处不在的数据类型 tree
分别 rtl
用于在编译器前端和后端表示程序,仍然发挥其作用。
当然,这并不意味着当前版本的 GCC 停留在八十年代。尽管有一些相似之处,但主要增强功能的数量确实很大,我不会费心列出它们(对于包含一些基准、图表和信息图表的帖子来说,这是一个很好的主题)。
旧版本的 GCC 没有 bug 跟踪器站点,因此,错误和增强请求列表保存在一个名为 PROBLEMS
.从 1.27 版开始,它包含 27 个条目,编号上有许多空白。实际上,最后一项的编号为 122。这可能意味着在此版本之前已修复剩余的 95 个问题。
最新评论
这个软件有bug的,客户端windows有些键不能用如逗号、句号
没有收到邮件通知
我的评论通知貌似坏掉了,定位一下问题
测试一下重新部署后的邮件功能
居然看到自己公司的MIB库,诚惶诚恐
那可能是RobotFramework-ride的版本问题。我装的1.7.4.2,有这个限制。我有空再尝试下旧版本吧,感谢回复。
你好!我在python2.7中安装RobotFramework-ride的时候提示wxPython的版本最高是2.18.12,用pip下载的wxPython版本是4.10,而且我在那个路径下没有找到2
真的太好了,太感谢了,在bilibili和CSDN上都找遍了,终于在你这里找到了