C(或任何)编译器的确定性性能

时间:2008-09-13 13:47:04

标签: c compiler-construction deterministic embedded

在最近的一个项目上工作时,我遇到了一位客户质量保证代表,他向我提出了一个我以前没有考虑过的问题:

  

您如何知道您使用的编译器生成的机器代码与C代码的功能完全匹配,并且编译器是完全确定的?

对于这个问题,我完全没有回复,因为我一直认为编译器是理所当然的。它接收代码并喷出机器代码。我怎样才能测试编译器实际上没有添加我没有要求的功能?或者甚至更危险地以与我期望的方式略有不同的方式实现代码?

我知道这对所有人来说并不是一个真正的问题,而且答案可能只是......“你已经超过桶并处理它”。但是,在嵌入式环境中工作时,您会隐式地信任您的编译器。我怎样才能向自己和QA证明我这样做是对的?

22 个答案:

答案 0 :(得分:12)

您可以在任何级别应用该参数:您信任第三方库吗?你相信操作系统吗?你相信处理器吗?

为什么这可能是一个有效的关注当然是一个很好的例子,肯汤普森如何将后门放入原始的“登录”程序......并修改了C编译器,这样即使你重新编译登录,你仍然得到了后门。有关详细信息,请参阅此posting

关于加密算法也提出了类似的问题 - 我们怎么知道DES中没有后门可供NSA窥探?

在结束时,您必须决定是否信任您正在构建的基础设施,而不必担心它,否则您必须开始开发自己的硅芯片!

答案 1 :(得分:11)

对于安全性至关重要的嵌入式应用程序,认证机构需要满足编译器的“已证实使用”要求。通常需要满足某些要求(类似于“营业时间”),并通过详细的文档证明。但是,大多数人不能或不想满足这些要求,因为它可能非常困难,特别是在您使用新目标/编译器的第一个项目时。

另一种方法基本上是完全不信任编译器的输出。除了后面的功能测试之外,任何编译器甚至依赖于语言(C-90标准的附录G,任何人?)缺陷都需要通过严格的静态分析,单元和覆盖测试来涵盖。

MISRA-C这样的标准可以帮助将编译器的输入限制为C语言的“安全”子集。另一种方法是将编译器的输入限制为语言的子集,并测试整个子集的输出是什么。如果我们的应用程序仅由子集中的组件构建,则假定已知编译器的输出将是什么。通常是“编译器的资格”。

所有这一切的目标是能够回答质量保证代表的问题:“我们不仅仅依赖于编译器的确定性,而是我们证明它的方式......”。

答案 2 :(得分:7)

你知道通过测试。在测试时,您正在测试您的代码和编译器。

您会发现,如果您以某种汇编语言编写了有问题的程序,那么您或编译器编写器发生错误的几率远小于出错的几率。

答案 3 :(得分:5)

有可用的编译器验证服 我记得的是"Perennial"

当我在嵌入式SOC处理器的C编译器上工作时,我们不得不针对这个以及另外两个验证套件验证编译器(我忘记了它的名称)。验证编译器符合这些测试诉讼的一定程度是合同的一部分。

答案 4 :(得分:3)

这一切归结为信任。您的客户是否信任任何编译器?使用它,或者至少比较你和他们之间的输出代码。

如果他们不信任,是否有该语言的参考实现?你能说服他们相信吗?然后将您的参考文献与参考文献进行比较或使用参考文献。

这一切都假设您实际验证了从供应商/提供商处获得的实际代码,并且检查编译器是否未被篡改,这应该是第一步。

无论如何,这仍然存在一个问题,即如何在没有引用的情况下从头开始验证编译器。这当然看起来像一大堆工作,需要语言的定义,这并不总是可用,有时定义是编译器。

答案 5 :(得分:3)

你永远不能完全信任编译器,即使是高度推荐的编译器。他们可以发布有bug的更新,并且您的代码编译相同。使用有缺陷的编译器更新旧代码,进行测试和发货时,这个问题更加复杂,只是让客户在3个月后遇到问题给你打电话。

这一切都回到了测试中,如果有一件事我已经学会了,那就是在任何非平凡的变化之后进行彻底的测试。如果问题似乎无法找到,请查看已编译的汇编程序,并检查它正在做它应该做的事情。

