【问题标题】:C (or any) compilers deterministic performanceC(或任何)编译器确定性性​​能
【发布时间】:2010-09-08 19:13:18
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: c compiler-construction deterministic embedded


    【解决方案1】:

    尝试单元测试。

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

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

    【讨论】:

      【解决方案2】:

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

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

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

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

      【讨论】:

        【解决方案3】:

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

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

        人们对加密算法提出了类似的问题——我们如何知道 DES 中没有后门可供 NSA 窥探?

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

        【讨论】:

        • 没错。我假设如果那个人问是因为这件事很敏感并且他们有理由这样做,而不是因为他们是偏执狂......
        • 不,Ken 没有,引用一些博主的话也不是这样。阅读 Ken 的真实论文(Reflections on Trusting Trust),看看他真正做了什么:他建议他可以做到。从未见过所谓的编译器和所谓的登录二进制文件。
        • 答案实际上并没有解决问题。
        • 行话文件声称肯确实做到了。 catb.org/jargon/html/B/back-door.html
        【解决方案4】:
        1. 更改编译器的优化级别将更改输出。
        2. 对函数的微小更改可能会使编译器内联或不再内联函数。
        3. 对编译器的更改(例如 gcc 版本)可能会更改输出
        4. 某些库函数可能是固有的(即发出优化的程序集),而其他大多数函数不是。

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

        【讨论】:

          【解决方案5】:

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

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

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

          【讨论】:

            【解决方案6】:

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

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

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

            【讨论】:

              【解决方案7】:

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

              还有优化和浮点数?算了!

              【讨论】:

                【解决方案8】:

                我认为有可能以某种方式将此问题减少到Halting Problem

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

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

                【讨论】:

                • 不,完全有可能编写一个可证明正确的编译器,并最终将证明建立在一个小型手工检查证明检查器的正确性上。无法计算的是任意程序并确定它是否是您想要的正确编译器。
                【解决方案9】:

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

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

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

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

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

                将注意力集中在正确编写代码上。如果它清晰简单,那么当你确实遇到编译器错误时,你就赢了不必费力思考就可以确定问题出在哪里。 编写好的单元测试,这将确保您的代码按照您的预期执行。

                【讨论】:

                  【解决方案10】:

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

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

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

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

                  【讨论】:

                  • 正确。使用单元测试、静态测试和集成测试来验证系统行为。客户的 QQ 人想知道您是否知道如何测试软件以确保质量。使用测试 oracle 或一些经过验证的独立方法来验证您的单元和集成测试数据集。 (例如,经过多位工程师验证的手工生成的单元测试结果。)
                  【解决方案11】:

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

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

                  【讨论】:

                  • 确保使用足够的测试。如果应用领域对正确性的要求不高,测试就很容易。为了严格做到这一点,正式的需求必须由软件表示,并通过单元和集成测试进行测试。如果没有定义正式的正确性要求,那么任何答案都是正确的。客户会选择您没有选择的任何一个。
                  【解决方案12】:

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

                  在安全关键系统(想想核电厂和商业航空电子设备)中,您关心并且监管机构会要求您证明这一点。根据我的经验,您可以选择以下两种方式之一:

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

                  【讨论】:

                    【解决方案13】:

                    有可用的编译器验证套装。
                    我记得的那个是"Perennial"

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

                    【讨论】:

                      【解决方案14】:

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

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

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

                      【讨论】:

                        【解决方案15】:

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

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

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

                        【讨论】:

                        • +1 为"You'll stop doing that the first time you come across a compiler bug"
                        【解决方案16】:

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

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

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

                        这实际上并不像我说的那么不诚实,总的来说,FAA 更关心的是你是否认真和自信地尝试做好工作,而不是关心你到底是做什么的。

                        【讨论】:

                          【解决方案17】:

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

                          【讨论】:

                            【解决方案18】:

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

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

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

                            【讨论】:

                              【解决方案19】:

                              你得到了 Dijkstra 写的那个。

                              【讨论】:

                                【解决方案20】:

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

                                【讨论】:

                                • 但是你怎么知道验证者(Coq)是正确的呢?你怎么知道 C 规范被正确地转录成 Coq 的语言?
                                • @Mike:有一些程序的行为将由 C 标准定义,而不是由 CompCert C 定义,最值得注意的是那些将指针分解为整数或字节序列、操作这些值以及然后从结果中形成新的指针。 ComperCert C 的设计使得任何优化阶段的输出代码的行为必须与该阶段的输入代码的行为相匹配,以便定义行为的所有可能输入值。为了实现这一点,CompCert 定义了 C 标准不要求它这样做的某些情况下的行为。
                                【解决方案21】:

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

                                【讨论】:

                                • 如果您担心编译器中的恶意错误,您可能会很高兴知道已经开发了另一种防御措施:“多样化双重编译”(DDC)。 dwheeler.com/trusting-trust
                                • @David Cary:一个相关的工作是这个家伙使用的编译器错误发现技术:blog.regehr.org
                                • 谢谢,这个博客看起来很吸引人。
                                【解决方案22】:

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

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

                                【讨论】:

                                  猜你喜欢
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2010-11-10
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2011-07-30
                                  • 2011-06-19
                                  相关资源
                                  最近更新 更多