2011. 5. 17. 05:31

Debug Tutorial Part1: Beginning Debugging Using CDB and NTSD

원본: http://www.codeproject.com/KB/debug/cdbntsd.aspx
저자: Toby Opferman

고급 디버깅(advanced debugging) 이란?

기본적으로 재컴파일하지 않는 것, 메시지 박스나 printf 를 이용하여 디버깅을 하지 않는 것이라고 이 글의 저자는 말합니다.

Microsoft 디버깅 도구는 아래 링크에서 다운받을 수 있습니다.
http://www.codeproject.com/KB/debug/cdbntsd.aspx

CDB, NTSD 그리고 Windbg

Windows 2000 이상의 시스템은 NTSD 가 이미 설치되어 있습니다. 이것은 디버깅을 빨리 하기 위해서 다른 것을 설치할 필요가 없다는 것을 의미하며 이것은 큰 장점입니다.

CDB 와 NTSD 의 차이점은 무엇일까요? 문서는 “NTSD 는 콘솔 윈도우를 필요로 하지 않으나 CDB  는 필요로 한다” 라고 말합니다. 하지만, 좀더 많은 차이점을 발견하였습니다. 첫번째는 좀더 이전의 NTSD 는 PDB 심볼 파일을 지원하지 않는 다는 것입니다, NTSD 는 DBG 만 지원합니다. 또한, NTSD 는 심볼 서버를 지원하지 않습니다, 반면에 CDB 는 지원을 합니다. 좀더 이전의 NTSD 는 메모리 덤프를 생성할 수 없으며, 또한 NTSD 가 단지 2개의 중단점 명령만을 지원한다는 다른 문제점도 발견하였습니다. CDB 가 가지고 있지 않은 NTSD 만의 한가지 이점은 콘솔 윈도우를 필요로 하지 않는다는 것입니다.

이 이점은 시스템에 로그인 하기 전의 사용자-모드 서비스(user-mode ervice)나 프로세스를 디버깅할 때 매우 중요합니다. 만일 아무도 시스템에 로그인 하지 않았다면, 콘솔 윈도우를 생성할 수 없습니다. NTSD 가 현재 부착된 커널 디버거와 통신하도록 하는 -d 명령어 옵션이 있습니다. (CDB 도 같은 옵션이 있습니다.) 이것은 커널 디버거를 통하여 startup 시의 프로세스들을 디버깅하는데 사용될 수 있습니다. 커널 디버거를 이용하여 프로세스를 디버깅하는 동안은, 사용자-모드 디버거를 이용하여 프로세스를 디버깅할 수 있는 유연성을 제공합니다. 이것은 현재 소개 문서의 범위 밖이며, 단지 지금은 개념만 소화하세요.

WinDbg와 CDB 는 몇가지 예외만 빼고는 기본적으로 동일합니다. 첫번째는 WinDbg 는 GUI 애플리케이션이고 CDB 는 콘솔 애플리케이션입니다. WinDbg 는 또한 커널 디버깅과 소스 레벨 디버깅을 지원합니다.

비쥬얼 C++ 디버거

저는 이 디버거를 사용하지 않기에 이것을 사용하라고 권장하지 않을 것입니다. 그 이유는 첫째로 이 디버거는 리소스를 엄청나게 먹는 돼지입니다. 로딩이 느리고 디버거를 다루기 힘들게 만드는 디버깅 툴보다 많은 것을 포함하고 있습니다. 두번째 이유는 일반적으로, 이 디버거를 설치한 이후에는 리부팅을 해야한다는 것입니다. 소프트웨어를 실행하고 있는 또는 테스팅하고 있는 머신은 디버거가 설치되어 있지 않은 경우가 있으며 이를 해소해야 할 필요가 있습니다. VC++ 는 또한 설치시 크고, 시간을 많이 잡아먹습니다.

Windows 9x/ME

Windows 9x/ME 에서는 우리가 멀 할 수 있을까요? WinDbg 를 사용할 수 있습니다. debug API 는 모든 시스템에 대해서 동일합니다, 그래서 WinDbg 는 Windows 9x/ME 에서도 동작해야 한다고 오래전부터 알고 있었습니다. 단 한가지 우려는  만일 WinDbg 가 Windows 9x 상에서 감지하려고(detect) 시도할 때, 디버깅이 허락되지 않느냐 였습니다. 저는 최근에 이것이 사실이 아니라는 것을 발견하였습니다. 단 한가지 문제는 최신 WinDbg 설치는 Windows 9x 에 기본적으로 설치되어 있지 않은 MSI 패키지라는 것입니다. 이것은 MSI 패키지를 설치함으로써 간단하게 해결될 수 있습니다. 이것은 분명히 다른 사이드 이펙트를 가지고 있을지라도, 모든 !xxx 명령어를 사용할 수 있으며 데이터를 다른 메모리 위치에 놓을 수 있다는 것에 비하면 아무것도 아닐 것입니다. 심볼이 동작할까요? 물론, PDB 도 동작합니다. 이 문서는 Windows 9x/ME 는 다루지 않습니다.

환경 설정하기

이것은 디버깅을 시작하기 전에 매우 중요한 단계입니다. 시스템은 당신의 기호대로 설정될 필요가 있으며 필요로 하는 모든 도구를 포함하고 있어야 합니다.

심볼과 심볼 서버

