UFO ET IT

컴파일러 최적화로 버그가 발생할 수 있습니까?

ufoet 2020. 11. 7. 18:16
반응형

컴파일러 최적화로 버그가 발생할 수 있습니까?


오늘 저는 제 친구와 토론을했고 우리는 "컴파일러 최적화"에 대해 몇 시간 동안 토론했습니다.

나는 때때로 컴파일러 최적화가 버그 나 적어도 원하지 않는 동작을 일으킬 수 있다는 점을 옹호했습니다 .

내 친구는 "컴파일러는 똑똑한 사람들이 만들고 똑똑한 일을한다"고 말하면서 완전히 동의하지 않았습니다. 따라서 결코 잘못 될 수 없습니다 .

그는 나를 전혀 설득하지 못했지만 내 요점을 강화하기 위해 실제 사례가 부족하다는 것을 인정해야합니다.

여기 누구야? 그렇다면 컴파일러 최적화가 결과 소프트웨어에서 버그를 생성 한 실제 사례가 있습니까? 착각하는 경우 프로그래밍을 중단하고 낚시를 배워야합니까?


컴파일러 최적화는 버그 또는 바람직하지 않은 동작을 유발할 수 있습니다. 그것이 당신이 그들을 끌 수있는 이유입니다.

한 가지 예 : 컴파일러는 메모리 위치에 대한 읽기 / 쓰기 액세스를 최적화하여 중복 읽기 또는 중복 쓰기를 제거하거나 특정 작업을 다시 정렬 할 수 있습니다. 문제의 메모리 위치가 단일 스레드에서만 사용되고 실제로 메모리 인 경우 괜찮을 수 있습니다. 그러나 메모리 위치가 하드웨어 장치 IO 레지스터 인 경우 쓰기를 다시 정렬하거나 제거하는 것은 완전히 잘못된 것일 수 있습니다. 이 상황에서 일반적으로 컴파일러가 "최적화"할 수 있다는 것을 알고 순진한 접근 방식이 작동하지 않는다는 것을 알고있는 코드를 작성해야합니다.

최신 정보:Adam Robinson이 주석에서 지적했듯이 위에서 설명한 시나리오는 최적화 오류보다 프로그래밍 오류에 가깝습니다. 그러나 제가 설명하려고했던 요점은 그렇지 않으면 올바르게 작동하는 일부 최적화와 결합 된 일부 프로그램이 결합 될 때 프로그램에 버그를 유발할 수 있다는 것입니다. 어떤 경우에는 언어 사양에 "이러한 종류의 최적화가 발생할 수 있고 프로그램이 실패 할 수 있기 때문에 이러한 방식으로 작업을 수행해야합니다"라고되어 있으며,이 경우 코드의 버그입니다. 그러나 때로는 컴파일러가 코드를 최적화하기 위해 너무 열심히 노력하거나 최적화가 부적절하다는 것을 감지 할 수 없기 때문에 잘못된 코드를 생성 할 수있는 (일반적으로 선택적인) 최적화 기능이 컴파일러에 있습니다.

또 다른 예 : Linux 커널에는 해당 포인터가 null인지 테스트하기 전에 잠재적으로 NULL 포인터가 역 참조되는 버그 가 있습니다. 그러나 경우에 따라 메모리를 주소 0에 매핑하여 역 참조가 성공할 수 있습니다. 컴파일러는 포인터가 역 참조되었음을 알아 차렸을 때 NULL이 될 수 없다고 가정하고 나중에 NULL 테스트와 해당 분기의 모든 코드를 제거했습니다. 이로 인해 코드에 보안 취약점이 생겼습니다., 함수가 공격자가 제공 한 데이터를 포함하는 잘못된 포인터를 계속 사용하기 때문입니다. 포인터가 합법적으로 null이고 메모리가 주소 0에 매핑되지 않은 경우 커널은 이전과 같이 여전히 OOPS입니다. 따라서 최적화 이전에 코드에는 하나의 버그가 포함되었습니다. 두 개를 포함하고 그중 하나가 로컬 루트 익스플로잇을 허용했습니다.

