Finding crash information using the MAP file
아래의 내용은 내친구 헌기군 블로그에서 퍼왔음을 알려드리고
저작권은 그 친구하테 물어보시오...

=========================================================================================================

Finding crash information using the MAP file
 
 
당신이 제작한 프로그램을 사용하는 사용자들이 당신에게 해당 프로그램에서 "crash"가 발생했다고 투덜거렸다고 하자. 당신은 단지 "crash"가 발생했다는 사실만을 알뿐, 그래서 프로그램에 어딘가 문제가 있다는 사실만 알게 될 뿐, 어디에 문제가 있는지 발견해 수정하기는 여간 어려운 일일 것이다. 그런데, 만약 그 유저가 "crash address"를 보내주었다면, 또는 "crash"가 발생했을 때 "crash address"를 전송 받을 수 있는 구조의 프로그램이라면, MAP 파일을 통해 코드의 어느 부분에서 crash가 발생했는지 찾을 수 있다. 지금부터 MAP 파일을 이용해서 crash address로부터 코드의 어디에서 crash가 발생했는지찾아내는 방법을 알아보도록 하자.

2005년 4월 8일 박헌기 정리

P.S. 꽤 열심히 정리했어요. ^^ 혹시 다른 곳으로 퍼가실 때는 출처를 남겨주세요.

Creating a MAP File

먼저 MAP 파일을 가지고 있어야 분석을 할 수 있을 것이다. 따러서 MAP 파일을 만드는 방법부터 알아보도록 하자. Visual Studio를 기준으로 기본 releas 셋팅으로부터 몇몇 옵션을 조정하면 MAP 파일을 얻을 수 있다. 다음의 셋팅을 조정해 주자. 뭐, 영문판 Visual Studio이든, 한글판이든 비슷하니까 별로 어렵지 않게 셋팅을 조정할 수 있을 것이다.

1. Project Setting의 C/C++ 탭 -> Debug Info. -> Line Number Only 를 선택한다. (이 셋팅을 하지 않으면 MAP 파일은 얻을 수 있지만, 어떤 함수에서 crash 가 발생했는지까지만 찾을 수 있고, 어떤 라인에서 crash가 발생했는지는 찾기 어려워 진다. 함수까지만으로도^^;; 자신 있다면, 이 셋팅은 하지 않아도 되겠다. 이 셋팅을 바꾼다고 exe/dll 자체에 변화가 생기는 것은 아니니 이왕이면 셋팅해 주면 좋겠다.)

2. Link 탭 -> Generate mapfile 옵션을 선택해준다. 아마도 여기까지 하면 빌드 옵션의 명령줄 부분에 /MAPINFO:LINES 와 /MAPINFO:EXPORTS 등이 생길 것이다.

이 셋팅으로 빌드하면 실행파일과 이름이 같은 .map 파일이 생성될 것이다. 코드가 수정되면 당연히 MAP 파일도 바뀌게 되므로 버젼을 하나 만들때마다 이 MAP 파일을 보관해 두는 센스가 필요하다. (Build 후 이벤트 등을 이용하면 좋겠다.)


Reading the MAP file

이제 crash address로부터 어디가 문제인지 찾아보자. 만약 코드에 다음과 같은 "무시무시한" 라인이 있다고 예를 들어보자.

char* pEmpty = NULL;
*pEmpty = 'x'; // This is line 119

당연히도 위의 코드는 crash 를 유발하는 코드가 된다.
이제 MAP 파일을 보자. MAP 파일의 앞부분은 다음과 같다.
뭐 약간씩 다를 수도 있지만 Visual Studio에서 컴파일 했다면 대강 비슷한 모습을 가질 것이다. 앞부분에서는 Preferred load address를 주의 깊게 보자.
(이 Preferred load address는 Project Setting에서 수정이 가능하다. "Link 탭의 고급 -> 기준 주소" 부분을 수정해 주면 된다. 이 Preferred load address 값은 이후 계산에 포함되는 값이므로 좀 더 단순한 값으로 수정해 두는 것도 좋을 것으로 보인다. 사실 기본값인 00400000으로도 충분하긴 하다. 단 여러 모듈을 복합적으로 사용하는 어플리캐이션의 경우에는 각 dll, exe 마다 다른 다른 기준주소를 지정해 두는 편이 구분을 위해 좋을 것 같다.)

*********************************************************************

Test
Timestamp is 4254ed7d (Thu Apr 07 17:21:17 2005)
Preferred load address is 00400000

Start Length Name Class
0001:00000000 000ef7e6H .text CODE
0001:000ef7f0 00006d47H .text$x CODE
0001:000f6540 000001a0H .text$yc CODE

.............. ( 중략 ) ..............

Address Publics by Value Rva+Base Lib:Object
0001:00000000 _WinMain@16 00401000 f MAPFILE.obj
0001:000000c0 ?MyRegisterClass@@YAGPAUHINSTANCE__@@@Z 004010c0 f MAPFILE.obj
0001:00000150 ?InitInstance@@YAHPAUHINSTANCE__@@H@Z 00401150 f MAPFILE.obj
0001:000001b0 ?WndProc@@YGJPAUHWND__@@IIJ@Z 004011b0 f MAPFILE.obj

.............. ( 중략 ) ..............

entry point at 0001:00000350