我曾多次在编译器中发现错误。曾经有一个错误,16位变量会增加但没有进位,只有16位变量是头文件中定义的外部结构的一部分。

答案 6 :(得分:3)

可能会在Crosstalk中发现一些知识产权弹药,这是一本针对国防软件工程师的杂志。这个问题就是他们花了很多时间才醒来的事情。 http://www.stsc.hill.af.mil/crosstalk/2006/08/index.html(如果我能从旧项目中找到我的旧笔记,我会回到这里......)

答案 7 :(得分:3)

  

您如何知道您使用的编译器生成的机器代码与C代码的功能完全匹配,并且编译器是完全确定的?

你不这样做,这就是为什么你测试生成的二进制文件,以及为什么你确保运送你测试过的相同二进制文件。为什么当你进行“次要”软件更改时,你会进行回归测试,以确保没有任何旧功能发生故障。

我认证的唯一软件是航空电子设备。 FAA认证不够严格,无法证明软件正常运行,同时它也会迫使您跳过一定数量的环节。诀窍在于构建你的“过程”,以便尽可能地提高质量,尽可能少的无关跳跃。所以你知道的任何东西都是毫无价值的,并且实际上不会发现错误,你可能会狡猾地离开。而且你知道你应该做的任何事情,因为它找到美国联邦航空局没有明确要求的错误,你最好的选择是扭曲单词直到听起来像是你给了FAA /你的QA人们要求的是什么。

这实际上并不像我说的那样不诚实,一般情况下,美国联邦航空局更关心你是否尽心尽力并确信你尝试做好工作,而不是你究竟做了什么。

答案 8 :(得分:2)

嗯..您不能简单地说您信任编译器的输出 - 特别是如果您使用嵌入式代码。在使用不同的编译器编译完全相同的代码时生成的代码之间并不难发现差异。情况就是如此,因为C标准本身太松散了。不同的编译器可以以不同的方式实现许多细节而不违反标准。我们如何处理这些东西?我们尽可能避免使用编译器相关的构造。我们可以通过选择更安全的C子集来处理它,如用户cschol之前提到的Misra-C。我很少检查编译器生成的代码,但有时也发生在我身上。但是,最终,您依靠测试来确保代码按预期运行。

那里有更好的选择吗?有些人声称有。另一种选择是在SPARK/Ada中编写代码。我从来没有在SPARK中编写代码,但我的理解是你仍然需要将它与用C编写的用于处理“裸机”内容的例程联系起来。 SPARK / Ada的美妙之处在于绝对保证任何编译器生成的代码始终都是一样的。没有任何含糊之处。最重要的是,该语言允许您注释代码,并解释代码的行为方式。 SPARK工具集将使用这些注释来正式证明所编写的代码确实执行了注释所描述的内容。所以我被告知,对于关键系统,SPARK / Ada是一个不错的选择。我自己从未尝试过。

答案 9 :(得分:2)

  

...你信任你的编译器

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

但最终这就是测试的目的。对您的测试制度而言,首先如何将错误输入您的产品并不重要,重要的是它没有通过您广泛的测试制度。

答案 10 :(得分:2)

您不确定编译器是否会完全符合您的预期。当然,原因在于编译器是软件的一部分,因此容易出错。

编译器编写者的优势在于使用高质量的规范,而我们其他人必须弄清楚我们正在进行的工作。但是,编译器规范有错误,复杂的部分有微妙的交互。因此,弄清楚编译器应该做什么并不是一件容易的事。

但是,一旦你决定了你认为语言规范意味着什么,你就可以为每一个细微差别编写一个好的,快速的,自动化的测试。这是编译器编写与编写其他类型软件相比具有巨大优势的地方:在测试中。每个错误都成为一个自动化测试用例,测试套件可以非常彻底。编译器供应商需要投入更多的预算来验证编译器的正确性(你已经有了一份日常工作,对吧?)。

这对你意味着什么?这意味着您需要对编译器中的错误可能性持开放态度,但您可能无法找到自己的错误。