CERT는 Robert C. Seacord의 "위험한 최적화와 인과 관계의 손실"이라는 프레젠테이션을 가지고 있습니다 . 여기에는 프로그램에 버그를 도입 (또는 노출)하는 많은 최적화가 나열되어 있습니다. "하드웨어가 수행하는 작업"에서 "가능한 모든 정의되지 않은 동작 트랩", "허용되지 않는 작업 수행"에 이르기까지 가능한 다양한 종류의 최적화에 대해 설명합니다.

적극적으로 최적화하는 컴파일러가 손을 잡을 때까지 완벽하게 괜찮은 코드의 몇 가지 예 :

  • 오버플로 확인

    // fails because the overflow test gets removed
    if (ptr + len < ptr || ptr + len > max) return EINVAL;
    
  • 오버플로 artithmetic 사용 :

    // The compiler optimizes this to an infinite loop
    for (i = 1; i > 0; i += i) ++j;
    
  • 민감한 정보의 메모리 지우기 :

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

여기서 문제는 컴파일러가 수십 년 동안 최적화에서 덜 공격적이어서 C 프로그래머 세대가 고정 크기 2 보완 추가 및 오버플로와 같은 것을 배우고 이해한다는 것입니다. 그런 다음 컴파일러 개발자가 C 언어 표준을 수정하고 하드웨어가 변경되지 않더라도 미묘한 규칙이 변경됩니다. C 언어 사양은 개발자와 컴파일러 간의 계약이지만 계약 조건은 시간이 지남에 따라 변경 될 수 있으며 모든 사람이 모든 세부 사항을 이해하거나 세부 사항이 합리적이라는 데 동의하는 것은 아닙니다.

이것이 대부분의 컴파일러가 최적화를 끄거나 켜는 플래그를 제공하는 이유입니다. 프로그램이 정수가 오버플로 될 수 있다는 이해로 작성 되었습니까? 그런 다음 버그를 유발할 수 있으므로 오버플로 최적화를 꺼야합니다. 프로그램이 앨리어싱 포인터를 엄격히 피합니까? 그런 다음 포인터가 앨리어싱되지 않는다고 가정하는 최적화를 켤 수 있습니다. 프로그램이 정보 유출을 피하기 위해 메모리를 지우려고합니까? 오,이 경우 운이 좋지 않습니다. 데드 코드 제거 기능을 끄거나 컴파일러가 "죽은"코드를 제거 할 것임을 미리 알고 일부 작업을 사용해야합니다. -주위에.


최적화를 비활성화하여 버그가 사라지더라도 대부분의 경우 여전히 귀하의 잘못입니다.

저는 주로 C ++로 작성된 상용 앱을 담당하고 있습니다. VC5로 시작하여 초기에 VC6로 포팅되어 이제는 VC2008로 성공적으로 포팅되었습니다. 지난 10 년 동안 100 만 라인 이상으로 성장했습니다.

그 시간에 공격적인 최적화를 활성화했을 때 단일 코드 생성 버그가 발생했음을 확인할 수있었습니다.

그래서 내가 왜 불평하고 있습니까? 동시에, 컴파일러를 의심하게 만드는 수십 개의 버그가 있었기 때문에 C ++ 표준에 대한 이해가 부족한 것으로 판명되었습니다. 표준은 컴파일러가 사용하거나 사용하지 않을 수있는 최적화를위한 공간을 만듭니다.

여러 포럼에서 수년 동안 컴파일러를 비난하는 많은 게시물을 보았으며 궁극적으로 원래 코드의 버그로 판명되었습니다. 의심 할 여지없이 많은 이들이 표준에 사용 된 개념에 대한 자세한 이해가 필요한 모호한 버그이지만 그럼에도 불구하고 소스 코드 버그입니다.

내가 그렇게 늦게 대답하는 이유 : 실제로 컴파일러의 잘못임을 확인하기 전에 컴파일러를 비난하지 마십시오.