Static symbols

0001:000035d0 LeadUp1 004045d0 f LIBC:memmove.obj
0001:000035fc LeadUp2 004045fc f LIBC:memmove.obj

.............. ( 중략 ) ..............

Line numbers for .ReleaseMAPFILE.obj(F:MAPFILEMAPFILE.cpp) segment .text

24 0001:00000000 30 0001:00000004 31 0001:0000001b 32 0001:00000027
35 0001:0000002d 53 0001:00000041 40 0001:00000047 43 0001:00000050
45 0001:00000077 47 0001:00000088 48 0001:0000008f 52 0001:000000ad
53 0001:000000b3 71 0001:000000c0 80 0001:000000c3 81 0001:000000c8
82 0001:000000ff 86 0001:00000114 88 0001:00000135 89 0001:00000145
102 0001:00000150 108 0001:00000155 110 0001:00000188 122 0001:0000018d
115 0001:0000018e 116 0001:0000019a 119 0001:000001a1 121 0001:000001a8
122 0001:000001ae 135 0001:000001b0 143 0001:000001cc 172 0001:000001ee

.............. ( 이하 생략 ) ..............

*********************************************************************


설명의 편의를 위해 예를 들어 설명하겠다. 사용자가 보내온 crash address가 "0x004011a1" 라고 하자. 이 값은 "00401150"와 "004011b0" 사이의 값임을 알 수 있다. 이는 crash가 InitInstance@@YAHPAUHINSTANCE__@@H@Z 함수에서 발생했음을 이야기 한다. 다시 말해 crash address를 얻었다면 "이를 넘지 않는 가장 큰 Rva+Base 값"을 가진 함수를 찾으면 되는 것이다. 이정도만 하더라도 crash 문제를 해결하는데 있어서 많은 도움이 될 것인데, 추가로 어느 라인에서 crash가 발생했는지 알 수 있다. 자~ 잘 따라와 보자. 필자는 글을 잘 못 쓴다 ^^; 여러분이 이 글을 잘 이해해 주는 것이 가장 중요하다.
먼저 Portable Excutable (PE)의 크기값을 알아야 한다. (PE가 무엇인지 궁금하다면 다른 자료를 찾아서 보도록 하자) 이는 MAP 파일에서 알 수 있다.
앞의 예에서 다음의 부분을 주목하자.

0001:000000c0 ?MyRegisterClass@@YAGPAUHINSTANCE__@@@Z 004010c0 f MAPFILE.obj

처음에 Address, 000000c0 과 Rva+Base, 004010c0 의 차를 보자. Address에서 Preferred load address를 더하고 PE를 더하면 Rva+Base가 된다. 우리는 Preferred load address의 값이 00400000임을 이미 알고 있으므로, PE의 값은 1000임을 알 수 있다.
이제 본격적으로 어떤 라인에서 crash가 발생했는지를 찾아보자. 다음과 같은 계산을 한다. 여기서 말하는 값은 모두 16진수임을 주목하여 생각한다.

"crash address" - "Preferred load address" - "the Size of PE"

우리의 예에서는 0x004011a1 - 0x00400000 - 0x1000 = 0x1a1 와 같이 계산되겠다.
이제 이 값을 MAP 파일에서 찾아보자. 앞서 crash가 발생한 함수를 찾을 때와 같이 해당 소스파일에서 "넘지 않는 가장 근접한 최대값"을 찾아보면 119번 라인이라는 사실을 알 수 있다. 아싸~ 이제 119번 라인에서 crash가 발생했음을 알게 되었다.

이제 우리는 release 빌드에서도 어디서 crash 오류가 발생했는지 알게 되었다. 뭐, memory dump 같은 방법을 사용하면 이 상황에서 변수의 값도 볼 수 있겠지만, 코드의 어느 라인에서 crash가 발생했는지 아는 것만으로도 프로그램의 유지보수에 많은 도움이 될 것임에는 틀림 없다.

여러분은 "BugslayerUtil" 라는것을 아시는가? 이를 이용하면 앞에서 이야기한 crash가 발생했을 때 crash address를 사용자가 프로그램을 유지보수하는 사람에게 보내줄 수 있는 구조를 만들 수 있을 것이다. 추가로, crash가 발생했을 때의 call stack 역시 받아볼 수 있을 것이다. 관심 있으신 분은 한번 찾아보시기를 권해 드린다. 나중에 이 부분도 정리하도록 해야겠다 ^^

2005년 4월 8일 박헌기 정리
by 열혈보이 | 2005/12/22 10:04 | STL | 트랙백 | 핑백(1) | 덧글(4)
트랙백 주소 : http://iskim.egloos.com/tb/2045804
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at homo-nightcus : .. at 2009/05/26 11:00

... http://iskim.egloos.com/2045804</a> ========================================================================================================= <a name="1173795" style="border-bottom-width: 1px; border-bottom-style: dotted; border-bottom-color: rgb(0, 0 ... more

Commented by Camy at 2007/04/06 01:08
Hello
Commented by Robert at 2007/04/06 01:29
nice
Commented by Naomi at 2007/04/06 01:45
hello
Commented by huhu at 2007/06/08 12:08
제 블로그에 퍼갑니다.

:         :

:

비공개 덧글

< 이전페이지 다음페이지 >