我会选择一个不太可能很快破产的编译器供应商,它的编译器具有高质量的历史,并证明了他们能够为其产品提供服务(补丁)。随着时间的推移,编译器似乎变得更加正确,所以我选择了一个大约十年或两年的编译器。

将注意力集中在正确的代码上。如果清晰简单,那么当你命中编译器错误时,你赢了我不得不认真地决定问题所在。 编写好的单元测试,这将确保您的代码完成您期望的操作。

答案 11 :(得分:1)

对于大多数软件开发(想想桌面应用程序),答案可能是您不知道也不关心。

在安全关键系统(想想核电站和商用航空电子设备)中,您护理,监管机构将要求您进行证明。根据我的经验,您可以采用以下两种方式之一:

  1. 使用合格的编译器,其中“合格”表示已根据监管机构制定的标准进行验证。
  2. 执行对象代码分析。基本上,您编译一段参考代码,然后手动分析输出,以证明编译器没有插入任何无法追溯到源代码的指令。

答案 12 :(得分:1)

当您请求积极的优化级别时,有时会发生行为更改。

优化和浮点数?算了吧!

答案 13 :(得分:1)

您可以轻松证明的最多是您使用来自提供商X的未经过篡改的编译器。如果他们不信任提供商X,那就是他们的问题(如果X值得信赖)。如果他们不信任任何编译器提供者,那么他们是完全不合理的。

回答他们的问题:我确保通过这些方式使用来自X的无阻尼编译器。 X非常有名,而且我有一组很好的测试表明我们的应用程序按预期运行。

其他一切都开始打开蠕虫病毒。正如Rob所说,你必须停在某个地方。

答案 14 :(得分:1)

你得到Dijkstra写的那个。

答案 15 :(得分:1)

选择经过验证的正式编译器,如Compcert C编译器。

答案 16 :(得分:1)

尝试单元测试。

如果这还不够,请使用不同的编译器并比较单元测试的结果。比较strace输出,在VM中运行测试,保留磁盘和网络I / O的日志,然后进行比较。

或者建议编写自己的编译器并告诉他们将花费多少。

答案 17 :(得分:0)

我认为可以将这个问题以某种方式减少到Halting Problem

最明显的问题是,如果你使用某种程序来分析编译器及其确定性,你怎么知道你的程序被正确编译并产生正确的结果?

如果您正在使用另一个“安全”编译器,我不确定。我确信从头开始编写编译器可能会更容易。

答案 18 :(得分:0)

即使是合格或经过认证的编译器也会产生不良结果。保持代码简单,测试,测试和测试。那或者手动遍历机器代码而不允许任何人为错误。 PLus操作系统或您正在运行的任何环境(最好没有操作系统,只有您的程序)。

自从软件和编译器开始以来,这个问题已在关键任务环境中得到解决。正如许多其他回应者也知道的那样。每个行业都有自己的规则,从认证编译器到编程风格(您必须始终以这种方式编程,绝不使用这个或那个或那个),大量的测试和同行评审。验证每个执行路径等

如果你不属于这些行业之一,那么你得到的就是你得到的。 COTS硬件上COTS操作系统的商业程序。它会失败,这是一种保证。

答案 19 :(得分:0)

如果您担心不会产生可见结果的意外机器代码,唯一的方法可能是联系编译器供应商以获得某种满足您客户要求的认证。

否则你就会知道你对代码中的错误了解一致 - 测试。

来自现代编译器的机器代码可能会有很大的不同,对于微不足道的人来说完全不可理解。

答案 20 :(得分:0)

  1. 更改编译器的优化级别将更改输出。
  2. 对函数的轻微更改可能会使编译器内联或不再内联函数。
  3. 对编译器的更改(例如gcc版本)可能会更改输出
  4. 某些库函数可能是instrinic(即,发出优化的汇编),而其他函数则不是。
  5. 好消息是,对于大多数事情而言,这并不重要。如果确实如此(例如,在ISR中),您可能需要考虑装配。

答案 21 :(得分:0)

如果你担心编译器中的恶意错误,一个建议(IIRC,某些项目的NSA要求)是编译器二进制文件在编写代码之前。至少你知道没有人添加针对你的程序的错误。