C(或任何语言)编译器如何保证确定性能?

18
在最近的一个项目中,我被一位客户QA代表拜访了,他问了我一个我之前从未考虑过的问题:
“你如何知道你使用的编译器生成的机器代码完全符合C语言代码的功能,并且编译器是完全确定性的?”
对于这个问题,我完全没有回答,因为我一直认为编译器是理所当然的。它接受代码并输出机器代码。我怎么能测试编译器是否实际上添加了我没有要求的功能?或者更危险的是,以与我预期不同的方式实现代码?
我知道这可能并不是每个人都面临的问题,而且答案可能只是...“你没有选择,必须处理它”。然而,在嵌入式环境中工作时,你会完全信任你的编译器。我如何向自己和QA证明我在这样做时是正确的?
22个回答

12

您可以在任何层面上应用该论点:您信任第三方库吗? 您信任操作系统吗? 您信任处理器吗?

一个很好的例子,说明这可能是一个有效的关注点,就是肯·汤普森(Ken Thompson)如何在原始的“登录”程序中放置了后门 ... 并修改了C编译器,以便即使重新编译登录程序仍然会得到后门。有关更多详细信息,请参见此帖子。

类似的问题已经针对加密算法提出--我们怎么知道DES中没有NSA用于窥探的后门?

最终,您必须决定是否信任构建基础设施足够,以不必担心它,否则您必须开始开发自己的硅芯片!


没错。我假设那个人提问是因为这件事很敏感,他们有理由这么做,而不是因为他们是神经质的人。 - Vinko Vrsalovic
2
不,Ken没有这样做,引用一些博主的话也不能证明他这样做了。阅读Ken的真实论文(Reflections on Trusting Trust)可以看到他真正的想法:他只是建议他可以这样做。所谓的编译器和登录二进制文件从未被发现。 - MSalters
答案实际上并没有回答问题。 - Tim Williscroft
《术语文件》声称肯实际上确实这样做了。http://www.catb.org/jargon/html/B/back-door.html - David Cary

11

对于安全关键的嵌入式应用,认证机构要求编译器满足“已经在使用中验证”的要求。通常需要满足某些要求(有点像“运行时间”),并通过详细的文件记录来证明。但大多数人要么无法满足这些要求,要么不想满足,因为这对于首次使用新目标/编译器的项目而言可能非常困难。

另一种方法基本上是完全不信任编译器的输出。任何编译器甚至依赖于语言的缺陷(C-90标准的附录G,有人会用吗?)都需要采用严格的静态分析、单元测试和覆盖测试以及后期的功能测试来进行覆盖。

MISRA-C这样的标准可以帮助限制输入到编译器的C语言子集的“安全性”。另一种方法是将输入限制为语言的子集,并测试整个子集的输出。如果我们的应用程序只由该子集的组件构建,则认为已知编译器的输出内容。这通常称为“对编译器的资格认证”。

所有这些的目标都是为了能够回答QA代表的问题:“我们不仅仅依赖于编译器的确定性,而是通过以下方式来证明...”。


没错。使用单元测试、静态测试和集成测试来验证系统的行为。客户的 QQ 伙计想知道您是否知道如何测试软件以确保质量。使用测试预言或一些经过验证的独立手段来验证您的单元和集成测试数据集。(例如:由几个工程师验证的手动生成的单元测试结果。) - Tim Williscroft

7

通过测试来验证。当你进行测试时,你既在测试你的代码,也在测试编译器。

你会发现,你或者编译器作者犯错的概率要比你用某种汇编语言编写程序时犯错的概率小得多。


确保您使用足够的测试。如果应用程序域对正确性要求不高,则测试很容易。要严格执行此操作,必须由软件表示正式要求,并通过单元测试和集成测试进行测试。如果未定义正式的正确性要求,则任何答案都是正确的。客户会选择您没有选择的答案。 - Tim Williscroft

5

有编译器验证套件可用。
我记得的一个是“Perennial”

当我为嵌入式SOC处理器编写C编译器时,我们必须对其进行验证,并通过其他两个验证套件(名称我忘记了)进行验证。将编译器验证到这些测试套件的某个符合度水平是合同的一部分。