심볼은 모든 디버그 연산에서 중요한 부분입니다. Microsoft 는 임의의 OS (Windows XP 등)에 대한 심볼을 다운로드 할 수 있는 장소를 가지고 있습니다. 문제는, 많은 하드 디스크 공간을 필요로 한다는 것입니다. 그리고 만약 하나의 머신에서 많은 OS 를 디버깅 한다면, (크래쉬 덤프와 같은), 이것은 번거로울 수 있습니다.

많은 OS 를 디버깅하는데 있어 편리함을 제공하기 위해, Microsoft 는 “심볼 서버”를 지원합니다. 이것은 시스템에 맞는 올바른 심볼을 얻을 수 있도록 해줍니다. 심볼 서버의 위치는 아래와 같습니다.

http://msdl.microsoft.com/download/symbols

만약 심볼 경로를 이 위치로 설정한다면, 디버거는 자동으로 필요로 하는 시스템 심볼들을 다운로드할 것입니다. 애플리케이션이 필요로하는 심볼은 당신에게 달려있습니다.

이미지 파일 실행 옵션 (Image File Execution Options)

애플리케이션이 실행될 때 해당 애플리케이션에 자동으로 디버거를 부착시키는 레지스트리가 있습니다. 이 레지스트리의 위치는 아래와 같습니다:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
    NT\CurrentVersion\Image File Execution Options


이 레지스트리 키 밑에, 디버깅하고자 하는 프로세스의 이름을 값으로 가지는 새로운 레지스트리 키를 생성하기만 하면 됩니다, 예로 “myapplication.exe”. 만일 이것을 이전에 사용한적이 없다면, “Your Application Here” 라는 또는 그와 유사한 디폴트 키가 생성되어 있을 수 있습니다. 이 키의 이름을 변경하여 원하는 대로 사용할 수 있습니다.

이 키에서 중요한 값들 중의 하나는 “Debugger”입니다. 애플리케이션이 실행될 때 시작되는 디버거를 가리킵니다. “Your Application Here” 를 위한 디폴트는 “ntsd -d”입니다. 만일 부착된 커널 디버거가 없다면 이것을 사용할 수 없기때문에 “-d” 부분을 삭제할 것입니다.

Note: 부착된 커널 디버거 없이 “-d” 옵션을 유지하는 것은 애플리케이션이 실행될 때마다 시스템이 락업되는 것을 초래할 수 있습니다. 주의 하세요. 커널 디버거 설정이 되어 있다면, “g” 키를 두드림으로써 시스템을 해제할 수 있습니다.

“GlobalFlags”라는 또다른 값이 있습니다. 이것은 디버깅을 위해 사용되는 또다른 도구입니다, 하지만 이 문서 범위 밖입니다. 더 자세한 정보를 원하면, “gflags.exe”로 찾아보세요.

커널 디버깅 장치 (Kernel Debugging Equipment)

커널을 디버깅하기 위해서는, 우선 OS 를 디버깅 모드로 부팅해야 합니다. 비록 이것을 설정하는 GUI 시스템 속성이 있을지라도, 저는 일반적으로 boot.ini 파일을 직접 편집합니다. C:\ 드라이브 루트 밑에 boot.ini 파일이 있습니다. 대부분 숨겨진 시스템 파일입니다. “attrib -r -s -h boot.ini” 를 이용하여 속성을 변경하고 편집을 위해 이 파일을 엽니다.

Caution: 이 파일을 잘못 편집하면 다시는 부팅이 되지 않을 수 있습니다.

boot 파일은 아래와 유사할 것입니다.

timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=
   "Microsoft Windows XP Professional" /fastdetect



“Operating Systems” 밑에 첫번째 줄을 복사할 것입니다:

timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=
   "Microsoft Windows XP Professional" /fastdetect
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS.0=
  "Microsoft Windows XP Professional"
  /fastdetect /debug /debugport=COM1 /baudrate=115200


복사된 줄은 당신의 설정을 담고 있습니다. /debug 그다음 /debugport=port 그리고 마지막으로 /baudrate=baudrate. 사용할 디버그 포트는 SERIAL_NULL_MODEM_CABLE 에 연결할 머신의 포트입니다. 또 다른 머신이 필요할 것입니다. COM 포트를 사용하는 것 외에, 좀더 빠른 firewire 를 사용할 수 있습니다.

다음 부팅시, 디버깅 모드로 부팅하기 위해 “Debugger Enagled” 항목을 선택하세요.

환경 변수

_NT_SYMBOL_PATH 에 Microsoft 심볼 서버나 심볼 기호를 가지고 있는 로컬 디렉토리를 가리키도록 설정할 것입니다. 이 환경 경로를 설정하기 위해서는, “System Properties > Advanced > Environment Variables” 로 가세요.

디폴트 디버거

이것은 시스템에 크래쉬가 발생했을 때 사용될 디폴트 디버거입니다. 디폴트로, 일반적으로 “Doctor Watson” 으로 설정되어 있습니다. 이 프로그램은 여기에서는 언급할 가치가 없습니다. 레지스트리 키는 아래 위치입니다.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug


“Auto” 는 1 로 “Debugger”는 당신의 디버거로 설정하세요.

Assembly

어셈블리 프로그래밍을 공부하라고 강력하게 추천합니다. 이 튜터리얼에서는 소스 레벨 디버깅을 보여주진 않을 것입니다. 소스 레벨 디버깅의 문제점은 소스가 항상 접근 가능하지 않다는 것과 때로는 소스를 보는 것 만으로는 찾을 수 없고, 대신 생성된 코드에 그 문제점이 있을 수 있다는 것입니다.환경이 어떻게 설정되었는지를 이해한다면, 알아야 할 필요가 있는 정보를 찾기 위해 시스템을 쉽게 리버스할 수 있습니다 그리고 그러한 정보는 소스 레벨 디버깅에 항상 접근 가능한 것은 아닙니다.

