编译器优化会引入错误吗? [英] Can compiler optimization introduce bugs?

查看:33
本文介绍了编译器优化会引入错误吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

今天我和我的一个朋友讨论了几个小时,关于编译器优化".

Today I had a discussion with a friend of mine and we debated for a couple of hours about "compiler optimization".

我为有时的观点辩护,编译器优化可能会引入错误,或者至少是不受欢迎的行为.

I defended the point that sometimes, a compiler optimization might introduce bugs or at least, undesired behavior.

我的朋友完全不同意,他说编译器是由聪明的人构建的,做的事情很聪明",因此永远不会出错.

My friend totally disagreed, saying that "compilers are built by smart people and do smart things" and thus, can never go wrong.

他根本没有说服我,但我不得不承认我缺乏现实生活中的例子来加强我的观点.

He didn't convince me at all, but I have to admit I lack of real-life examples to strengthen my point.

谁在这里?如果是,您是否有任何实际示例,其中编译器优化在生成的软件中产生了错误?如果我错了,我应该停止编程并学习钓鱼吗?

Who is right here? If I am, do you have any real-life example where a compiler optimization produced a bug in the resulting software? If I'm mistaking, should I stop programming and learn fishing instead?

推荐答案

编译器优化可能会引入错误或不良行为.这就是您可以关闭它们的原因.

Compiler optimizations can introduce bugs or undesirable behaviour. That's why you can turn them off.

一个例子:编译器可以优化对内存位置的读/写访问,做一些事情,比如消除重复读或重复写,或者重新排序某些操作.如果有问题的内存位置仅由单个线程使用并且实际上是内存,那可能没问题.但是如果内存位置是硬件设备IO寄存器,那么重新排序或者消除写入可能是完全错误的.在这种情况下,您通常必须编写代码,知道编译器可能会优化"它,因此知道天真的方法行不通.

One example: a compiler can optimize the read/write access to a memory location, doing things like eliminating duplicate reads or duplicate writes, or re-ordering certain operations. If the memory location in question is only used by a single thread and is actually memory, that may be ok. But if the memory location is a hardware device IO register, then re-ordering or eliminating writes may be completely wrong. In this situation you normally have to write code knowing that the compiler might "optimize" it, and thus knowing that the naive approach doesn't work.

更新: 正如 Adam Robinson 在评论中指出的那样,我上面描述的场景更多是编程错误而不是优化器错误.但我试图说明的一点是,一些程序,在其他方面是正确的,结合一些优化,否则可以正常工作,当它们组合在一起时可能会在程序中引入错误.在某些情况下,语言规范说您必须以这种方式做事,因为可能会发生这些类型的优化并且您的程序将失败",在这种情况下,这是代码中的错误.但有时编译器有一个(通常是可选的)优化特性,它可能会生成错误的代码,因为编译器太努力地优化代码或者无法检测到优化是不合适的.在这种情况下,程序员必须知道何时可以安全地开启相关优化.

Update: As Adam Robinson pointed out in a comment, the scenario I describe above is more of a programming error than an optimizer error. But the point I was trying to illustrate is that some programs, which are otherwise correct, combined with some optimizations, which otherwise work properly, can introduce bugs in the program when they are combined together. In some cases the language specification says "You must do things this way because these kinds of optimizations may occur and your program will fail", in which case it's a bug in the code. But sometimes a compiler has a (usually optional) optimization feature that can generate incorrect code because the compiler is trying too hard to optimize the code or can't detect that the optimization is inappropriate. In this case the programmer must know when it is safe to turn on the optimization in question.

另一个例子:linux 内核有一个错误,在测试该指针为空之前,潜在的空指针被取消引用.但是,在某些情况下,可以将内存映射到地址零,从而使取消引用成功.编译器在注意到指针被取消引用后,假定它不能为 NULL,然后稍后删除 NULL 测试以及该分支中的所有代码.这在代码中引入了一个安全漏洞,因为该函数将继续使用包含攻击者提供的数据的无效指针.对于指针合法为空且内存未映射到地址零的情况,内核仍会像以前一样 OOPS.所以在优化之前,代码包含一个错误;在它包含两个之后,其中一个允许本地 root 漏洞利用.

