자료 저장소

Debug mode vs Release mode

 

디버그 모드에서는 잘 돌아가는데 릴리즈 모드로 빌드해서 프로그램을 실행하면 뻑(?)이 나요. 왜 이럴까요?

이런 질문을 본적이 있거나 혹은 직접 경험해본 사람들이라면 이 버그를 잡는 고통을 잘 알것이다. 디버그와 릴리즈모드의 컴파일러 뒷단에서 일어나는 내용에 대해서 충분히 숙지 하지 못하고 있다면 이런 류의 버그를 잡기란 여간 까다로운 것이 아니다.

Visual Studio의 디버그 모드와 릴리즈 모드의 차이점은 무엇이고 어떤 일들이 일어나는지 본인의 경험, 웹상에서 찾은 정보들을 정리해두어 이런 버그로 고생하는데 도움(?)이 되었으면 한다.


 

Check List

    Pointer
    초기화 되지 않은 포인터의 경우 디버그모드에서는 임의값 0xCD로 초기화를 수행하지만 릴리즈에서는 초기화를 수행하지 않는다. 디버그 모드에서 컴파일러 옵션을 조정하여 초기화 하지 않은 포인터 변수를 사용하는것을 예방 할 수 있다. /GZ 컴파일러 옵션은 기본적으로 VC++ 프로젝트 셋팅에서 기본값이 아니므로 필요하다면 추가해서 초기화 되지 않는 포인터의 값을 0xCC로 채우도록 해줘야 한다. /GZ 컴파일러 옵션의 가장 큰 목적은 초기화 하지 않은 메모리 변수의 값을 0xCCCCCCCC로 채워서 디버깅중에 개발자가 초기화 하지 않은 값임을 알 수 있도록 하는것이다.
    • 디버그모드에서 deallocator에 의해 해제된 메모리에 채워지는 값은 0xDD 이다.

 

  • Heap
    디버그 모드에서 힙영역에 메모리를 할당하게 되면 guard byte( 0xFD로 초기화 )를 추가적으로 할당하여 가장 흔하게 범하는 zero-base의 arrary 인덱스를 잘못 계산하여 경계를 벗어난 영역을 접근하거나 지우려고 하는 코드를 작성했다고 하더라도 디버그 모드에서는 크래쉬가 발생하지 않을 수 있다. 이런 잠재적인 버그를 가진 프로그램을 릴리즈 모드에서 실행시키게 되면 크래쉬가 발생하게 된다.

 

  • ASSERT
    ASSERT 구문은 디버그 모드에서는 공백으로 대체되어지는 점을 잊고 아래와 같이 쓰게 되는 경우 이 코드는 릴리즈에서는 공백으로 대체되어 실제로 체크를 할 수 없게 된다.
    ASSERT (OpenMyWindow () != NULL);
    위의 코드를 다음과 같이 바꾸게 되면 디버그와 릴리즈 모드 모두 정상적으로 동작하게 된다.
    hWND = OpenMyWindow();ASSERT (hWND != NULL);
    이런 체크를 릴리즈에서도 하고 싶다면 VERITY 매크로를 사용하도록 하자.

 

  • Prototypes 사용자 정의 메시지 처리를 위해 ON_MESSAGE 매크로를 사용중이라면 메시지 핸들러의 원형을 요구하는 타입에 정확하게 맞게 선언해줘야 한다. 디버그 모드에서는 원형에 일치하지 않더라도 컴파일러가 수정하여 동작하도록 만들어줘 버그를 발견하기 힘들게 만든다.
    afx_msg LRESULT <class>::OnMyMessage (WPARAM wParam, LPARAM lParam);
  • Optimization Max Speed 옵션은 속도 최적화에 촛점을 맞추고 있기 때문에 최적화 과정에서 안전하지 않을수 있다. 기본적으로 릴리즈 모드에서는 Maximize Speed 옵션이 기본이지만 안전하게 속도 최적화를 보장하는 Minimize Size를 추천한다. 그리고, #pragma 지시자를 사용하여 특정 영역의 옵션을 설정 할 수 있다는것을 기억하자.
    #pragma optimize("", off)// some code here #pragma optimize("", on)

 