소스 레벨 디비겅을 싫어하는 다른 이유는 만일 소스가 심볼과 일치하지 않는 경우 소스 디버거는 올바른 정보를 보여주지 않을 것입니다. 이것은 프로그램을 만약 다중 빌드로 생성하거나 또는 빌드 이후에 프로그램을 수정하였다면, 디버깅하고 있는 빌드와 일치하는 소스를 찾아야 한다는 것을 의미합니다.

Let’s Get Started

이 튜터리얼은 기본적으로 첫번째 파트이고 추후 좀더 자세하고 고급 기법들을 다룰 것입니다. 이 첫번째 튜터리얼은 사용자-모드 프로그래밍 문제의 몇가지 간단한 시나리오를 살펴볼 것입니다.

Symbols For Release Executables

먼저, 릴리즈 바이너리를 위한 심볼을 어떻게 생성할까요? 간단합니다. 바이너리를 적절하게 rebase 시키는 make 파일을 생성하는 것입니다. cl.exe 에 사용하는 일반적인 옵션은 다음과 같습니다.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug

link.exe 에 사용하는 일반적인 옵션은 다음과 같습니다.

/nologo /subsystem:console
 /out:$(TARGETDIR)\$(TARGET)/pdb:<YourProjectName>.pdb
 /debug /debugtype:both
/LIBPATH:"..\..\..\bin\lib"


이것은 .PDB 파일을 생성합니다. 물론, VC++ 7 이 소개될 때, .DBG 가 삭제되었습니다. (그래서 /debugtype:both 는 이 컴파일러에서는 에러를 야기합니다.) .DBG 는 .PDB 의 작은 버전이며 소스 정보를 포함하고 있지 않으며, 엄격하게 심볼만 찾습니다. .DBG 는 심지어 파라미터 같은 정보도 가지고 있지 않습니다. 만일 .DBG 를 아직도 생성하는 컴파일러를 사용하고 있다면, 당신은 아래처럼 해야 합니다.

rebase -b 0x00100000 -x $(TARGETDIR) -a $(TARGETDIR)\$(TARGET)

-b 는 실행화일을 rebase 하기 위한 새로운 메모리 위치입니다. 그러나, 이것은 릴리즈 executable 을 만들 때 사이즈를 좀더 줄이기 위해 디버그 심볼을 빼버립니다. 만일 디폴트 Visual Studio 도구를 이용하여 executable 을 빌드한다면, 좀더 작아 질 수도 있습니다. 하지만, 심볼을 가지고 있지 않습니다. 생성된 코드는 동일하고 단지 최적화 플래그를 사용하여 최적화 되는 것만이 다를 뿐입니다. 차이점이라면 이러한 바이너리는 이제 좀 더 유용하며, 어디에 있든 누가 사용하든 심볼을 구할 수 있다는 것입니다.

기억하세요, 최선의 디버깅은 항상 executable 을 재빌드 하지 않는다는 것을 말이죠. 일단 executable 을 재빌드 해야 한다면, executable 의 memory foot print 를 변경했다는 것을 알고 있어야만 합니다. 또한, executable 의 속도도 변경될 수 있습니다. 이 바이너리를 이용하여 문제를 재현해야 하기 때문에 이것은 치명적입니다. 만일 이 문베를 발생시키리면 4일이 걸린다면 어떻해야 할까요? 즉석에서 (on the spot) 디버깅할 수 있는 것이 최선일 것입니다.

Simple Access Violation Trap

한가지 간단한 문제에 대해서 살펴봅시다. 프로그램이 “Access Violation” 으로 충돌하였습니다, 이것은 드문 경우가 아닙니다. 이것은 executable 을 수행 중에 가장 흔히 발생하는 것입니다. 이 문제를 해결하기 위한 세가지 단계가 있습니다.

1. 누가 접근하려고 하고 있는가? 어떤 모듈?
2. 무엇이 접근하려고 하고 있는가? 메모리는 어디에서 온 것인가?
3. 왜 그것에 접근하려고 하고 있는가? 그것을 가지고 무엇을 하길 원하는가?

이것은 이 문제를 해결하기 위한 일반적인 가이드라인입니다. 2번이 3 개 중에서 가장 중요하기 때문에 진하게 강조하였습니다. 그러나, 즉각적으로 명확하지 않은 경우 1과 3을 해결하는 것 역시 2번을 결정하는데 도움을 줄 수 있습니다.

충돌을 일으키는 아주 간단한 프로그램을 작성하였습니다. 디폴트 디버거를 CDB 로 설정하여 프로그램을  실행하였습니다. 또한, executable 을 위한 심볼을 생성하였고, _NT_SYMBOL_PATH 를 Microsoft 심볼 서버로 설정하였습니다.

보다시피, 아래는 프로그램을 실행할 때 일어나는 것입니다.

C:\programs\DirectX\Games\src\Games\temp\bin>temp

Microsoft (R) Windows Debugger  Version 6.3.0005.1
Copyright (c) Microsoft Corporation. All rights reserved.

*** wait with pending attach
Symbol search path is:
 SRV*c:\symbols*http://msdl.microsoft.com/download/symbols

