18

在处理最近的一个项目时,一位客户 QA 代表拜访了我,他问了我一个我以前没有真正考虑过的问题:

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

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

我知道这对每个人来说可能并不是一个真正的问题,实际上答案可能只是......“你已经过了桶并处理它”。但是,在嵌入式环境中工作时,您隐含地信任您的编译器。我如何向自己和 QA 证明我这样做是正确的?

4

22 回答 22

12

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

当然,为什么这可能是一个有效问题的一个很好的例子是,Ken Thompson 如何在原始的“登录”程序中放置一个后门......并修改了 C 编译器,以便即使您重新编译登录,您仍然可以获得后门。有关更多详细信息,请参阅此帖子

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

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

于 2008-09-13T14:10:58.830 回答
11

对于安全关键的嵌入式应用,认证机构需要满足编译器的“使用验证”要求。通常有某些要求(有点像“营业时间”)需要通过详细的文档来满足和证明。然而,大多数人不能或不想满足这些要求,因为这可能非常困难,尤其是在您的第一个项目中使用新的目标/编译器。

另一种方法基本上是根本不信任编译器的输出。除了后来的功能测试之外,任何编译器甚至与语言相关的(C-90 标准的附录 G,有人知道吗?)缺陷都需要通过一组严格的静态分析、单元和覆盖测试来覆盖。

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

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

于 2008-09-13T18:05:12.857 回答
7

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

您会发现,您或编译器编写者出错的几率远小于使用某种汇编语言编写相关程序时出错的几率。

于 2008-09-16T21:33:37.947 回答
5

有可用的编译器验证套装。
我记得的一个是“多年生”

当我为嵌入式 SOC 处理器开发 C 编译器时,我们必须针对这个和另外两个验证套装(我忘记了它的名字)来验证编译器。验证编译器与这些测试套件的一致性程度是合同的一部分。

于 2008-09-17T22:52:06.593 回答
3

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

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

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

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

于 2008-09-13T14:08:01.757 回答
3

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

你没有,这就是你测试生成的二进制文件的原因,以及为什么你确保发布你测试过的相同的二进制文件。以及为什么当您进行“次要”软件更改时,您进行回归测试以确保没有旧功能损坏。

我认证的唯一软件是航空电子设备。FAA 认证不足以证明该软件可以正常工作,同时它确实会迫使您跳过一定数量的障碍。诀窍是构建你的“过程”,尽可能地提高质量,尽可能少地避免多余的跳圈。因此,任何你知道的东西都是毫无价值的,并且实际上不会发现错误,你可能可以摆脱。你知道你应该做的任何事情,因为它发现 FAA 没有明确要求的错误,你最好的办法是扭曲词,直到听起来你给 FAA/你的 QA 人员他们要求的东西。

这实际上并不像我所说的那样不诚实,总的来说,FAA 更关心你是否认真和自信,你正在努力做好工作,而不是你到底做了什么。

于 2008-10-07T00:56:04.890 回答
3

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

于 2008-10-08T02:28:33.267 回答
3

您永远无法完全信任编译器,即使是强烈推荐的编译器。他们可以发布有错误的更新,并且您的代码编译相同。当使用有缺陷的编译器更新旧代码时,这个问题变得更加复杂,进行测试并运送货物只是让客户在 3 个月后给您打电话给您一个问题。

这一切都回到了测试上,如果我学到了一件事,那就是在任何重要的变化之后彻底测试。如果问题似乎无法找到,请查看已编译的汇编器并检查它是否在做它应该做的事情。

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

于 2008-10-26T14:23:58.397 回答
2

您不确定编译器是否会完全按照您的预期执行。当然,原因是编译器是软件的一部分,因此容易出现错误。

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

尽管如此,一旦你确定了你认为语言规范的含义,你就可以为每一个细微差别编写一个好的、快速的、自动化的测试。这就是编译器编写比编写其他类型的软件具有巨大优势的地方:在测试方面。每个 bug 都成为一个自动化测试用例,并且测试套件可以非常彻底。编译器供应商在验证编译器正确性方面投入的预算比您多得多(您已经有一份日常工作,对吧?)。

这对你意味着什么?这意味着您需要对编译器中可能存在的错误持开放态度,但您自己可能不会发现任何错误。

我会选择一个编译器供应商,该供应商短期内不太可能倒闭,其编译器具有高质量的历史,并且已经证明他们有能力为他们的产品提供服务(修补)。随着时间的推移,编译器似乎变得更加正确,所以我会选择一个已经存在大约一两年的编译器。

将注意力集中在正确编写代码上。 如果它是清晰和简单的,那么当您遇到编译器错误时,您将不必费力地思考问​​题所在。 编写好的单元测试,这将确保您的代码按照您的预期执行。

于 2008-09-13T15:27:22.297 回答
2

...您隐含地信任您的编译器

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

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

于 2008-09-24T03:38:34.240 回答
2

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

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

于 2010-01-08T02:15:23.137 回答
1

尝试单元测试。

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

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

于 2008-09-13T13:58:34.493 回答
1

您最容易证明的是您正在使用来自提供者 X 的未篡改编译器。如果他们不信任提供者 X,那就是他们的问题(如果 X 相当值得信赖)。如果他们不信任任何编译器提供者,那么他们是完全不合理的。

回答他们的问题:我确保通过这些方式使用来自 X 的未篡改编译器。X 享有盛誉,而且我有一组很好的测试表明我们的应用程序的行为符合预期。

其他一切都开始打开蠕虫罐头。正如罗布所说,你必须在某个地方停下来。

于 2008-09-13T14:27:29.680 回答
1

有时,当您要求进行积极的优化时,您确实会改变行为。

以及优化和浮点数?忘了它!

于 2008-09-13T15:00:48.320 回答
1

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

在安全关键系统(想想核电站和商业航空电子设备)中,您需要注意,监管机构会要求您证明这一点。根据我的经验,您可以通过以下两种方式之一执行此操作:

  1. 使用合格的编译器,其中“合格”意味着它已根据监管机构制定的标准进行了验证。
  2. 执行目标代码分析。本质上,您编译一段参考代码,然后手动分析输出以证明编译器没有插入任何无法追溯到您的源代码的指令。
于 2008-09-17T20:04:56.013 回答
1

你得到了 Dijkstra 写的那个。

于 2008-10-26T14:38:58.927 回答
1

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

于 2010-01-07T12:51:49.597 回答
0
  1. 更改编译器的优化级别将更改输出。
  2. 对函数的轻微更改可能会使编译器内联或不再内联函数。
  3. 对编译器的更改(例如 gcc 版本)可能会更改输出
  4. 某些库函数可能是内在的(即,发出优化的程序集),而其他大多数不是。

好消息是,对于大多数事情来说,它真的没有那么重要。如果它确实很重要(例如,在 ISR 中),您可能需要考虑组装。

于 2008-09-13T14:16:46.197 回答
0

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

否则,您会像了解代码中的错误一样了解它 - 测试。

现代编译器的机器代码可能有很大的不同,对于弱小的人类来说是完全无法理解的。

于 2008-09-13T14:27:35.123 回答
0

我认为有可能以某种方式将此问题简化为停机问题

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

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

于 2008-09-13T15:02:57.147 回答
0

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

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

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

于 2008-09-19T00:57:33.900 回答
0

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

于 2010-01-07T18:53:39.597 回答