Create TOC

2006년 4월 25일

Debugging/Map파일 해석

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가 발생한 것으로 보인다.

Debugging/VC6 설정

좀더 편한 디버깅을 위해서는 Release Build 에도 몇가지 옵션을 조정해주는 것이 좋다.

Compiler 옵션

Compiler 옵션은 Project - Settings - C/C++ 탭에서 Project Settings에 넣는다.

옵션설명
/W4경고 레벨을 최대로
/WX경고를 에러로.
/ZiPDB 생성을 위해서 넣는다.
/GFstring literal을 read only 상태로 만든다. 이 옵션 없이 빌드하면 string literal을 수정할 수 있다.

Link옵션

Linker 옵션은 Project - Settings - Link 탭에서 Project Settings에 넣는다.

옵션설명
/release /debugpdb 생성을 위해 넣는다. /debug 만 있으면 생성하지만 /release 옵션이 없으면 WinDbg에서 symbol을 맞출 때 checksum 경고를 보여주기 때문에 시간이 오래 걸린다.
/pdbtype:conpdb를 하나로 만들어준다.
/map:map파일명map파일을 생성한다. 보통 map파일이름은 <project이름>.map 이다.
/mapinfo:EXPORTSexport symbol을 표시한다.
/mapinfo:LINESline 정보를 포함한다.

2006년 4월 17일

Python/숫자 세 자리마다 콤마 찍기

간단하게

  1. 뒤에서 부터 3자리씩 끊는다.
  2. 사이사이 ','를 넣어서 합친다.

def foo(num) :
    # num을 문자열로 만들어서 뒤집는다. num이 1234이면 a는 '4321'
    a = str(num)[::-1]
    # a를 가지고 (index, a[index]) 의 iter 를 만든다.
    # 그리고  index > 0 and index % 3 == 0 일때 a[index] 뒤에 ','를 붙인 list를 만든다.
    # b = ['4', '3', '2', '1,'] 형태가 된다.
    b = [c + ',' if (i and (i % 3 == 0)) else c for i, c in enumerate(a)]
    # 뒤집어진 문자열로 작업했기 때문에 다시 뒤집는다.
    b = b[::-1]
    # 문자열로 합쳐서 결과를 돌려준다.
    return string.join(b, '').replace('-,', '-')

한줄로 정리하면

def foo2(num) :
    return string.join([c + ',' if (i and (i % 3 == 0)) else c for i, c in enumerate(str(num)[::-1])][::-1], '').replace('-,', '-')