크래시 덤프 이용하기

프로그래머라면 피할 수 없는 것이 버그와의 싸움인데요. 오늘은 크래시 덤프를 이용하는 방법에 대해서 정리해보려고 합니다. 조금이라도 경력있는 윈도우즈 개발자라면 당연히 알고 있을만한 내용이라고 생각하지만, 오늘부터는 하나씩 정리해나가면서 알고 있는 것들을 제대로 설명할 수 있는지 확인하려고 합니다. 이하의 내용은 Visual Studio 2003 기준입니다.

크래시 덤프가 필요한 이유는 단순합니다. 자기 컴퓨터에서 개발하다가 버그를 발견하고 디버깅할 수 있다면 더할 나위 없이 행복한 경우겠죠. 그렇지만 자기가 발견하지 못한 버그가 포함된 프로그램이 이미 사이트에 배포되고 거기에서 버그가 발생했다면 어떻게 해야할까요? 가까운 곳이라면 직접 현장에 뛰어가서 디버깅 할 수도 있겠지만, 저는 서울에 있고 버그는 저 멀리 강원도 산골짜기에서 났다면 거기까지 뛰어갈 수도 없는 노릇입니다.

이럴 때에 "내 컴퓨터에서는 제대로 돌아갔었단 말야!"라고 소리쳐봐야 아무런 도움이 되지 않습니다. 프로그램을 배포하기 전에 처음부터 완전히 모든 버그를 제거할 수 있으면 좋겠지만 현실적으로는 그렇지 못하니까 원격에서 디버깅할 수 있는 방법을 찾을 수 밖에 없겠죠. 그 때 이용하는 것이 크래시 덤프입니다.

버그를 잡는데 가장 중요한 것이 무엇입니까? 바로 버그를 재현하는 것입니다. 수십 시간, 수십 일에 한 번 발생하는 버그가 나온다면 인생이 피곤해지겠죠. 크래시 덤프는 프로그램이 사망했을 때의 메모리 상태를 가지고 있는데, 이것을 이용해서 어떤 데이터를 가지고 있을 때 어느 지점에서 문제가 발생했는지 확인할 수 있습니다.

일단 첫번째로 해야할 일은 Release 모드 프로젝트 설정에서 기준 주소를 설정하는 일입니다. 원래 운영체제가 프로그램을 메모리에 읽어들일 때 여러 모듈이 같은 위치를 차지할 수는 없으므로 재배치가 일어나게 되는데, 일반적으로 쓰이지 않는 자기만의 기준 주소를 지정하면 원하는 주소에 배치될 가능성을 높일 수 있습니다. 이렇게 기준 주소를 지정하는 이유는 나중에 크래시 덤프와 MAP 파일을 놓고 분석할 때 주소가 달라져서 짜증나는 일이 없도록 하기 위해서죠. 대충 0x05100000 정도의 주소라면 무난하지 않을까 하는 생각이 듭니다.

MAP 파일이 무엇인가에 대해 설명하지 않았는데요. 이 파일에는 원래 프로그램에 존재하던 각종 심볼(변수명, 함수명 등)들의 정보와 컴파일하면서 변환된 메모리 주소값을 대응시킨 정보가 포함되어 있습니다. 일단 링커의 디버깅 옵션을 설정할 때 "디버그 정보 생성", "맵 파일 생성", "맵 내보내기", "맵 줄"을 "예"로 설정하도록 합니다.

그 다음에 해야할 일은 컴파일하고 생성된 MAP 파일과 그 시점의 소스들을 잘 보관하는 일입니다. 아무리 MAP 파일에 메모리 주소값과 줄 번호 정보가 잔뜩 들어있어도 그 시점에 존재하던 소스를 문제가 발생했을 때 다시 확인할 수 없다면 무용지물이 되어버립니다. VSS, CVS, Subversion 등 많은 형상 관리 도구가 존재하니 하나쯤은 골라서 프로젝트에 활용해야만 합니다. 개인적으로는 Subversion 처럼 Change Set을 통째로 관리해주는 도구를 추천합니다. 배포할 때 살짝 리비전 번호만 기록해두면 나중에 아무 문제없이 되살릴 수 있습니다. CVS의 경우에는 배포할 때마다 태깅을 하지 않으면 안 되는 귀찮음이 있으니까요.