Executable search path is:
ModLoad: 00400000 00404000   C:\programs\DirectX\Games\src\Games\temp\bin\temp.e
xe
ModLoad: 77f50000 77ff7000   C:\WINDOWS.0\System32\ntdll.dll
ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll
ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll
ModLoad: 77dd0000 77e5d000   C:\WINDOWS.0\system32\ADVAPI32.DLL
ModLoad: 78000000 78086000   C:\WINDOWS.0\system32\RPCRT4.dll
(ee8.c38): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=7ffdf000 ecx=00001000 edx=00320608 esi=77c5aca0 edi=77f944a8
eip=77c3f10b esp=0012fb0c ebp=0012fd60 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
MSVCRT!_output+0x18:
77c3f10b 8a18             mov     bl,[eax]                ds:0023:00000000=??
0:000>


가장 먼저 알 수 있는 것이 무엇인가요? 이 트랩은 MSVCRT.DLL 에서 일어났습니다. 디버거는 일반적으로 <module>!<nearest symbol>+offset 을 이용하여 이러한 정보를 보여주기 때문에 이것은 명백합니다. 이것은 MSVCRT.DLL 에서 가장 근접한 심볼은 _output 이고 그것으로부터 +18h 바이트에 있다는 것을 의미합니다. Offset 이 작고 심볼이 올바르다고 가정하면, (심볼이 틀릴수도 있지만, 이것은 다음 튜터리얼에서 다루겠습니다.) MSVCRT 의 _output() 함수에 있다고 가정할 수 있습니다.

(ee8.c38): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=7ffdf000 ecx=00001000 edx=00320608 esi=77c5aca0 edi=77f944a8
eip=77c3f10b esp=0012fb0c ebp=0012fd60 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
MSVCRT!_output+0x18:
77c3f10b 8a18             mov     bl,[eax]                ds:0023:00000000=??
0:000>


이것을 증명하고 싶다면 어떻해야 할까요?

<0:000> x *!
start    end        module name
00400000 00404000   temp         (deferred)
77c10000 77c63000   MSVCRT       (pdb symbols)  
                                c:\symbols\msvcrt.pdb\3D6DD5921\msvcrt.pdb
77dd0000 77e5d000   ADVAPI32     (deferred)
77e60000 77f46000   kernel32     (deferred)
77f50000 77ff7000   ntdll        (deferred)
78000000 78086000   RPCRT4       (deferred)


이 명령은 프로세스 내에 적재된 모든 모듈의 리스트를 시작과 마지막 메모리 위치와 함께 보여줍니다. 우리의 트랩은 77c3f10b 위치였고, 이것은 77c10000 <= 77c3f10b <= 77c63000 에 있으며, 그래서 확실히 MSVCRT 내에 잡혀있습니다. 다음으로 해야 할 일은 이 메모리가 어디에서 유래된 것인지를 알아내는 것입니다.

이것을 하기 위한 몇가지 메소드가 있으며, 메모리가 어디에서 유래된 것인지를 알아내기 위해 코드를 디스어셈블리할 수 있습니다. 또한 스택상에서 누구인지를 알아내기 위해 스택 트레이스를 얻을 수 있습니다. 먼저 메모리가 어디에서 유래하였는 지를 알아내기 위해 _output 함수에 대해 디스어셈블리를 시도해 봅시다.

0:000> u MSVCRT!_output
MSVCRT!_output:
77c3f0f3 55               push    ebp
77c3f0f4 8bec             mov     ebp,esp
77c3f0f6 81ec50020000     sub     esp,0x250
77c3f0fc 33c0             xor     eax,eax
77c3f0fe 8945d8           mov     [ebp-0x28],eax
77c3f101 8945f0           mov     [ebp-0x10],eax
77c3f104 8945ec           mov     [ebp-0x14],eax
77c3f107 8b450c           mov     eax,[ebp+0xc]
0:000> u
MSVCRT!_output+0x17:
77c3f10a 53               push    ebx
77c3f10b 8a18             mov     bl,[eax]

주의 깊게 봐야 할 모든 중요한 명령에 강조를 주었습니다. 어셈블리를 모르더라도, 이것이 무엇인지 끝까지 듣고 싶을 것입니다. 우선, 메모리는 EAX 에서 유래한다는 것을 알아야 합니다. 이것은 CPU 내의 레지스터이지만, 단순히 변수로 취급할 수 있습니다. [ ] 로 둘러싸여진 EAX 는 C 에서 *MyPointer 가 하는 것과 동일합니다. 이것은 EAX 가 가리키는 메모리를 참조하고 있다는 것을 의미합니다. EAX 는 어디에서 유래되었을까요? EAX 는 [EBP + 0Ch] 에서 유래하였으며, “DWORD * EBP EAX = EBP[3]” 과 같이 간주할 수 있습니다. 어셈블리에서는 타입이 없기 때문입니다. EAX 는 32 비트 (DWORD) 레지스터입니다. EBP + 12 에서 DWORD 로 Dereference 하는 것은 C 에서 DWORD 포인터에 3을 더하는 것과 동일합니다. (또는 바이트 포인터에 12만큼 더한 후 이를 DWORD 로 형변환)

다음으로 살펴보아야 할 것은 MOV EBP, ESP 입니다. ESP 는 스택 포인터입니다. 파라미터 (호출 규약과 최적화에 의존하는) 는 스택에 푸쉬되고, 반환 주소(return addresses)는 스택에 푸쉬되고, 그리고 지역 변수도 스택에 푸쉬됩니다. ESP 는 스택을 가리킵니다. 메모리에서, 함수 호출은 C 호출 규약에 따르면 다음과 같을 것입니다.

