디버그 이야기 - 비정상종료

2007.08.21 08:10개발

버그를 찾는 작업인 디버그에는 그 종류가 수도 없이 많지만, 오늘 글의 주제는 버그 중에 가장 비극적인 결말을 가져오는 프로그램 비정상종료에 대해 써보려고 합니다.

 

사용하던 프로그램이 비정상종료 되었을 때의 아픔을 겪어 보신 적 있으신가요?

열심히 몇 시간을 투자해 만든 문서가 프로그램 비정상 종료 한번으로 날아 간 경험 등은 누구나 한번쯤 갖고 계실 것입니다.

최고의 개발자들이 만들었다는 마이크로소프트의 제품들마저도 이러한 비정상종료 문제를 완벽하게 해결하지 못하는데 저 같은 하수가 개발하는 프로그램이야 오죽할까요.

사용자 삽입 이미지


IE7 보다 더 안정적이라고 평가 받은 불여우도 자주 죽습니다 --;

프로그램 비정상종료로 하도 고생을 많이 하다 보니 어느 정도 내공이 쌓여서 그간 쌓은 내공 중 천기누설이 아닌 일반적인 내용만 적어 보려고 합니다.

 

프로그램 비정상종료를 대하는 개발자의 태도

비정상종료는 정말이지 개발자에게 악몽과 같습니다.

그래서 개발자들은 종종 해당 문제는 절대로 자기가 만든 것이 아니라고 주장합니다. (증거를 들이댈 때까지)

저도 초짜 개발자일 때 그랬던 거 같습니다.

일단 제가 만든 프로그램이 잘못되었다는 보고를 받으면, 처음 반응은 정색을 하면서 그럴 리가 없다고 오리발을 내미는 것입니다. (아직도 저희 팀에 그런 직원이 있습니다. 매번 해당 직원이 만든 코드가 문제라는 게 증명되는데도 새로운 문제가 발생하면 딱 잡아 뗍니다 ^^)

절대 그럴 일이 없습니다

한번도 그런 식으로 죽은 적 없습니다

관련된 테스트를 수십 번도 더 했는데요

제 코드에서 그런 일이 일어날 일은 없습니다

 

와 같이 자신감과 약간의 공포감이 더한 자만으로 밖에 볼 수 없는 대답을 하는 개발자를 많이 겪었습니다.

하지만 오류에서 자유로운 개발자는 아무도 없습니다.  저나 제 동료 누구나 이러한 잘못을 할 수 있으며, 중요한 것은 하시라도 빨리 오류를 발견하고 수정하는 것입니다.

 

비정상종료의 원인을 찾는 방법 재현만 되면 OK!

비정상종료를 겪는 수많은 오류 중 가장 해결하기 쉬운 오류는 단연코 재현이 가능한 오류입니다. 재현이 되지 않으면 수십~수백만 라인의 코드 중 어느 부분이 오류를 일으켜 문제를 만들었는지 찾을 수 없을 뿐만 아니라, 디버깅 해 볼 수도 없어서 오류 확인이 무한대로 늦어질 수 있습니다.

개인적으로 작년 한해 재현되지 않는 오류로 인해 정말 끔찍한 일을 당했습니다.

고객 중 한 분의 PC 에서 발생한 비정상종료로 인해 저희 팀 전원이 위기에 몰린 일이 있었습니다.

수백 개의 소스를 코드리뷰하고, 비슷한 환경을 만들어 재현해 보려고 노력했지만 허사였습니다.

아무런 흔적도 없이 프로그램이 비정상종료 되어 버렸고, 그 후 다시는 그러한 현상이 발생하지 않았기 때문에 사실 저희 프로그램 문제였는지도, 고객 PC 환경상의 문제였는지도 명백하지 않았지만, 어쨌거나 워낙 중요한 고객이었기 때문에 문제가 커지게 되었습니다.

이렇듯, 재현되지 않는 오류에 대해서는 원인 파악과 해결이 정말 막막하기만 합니다.

반면 개발자가 쉽게 재현할 수 있는 오류라면 디버깅이나 여타 방법을 통해 오류를 파악할 수 있습니다.

하지만, 재현이 되는 오류를 만나기는 쉬운 일이 아닙니다.

고객 PC 에서는 일주일에 2-3일 정도 오류가 발생하는데 개발자 PC 에서는 백날 같은 테스트를 해도 문제의 현상이 나타나지 않는 경우도 있습니다.

이런 경우는 고객 PC를 원격 접속해서 고객에게 발생하는 문제를 눈으로 직접 확인해 보는 게 좋습니다.

또한 비정상종료를 겪은 사용자의 얘기를 주의 깊게 듣고 해당 오퍼레이션을 사용자의 입장에서 열심히 반복해 보는 것이 중요합니다.