Another example: The linux kernel had a bug where a potentially NULL pointer was being dereferenced before a test for that pointer being null. However, in some cases it was possible to map memory to address zero, thus allowing the dereferencing to succeed. The compiler, upon noticing that the pointer was dereferenced, assumed that it couldn't be NULL, then removed the NULL test later and all the code in that branch. This introduced a security vulnerability into the code, as the function would proceed to use an invalid pointer containing attacker-supplied data. For cases where the pointer was legitimately null and the memory wasn't mapped to address zero, the kernel would still OOPS as before. So prior to optimization the code contained one bug; after it contained two, and one of them allowed a local root exploit.

CERT 有一个名为危险"的演示文稿优化和因果关系的损失",作者 Robert C. Seacord,其中列出了许多在程序中引入(或暴露)错误的优化.它讨论了各种可能的优化,从做硬件做的事情"到​​捕获所有可能的未定义行为"到做任何不被禁止的事情".

CERT has a presentation called "Dangerous Optimizations and the Loss of Causality" by Robert C. Seacord which lists a lot of optimizations that introduce (or expose) bugs in programs. It discusses the various kinds of optimizations that are possible, from "doing what the hardware does" to "trap all possible undefined behaviour" to "do anything that's not disallowed".

一些代码示例在积极优化的编译器掌握之前完全没问题:

Some examples of code that's perfectly fine until an aggressively-optimizing compiler gets its hands on it:

  • 检查溢出

  • Checking for overflow

// fails because the overflow test gets removed
if (ptr + len < ptr || ptr + len > max) return EINVAL;

  • 完全使用溢出算法:

  • Using overflow artithmetic at all:

    // The compiler optimizes this to an infinite loop
    for (i = 1; i > 0; i += i) ++j;
    

  • 清除敏感信息的记忆:

  • Clearing memory of sensitive information:

    // the compiler can remove these "useless writes"
    memset(password_buffer, 0, sizeof(password_buffer));
    

  • 这里的问题是,几十年来,编译器在优化方面一直不那么积极,因此几代 C 程序员学习和理解诸如固定大小的二进制补码加法及其如何溢出之类的事情.然后编译器开发人员修改了 C 语言标准,尽管硬件没有改变,但细微的规则发生了变化.C 语言规范是开发者和编译器之间的契约,但协议的条款会随着时间的推移而变化,并不是每个人都了解每一个细节,或者同意这些细节是合理的.

    The problem here is that compilers have, for decades, been less aggressive in optimization, and so generations of C programmers learn and understand things like fixed-size twos complement addition and how it overflows. Then the C language standard is amended by compiler developers, and the subtle rules change, despite the hardware not changing. The C language spec is a contract between the developers and compilers, but the terms of the agreement are subject to change over time and not everyone understands every detail, or agrees that the details are even sensible.

    这就是大多数编译器提供关闭(或打开)优化的标志的原因.你的程序是在理解整数可能溢出的情况下编写的吗?然后你应该关闭溢出优化,因为它们会引入错误.您的程序是否严格避免别名指针?然后您可以打开假设指针永远不会别名的优化.您的程序是否尝试清除内存以避免泄漏信息?哦,在那种情况下,你不走运:你要么需要关闭死代码删除,要么你需要提前知道你的编译器将消除你的死"代码,并使用一些工作-围绕它.

    This is why most compilers offer flags to turn off (or turn on) optimizations. Is your program written with the understanding that integers might overflow? Then you should turn off overflow optimizations, because they can introduce bugs. Does your program strictly avoid aliasing pointers? Then you can turn on the optimizations that assume pointers are never aliased. Does your program try to clear memory to avoid leaking information? Oh, in that case you're out of luck: you either need to turn off dead-code-removal or you need to know, ahead of time, that your compiler is going to eliminate your "dead" code, and use some work-around for it.

    这篇关于编译器优化会引入错误吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