最低 GCC 版本可能从 3.2 跃升至 4.8

作者: Zack Brown

关于构建 Linux 内核所支持的最早 GCC 编译器版本的问题会定期出现。理想情况是 Linux 能够在所有 GCC 版本下编译,因为你永远不知道某人正在运行什么样的系统。也许他们公司的安全团队必须批准对其高度敏感设备的所有软件升级,而 GCC 在该列表中排名靠后。也许他们需要尽可能节省空间,而最新版本的 GCC 太大了。人们可能会被旧软件困住的原因有很多。但是,他们可能需要最新的 Linux 内核,因为它关系到他们整个产品的根基,因此他们不得不尝试使用旧编译器来编译它。

然而,Linux 实际上不可能支持每个 GCC 版本。有时 GCC 团队和内核团队在 GCC 应该如何生成代码的方式上存在分歧。有时这意味着内核在特定版本的 GCC 上确实无法很好地编译。因此,这些冲突偶尔会导致项目战争爆发。GCC 团队会说编译器正在尽其所能,而内核团队会说编译器正在搞砸他们的代码。有时 GCC 团队会在以后的版本中更改行为,但这仍然留下了一个特定版本的 GCC,它会生成糟糕的 Linux 代码。

因此,内核团队将以编程方式决定排除特定版本的 GCC 用于编译内核。任何尝试在内核代码上使用该编译器的行为都会产生错误。

但是,有时 GCC 团队会添加一个新的语言功能,该功能非常有用,内核团队会决定在其源代码中大量依赖它。在这种情况下,可能会有一段时间,内核团队会为较新的、更好的编译器维护一个代码分支,并为较旧的、较差的编译器维护一个单独的、速度较慢或更复杂的代码分支。在这种情况下,内核团队——或者实际上是 Linus Torvalds——最终可能会决定停止支持比某个版本更旧的编译器,以便他们可以删除所有那些速度较慢和更复杂的代码分支。

出于类似的原因,对于内核人员来说,放弃对旧编译器的支持也只是一个更容易的维护任务;因此,如果可能的话,这是他们一直希望做的事情。

但是,这是一个重大的决定,通常会权衡无法升级编译器的估计用户数量。Linus 真的不希望即使是一个普通用户(即非内核开发人员)也因为这种决定而无法构建 Linux。他愿意让内核携带大量相当陈旧和/或难以维护的代码,以防止这种情况发生。

最近,当 Masahiro Yamada 发布一个补丁来更新 Linux kconfig 系统以支持他自己设计的一种新的特殊属性时,这个问题出现了。讨论转为理论层面,Ulf Magnusson 思考概念化内核配置选项的最佳方法,特别是在用户实际上由于系统约束而无法设置特定选项的情况下。

这些思考开始为讨论 GCC 版本号支持奠定基础。因此,我将描述该上下文是如何发展的,然后再回到 GCC 版本支持的问题。

Kees Cook 指出,内核配置选项的约束问题也可能具有严重的安全隐患。长期以来,GCC 编译器已经包含了代码,用于识别和防范其编译输出中的某些缓冲区溢出攻击。但是,根据编译器版本,用户可能可以使用也可能无法使用执行此操作的 kconfig 选项。正如 Kees 所说

目标是记录用户的选择,而无需考虑编译器的能力……拥有 _AUTO 提供了一种选择“此编译器的最佳可能”的方式。如果用户之前选择了 _STRONG,但他们正在使用较旧的编译器(或配置错误的新编译器)进行构建,而这些编译器不支持 _STRONG,则目标是 _fail_ 构建,而不是静默地选择 _REGULAR。

Kees 继续说道,“所获得的是 _AUTO 的逻辑,以及在编译器中不可用时,不显示 _STRONG 的逻辑。但是,如果 _STRONG 在 .config 中,我们仍然必须构建失败。它不能静默地将其重写为 _REGULAR,因为编译器对 _STRONG 的支持退步了。”

Linus 在此时加入了讨论,部分原因是误解了情况。Linus 没有意识到 Kees 指的是内核源代码树中已经存在的特殊 stackprotector 脚本,他认为 Kees 建议专门为此情况实现这样一个脚本。他对 Kees 大吼大叫,说:“重点是简化 Kconfig,而不是让它变得更糟。” 他补充说,“不要为 stackprotector 制作一些愚蠢的脚本。如果用户没有支持 -fstackprotector-* 的 gcc,则不要显示这些选项。这与稍后是否默认关闭 stackprotector 无关紧要。”