[Parameter n]
...
[Parameter 2]
[Parameter 1]
[Return Address]
[Previous EBP]


이제, PUSH EBP 를 봅시다. PUSH 는 스택에 먼가를 넣으라는 의미입니다. 따라서, EBP 의 이전 값을 스택에 저장하고 있습니다. 그래서, 스택은 아래와 같을 것입니다.

[Parameter n]
...
[Parameter 2]
[Parameter 1]
[Return Address]
[Previous EBP]


이제 EBP 를 ESP 에 설정했으므로, EBP 를 포인터로 취급할 수 있으며 그리고 스택은 단지 DWORD 값의 배열입니다. 아래는 EBP 의 offset 과 그들이 가리키는 곳을 보여줍니다.

[Parameter n]     ==  [EBP + n*4 + 4] (The formula)
...
[Parameter 2]     ==  [EBP + 12]
[Parameter 1]     ==  [EBP + 8]
[Return Address]  ==  [EBP + 4]
[Previous EBP]    ==  [EBP + 0]


경우가 이러함으로, 변수가 _output 의 두 번째 파라미터에서 유래되었다는 것을 알 수 있습니다. 이제 무엇을 해야 할까요? 호출 함수(calling funciton)를 디어셈블리 합시다. EBP + 4 가 반환 주소를 가리킨다는 것을 알고 있습니다, 스택 트레이스를 가져와 보겠습니다.

0:000> kb
ChildEBP RetAddr  Args to Child
0012fd60 77c3e68d 77c5aca0 00000000 0012fdb0 MSVCRT!_output+0x18
0012fda4 0040102f 00000000 00000000 00403010 MSVCRT!printf+0x35
0012ff4c 00401125 00000001 00323d70 00322ca8 temp!main+0x2f
0012ffc0 77e814c7 77f944a8 00000007 7ffdf000 temp!mainCRTStartup+0xe3
0012fff0 00000000 00401042 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000>


“KB”는 스택 트레이스를 얻기 위한 명령어 중의 하나입니다. 이제, 항상 전체 스택 트레이스를 얻을 수 있는 것은 아닙니다. 하지만, 이것은 역시 좀더 고급스런 기벙에 해당합니다. 이 튜터리얼에서는 전체 스택 트레이스를 얻었다고 가정하겠습니다. printf 함수 호출을 알 수 있으며, printf 가 _output 을 호출하였다는 것을 알 수 있습니다. printf 를 디스어셈블리 합시다. 전체 함수를 디스어셈블리 하길 원하지 않는다는 것을 명심하세요. 가끔씩은, 단지 스택 트레이스만으로도 트랩을 발견할 수 있습니다. (마지막에 이것에 관해 살펴볼 것입니다) 작은 함수들이 있으며 간단하게 그것들을 추적할 수 있습니다.