며칠 전 저희 고객 중에 한 분으로부터 전화가 왔습니다.

이틀에 한번 꼴로 프로그램이 죽는데, 어떤 화면에서 뭔가를 클릭하면 죽는 것 같다는 것이었습니다.

다행히 이 고객은 어느 화면인지도 기억하고 있어서 범위가 상당히 좁혀졌습니다. 이 고객의 얘기를 바탕으로, 반나절 열심히 해당 화면을 클릭해 본 결과 정말로 고객의 얘기대로 저희 프로그램이 하고 사라졌습니다.

비정상종료에 대해서는 자동보고를 하도록 되어 있었지만, 전혀 걸려들지 않고 프로그램이 바로 사라져 버리는, CRASH 중에 아주 독한 경우였습니다.

하지만 일단 재현이 되었기 때문에 그 처리 과정은 힘들지 않았습니다.

이처럼, 사용자의 오류 보고에 대해 적극적인 방법으로 문제의 원인을 찾는 마인드가 중요합니다.

 

비정상종료의 원인을 찾는 방법 자동 오류 보고 시스템

프로그램이 비정상 종료되는 시점에 가능한 많은 오류관련 정보를 모은 다음 이 사실을 개발자에게 보고 할 수 있도록 해야 합니다.

마이크로소프트는 자사의 OFFICE 프로그램의 비정상종료를 줄이기 위해 미니덤프라는 획기적인 비정상 종료 보완책을 내놓았습니다.

이 덕분에 많은 어플리케이션은 프로그램 비정상종료에 대한 이전과는 비교할 수 없는 많은 정보를 얻을 수 있게 되었습니다.

물론 이 이전에도 CRASH HANDLER 등록을 통해 비정상종료가 발생한 시점의 모듈, 오류 종류 등의 정보를 구할 수 있었습니다.

이러한 정보를 매일 매일 자동으로 수집하고, 일자별 증가 추이를 감시해야 합니다.

이를 기반으로 갑자기 CRASH 가 증가한 경우 가장 최근 수정한 소스의 변경사항을 의심해서 확인해 보는 것이 좋습니다.

사실 자동오류보고시스템은 저희 회사 프로그램의 비정상종료를 획기적으로 줄이는데 공헌했습니다.

아직 이러한 체계가 되어 있지 않다면 지금 바로 시작해 보세요.

 

비정상종료의 원인을 찾는 방법 단순 무식한 TRACE을 이용하는 방법

가끔은 DUMP CRASH 정보를 전혀 구할 수 없는 경우가 있습니다.

구할 수 있는 경우라고 하더라도 CALL STACK 이 완전 망가진 경우 전혀 소득이 없어 난감할 수 밖에 없는데요.

재현되지 않는다면 어쩔 수 없지만, 며칠에 한번이라도 재현이 된다면 TRACE 나 로그 파일에 의심될만한 사항을 로깅 함으로써, 원인을 찾는데 도움이 됩니다.

사용자 PC에서 오류를 확인하는 경우는 의심할만한 소스 여기 저기에 실행정보를 기록하고 파일에 로깅정보를 남기도록 조치하는 방법을 사용합니다.

개발자 PC 에서 재현되는 경우라면, 파일에 기록할 필요 없이 TRACE() 함수를 이용하도 충분합니다.(개발자 PC 에서 재현은 되지만 디버깅은 불가능할 때)

 

 

비정상종료의 원인을 찾는 방법 다양한 도구들

릴리즈 모드인 경우라도 프로덕트 모듈에 대한 SYMBOL 정보, PDB 같은 유용한 디버깅 정보를 빌드 서버에서 관리할 수가 있습니다. 이를 바탕으로 WINDBG 를 이용하여 프로덕트로 실행중인 릴리즈 모드 프로그램을 CALL STACK 과 소스까지 보면서 디버깅 할 수 있습니다.

또한 REBASING 과 심볼파일을 이용하면 DUMP 가 없더라도 어느 모듈의 어느 소스에서 CRASH 가 발생했는지 찾아 볼 수가 있습니다.

MFC 개발자의 경우 MSDN 사이트에서 (확실한가 모르겠네요 ^^) MFC, MSVCRT 과 같은 마이크로소프트사의 PDB까지 구할 수 있으니 참고하시기 바랍니다.

 

비정상종료의 원인을 찾는 방법 코드 리뷰