3
你如何知道你使用的编译器生成的机器代码与c代码的功能完全相同,并且编译器是完全确定性的?
你不知道,这就是为什么你要测试生成的二进制文件,并确保使用的是与测试相同的二进制文件。当你进行“微小”的软件更改时,你需要进行回归测试,以确保旧功能没有出现问题。
我唯一认证过的软件是航空电子设备。FAA的认证不够严格,无法证明软件的正确性,同时也会让你跳过一定数量的障碍。诀窍在于构建你的“流程”,使其尽可能提高质量,同时尽可能少地跳过不必要的障碍。因此,任何你知道是无用的、不会真正发现错误的东西,你都可以逃避。而任何你知道应该做的事情,因为它会发现错误,而FAA并没有明确要求的,你最好把话扭曲到听起来像是在给FAA/你的QA人员他们要求的东西。
实际上,这并不像我所说的那样不诚实,总的来说,FAA更关心你是否有良心和信心,认为你正在努力做好工作,而不是具体做了什么。

3

在为国防软件工程师设计的杂志《Crosstalk》中,你可以找到一些有关知识储备。这个问题是他们花费许多时间思考的类型。 http://www.stsc.hill.af.mil/crosstalk/2006/08/index.html (如果我能找到旧项目的笔记,我会回来的...)


3
你永远不能完全信任编译器,即使是高度推荐的编译器也一样。他们可能会发布一个有缺陷的更新,而你的代码仍然可以编译通过。当使用带有缺陷的编译器更新旧代码时,这个问题就会变得更加复杂,进行测试和交付产品后,三个月后客户打电话告诉你出现了问题。
所有问题都归结于测试,如果有一件事我学到的是,在进行任何非微不足道的更改后,要彻底测试。如果问题似乎无法找到,可以查看已编译的汇编程序,并检查它是否正在执行应该执行的操作。
在几次情况下,我发现编译器中存在错误。一次,有一个错误,16位变量将被递增但没有进位,只有当16位变量是在头文件中定义的外部结构的一部分时才会出现这种情况。

3
一切都归结于信任。你的客户是否信任任何编译器?使用它,或者至少比较你和他们之间的输出代码。
如果他们不信任任何编译器,那么是否有该语言的参考实现?你能说服他们相信它吗?然后将你的代码与参考实现进行比较或使用参考实现。
这一切都假设你实际验证了从供应商/提供商获得的实际代码,并检查编译器是否被篡改,这应该是第一步。
无论如何,这仍然留下一个问题,即如何在没有参考的情况下验证编译器。这肯定需要大量的工作,并需要定义语言,而这并不总是可用的,有时定义就是编译器本身。

2
嗯...你不能简单地说你信任编译器的输出 - 特别是当你处理嵌入式代码时。很容易发现在使用不同的编译器编译相同代码时生成的代码存在差异。这是因为C标准本身太宽松了。许多细节可以由不同的编译器以不违反标准的方式实现。我们如何处理这些问题?我们尽可能避免使用依赖于编译器的结构。我们可以通过选择更安全的C子集来处理它,例如用户cschol之前提到的Misra-C。我很少需要检查编译器生成的代码,但有时也会发生这种情况。但是,最终,您仍然要依靠测试来确保代码的行为符合预期。

还有更好的选择吗?有些人声称有。另一个选择是使用SPARK/Ada编写代码。我从未用过SPARK编写代码,但我的理解是您仍然需要将其链接到用C编写的例程上,以处理“裸机”内容。 SPARK/Ada的优点在于,您绝对可以保证任何编译器生成的代码始终相同。毫无歧义。此外,该语言允许您使用解释说明代码预期如何运行的注释进行标注。 SPARK工具集将使用这些注释来正式证明编写的代码确实执行了注释所描述的操作。所以我听说对于关键系统,SPARK/Ada是一个不错的选择。但我自己从未尝试过。


2

......你完全信任你的编译器

但第一次遇到编译器错误时,你会停止这样做。;-)

但最终,这就是测试的目的。对于您的测试计划来说,如何在产品中引入错误并不重要,重要的是它未能通过您的广泛测试计划。


+1 对于“你第一次遇到编译器错误时就会停止这样做”的支持。 - Joseph Quinsey

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