0:000> u MSVCRT!_output
MSVCRT!_output:
77c3f0f3 55               push    ebp
77c3f0f4 8bec             mov     ebp,esp
77c3f0f6 81ec50020000     sub     esp,0x250
77c3f0fc 33c0             xor     eax,eax
77c3f0fe 8945d8           mov     [ebp-0x28],eax
77c3f101 8945f0           mov     [ebp-0x10],eax
77c3f104 8945ec           mov     [ebp-0x14],eax
77c3f107 8b450c           mov     eax,[ebp+0xc]
0:000> u
MSVCRT!_output+0x17:
77c3f10a 53               push    ebx
77c3f10b 8a18             mov     bl,[eax]
77c3f10d 33c9             xor     ecx,ecx
77c3f10f 84db             test    bl,bl
77c3f111 0f8445070000     je      MSVCRT!_output+0x769 (77c3
77c3f117 56               push    esi
77c3f118 57               push    edi
77c3f119 8bf8             mov     edi,eax
0:000> u MSVCRT!printf
MSVCRT!printf:
77c3e658 6a10             push    0x10
77c3e65a 68e046c177       push    0x77c146e0
77c3e65f e8606effff       call    MSVCRT!_SEH_prolog (77c354
77c3e664 bea0acc577       mov     esi,0x77c5aca0
77c3e669 56               push    esi
77c3e66a 6a01             push    0x1
77c3e66c e8bdadffff       call    MSVCRT!_lock_file2 (77c394
77c3e671 59               pop     ecx
0:000> u
MSVCRT!printf+0x1a:
77c3e672 59               pop     ecx
77c3e673 8365fc00         and     dword ptr [ebp-0x4],0x0
77c3e677 56               push    esi
77c3e678 e8c7140000       call    MSVCRT!_stbuf (77c3fb44)
77c3e67d 8945e4           mov     [ebp-0x1c],eax
77c3e680 8d450c           lea     eax,[ebp+0xc]
77c3e683 50               push    eax
77c3e684 ff7508           push    dword ptr [ebp+0x8]
0:000> u
MSVCRT!printf+0x2f:
77c3e687 56               push    esi
77c3e688 e8660a0000       call    MSVCRT!_output (77c3f0f3)


간단합니다. _output 의 두 번째 파라미터가 [EBP+8] 이라는 것을 알 수 있습니다. PUSH EBP 와 MOV EBP, ESP 를 통해서 이전에 언급한 것과 동일한 방식으로 스택이 설정된다는 것을 알 수 있습니다. 늘 그러는 것은 아니지만, 여기에서는 느리게 시작하고 있습니다.

printf() 의 두 번째 파라미터가 메모리 어디에서 유래했는 지를 파악할 수 있으며, 운이 좋게도 printf() 는 우리의 프로그램에서 호출되었습니다. 트랩 정보로부터, EAX 가 0 이었다는 것을 알 수 있으며, 프로그램은 NULL 포인터에 대해서 참조를 시도하고 있었습니다.

77c3f10b 8a18    mov bl,[eax]   ds:0023:00000000=??

아래가 사용된 코드입니다.

int main(int argc, char *argv[])
{  
 char *TheLastParameter[100];

 sprintf(*TheLastParameter, "The last parameter is %s", argv[argc]);
 printf(*TheLastParameter);

 return 0;
}


프로그램에는 많은 문제가 있다는 것을 알 수 있습니다. 하지만, printf 는 NULL 에서 중지되었습니다. *TheLastParameter 는 NULL 입니다. 놀랍게도 sprintf() 에서 중단되지 않았습니다. 자 , 이것을 어떻게 단지 KB 만으로 해결할 수 있을까요? 아래 트레이스를 보세요.

0:000> kb
ChildEBP RetAddr  Args to Child
0012fd60 77c3e68d 77c5aca0 00000000 0012fdb0 MSVCRT!_output+0x18
0012fda4 0040102f 00000000 00000000 00403010 MSVCRT!printf+0x35
0012ff4c 00401125 00000001 00323d70 00322ca8 temp!main+0x2f

0012ffc0 77e814c7 77f944a8 00000007 7ffdf000 temp!mainCRTStartup+0xe3
0012fff0 00000000 00401042 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000>


심볼과 스택 트레이스를 가지고 있습니다. 이탤릭체는 첫번째 파라미터 입니다. 그것은 0 이죠. 프로그램이 그것을 호출했다는 것 역시 알 수 있습니다. 이것은 매우 단순한 시나리오이며 문제의 위치에서부터 백트레이스 하기 위해 사용될 수 있는 몇가지 기법들을 묘사하기 위해 사용하였습니다. 스택을 공부하세요. 스택이 설정되는 방법과 어떤 메모리가 스택에 있는 지를 아는 것은 데이터가 어디에서 유래되었는지를 발견하고 추적하는 데 매우 중요합니다. 모든 정보가 단지 “kb” 만으로 발견될 만큼 항상 운이 좋지는 않을 것입니다.

Program Not Working As Expected

이것은 흔한 에러입니다. 프로그램을 실행하고 올바른 결과를 볼 수 없거나 프로그램이 계속해서 에러 메시지를 보여줍니다. 예를 들어, 생성하려고 하는 파일은 생성되지 않습니다. 이것은 해결하기 복잡한 아주 흔한 문제입니다. 이것을 디버깅하기 위한 첫번째 단계는 무엇일까요?

1. 무엇이 동작하지 않는가?
2. 어떤 API 또는 모듈을 중심으로 돌아가는가?
3. 무엇이 그러한 AIP 들이 제대로 동작하지 않도록 하는가?

몇가지 스텝이 있지만 그것은 일반적이지 않습니다. 윈도우에서 파일을 하나 생성하는 프로그램이 있다고 합시다. 파일이 생성되지 않았습니다. 아래 코드를 살펴봅시다.

HANDLE hFile;
 DWORD dwWritten;

 hFile = CreateFile("c:\MyFile.txt", GENERIC_READ,
                      0, NULL, OPEN_EXISTING, 0, NULL);

 if(hFile != INVALID_HANDLE_VALUE)
 {
  WriteFile(hFile, "Test", strlen("Test"), &dwWritten, NULL);
  CloseHandle(hFile);
 }


일반적으로, GetLastError() 를 이용하여 재컴파일하고 그것을 출력하려고 할 것입니다. 그러나, 그렇게 해서는 안됩니다. 비록 지금과 같은 경우는 단순하지만, 만일 코드를 단계별로 실행하고 있고 어떤 함수가 실패했다면, 바로 이 시점에 어떤 일이 일어났는지 알고 싶지 않을까요? 이것을 한번 디버깅 해 봅시다. 먼저, 디버거를 시작하고 한 함수를 중단 시킬 것입니다. 심볼을 가지고 있기 때문에, 이것은 쉽습니다. 만약 그렇지 않다면, CreateFile 에다가 중단점을 설정할 수 있습니다. 이 함수는 심볼이 노출되어 있으며, 항상 접근가능하기 때문입니다.

C:\programs\DirectX\Games\src\Games\temp\bin>cdb temp

Microsoft (R) Windows Debugger  Version 6.3.0005.1
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: temp
Symbol search path is:
   SRV*c:\symbols*http://msdl.microsoft.com/download/symbols

Executable search path is:
ModLoad: 00400000 00404000   temp.exe
ModLoad: 77f50000 77ff7000   ntdll.dll
ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll
ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll
(2a0.94): Break instruction exception - code 80000003 (first chance)
eax=00241eb4 ebx=7ffdf000 ecx=00000004 edx=77f51310 esi=00241eb4 edi=00241f48
eip=77f75a58 esp=0012fb38 ebp=0012fc2c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!DbgBreakPoint:
77f75a58 cc               int     3
0:000> bp temp!main
0:000> g


main() 에 중단점을 설정하고 “go” 를 입력합니다. 중단점에서 실행이 중지되고, CreateFile 함수까지 단계별 실행을 위해 “p” 를 사용합시다.

Breakpoint 0 hit
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401000 esp=0012ff50 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main:
00401000 51               push    ecx
0:000> p
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401001 esp=0012ff4c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x1:
00401001 56               push    esi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401002 esp=0012ff48 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x2:
00401002 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401003 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x3:
00401003 33ff             xor     edi,edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401005 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x5:
00401005 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401006 esp=0012ff40 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x6:
00401006 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401007 esp=0012ff3c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x7:
00401007 6a03             push    0x3
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401009 esp=0012ff38 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x9:
00401009 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=0040100a esp=0012ff34 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0xa:
0040100a 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=0040100b esp=0012ff30 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0xb:
0040100b 6800000080       push    0x80000000
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x10:
00401010 6810304000       push    0x403010
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x15:
00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA (00402004)]{kernel3
2!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476
0:000> p
eax=ffffffff ebx=7ffdf000 ecx=77f939e3 edx=00000002 esi=00000000 edi=00000000
eip=0040101b esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000286
temp!main+0x1b:
0040101b 8bf0             mov     esi,eax


CreateFile 이 호출된 후에, EAX 는 반환값을 가지고 있을 것입니다. 반환값이 ffffffff 또는 “Invalid Handle Value” 라는 것을 알 수 있습니다. GetLastError 를 알고 싶습니다. 이것은 fs:34 에 저장되어 있습니다. FS 는 TEB selector 이며, 그래서 우리는 이것을 덤프할 수 있습니다.

0:000> dd fs:34
0038:00000034  00000002 00000000 00000000 00000000
0038:00000044  00000000 00000000 00000000 00000000
0038:00000054  00000000 00000000 00000000 00000000
0038:00000064  00000000 00000000 00000000 00000000
0038:00000074  00000000 00000000 00000000 00000000
0038:00000084  00000000 00000000 00000000 00000000
0038:00000094  00000000 00000000 00000000 00000000
0038:000000a4  00000000 00000000 00000000 00000000


CDB 는 이것을 하기 위한 좀더 간편한 방법을 가지고 있습니다. !gle:

0:000> !gle
LastErrorValue: (Win32) 0x2 (2) - The system cannot find the file specified.
LastStatusValue: (NTSTATUS) 0xc0000034 - Object Name not found.
0:000>


파일을 발견할 수 없었습니다. 하지만 파일은 거기 있습니다. 문제가 무엇일까요? 좀더 디버깅할 필요가 있습니다. 살펴보아야 할 한가지는 CreateFile 에 전달된 파라미터가 무엇이냐 입니다.

eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x10:
00401010 6810304000       push    
0x403010
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x15:
00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA
  (00402004)]{kernel32!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476


운이 좋게도, 그것은 상수라서 메모리에 아직 존재합니다. 만일 상수가 아니더라도 CreateFile 이 반환된 곳에서부터 많은 실행을 수행하지 않았기 때문에 메모리는 여전히 존재할 것입니다.

이제 “da”, “dc” 또는 “du” 를 사용할 수 있습니다. “da”는 dump ansi string 입니다. “du” 는 dump unicode string 입니다. 그리고 “dc” 는 출력할 수 없는 문자까지 포함하여 모든 문자를 덤프하는 것 외에는 “dd” 와 유사합니다. 우리는 ANSI 문자열 인 것을 알고 있기 때문에, “da”를 사용할 것입니다.

0:000> da 403010
00403010  "c:MyFile.txt"
0:000>


잘못됬군요! 제대로 동작하게 하려면 C:\\MyFile.txt 가 사용되어야 합니다. 이제, 이것을 고칠 수 있습니다. 하지만 아직 파일에 작성하지 않을 것입니다. 좀더 디버깅 할 필요가 있습니다. 다시 똑같이 해보겠습니다.

C:\programs\DirectX\Games\src\Games\temp\bin>cdb temp

Microsoft (R) Windows Debugger  Version 6.3.0005.1
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: temp
Symbol search path is:
 SRV*c:\symbols*http://msdl.microsoft.com/download/symbols

Executable search path is:
ModLoad: 00400000 00404000   temp.exe
ModLoad: 77f50000 77ff7000   ntdll.dll
ModLoad: 77e60000 77f46000   C:\WINDOWS.0\system32\kernel32.dll
ModLoad: 77c10000 77c63000   C:\WINDOWS.0\system32\MSVCRT.dll
(80c.c94): Break instruction exception - code 80000003 (first chance)
eax=00241eb4 ebx=7ffdf000 ecx=00000004 edx=77f51310 esi=00241eb4 edi=00241f48
eip=77f75a58 esp=0012fb38 ebp=0012fc2c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!DbgBreakPoint:
77f75a58 cc               int     3
0:000> bp temp!main
0:000> g
Breakpoint 0 hit
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401000 esp=0012ff50 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main:
00401000 51               push    ecx
0:000> p
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401001 esp=0012ff4c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x1:
00401001 56               push    esi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401002 esp=0012ff48 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x2:
00401002 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401003 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x3:
00401003 33ff             xor     edi,edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401005 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x5:
00401005 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401006 esp=0012ff40 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x6:
00401006 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401007 esp=0012ff3c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x7:
00401007 6a03             push    0x3
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401009 esp=0012ff38 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x9:
00401009 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=0040100a esp=0012ff34 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0xa:
0040100a 57               push    edi
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=0040100b esp=0012ff30 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0xb:
0040100b 6800000080       push    0x80000000
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401010 esp=0012ff2c ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x10:
00401010 6810304000       push    0x403010
0:000>
eax=77c5c9e4 ebx=7ffdf000 ecx=00322cf8 edx=00322cf8 esi=00000000 edi=00000000
eip=00401015 esp=0012ff28 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x15:
00401015 ff1504204000 call dword ptr [temp!_imp__CreateFileA (00402004)]{kernel3
2!CreateFileA (77e7b476)} ds:0023:00402004=77e7b476
0:000>
eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=00000000 edi=00000000
eip=0040101b esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000293
temp!main+0x1b:
0040101b 8bf0             mov     esi,eax
0:000> p


여기에서 EAX 가 유효하지 않은 핸들 값이 아니라 유효한 핸들이라는 것을 알 수 있습니다. 계속할께요.

eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=0040101d esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei ng nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000293
temp!main+0x1d:
0040101d 83feff           cmp     esi,0xffffffff
0:000>
eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=00401020 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
temp!main+0x20:
00401020 741b             jz      temp!main+0x3d (0040103d)            
0:000>
eax=000007e8 ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=00401022 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
temp!main+0x22:
00401022 8d442408         lea     eax,[esp+0x8]     ss:0023:0012ff4c=00322cf8
0:000>
eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=00401026 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
temp!main+0x26:
00401026 57               push    edi
0:000>
eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=00401027 esp=0012ff40 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
temp!main+0x27:
00401027 50               push    eax
0:000>
eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=00401028 esp=0012ff3c ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
temp!main+0x28:
00401028 6a04             push    0x4
0:000>
eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=0040102a esp=0012ff38 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
temp!main+0x2a:
0040102a 6820304000       push    0x403020
0:000>
eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=0040102f esp=0012ff34 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
temp!main+0x2f:
0040102f 56               push    esi
0:000>
eax=0012ff4c ebx=7ffdf000 ecx=77f59037 edx=00140608 esi=000007e8 edi=00000000
eip=00401030 esp=0012ff30 ebp=0012ffc0 iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000213
temp!main+0x30:
00401030 ff1500204000 call dword ptr [temp!_imp__WriteFile (00402000)]{kernel32!
WriteFile (77e7f13a)} ds:0023:00402000=77e7f13a
0:000> p
eax=00000000 ebx=7ffdf000 ecx=77e7f1c9 edx=00000015 esi=000007e8 edi=00000000
eip=00401036 esp=0012ff44 ebp=0012ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
temp!main+0x36:
00401036 56               push    esi


막 WrifeFile 을 호출하였고,  EAX == 0 입니다. 실패를 의미합니다. 다른 변수를 살펴봅시다. 두번째 파라미터는 올바르며 길이가 4입니다.

0:000> da 403020
00403020  "Test"


진하게 칠해진 네번째 파라미터는 작성된 바이트의 수를 가리키는 포인터입니다. 0 이군요.

0:000> dd 012ff4c
0012ff4c  00000000 00401139 00000001 00322470
0012ff5c  00322cf8 00403000 00403004 0012ffa4
0012ff6c  0012ff94 0012ffa0 00000000 0012ff98
0012ff7c  00403008 0040300c 00000000 00000000
0012ff8c  7ffdf000 00000001 00322470 00000000
0012ff9c  8053476f 00322cf8 00000001 0012ff84
0012ffac  e1176590 0012ffe0 00401200 004020c0
0012ffbc  00000000 0012fff0 77e814c7 00000000


이제, GetLastError 를 살펴봅시다.

0:000> dd 012ff4c
0012ff4c  00000000 00401139 00000001 00322470
0012ff5c  00322cf8 00403000 00403004 0012ffa4
0012ff6c  0012ff94 0012ffa0 00000000 0012ff98
0012ff7c  00403008 0040300c 00000000 00000000
0012ff8c  7ffdf000 00000001 00322470 00000000
0012ff9c  8053476f 00322cf8 00000001 0012ff84
0012ffac  e1176590 0012ffe0 00401200 004020c0
0012ffbc  00000000 0012fff0 77e814c7 00000000


접근이 거부되었습니다? 무엇이 이걸 초래하였을까요? 살펴 봅시다. 잠깐, 파일을 READ 접근 만으로 열었군요! 파일을 WRITE 접근으로 열지 않았습니다. 이제, 이 문제를 쉽게 고칠수 있으며 다음 프로젝트로 넘어갈 수 있습니다.

hFile = CreateFile("c:\\MyFile.txt", GENERIC_READ,
       0, NULL, OPEN_EXISTING, 0, NULL);


Concolusion

요약하면, 이 글은 몇 가지 아주 기본적인 디버깅 기술에 관해서 소개할 뿐입니다. 예제는 단순하지만 테크닉을 보여주기에는 충분한 가치가 있습니다. 이것은 디버깅 튜터리얼의 첫번째 일 뿐입니다. 바라건데, 관심이 있다면 좀더 고급 기법에 관한 튜터리얼을 추가하겠습니다.

몇몇에게는 이 튜터리얼이 단순할 수도 있고, 다른 이들에게는 좀더 고급일 수도 있습니다. 하룻밤만에 훌륭한 디버거가 될 수는 없을 것입니다. 연습이 필요합니다. 아주 간단한 문제일지라도 이를 해결하기 위해 디버거를 사용할 것을 추천합니다. 연습이 많을수록, 좀 더 나아 질 것입니다. 툴을 가지고 바보 짓을 많이 할 수록, 더 많이 배우리란 것을 확신합니다.

끝.