컴파일러 (및 런타임) 최적화는 확실히 원치 않는 동작을 유발할 수 있지만 적어도 지정되지 않은 동작에 의존하는 경우 (또는 실제로 잘 지정된 동작에 대해 잘못된 가정을하는 경우)에만 발생 해야 합니다.

이제 그 이상으로 컴파일러는 버그를 가질 수 있습니다. 그 중 일부는 최적화에 관한 것일 수 있으며 그 영향은 매우 미묘 할 수 있습니다. 명백한 버그가 수정 될 가능성이 더 높기 때문에 실제로 그럴 가능성높습니다 .

JIT를 컴파일러로 포함한다고 가정하면 .NET JIT와 Hotspot JVM의 릴리스 버전 (불행하게도 현재 세부 정보가 없음)에서 버그가 발견되어 특히 이상한 상황에서 재현 할 수있었습니다. 특정 최적화 때문인지 여부는 모르겠습니다.


다른 게시물을 결합하려면 :

  1. 컴파일러는 대부분의 소프트웨어처럼 때때로 코드에 버그가 있습니다. 똑똑한 사람들이 만든 NASA 위성 및 기타 앱에도 버그가 있기 때문에 "똑똑한 사람"주장은 이것과 완전히 관련이 없습니다. 최적화를 수행하는 코딩은 그렇지 않은 코딩과 다르므로 최적화 프로그램에 버그가 발생하면 실제로 최적화 된 코드에 오류가있을 수 있지만 최적화되지 않은 코드는 그렇지 않습니다.

  2. Shiny와 New가 지적했듯이 동시성 및 / 또는 타이밍 문제와 관련하여 순진한 코드가 최적화없이 만족스럽게 실행되지만 최적화로 실패하면 실행 타이밍이 변경 될 수 있습니다. 이러한 문제는 소스 코드에서 비난 할 수 있지만 최적화되었을 때만 나타나는 경우 일부 사람들은 최적화를 비난 할 수 있습니다.


한 가지 예입니다. 며칠 전 누군가 가 옵션 (으로 암시 됨 )이있는 gcc 4.5가 시작시 segfault하는 Emacs 실행 파일을 생성 한다는 사실발견 했습니다 .-foptimize-sibling-calls-O2

이것은 분명히 그 이후 로 수정 되었습니다.


지시문이 프로그램의 동작을 변경할 수없는 컴파일러에 대해 들어 보거나 사용한 적이 없습니다. 일반적으로 이것은 좋은 일 이지만 매뉴얼을 읽어야합니다.

그리고 최근 컴파일러 지시문이 버그를 '제거'한 상황이있었습니다. 물론 버그는 여전히 존재하지만 프로그램을 제대로 수정할 때까지 임시 해결 방법이 있습니다.


예. 좋은 예는 이중 확인 잠금 패턴입니다. C ++에서는 컴파일러가 단일 스레드 시스템에서는 의미가 있지만 다중 스레드 시스템에서는 의미가없는 방식으로 명령을 다시 정렬 할 수 있기 때문에 이중 검사 잠금을 안전하게 구현할 수있는 방법이 없습니다. 전체 토론은 http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf 에서 찾을 수 있습니다 .


가능성이 있습니까? 주요 제품은 아니지만 확실히 가능합니다. 컴파일러 최적화는 생성 된 코드입니다. 코드의 출처 (작성하거나 생성하는 코드)에 관계없이 오류가 포함될 수 있습니다.


나는 오래된 코드를 작성하는 새로운 컴파일러에서 이것을 몇 번 만났습니다. 이전 코드는 작동하지만 부적절하게 정의 된 / 캐스트 연산자 오버로드와 같은 경우에 정의되지 않은 동작에 의존했습니다. VS2003 또는 VS2005 디버그 빌드에서 작동하지만 릴리스에서는 충돌이 발생합니다.

생성 된 어셈블리를 열면 컴파일러가 문제의 함수 기능의 80 %를 방금 제거 했음이 분명했습니다. 정의되지 않은 동작을 사용하지 않도록 코드를 다시 작성하면 문제가 해결되었습니다.