소스코드를 여러 사람이 함께 보면서 발생 가능한 문제를 되짚어 보는 방법이 있습니다. 이 방법은 시간이 많이 걸리는 대신, 코드상의 다른 문제도 같이 발견할 가능성이 있어 시간이 허락된다면 이 방법도 추천할 만합니다. 사실 코드리뷰는 디버깅 시 하기 보다 코딩과 테스트 전에 개발자간 리뷰를 하는 것이 좋은데, 우리 나라 현실상 CMMI 요건을 충족하는 프로젝트라 하더라도 제대로 된 코드 리뷰를 하는 회사가 몇 군데나 있을까 싶네요. (아 있다면 알려주세요. 문서 내용수정할게요^^)

코드 리뷰를 하기 위해서는 아무래도 중상급 이상의 개발자가 함께 코드를 리뷰 하는 것이 핵심입니다.

 

비정상종료의 유형

프로그램이 비정상적으로 종료되는 유형에는

ACCESS_VIOLATION

DATATYPE_MISALIGNMENT

BREAKPOINT

SINGLE_STEP

ARRAY_BOUNDS_EXCEEDED

FLT_DENORMAL_OPERAND

FLT_DIVIDE_BY_ZERO

FLT_INEXACT_RESULT

FLT_INVALID_OPERATION

FLT_OVERFLOW

FLT_STACK_CHECK

FLT_UNDERFLOW

INT_DIVIDE_BY_ZERO

INT_OVERFLOW

PRIV_INSTRUCTION

IN_PAGE_ERROR

ILLEGAL_INSTRUCTION

NONCONTINUABLE_EXCEPTIO

STACK_OVERFLOW

INVALID_DISPOSITION

GUARD_PAGE

INVALID_HANDLE

 

등과 같이 다양한 원인이 있습니다.

개인적으로 가장 흔하게 만나지만, 원인파악 역시 힘든 오류는 ACCESS_VIOLATION 오류 입니다.

주로 잘못된 메모리 접근에 의한 오류인데, C C++ 의 경우 포인터 사용으로 인해 잘못된 메모리 접근이 언어 차원에서부터 손쉽게 이루어지기 때문에 발생 빈도가 아주 높습니다.

하지만, 문제가 발생한 경우 비정상 종료 시점에는 근처 메모리가 이미 손상된 후이거나 전혀 엉뚱한 곳에서 메모리가 손상되어 죽기 때문에 CALL STACK 까지 확인하고도 허탕치는 경우가 많습니다.

그나마 쉬운 오류들은 INT_DIVIDE_BY_ZERO 과 같이 단순이 0 으로 나누기 부분만 오류체크해 주면 오류가 해결되는 경우도 있습니다.

그 외 FLOAT 관련 연산 오류들은, 프로그램을 비정상 종료까지 시켜야 하는지, 아니면, 경고 정도만 하고 계속 진행하도록 해야 할지 고민되는 부분입니다.(어플리케이션의 성격에 따라 결정하세요)


예전 저희 회사 프로그램에는 CString 관련 오류가 상당히 많았습니다.

컴파일러를 새 버전으로 바꾸자 CString 의 내부 소스가 변경되면서 CString 내의 메모리 체크가 까다로워졌는데 이전에 대충 만들어 놓은 CString 사용 코드가 문제가 되기 시작한 것입니다.

특히 Visual C++ 6.0 에서 .NET 2003으로 갈 때 이러한 오류가 많아 한동안 전체 코드를 뒤져가며 문제가 되는 코드를 수정한 기억이 납니다. (이때 이후로 CString 이 미워졌습니다)

그리고, 프로그램 로직에 의한 오류도 제법 많은데 기억에 나는 것은

If () ~ else if() 문을 나열했는데 어느 조건에도 참이 되지 않은 처리되지 않은 조건이 문제를 일으키는 경우도 있었습니다.

또한 배열의 인덱스를 잘못 찾아 가는 경우,

CPtrArray 와 같이 포인터를 다루는 컨테이너를 잘못 사용한 경우,

OS 별 서로 다른 처리를 주의 깊게 하지 않은 경우 등 비정상적인 종료를 일으키는 원인은 정말로 많네요.

가끔은 MFC 내부에서 STL 을 다루는 황당한 규칙(PRB: Access Violation When Accessing STL Object in DLL 참고)때문에 문제가 생기는 경우도 있었습니다만 대부분 개발자가 코딩 시 주의를 기울이지 못한 부분들이 결국에는 사용자의 안타까운 시간낭비로 이어지고 결국은 개발자가 디버깅하기 위해 엄청난 시간을 투입해야 하는 악순환으로 이어집니다.

 

마지막으로