MSDN에서 권고하는 릴리즈 빌드 문제 해결

  • ASSERT 문 검사
    • CheckList의 ASSERT
  • 디버그 빌드를 사용한 메모리 덮어쓰기 확인
    1. InitInstance 함수의 맨 처음 부분에 다음 줄을 추가합니다. 이렇게 하면 디버그 메모리 할당자가 모든 할당된 메모리 주위에 보호 바이트를 배치합니다. 그러나 이 보호 바이트의 변경 여부(변경되었으면 메모리 덮어쓰기가 발생했음)를 확인하지 않으면 보호 바이트는 아무런 소용이 없습니다. 보호 바이트의 변경 여부를 확인하면 버퍼가 제공되어 메모리 덮어쓰기를 해결할 수 있습니다.

      afxMemDF |= checkAlwaysMemDF;

      checkAlwaysMemDF 변수를 설정하면 MFC에서는 new 또는 delete가 호출될 때마다 AfxCheckMemory 함수를 호출하게 됩니다. 메모리 덮어쓰기가 감지된 경우에는 다음과 같은 TRACE 메시지가 생성됩니다.

      Damage Occurred! Block=0x5533

      이러한 메시지가 표시되는 경우에는 코드를 단계별로 실행하여 손상된 부분을 확인해야 합니다. 메모리 덮어쓰기가 발생한 부분을 보다 정확하게 구별하려면 사용자가 AfxCheckMemory를 명시적으로 호출하면 됩니다. 예를 들면 다음과 같습니다.

      ASSERT(AfxCheckMemory());    DoABunchOfStuff();ASSERT(AfxCheckMemory());

      첫 번째 ASSERT는 성공하고 두 번째 ASSERT는 실패하는 경우에는 두 호출 사이의 함수에서 메모리 덮어쓰기가 발생했을 가능성이 큽니다.

      응용 프로그램의 특성에 따라 afxMemDF를 사용하면 프로그램 실행이 너무 느려져서 테스트조차 수행할 수 없는 경우도 있습니다. 왜냐하면afxMemDF 변수는 new 및 delete가 호출될 때마다 AfxCheckMemory가 호출되도록 하기 때문입니다. 이 경우에는 위의 예제와 같이 AfxCheckMemory( ) 호출을 분산시켜야 하며, 이러한 방법으로 메모리 덮어쓰기를 구별해야 합니다.

 

  • 릴리스 빌드에 대한 디버그 정보 생성 활성화
    1. 프로젝트의 속성 페이지 대화 상자를 엽니다. 자세한 내용은 Visual C++ 프로젝트 속성 설정을 참조하십시오.
    2. /Z7 또는 /Zi를 활성화합니다.
    3. /INCREMENTAL:NO를 선택합니다.
    4. /DEBUG:Yes를 선택합니다.
    5. /OPT:REF를 선택합니다.
    6. /OPT:ICF를 선택합니다.
    • 이제 릴리스 빌드 응용 프로그램을 디버깅할 수 있습니다. 문제를 찾으려면 오류가 발생한 부분을 찾을 때까지 코드를 단계별로 실행하거나, Just-In-Time 디버깅을 사용하여 올바르지 않은 매개 변수 또는 코드를 확인합니다.

      프로그램이 디버그 빌드에서는 작동하지만 릴리스 빌드에서 작동하지 않으면 소스 코드에서 컴파일러 최적화 중 하나에 결함이 있는 경우일 수 있습니다. 문제를 격리하려면 문제의 원인이 되는 최적화와 파일을 찾을 때까지 각 소스 코드 파일에 대해 선택한 최적화를 비활성화해야 합니다. 예를 들어, 파일을 두 그룹으로 나누고 한 그룹에서 최적화를 비활성화한 다음 문제가 파일 하나에서만 발생할 때까지 각 그룹을 계속 나눌 수 있습니다.

      디버그 빌드에서 그러한 버그를 노출시키려면 /RTC를 사용합니다.

 

  • 메모리 덮어쓰기 확인
    힙 조작 함수 호출 시 액세스 위반이 발생되면 프로그램이 힙을 손상시켰을 가능성이 있습니다. 이러한 경우의 일반적인 증상은 다음과 같습니다.
    Access Violation in _searchseg
    _heapchk 함수는 디버그 빌드와 릴리스 빌드 모두에서(Windows NT에만 해당) 런타임 라이브러리 힙의 무결성을 확인하는 데 사용할 수 있습니다. _heapchk는 AfxCheckMemory 함수를 사용하여 힙 덮어쓰기를 확인하는 것과 같은 방법으로 사용할 수 있습니다. 예를 들면 다음과 같습니다.
    if(_heapchk()!=_HEAPOK)   DebugBreak();
    이 함수가 실패하는 경우에는 힙이 손상된 시점을 확인해야 합니다.

 

Reference

 

http://sungod0.egloos.com/3920875

댓글 로드 중…

최근에 게시된 글