Kees 指出,所讨论的脚本不是他试图添加的东西,而是已经存在于树中的东西。他说

自从 Arjan 最早添加它以验证编译器在您提供 -fstack-protector 时是否实际生成堆栈保护程序以来,它就一直存在。较旧的 gcc 完全破坏了这一点,最近的错误配置(如 Arnd 的一些本地 gcc 构建中所见)也做了类似的事情,并且在某些版本中出现了回归,其中 gcc 的 x86 支持翻转到了全局 canary 而不是 %gs-offset canary。

Linus 拍了拍自己的脑袋,说:“提到的脚本(和 bugzilla)来自 2006 年,我以为这一切都是历史了。”

他对这种情况感到恼火,继续说道,“但是,如果它在那之后再次损坏,我想我们需要一个愚蠢的脚本。Grr。”

但是,他坚持认为他的分歧在于当编译器不支持比用户想要的更强的缓冲区溢出保护形式时该怎么办。Linus 说

我对您早些时候的“它不能静默地将其重写为 _REGULAR,因为编译器对 _STRONG 的支持退步了”也做出了反应。因为它完全可以。如果编译器不支持 -fstack-protector-strong,我们可以退回到 -fstack-protector。静默地。也没有额外的疯狂复杂逻辑。

过了一会儿,Linus 再次发帖。他无法静静地看着那个脚本弄乱配置系统,不得不进一步调查。他说

我希望真的只是统一所有愚蠢的编译器标志测试,只在 Kconfig 文件中进行,并希望我们真的可以使用


config CC_xyz
    bool
    option cc_option '-fwhatever-xyz'

来设置它们,然后从中构建 Kconfig 规则


config USE_xyz
    bool 'Some question that needs xyz'
    depends on CC_xyz

并拥有一个简洁的


ccflags-$(CONFIG_USE_xyz) += -fwhatever-xyz

在 Makefile 中。

我曾经想过“嘿,如果我们需要一个用于 -fstack-protector 的脚本,也许我们可以简单地将 _所有_ 内容都标准化为一个脚本”。

但是做统计,我们测试了大约两 _百_ 个不同的编译器选项,并且看起来真的只有 -fstack-protector 使用了专用脚本。其他所有内容都只是使用“查看编译器是否接受该标志”。所以不,我们不想围绕脚本进行标准化。

我们确实有一个用于与 gcc 崩溃相关的其他一些构建选项的脚本,但不是命令​​行标志本身:包括“asm goto”和用于 gcc 版本生成。以及 gcc 插件兼容性检查。

好吧。看起来我们真的必须从正常规则中排除那些讨厌的例外情况。

Kees 同意了,他说