디버깅의 처음과 끝은 개발자의 꾸준한 관심과 적극적인 마인드라고 생각합니다.  사용자의 비정상종료를 하나라도 빨리 없애기 위해 자동 오류 보고에 늘 관심을 두어야 하고, 고객의 소리에 귀 기울여야 하며, 가장 좋은 것은 오류가 나지 않는 이상적인 코드를 만드는 것입니다. 결코 완벽한 프로그램은 없지만, 늘 좋은 코드를 만들기 위해 오늘도 열심히 야근하는 개발자 여러분 파이팅 입니다 ^^;



Daum 블로거뉴스에서 이 포스트를 추천해주세요. [추천]

  • 프로필사진
    BlogIcon 영민C2007.08.21 09:09

    공감가는 글 잘 읽고 갑니다. 역시 저도 개발자 화이팅 외쳐봅니다. ^^;

  • 프로필사진
    BlogIcon 겐도2007.08.21 11:19 신고

    MS Press의 "Debugging Applications" 시리즈(최신판이 구판을 대부분 포함하니 최신판이면 충분) 정도가 추천되겠지요. 장난 스러운 제 글 하나도 트랙백으로 남겨 봅니다.

    크래싱 버그는 작업중인 모든 데이터가 날라가고 심지어 복구 불가능한 상태로 빠지기도 하기 때문에 매우 중요하게 다루어야 할 것입니다. Divide by zero에 대해 코드 찾아서 그 앞에 "if (변수 == 0)"라고 넣고 끝내버리는 것은 매무 위험한 접근일 것입니다. 발견하지 못한 케이스가 있는 것인지 아니면 다른 부분의 버그로 인해 변수값이 0으로 나온것인지 확실히 분석을 해야 할 것입니다.

    박살난 스택을 보고도 레지스터 역추적 하고 다른 쓰레드들이 뭐하고 있나 등등 해서 메모리를 다시 읽어 내려면 하드웨어, OS, 언어적 특성등 많은 지식들이 요구됩니다. 이는 코딩 영역의 중급 프로그래머와는 다른 지식을 요구하겠죠. 디버깅팀도 역시 전문화 될 필요가 있다고 생각됩니다.

    • 프로필사진
      BlogIcon esstory2007.08.21 12:39 신고

      Debugging Applications 구판을 가지고 있습니다. 워낙 번역이 안좋아 난독증걸릴뻔한 책인데 나름 있을만한 내용은 다 있긴 합니다.. 너무 오래된 내용이어서.. 새 책도 꼭 한번 보고 싶더군요.
      레지스터리나, 어셈블리 역추적같은 작업은 아직 제가 모르는 분야입니다.
      안연구소에 있는 개발자 분과 일전에 잠시 같이 디버깅해 보적이 있는데 이런 쪽에 일가견이 있더군요.
      며칠만 컨설팅 받아 보고 싶었는데 잘 안되었습니다.
      저 역시 이런 분야에 대해 전문적으로 교육하고 문제를 찾아 내는 인력이 반드시 필요하다고 봅니다.

  • 프로필사진
    BlogIcon 김윤수2007.08.21 11:54 신고

    제가 수행하고 있는 프로젝트는 거의 모두 Code Review 하고 있습니다. 근데 문제는 작년에 수행했던 프로젝트는 Code Review 와 Unit Test 를 했음에도 불구하고, System Test 시에 오류가 상당히 많이 발생했고, 데모할 때도 여전히 속을 썩히는 문제를 해결하지 못했다는 것입니다. 결국 한참 지난 지금와서 봤더니 저희들이 사용했던 가장 기본이 되는 라이브러리에 문제가 있었더군요.

    "비정상종료의 원인을 찾는 방법 – 다양한 도구들" 에서 SYMBOL 테이블을 언급하셨는데, 이것과 더불이 한 가지 더 "릴리즈를 할 때는 SYMBOL 테이블도 만들어 놓고, 더불어 형상 관리 시스템에서 그 릴리즈 버전의 소스에 대해 태깅이 필요하다는 것"도 언급해 주시면 좋을 듯 합니다.

    좋은 글 잘 보았습니다. 나중에 시간나면 저도 관련글 하나 써서 트랙백하겠습니다.

    • 프로필사진
      BlogIcon esstory2007.08.21 12:41 신고

      코드리뷰 하시는군요.. 대단하십니다. (지켜가며 프로젝트 수행하는 것이 쉬운일은 아니더군요)
      저희는 매일 수십개의 모듈을 자동업데이트해서 고객에게 배포하고 있는데요 배포할때마다 pdb 의 히스토리가 자동으로 쌓이도록 하고 있습니다.
      문제는 pdb 의 파일 사이즈가 너무 커서 형상관리에는 부적합하더군요. 그냥 빌더 서버에 계속 쌓아 두다가 시간 지나면 일괄 지워버리는 식으로 하고 있습니다.
      트랙백 기대할게요 감사합니다.^^;