더 분명한 예 : VS2008 대 GCC

선언 :

Function foo( const type & tp ); 

부름 :

foo( foo2() );

여기서 foo2()클래스의 객체를 반환 type;

이 경우 객체가 스택에 할당되지 않았기 때문에 GCC에서 충돌하는 경향이 있지만 VS는이 문제를 해결하기 위해 몇 가지 최적화를 수행하며 아마도 작동 할 것입니다.


앨리어싱은 특정 최적화에 문제를 일으킬 수 있으므로 컴파일러에 이러한 최적화를 비활성화 할 수있는 옵션이 있습니다. 에서 위키 백과 :

이러한 최적화를 예측 가능한 방식으로 활성화하기 위해 C 프로그래밍 언어 (최신 C99 에디션 포함)에 대한 ISO 표준은 서로 다른 유형의 포인터가 동일한 메모리 위치를 참조하는 것이 불법 (일부 예외 포함)임을 지정합니다. "엄격한 앨리어싱"으로 알려진이 규칙은 성능을 크게 향상시킬 수 있지만 [인용 필요] 일부 다른 유효한 코드를 손상시키는 것으로 알려져 있습니다. 일부 소프트웨어 프로젝트는 의도적으로 C99 표준의이 부분을 위반합니다. 예를 들어, Python 2.x는 참조 횟수 [1]를 구현하기 위해 그렇게했으며이 최적화를 가능하게하기 위해 Python 3의 기본 객체 구조를 변경해야했습니다. Linux 커널은 엄격한 앨리어싱이 인라인 코드 최적화에 문제를 일으키기 때문에이 작업을 수행합니다. [2] 이러한 경우 gcc로 컴파일하면


Yes, compiler optimizations can be dangerous. Usually hard real-time software projects forbids optimizations for this very reason. Anyway, do you know of any software with no bugs?

Aggressive optimizations may cache or even do strange assumptions with your variables. The problem is not only with the stability of your code, but also they can fool your debugger. I have seen several times a debugger failing to represent the memory contents because some optimizations retained a variable value within the registers of the micro

The very same thing can happen to your code. The optimization puts a variable into a register and do not write to the variable until it has finished. Now imagine how different things can be if your code has pointers to variables in your stack and it has several threads


It's theoretically possible, sure. But if you don't trust the tools to do what they are supposed to do, why use them? But right away, anyone arguing from the position of

"compilers are built by smart people and do smart things" and thus, can never go wrong.

is making a foolish argument.

So, until you have reason to believe that a compiler is doing so, why posture about it?


I certainly agree that it's silly to say the because compilers are written by "smart people" that they are therefore infallible. Smart people designed the Hindenberg and the Tacoma Narrows Bridge, too. Even if it's true that compiler-writers are among the smartest programmers out there, it's also true that compilers are among the most complex programs out there. Of course they have bugs.

On the other hand, experience tells us that the reliability of commercial compilers is very high. I've had many many times that someone told me that the reason why is program doesn't work MUST be because of a bug in the compiler because he has checked it very carefully and he is sure that it is 100% correct ... and then we find that in fact the program has an error and not the compiler. I'm trying to think of times that I've personally run across something that I was truly sure was an error in the compiler, and I can only recall one example.

So in general: Trust your compiler. But are they ever wrong? Sure.


It can happen. It has even affected Linux.


As I recall, early Delphi 1 had a bug where the results of Min and Max were reversed. There was also an obscure bug with some floating point values only when the floating point value was used within a dll. Admittedly, it has been more than a decade, so my memory may be a bit fuzzy.


I have had a problem in .NET 3.5 if you build with optimization, add another variable to a method which is named similarly to an existing variable of the same type in the same scope then one of the two (new or old variable) will not be valid at runtime and all references to the invalid variable are replaced with references to the other.