是的,当我测试新的 ..._AUTO 选项时,我非常失望地发现了 Arnd 遇到的 gcc 崩溃情况。我以为我也能扔掉一大堆复杂的东西。:( 而这还是在最近关于将最低 gcc 级别提高到不需要“旧的损坏的 gcc”堆栈保护程序检查的位置的讨论之上。但是,不,那太容易了。:(

现在最后,让我们回到支持的 GCC 版本的问题。

在附近的讨论中,Ulf 曾询问看到以这种方式损坏的 GCC 版本有多常见。Kees 回答说

我 *以为* 这很罕见(即 gcc 4.2),但在使用 ..._AUTO 时,我发现 akpm 的 4.4 gcc 和 Arnd 的所有 gcc 都存在崩溃,原因是 gcc 构建环境和其他选项之间存在一些非常奇怪的配置错误。所以,事实证明这不幸地很常见。好消息是,这似乎不会发生在大多数发行版编译器中,尽管我大约在一年前看到 Android 的编译器至少有一次退步,将全局与 %gs 进行了比较。

但是,Linus 对 Kees 发现的案例的意义有着完全不同的看法。他说,“嗯。好的,所以它不是 *那么* 常见,也不会影响普通人。” 换句话说,几乎所有普通用户都使用预先打包的发行版,并且几乎所有这些发行版都没问题,因此总体情况还可以。

Linus 继续说道

这实际上听起来我们可以

(a)将 gcc 4.5 作为最低必需版本

(b)如果我们发现错误的编译器,实际上会报错。

无论如何,将最低要求的 gcc 版本升级到 4.5 几乎肯定会发生 _anyway_,因为我们开始依赖“asm goto”来避免推测。

最终结果:也许我们可以使配置阶段仅使用标准的“gcc 是否支持此标志”逻辑,然后只需运行一个单独的脚本来验证 gcc 是否没有生成垃圾,如果生成了垃圾,则大声报错。

对此,Kees 高兴地回答说:“我喜欢出于如此多的原因而提高最低要求,而不仅仅是堆栈保护程序。:)”

Linus 也扩展了他的帖子,说

只是为了解释一下这与我们现在所做的有什么不同(除了“报错”以实际主动阻止损坏的编译器之外):如果我们可以在构建过程中添加一个简单的检查,而不是作为配置的一部分,那么事情就会变得简单。

如果我们不必将其作为配置的一部分,我们可以使用所有正常的 Kconfig 规则和构建规则,然后我们可以将错误检查添加到我们已经进行各种目标文件处理的任何数量的位置。

例如,将“检查是否存在正确的段访问”作为 link-vmlinux.sh 或类似内容的一部分来完成是非常简单的。

这将使我们能够仅使用所有常规配置基础设施,包括(希望在 4.17 版本中)Kconfig 时的“cc-option”内容。

并且,在回复 Kees 关于喜欢提高编译器版本的评论时,Linus 说

这仍然不是一个非常 *大* 的提升。由于现代发行版都在 7.3 版本,并且人们正在测试 gcc-8 的预发布版本,因此像 gcc-4.5 这样的版本仍然非常古老。

但是,能够依赖 asm goto 而不是对“必须手动完成”使用完全不同的逻辑会很好。而且我确实想知道我们有多少“让我们测试 gcc 是否支持此选项”已经完全过时了。

在 <linux/compiler-gcc.h> 中,我们仍然有针对真正古老的垃圾的测试。

他补充说,“我主要担心的是不是那些可以升级的奇怪的开发人员,而是野外的随机人员。我不想失去偶尔进行无人会做的事情的奇怪的测试人员。但是由于 gcc-4.5 已经有 7 年多了,我无法想象这是一个巨大的问题。”

Linus 还对版本支持问题提供了一些额外的见解

值得注意的是,我们的 _文档_ 可能声称 gcc-3.2 是最低支持的版本,但 Arnd 在几个月前指出,显然没有任何比 4.1 更旧的版本已经工作了很长时间,并且在几个架构上需要 gcc-4.3。

因此,所需的 gcc 版本的 _真正_ 跳跃将是从 4.1(在许多情况下为 4.3)到 4.5,而不是从我们记录的“最低 3.2”开始。

Arnd 声称某些架构需要比 4.3 更新的版本,但我认为这仅限于像 RISC-V 这样的根本没有旧 gcc 支持的东西。

那是关于一个仅在 gcc-4.4 中发生的错误报告的讨论,原因是 gcc-4.4 做了疯狂的事情,所以我们正在讨论是否值得支持它。

因此,我们实际上有很多不相关的原因可以说明仅仅说“gcc-4.5 或更新版本”是一件好事。

关于 Linus 对 RISC-V 芯片的猜测,Arnd Bergmann 回复道

对。此外,特定于架构的功能可能需要更新的版本,并且在某些情况下,例如“匿名联合的初始化器需要额外的花括号”,一个简单的更改就可以使其工作,但是许多架构显然从未在工具链足够旧的情况下构建过,以至于实际上遇到了这些情况。

Geert 是我认识的唯一一个积极使用 gcc-4.1 的人,并且他实际上发送了一些补丁,这些补丁似乎可以让其他架构在该版本上构建,而以前它们是在 gcc-4.3+ 上构建的。

gcc-4.3 反过来在 SLES11 上默认使用,SLES11 仍在支持中,我甚至与一位使用该编译器构建新内核的人合作过,因为这恰好安装在他的共享构建服务器上。在这种情况下,积极拒绝 gcc-4.3 迫使他使用新编译器将为我们节省一些调试麻烦。

在我去年的测试中,我确定 gcc-4.6 是一个不错的最低级别,我记得 gcc-4.5 无法构建一些较新的 ARM 目标。

Kees 同意 Arnd 的说法,他说:“如果 Linus 想要 4.5 而不是 4.3,我同意 Arnd 的观点:让我们将其提高到 4.6。”

Linus 回复道

所以听起来 Arnd 知道发行版的情况。

因为我认为这实际上是尝试确定我们要去哪里​​的最佳方法,因为它将决定对 _用户_ 来说最成问题的是什么。

如果没有发行版使用 4.5,那么就没有理由选择它。我提到 4.5 的原因是那是“asm goto”点,据我所知,这很可能在不久的将来成为一项要求。

如果 SLES11 是 4.3,那显然是一个问题。尽管 Arnd 似乎暗示这已经引起了问题。

但是 Geert Uytterhoeven 评论说,“只要 gcc-4.1 帮助我找到真正的错误(它在当前的合并窗口中做到了这一点),我就计划继续使用它。”

Peter Zijlstra 迅速发布了一些补丁,开始将 Linux 代码从必须支持旧编译器的束缚中解放出来。他迅速编写并发布了其中一些补丁,并说:“因此,非官方计划是通过硬性构建失败来强制执行 asm-goto 和 -fentry 支持,这将使我们达到 gcc-4.6,然后删除那些功能所需的所有后备 cruft——对于 x86。如果我们想在整个树中都这样做,对我来说显然也可以。”

在回复 Linus 关于哪些发行版可能需要 GCC 4.5 的查询时,Arnd 说

查看具有长期支持周期的旧发行版

红帽要么使用 gcc-4.1(EL5,扩展支持于 2020 年结束),要么使用 gcc-4.4(EL6,常规支持于 2020 年结束,扩展支持于 2024 年结束):https://access.redhat.com/solutions/19458

EL7 使用 gcc-4.8,并将支持到 2024 年(未计划扩展支持)。

SUSE 使用 gcc-4.3(SLES11,扩展支持于 2022 年结束)或 gcc-4.8(SLES12,支持于 2024 年结束,扩展支持于 2027 年结束):https://www.suse.com/lifecycle

Debian Jessie(旧稳定版)附带 gcc-4.8,支持到 2018 年 6 月,扩展支持到 2020 年。

Debian Wheezy(旧旧稳定版)根据架构使用 gcc-4.6 或 4.7,扩展支持于 2018 年 5 月结束。

Ubuntu 14.04 支持到 2019 年,并使用 gcc-4.8。

最新的 Android SDK 提供了(已知已损坏的)gcc-4.8 和 gcc-4.9 版本以及 clang。

OpenWRT 14.07 Barrier Breaker 使用 gcc-4.8,12.07 Attitude Adjustment 12.09 使用 gcc-4.6,但是几乎没有人关心使用其中任何一个构建新内核。

大多数嵌入式发行版只是从源代码构建所有内容,并且习惯于适应新的要求。

从上面的列表中可以看出,如果我们决定不再希望支持 4.3 和 4.4,那么一路升级到 gcc-4.8 将比 4.5 或 4.6 更好。

此时,关于最低 GCC 版本号的讨论停止了,对话的重点转移到与 Masahiro 发布的原始补丁相关的特定内核功能上。

观看这样的讨论的来龙去脉很有趣。我们可以看到 Kees(安全人员)在不再需要担心旧编译器的安全问题时的喜悦。我们可以看到 Linus 对给内核开发人员带来不便的漠不关心,但对“野外”可能在做无人做的事情的用户的谨慎关注,以及他们可能提交无人会发现的错误报告。我们看到了 Arnd 仔细检查可用发行版以了解它们的真实需求——以及普通用户的需求。我们可以看到“asm goto”功能为 Linux 开发人员提供了放弃不支持该功能的编译器的鼓励。在讨论中,Linus 毫不犹豫地说 GCC 4.5(第一个提供“asm goto”功能的版本)注定会成为新的最低要求,仅仅因为它提供了该功能。与 Kees 一样,对于 Linus 来说,消除大量不受欢迎的遗留内核代码的前景极具诱惑力。我们可以看到这些开发人员中的每一位都根据每一条新信息提出了自己关于最佳最低要求的新想法,一直到 GCC 4.8。

注意:如果您在上面被提及并希望在评论部分上方发布回复,请将包含您的回复文本的消息发送至 ljeditor@linuxjournal.com。

Zack Brown 是 Linux JournalLinux Magazine 的技术记者,并且曾是“Kernel Traffic”每周新闻通讯和“Learn Plover”速记打字教程的作者。他于 1993 年在他的 386 电脑上安装了 Slackware Linux,配备 8MB 内存,并且他的思想被开源社区永久震撼。他是 Crumble 纯策略棋盘游戏的发明者,您可以用几块纸板自己制作。他还喜欢写小说、尝试动画、改革 Labanotation、设计和缝制自己的衣服、学习法语以及与朋友和家人共度时光。

加载 Disqus 评论