Visual C++ 설정을 제대로 맞추었다면 빌드시 map파일이 생성된다.
최악의 경우 map파일만을 사용해서 문제 원인을 파악해야 하는데 map파일 해석 방법을 알아두면 유용하다.
예제 소스
우선 아래와 같은 프로그램이 있다고 하면
// HelloMap.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
const char *HELLO = "Hello, World";
void foo()
{
for (int i = 0; i < 10; i++)
{
printf("%d\n", i);
}
}
void bar(int x)
{
char *buf = const_cast<char *>(HELLO);
buf[0] = 'K';
for (int i = 0; i < x; i++)
{
printf("%d\n", i);
}
printf("%s", buf);
foo();
}
int main(int argc, char* argv[])
{
printf("%s!\n", HELLO);
bar(20);
return 0;
}
예제에서 생성된 map
생성된 map파일의 예는 아래와 같다.
HelloMap Timestamp is 444e24fb (Tue Apr 25 22:32:43 2006) Preferred load address is 00400000 Start Length Name Class 0001:00000000 00000232H .text CODE 0002:00000000 00000044H .idata$5 DATA 0002:00000048 00000054H .rdata DATA 0002:0000009c 00000014H .idata$2 DATA 0002:000000b0 00000014H .idata$3 DATA 0002:000000c4 00000044H .idata$4 DATA 0002:00000108 000000eeH .idata$6 DATA 0002:000001f6 00000000H .edata DATA 0003:00000000 00000004H .CRT$XCA DATA 0003:00000004 00000004H .CRT$XCZ DATA 0003:00000008 00000004H .CRT$XIA DATA 0003:0000000c 00000004H .CRT$XIZ DATA 0003:00000010 00000010H .data DATA 0003:00000020 0000001cH .bss DATA Address Publics by Value Rva+Base Lib:Object 0001:00000000 ?foo@@YAXXZ 00401000 f HelloMap.obj 0001:00000019 ?bar@@YAXH@Z 00401019 f HelloMap.obj 0001:00000057 _main 00401057 f HelloMap.obj 0001:00000076 _printf 00401076 f MSVCRT:MSVCRT.dll 0001:0000007c _mainCRTStartup 0040107c f MSVCRT:crtexe.obj 0001:0000018c __exit 0040118c f MSVCRT:MSVCRT.dll 0001:00000192 __XcptFilter 00401192 f MSVCRT:MSVCRT.dll 0001:00000198 _exit 00401198 f MSVCRT:MSVCRT.dll 0001:0000019e ___p___initenv 0040119e f MSVCRT:MSVCRT.dll 0001:000001a4 ___getmainargs 004011a4 f MSVCRT:MSVCRT.dll 0001:000001aa __initterm 004011aa f MSVCRT:MSVCRT.dll 0001:000001b0 __setdefaultprecision 004011b0 f MSVCRT:fp8.obj 0001:000001c2 ___setusermatherr 004011c2 f MSVCRT:MSVCRT.dll 0001:000001c8 __matherr 004011c8 f MSVCRT:merr.obj 0001:000001cb __setargv 004011cb f MSVCRT:dllargv.obj 0001:000001cc ___p__commode 004011cc f MSVCRT:MSVCRT.dll 0001:000001d2 ___p__fmode 004011d2 f MSVCRT:MSVCRT.dll 0001:000001d8 __onexit 004011d8 f MSVCRT:atonexit.obj 0001:00000204 _atexit 00401204 f MSVCRT:atonexit.obj 0001:00000216 ___set_app_type 00401216 f MSVCRT:MSVCRT.dll 0001:00000220 __except_handler3 00401220 f MSVCRT:MSVCRT.dll 0001:00000226 __controlfp 00401226 f MSVCRT:MSVCRT.dll 0001:0000022c ___dllonexit 0040122c f MSVCRT:MSVCRT.dll 0002:00000000 __imp__printf 00402000 MSVCRT:MSVCRT.dll 0002:00000004 __imp___exit 00402004 MSVCRT:MSVCRT.dll 0002:00000008 __imp___XcptFilter 00402008 MSVCRT:MSVCRT.dll 0002:0000000c __imp__exit 0040200c MSVCRT:MSVCRT.dll 0002:00000010 __imp____p___initenv 00402010 MSVCRT:MSVCRT.dll 0002:00000014 __imp____getmainargs 00402014 MSVCRT:MSVCRT.dll 0002:00000018 __imp___initterm 00402018 MSVCRT:MSVCRT.dll 0002:0000001c __imp____setusermatherr 0040201c MSVCRT:MSVCRT.dll 0002:00000020 __imp___adjust_fdiv 00402020 MSVCRT:MSVCRT.dll 0002:00000024 __imp____p__commode 00402024 MSVCRT:MSVCRT.dll 0002:00000028 __imp____p__fmode 00402028 MSVCRT:MSVCRT.dll 0002:0000002c __imp____set_app_type 0040202c MSVCRT:MSVCRT.dll 0002:00000030 __imp___except_handler3 00402030 MSVCRT:MSVCRT.dll 0002:00000034 __imp___controlfp 00402034 MSVCRT:MSVCRT.dll 0002:00000038 __imp____dllonexit 00402038 MSVCRT:MSVCRT.dll 0002:0000003c __imp___onexit 0040203c MSVCRT:MSVCRT.dll 0002:00000040 \177MSVCRT_NULL_THUNK_DATA 00402040 MSVCRT:MSVCRT.dll 0002:0000006c ??_C@_0N@PLDO@Hello?0?5World?$AA@ 0040206c HelloMap.obj 0002:0000007c ??_C@_03HMFC@?$CFd?6?$AA@ 0040207c HelloMap.obj 0002:00000080 ??_C@_02DILL@?$CFs?$AA@ 00402080 HelloMap.obj 0002:00000084 ??_C@_04MIBI@?$CFs?$CB?6?$AA@ 00402084 HelloMap.obj 0002:0000009c __IMPORT_DESCRIPTOR_MSVCRT 0040209c MSVCRT:MSVCRT.dll 0002:000000b0 __NULL_IMPORT_DESCRIPTOR 004020b0 MSVCRT:MSVCRT.dll 0003:00000000 ___xc_a 00403000 MSVCRT:cinitexe.obj 0003:00000004 ___xc_z 00403004 MSVCRT:cinitexe.obj 0003:00000008 ___xi_a 00403008 MSVCRT:cinitexe.obj 0003:0000000c ___xi_z 0040300c MSVCRT:cinitexe.obj 0003:00000010 ?HELLO@@3PBDB 00403010 HelloMap.obj 0003:00000014 ___defaultmatherr 00403014 MSVCRT:merr.obj 0003:00000020 __dowildcard 00403020 MSVCRT:wildcard.obj 0003:00000024 __newmode 00403024 MSVCRT:_newmode.obj 0003:00000028 __commode 00403028 MSVCRT:xncommod.obj 0003:0000002c __fmode 0040302c MSVCRT:xtxtmode.obj 0003:00000030 ___onexitend 00403030 <common> 0003:00000034 ___onexitbegin 00403034 <common> 0003:00000038 __adjust_fdiv 00403038 <common> entry point at 0001:0000007c Static symbols Line numbers for .\Release\HelloMap.obj(D:\MyData\My Projectz\SDK\HelloMap\HelloMap.cpp) segment .text 9 0001:00000000 12 0001:00000003 14 0001:00000018 17 0001:00000019 24 0001:00000033 27 0001:00000044 29 0001:0000004e 30 0001:00000056 33 0001:00000057 35 0001:00000068 36 0001:00000072 37 0001:00000074
map 해석
crash 주소 찾기
이 프로그램을 실행하면 그대로 crash가 일어난다.
프로그램이 crash가 일어났을 경우 crash가 발생한 주소를 얻을 수 있다. 주소를 확인해보면 아래와 같다.
crash가 발생한 함수 찾기
map파일에서 0x40102e 보다 주소가 뒤에 있는(rvs+base가 큰) 함수를 찾는다. _main이다.
그렇다면 에러는 바로 전 함수인 ?bar@@YAXH@Z 함수에서 발생되었다. 우선 함수 이름을 알아보자. Visual C++에 있는 UNDNAME.exe 프로그램을 사용한다.
C:\Program Files\Microsoft Visual Studio\Common\Tools>undname ?bar@@YAXH@Z Microsoft(R) Windows NT(R) Operating System UNDNAME Version 5.00.1768.1Copyright (C) Microsoft Corp. 1981-1998 >> ?bar@@YAXH@Z == bar
crash가 발생한 함수는 bar 함수이다.
source와 line 번호 찾기
이제 line 번호를 찾자. map 파일에서 line 번호는 아래와 같은 형식으로 되어있다.
9 0001:00000000
맨 앞이 line 번호, 뒤가 offset 정보다. 오류가 발생한 주소로 offset 정보를 계산하면 아래와 같다.
offset = 오류주소 - preferred load address - 0x1000
이같은 식으로 계산하면 40102e - 400000 - 1000 = 2e 가 되며 2e가 offset이 된다.
bar함수의 object 이름은 HelloMap.obj 이므로 눈치상 HelloMap.cpp에 속한 함수라는것을 알 수 있다.
map 파일의 HelloMap.cpp line 정보에 나와있는 offset 중에서 2e근처의 값을 찾는다. 여기서 주의할점은 작은 값을 찾아야 한다.
17 0001:00000019
19가 가장 근접한 값이다. 즉 bar 함수는 HelloMap.cpp의 19번째 줄부터 시작한다.
With WinDbg
WinDbg를 이용해 프로그램을 실행해보자.
esi가 가리키는 주소에 값을 쓰려다 AV가 발생했다. esi에 어떤 값이 있는지 register를 확인해보자.
esi는 언제 설정되었는지 보기 위해서 어셈코드를 보자.
esi에 00403010 값이 그대로 대입 된다. 혹시 map파일에 뭔가 있는지 찾아보자
?HELLO@@3PBDB 라는 이름이 붙어있다. undname으로 실제 이름을 알아내면
C:\Program Files\Microsoft Visual Studio\Common\Tools>UNDNAME.EXE ?HELLO@@3PBDB Microsoft(R) Windows NT(R) Operating System UNDNAME Version 5.00.1768.1Copyright (C) Microsoft Corp. 1981-1998 >> ?HELLO@@3PBDB == HELLO
HELLO라는 함수가 있나? 그런데 HELLO의 Address 부분이 아까 bar 함수와 다르게 0003 으로 시작한다. HELLO의 Address인 0003:00000010 이 어느 section 인지 map파일 처음을 보면 HELLO는 .data 영역에 있다. 전역변수가 된다.
오류가 발생한 부분의 어셈을 보면 {{|mov byte ptr , 0x4b|}}, 즉 HELLO 는 전역변수이면서 포인터 변수라고 유추할 수 있다. 원래는 memory에서 0x00403010 에 저장된 4byte를 읽어와야 하지만 windbg가 그 작업까지 해줬다.
결국 0x0040206c 에 어떤 값을 저장하려다 AV가 발생했다. HELLO와 같은 방법으로 map 파일을 뒤지면 ??_C@_0N@PLDO@Hello?0?5World?$AA@ 이런 녀석이 나오고 unname을 사용해보면 string 이라고만 나온다. 즉 0040206c에 있는 값은 string literal 이라고 유추할 수 있다.
결론을 내리면 bar 함수 시작 부분에 전역 변수 HELLO가 가리키는 주소에 어떤 값을 쓰려고 하는 코드가 존재하며 HELLO가 가리키는 주소는 string literal 이기 때문에 AV가 발생한 것으로 보인다.