So, for example, if I have abcd of MyCustomClass type and I have abdc of MyCustomClass type and I set abcd.a=5 and abdc.a=7 then both variables will have property a=7. To fix the issue both variables should be removed, the program compiled (hopefully without errors) then they should be re-added.

I think I have run into this problem a few times with .NET 4.0 and C# when doing Silverlight applications also. At my last job we ran into the problem quite often in C++. It might have been because the compilations took 15 minutes so we would only build the libraries we needed, but sometimes the optimized code was exactly the same as the previous build even though new code had been added and no build errors had been reported.

Yes, code optimizers are built by smart people. They are also very complicated so having bugs is common. I suggest fully testing any optimized release of a large product. Usually limited use products are not worth a full release, but they should still be generally tested to make sure they perform their common tasks correctly.


Compiler optimization can reveal (or activate) dormant (or hidden) bugs in your code. There may be a bug in your C++ code that you don't know of, that you just don't see it. In that case, it is a hidden or dormant bug, because that branch of the code is not executed [enough number of times].

The likelihood of a bug in your code is much bigger (thousands of times more) than a bug in the compiler's code: Because the compilers are tested extensively. By TDD plus practically by all people who have use them since their release!). So it is virtually unlikely that a bug is discovered by you and not discovered by literally hundreds of thousands of times it is used by other people.

A dormant bug or hidden bug is just a bug that is not revealed itself to the programmer yet. People who can claim that their C++ code does not have (hidden) bugs are very rare. It requires C++ knowledge (very few can claim for that) and extensive testing of the code. It is not just about the programmer, but about the code itself (the style of development). Being bug-prone is in the character of the code (how rigorously it is tested) or/and the programmer (how disciplined is in test and how well knows C++ and programming).

Security+Concurrency bugs: This is even worse if we include concurrency and security as bugs. But after all, these 'are' bugs. Writing a code that is in the first place bug-free in terms of concurrency and security is almost impossible. That's why there is always already a bug in the code, which can be revealed (or forgotten) in compiler optimization.


Everything that you can possibly imagine doing with or to a program will introduce bugs.


More, and more aggressive optimizations could be enabled if the program you compile has a good testing suite. Then it is possible to run that suite and be somewhat more sure the program operates correctly. Also, you can prepare your own tests that match closely that do you plan to do in production.

It is also true that any large program may have (and probably indeed has) some bugs independently on which switches do you use to compile it.


I work on a large engineering application, and every now and then we see release only crashes and other problems reported by clients. Our code has 37 files (out of around 6000) where we have this at the top of the file, to turn off optimization to fix such crashes:

#pragma optimize( "", off)

(We use Microsoft Visual C++ native, 2015, but it is true for just about any compiler, except maybe Intel Fortran 2016 update 2 where we have not yet turned of any optimizations.)

If you search through the Microsoft Visual Studio feedback site you can find some optimization bugs there as well. We occasionally log some of ours (if you can reproduce it easily enough with a small section of code and you are willing to take the time) and they do get fixed, but sadly others get introduced again. smiles

Compilers are programs written by people, and any big program has bugs, trust me on that. The compiler optimization options most certainly has bugs and turning on optimization can certainly introduce bugs in your program.


Because of exhaustive testing and the relative simplicity of actual C++ code (C++ has under 100 keywords / operators) compiler bugs are relatively rare. Bad programming style often is the only thing encounters them. And usually the compiler will crash or produce an internal compiler error instead. The only exception to this rule is GCC. GCC, especially older versions, had a lot of experimental optimizations enabled in O3 and sometimes even the other O levels. GCC also targets so many backends that this leaves more room for bugs in their intermediate representation.


I had a problem with .net 4 yesterday with something that looks like...

double x=0.4;
if(x<0.5) { below5(); } else { above5(); }

And it would call above5(); But if I actually use x somewhere, it would call below5();

double x=0.4;
if(x<0.5) { below5(); } else { System.Console.Write(x); above5(); }

Not the exact same code but similar.

참고URL : https://stackoverflow.com/questions/2722302/can-compiler-optimization-introduce-bugs

반응형