마지막으로 실제로 덤프를 생성하는 코드를 포함시켜야 할 필요가 있습니다. 물론 따로 덤프를 생성하는 코드를 작성하지 않아도 닥터 왓슨을 이용하면 덤프를 얻을 수 있겠지만, 그렇게 하면 사용자에게 디버거를 인스톨하도록 지시해야 하는 어려움이 있습니다. 게다가 닥터 왓슨이 생성해내는 덤프는 크기가 수십 메가에 이를 정도로 거대합니다. 속편하게 미니 덤프를 생성하는 코드를 포함시켜놓고 프로그램의 시작 부분에서 바로 미니덤프 코드를 실행시켜둡니다. 즉석에서 활용 가능한 미니덤프 클래스는 코드 프로젝트에 올라온 소스excel96님이 작성하신 미니덤프 코드가 있으니 살펴보시기 바랍니다.

배포 시에 주의할 점이 하나 있습니다. 반드시 버전 5.1.2600 이상의 dbghelp.dll을 패키지에 포함시켜야 합니다. 윈도우즈 2000의 경우에는 기본적으로 더 낮은 버전이 설치되기 때문에 반드시 dbghelp.dll을 같이 배포해야만 합니다.

준비 과정이 길었는데요. 써먹는 법은 훨씬 간단합니다. 일단 사용자에게서 미니 덤프 파일을 받게 되면 소스 저장소에서 해당 배포 버전의 소스를 복구시켜놓고, 덤프 파일을 더블 클릭해서 비주얼 스튜디오를 실행시키세요. F5를 누르거나 실행 버튼을 눌러서 디버거를 작동시킵니다. 그러면 하단에서 콜스택을 확인하거나 각종 메모리 값의 상태를 확인할 수 있습니다. WINDBG를 활용하는 경우 Ctrl-D를 눌러 덤프를 열고 명령창에서 !analyze -v를 치면 됩니다.

호출 스택에서 시스템 내부로 들어가는 부분은 무시하고 자신의 코드만 살펴보면 대부분의 문제는 해결할 수 있습니다. 일단 호출 스택을 보고 주소값을 적어봅니다. test.exe!0518d6d5이라면 0518d6d5가 해당되겠죠. 이제 MAP 파일을 열어봅시다. 검색 창 열고 Rva+Base를 쳐서 주소와 함수가 매핑된 정보가 써있는 부분을 찾습니다. 이제 아래로 쭉 내려가면서 0518d6d5에 가장 근접한 Rva+Base를 찾습니다. 해당 줄의 Publics by Value나 Lib:Object를 보면 어느 모듈의 어느 함수에서 문제가 발생했는지 알 수 있습니다. 이제 정확히 해당 함수의 어느 위치에서 문제가 발생했는지 줄번호를 알아내는 일이 남았군요.

offset = 충돌 주소 - 기준 주소 (Preferred load address) - 0x1000

MAP 하단에 충돌이 발생한 모듈의 Line numbers가 나오는 부분을 찾아보면, 왼쪽에 줄번호, 오른쪽에 오프셋이 써있는 목록이 보일 것입니다. 이제 오프셋을 넘지 않는 가장 가까운 줄번호를 찾으면 됩니다.

여기까지의 과정이 매우 복잡하게 보이지만, 2~3번 해보면 그럭저럭 빨리 해낼 수 있습니다. 더 편하게 하길 원한다면 이런 과정을 자동으로 수행해주는 다른 도구들을 사용하면 되는데 나중에 기회가 있으면 다루도록 하겠습니다.

by xeraph | 2006/03/20 20:43 | 학술 | 트랙백 | 덧글(0)

트랙백 주소 : http://xeraph.egloos.com/tb/1655107
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

◀ 이전 페이지다음 페이지 ▶