'4. Test/Debugging'에 해당되는 글 14건

  1. 2016.12.09 Debugging JavaScript in a WebBrowser Control from Visual Studio
  2. 2011.10.05 VirtualBox를 이용한 Windows XP 디버깅
  3. 2011.08.17 Debugging Heap Corruption in Visual C++ Using Microsoft Debugging Tools for Windows
  4. 2011.08.16 C# UserControl, ActiveX Control 디버깅 하기
  5. 2011.07.18 "디버깅 하시겠습니까" 창 나오지 않게 하기
  6. 2011.06.16 'A first chance exception' 디버깅 하기
  7. 2011.05.17 Debug Tutorial Part1: Beginning Debugging Using CDB and NTSD
  8. 2011.05.10 심볼 서버 사용하기
  9. 2011.05.09 Application Verifier (App Verifier)
  10. 2011.01.05 Debugging With Minidump
  11. 2009.06.09 자동화 Automation 디버깅 팁 : DebugBreak 활용
  12. 2009.05.18 [Visual Studio 2005] 시스템 함수에 중단점 설정하기
  13. 2009.03.27 메모리 릭 디버깅 하기
  14. 2008.09.02 Detecting and Isolating Memory Leaks Using Microsoft Visual C++
2016. 12. 9. 06:27

Debugging JavaScript in a WebBrowser Control from Visual Studio

<< 웹뷰 자바스크립트 디버깅하기 >>


참고 : http://blogs.perl.org/users/mark_leighton_fisher/2011/09/debugging-javascript-in-a-webbrowser-control-from-vs2010.html


You can debug JavaScript executing inside a WebBrowser control embedded in your .NET 4.0 application from VS2010 (Visual Studio 2010), but it takes a little effort.


  1. Enable Script Debugging (both IE and Other) in Internet Explorer.
  2. Disable friendly HTTP messages in Internet Explorer.
  3. Enable Display a notification about every script error in Internet Explorer.
  4. Modify VS2010 to debug Script from the Attach Process dialog. Please note that you cannot debug both JavaScript (Script) and .NET 4.0 code at the same time. (I don't know why.)
  5. Add a debugger statement at the start of your JavaScript.
  6. Start your WebBrowser-embedded application without debugging (Ctrl-F5).
  7. When your application hits the JavaScript debugger; statement, select your current instance of VS2010.
  8. Voilà! You can now debug the JavaScript executing inside the WebBrowser control in your .NET 4.0 application. 

여기서 꿀팁은 debugger statement 를 javascript 에 추가해서 디버깅하는 것 같다. 



IE 에서 참고해야할 설정
 

Open Internet Explorer, go to Settings, tab Advanced, and, in the “Navigation” section:


  • Uncheck “disable script debugging” (both IE and Other)
  • Uncheck “Display friendly HTTP messages”
  • Check “Display a notification about every script error” 



2011. 10. 5. 22:22

VirtualBox를 이용한 Windows XP 디버깅

WDK 를 이용한 디바이스 드라이버 개발에 대해서 잠깐 공부해 보았습니다.
(디버깅을 위한 필터 드라이버를 만들어 볼까? 에서 부터 시작되었습니다.)

책에서는 PC 2대를 이용하여 하나는 Debuggee 로 하나는 Debugger 로 사용하는 방법이 아주 자세하게 소개되어 있었습니다. 하지만 이렇게 환경세팅을 하기에는 아무래도 벅차다 싶어 걍 그만둘까 하다가 Virtual Machine 을 이용해서도 할 수 있다는 내용을  저자가 언급을 하였더랬죠.

이거다 싶어 찾아보았습니다. 구글링을 하다가 관련된 내용을 찾아서 해보았습니다. 아래는 참고한 사이트의 목록입니다. 한 사이트에서 VMWare 를 이용한 방법에 대해서 자세하게 다루고 있는데 제가 알기로는 VMWare 는 무료가 아닙니다. 그래서 VirtualBox 를 이용한 방법을 고집스럽게 찾아보았고 여러가지 정보를 모아서 성공하기에 이릅니다.

참고한 사이트
http://hasu0707.tistory.com/218
http://hermes2.egloos.com/710368
http://blog.naver.com/PostView.nhn?blogId=yundorri&logNo=80048631747&redirect=Dlog&widgetTypeCall=true
 
필요한 도구는 다음과 같습니다.

* WinDbg
커널 디버깅을 하기 위한 도구입니다.
WDK 를 설치하거나 (http://www.microsoft.com/download/en/details.aspx?id=11800)
Debugging Tools 를 설치하시면 됩니다. (http://msdn.microsoft.com/en-us/windows/hardware/gg463009)

* VirtualBox
* iso 포맷의 Windows XP 설치 파일
라이센스 문제가 있기 때문에 알아서 구하셔야 합니다.
설치 방법은 구글링을 해보시기 바랍니다. 
 
Windows XP 가 VirtualBox 에 설치되어 있으며, Host PC 에 WinDbg 가 설치되어 있다고 가정하고 진행하겠습니다.

1. VirtualBox 설정

디버깅에 사용할 Serial Port 를 설정합니다. PC 와 달리 VM 에서는 Pipe 를 사용해야 합니다. 설정방법은 다음과 같습니다.

 
"포트/파일 경로" \\.\pipe\com_1 은 기억해 둡니다.

확인을 누르고  Windows XP 를 시작합니다.

2. Windows XP 설정

폴더 옵션을 아래와 같이 설정한 후, 


확인을 누르고, C:\ (OS 가 설치된 드라이브)로 가면 boot.ini 파일이 보입니다. 

아래 그림처럼 

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="WinDBG Remote Debugging Mode (VM)" /fastdetect /debug

항목을 추가합니다.


만일 VM 이 아니라면 아래와 같이 port 를 지정합니다.

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="WinDBG Remote Debugging Mode" /fastdetect /debug /debugport=COM1 /baudrate=115200

이렇게 한후 재부팅을 하고 부팅시에 "WinDBG Remote Debugging Mode (VM)" 를 선택합니다.


3. WinDBG 를 이용한 디버깅

windbg 를 실행한 후  File > Kernel Debug 를 선택한 후 아래처럼 입력합니다.


확인을 누르고, VM의 Windows XP 에 디버깅이 성공하면, 아래 화면과 같이 됩니다.
혹 문제가 있는 경우, PC 를 재부팅한 후 다시 해보시기 바랍니다.

 
2011. 8. 17. 05:18

Debugging Heap Corruption in Visual C++ Using Microsoft Debugging Tools for Windows

출처: https://www.google.com/bookmarks/url?url=http://www.google.co.kr/url%3Fsa%3Dt%26source%3Dweb%26cd%3D4%26ved%3D0CFIQFjAD%26url%3Dhttp%253A%252F%252Fdaviddahlbacka.com%252FBugCleaner%252FDebuggingHeapCorruption.doc%26rct%3Dj%26q%3DWinDbg%2520heap%2520corruption%26ei%3DycfJTd2dIo2HrAf_49GVBQ%26usg%3DAFQjCNHFs7mh07WO73GQslU5songDsWGQQ%26cad%3Drja&ei=L9BKTvPSFMnhkAXHvNn6AQ&sig2=tMm2fV_u0AAW8eFZ5ssQgQ&ct=b

Contents

Heap Corruption. 2

Causes of Heap Corruption. 2

Debugging Heap Corruption. 2

Debugging Tools. 3

Specific WinDbg Commands. 3

Specific GFlags Commands. 4

Preparation for Debugging. 5

Program Installation and Compilation. 5

First-Time WinDbg Options. 5

Live Debugging. 6

Standard Heap Options. 6

Full or DLLs Heap Options. 7

Postmortem Debugging. 8

Analyzing a Memory Dump. 9

References. 9

Example Program.. 10

Example Program Code. 10



2011. 8. 16. 22:53

C# UserControl, ActiveX Control 디버깅 하기

C# 으로 UserControl 을 지정하여 Dll 을 만들면, 디버깅(F5) 실행시 해당 컨트롤을 디버깅할 수 있는 UserControlTestContainer 가 자동으로 실행되면서 target DLL 을 로딩합니다. 근데, 만일 그냥 클래스 라이브러리를 선택하여 Dll 을 만들고 UserControl 을 추가한 후 디버깅을 실행하면 시작 프로그램을 지정하라는 메시지가 나옵니다.

이를 다른 외부 응용 프로그램을 지정하지 않고 UserControlTestContainer 를 이용하여 디버깅하려면 다음과 같이 합니다.


Start External Program 에 다음의 경로에 있는 UserControlTestContainer 를 지정하고,

* 32 비트인 경우
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\UserControlTestContainer.exe
* 64 비트인 경우
C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\UserControlTestContainer.exe

(VS 버전이 다른 경우는 해당 버전의 경로를 참고하세요.)

command line arguments 에 생성되는 Dll 이름을 지정하면 됩니다. 안타깝게도 Visual Studio 에 버그가 있어서 command line arguments 에 $(
TargetPath) 를 지정시 자동으로 확장되지 않았습니다.

저처럼, $(
TargetPath) 를 찾을 수 없다는 에러메시지가  나오면 그냥 생성되는 Dll 파일을 수동으로 지정하시면 됩니다.

알면 별 것 아니지만 모르면 조금 답답한 팁입니다.  Visual Studio 2005 에서는 기본적으로 ActiveX Container 가 콤보 박스에 나와 있어서 훨씬 지정하기 편했습니다. ㅡㅡ;
2011. 7. 18. 21:35

"디버깅 하시겠습니까" 창 나오지 않게 하기

출처: http://stackoverflow.com/questions/396369/how-do-i-disable-the-debug-close-application-dialog-on-windows-vista

Stackoverflow 에서 다음과 같은 질문에 대한 답을 찾았습니다.

How do I disable the 'Debug / Close Application' dialog on Windows Vista?

 


: 비스타라고 질문했지만 Windows 7 에서도 동작하였습니다.

Windows Error Reporting (WER) 이 크래쉬 덤프를 취한 후에 app 을 강제로 종료하도록 하기 위해서는 아래와 같이 레지스트리를 설정하면 됩니다.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting]
"ForceQueue"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\Consent]
"DefaultConsent"=dword:00000001

이렇게 설정된 후 app 에서 크래쉬가 발생하면, 아래 위치에서 *.hdmp 와 *.mdmp 파일을 볼 수 있습니다.

%ALLUSERSPROFILE%\Microsoft\Windows\WER\
 
레지스트리 수정 외에도 코드에서 app의 코드 내에서도 수정할 수가 있군요.
코드는 아래와 같은데, 자세한 내용은 아래 링크를 참고하시기 바랍니다. 

DWORD dwMode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
SetErrorMode(dwMode | SEM_NOGPFAULTERRORBOX);

http://blogs.msdn.com/b/oldnewthing/archive/2004/07/27/198410.aspx
2011. 6. 16. 22:03

'A first chance exception' 디버깅 하기

Visual Studio Output 창에 보면

'A first chance exception ... '

머시기라는 메시지가 나오고 그냥 넘어가는 경우가 있습니다. 이러한 예외가 발생할 때 중단점이 걸리도록 설정하거나, 반대로 특정 예외에 대해서 중단점이 걸리는 걸 무시할 수 있습니다.

설정 방법은 아래와 같습니다. (Visual Studio 2008)

 Debug > Exceptions
 

위에서 원하는 예외 항목을 찾아서 중단점을 걸고 싶으면 '체크', 아니면 '체크해제'를 합니다.




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

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

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

끝.
2011. 5. 10. 21:45

심볼 서버 사용하기

심볼 서버를 설정하면 디버깅시 보다 자세한 정보를 얻을 수 있습니다. 설정하는 방법은 아래 그림처럼 Microsoft 에서 제공하는 Public Symbol Server 를 사용하면 아주 쉽습니다.

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

* Visual Studio 2008 예
Tools > Options > Debugging > Symbols

 
심볼 서버를 로컬로 캐싱하면 로딩 속도가 빨라 집니다. 아래는 마이크로소프트에서 제공하는 참조 페이지입니다.

How to: Use a Symbol Server 
2011. 5. 9. 07:21

Application Verifier (App Verifier)

디버깅을 하다가 Access Violation 에러를 잡지 못하고 GG 를 치다가 Application Verifier 라는 녀석을 사용해 보기로 하였습니다.

이 녀석을 사용하려면 Application Verifier 와 WinDbg 가 필요합니다. 아래 링크에서 Debugging Tools from the SDK 를 다운로드 받아 설치하시면 한번에 모두 설치됩니다.

http://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx

단독 설치
http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=20028
 

사용법은 좀 더 사용해 보고 올리도록 하겠습니다.  
2011. 1. 5. 22:22

Debugging With Minidump

예외 발생시 minidump 를 생성하는 Test Application 을 작성하였습니다. 1번째 참고한 사이트에서 제공하는 코드는 Multi-byte char set 으로 작성된거라 Unicode 에서는 빌드 에러 나더군요. 그리고 생성자에서 
strdup 함수를 사용하는데 여기에 메모리 릭이 있습니다. 예외가 나지 않은 경우 문제가 있더군요.
소소한 컴파일 에러와 버그를 수정한 버전을 아래 첨부하였습니다. VS 2008 에서 작성되었습니다.

소스 받은 후 빌드하고 실행해 보시면 되는데, 이상한 점은 Debug 모드에서 실행하면 정상적으로 minidump 가 수행되는데 Release 로 하면 minidump 가 수행되지 않았습니다. 이건 Visual Studio 가 설치된 것과 관련이 있는듯 한데 minidump 내용을 이해하는 것과는 무관하여 깊이 파고들지 않았습니다. 어쨋든 release 로 빌드된 exe 파일 역시 다른 환경에서는 정상적으로 dump 가 생성됩니다.

아래는 Windows 7 에서 빌드한 Release 버전의 실행 파일과 PDB 파일입니다.
PDB 파일은 나중에 minidump 를 open 할 때 필요합니다.


소스를 빌드한 동일한 PC 에서 생성된 minidump.dmp 파일을 열면 exe 나 pdb 파일을 동일한 위치에 복사하지 않아도 소스 정보를 볼 수 있었습니다. 하지만 다른 환경에서 dump 한 파일을 열려면 minidump.dmp 와 동일한 경로에 실행화일과 pdb 파일이 함께 있어야만 소스 정보를 제대로 보여주었습니다.

minidump 를 이용한 디버깅을 해보기 위해서 XP 에서 생성한 dump 파일을 사용하겠습니다. 아래 파일은 미리 
Windows XP 에서 dump 해놓은 파일입니다.


아래 그림처럼 minidump.dmp, MiniDump.exe, MiniDumpTest.pdb 파일을 동일한 폴더에 둡니다. 그냥 minidump.dmp 파일을 실행화일과 pdb 가 있는 곳에 복사하면 되겠죠.


minidump.dmp 파일을 더블 클릭합니다. (Visual Studio 가 설치되어 있다고 가정하겠습니다. WinDebug 로도 가능하다고 하니 WinDebug 를 이용한 방법은 구글에서 찾아보시기 바랍니다.)

Visual Studio 가 열린 상태에서 F5 를 누르거나 Start Debugging 메뉴를 선택하면 아래 그림과 같이 예외가 일어난 정보를 얻어올 수 있습니다.



놀랍기만 합니다. Windows XP 에서 재현된 문제를 Windows 7 에서 디버깅 하고 있다니요.

버그는 재현할 수 있다면 거의 해결한거나 마찬가지인데, 이런 minidump 를 이용한다면 보다 빨리 버그의 원인을 찾고 제거할 수 있을 것입니다.

내용 추가.

만약 불행히도 Minidump 를 생성하는 코드가 추가되어 있지 않은 경우에는 어떻게 minidump 를 생성하는지에 대해서도 알아보겠습니다. 

Dr.Watson 이용 (2000/2003/XP)

Dr. Watson 이라는 프로그램을 사용하면 crash 난 프로세스에 대해서 minidump 를 생성할 수 있습니다. 크래쉬가 발생하면 자동으로 Dr. Watson 이 덤프파일을 생성합니다. 이제 설정 방법과 실습을 해보겠습니다.

시작 > 실행 에서 drwtsn32 를 입력합니다. 


Dr.Watson 이 실행되면 아래처럼 설정 후 OK 를 선택합니다.

* Crash Dump Type: Mini
* Check "Dump Symbol Table", "Dump All Thread Contexts", & "Create Crash Dump File"
* Set "Number Of Instructions" and "Number Of Errors To Save" to 50
* Uncheck "Append to Existing Log"



확인을 누른 후 Crash 가 발생하면 Crash Dump 에 설정한 위치에 로그와 덤프파일이 자동으로 생성됩니다.

실습을 위해서는 Crash 가 일어나는 프로그램과 디버깅 정보를 가지고 있는 pdb 파일이 필요합니다.
CrashTest.exe 는 단순히 0 으로 나누기를 실행하는 코드가 있을뿐 이라 소스를 첨부하지는 않겠습니다.
디버깅 실습을 위해서는 소스가 필요할 것 같아 다시 생각을 바꿔 첨부합니다.

아래는 XP 에서 생성된 Dr. Watson 덤프 파일입니다.


이렇게 덤프파일 생성되면 디버깅하는 방법은 동일합니다. 아래 그럼처럼 user.dmp 파일을 exe 와 pdb 파일이 있는 위치로 복사를 합니다.


dmp 파일을 더블클릭하여 Visual Studio 를 띄웁니다. 그러고 나서 F5 를 누르면 아래와 같이 관련 정보를 얻을 수 있습니다.


Windows Vista, Server 2008, 그리고 Windows 7 에서 minidump 파일 생성하기

1. CrashTest.exe 실행 (Crash 재현)
2. 프로세스를 중지된 상태로 나둡니다.


3. 작업 관리자를 실행하고, 프로세스 탭에서 해당 프로세스를 찾습니다. 그런후 오른쪽 마우스를 클릭하면 아래 그림처럼 "덤프 파일 만들기"라는 메뉴가 있습니다. 


4. 덤프 파일 만들기를 선택합니다.



5. 로그 파일은 %USERPROFILE%\AppData\Local\Microsoft\Windows\WER\ReportArchive 위치에 생성됩니다.

Windows 7 에서 위 방법을 테스트 해보니 dump 파일은 생성이 되는데, 예외 지점을 가리키지 못하는 문제가 있습니다. 이는 좀더 살펴봐야 할 것 같습니다.

내용추가

Windows 7 에서 dump 파일을 자동으로 생성하는 방법을 드디어 찾았습니다.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps

레지스트리에 다음 사항을 추가해야 합니다. 만약 이 글에서 테스트한 CrashTest.exe 에 덤프 파일을 자동으로 생성하려고 한다면 아래 이미지 처럼 키와 값들을 추가해 주어야 합니다.


LocalDumps (존재하지 않으면 키를 만들어 주셔야 합니다) 하위에 CrashTest.exe 는 크래쉬가 난 경우 자동으로 dump 파일을 생성할 프로세스 명을 지정합니다.

DumpFolder 는 덤프 파일이 저장될 위치를 나타내는 문자열입니다. 
DumpCount 는 폴더에 생성할 최대 덤프 파일 개수를 가리킵니다. 만약 최대 값을 초과하면, 가장 오래된 파일은 새로운 파일로 교체 됩니다.
DumpType 은 덤프 타입을 가리킵니다. 0 은 Custom dump, 1 은 mini dump, 2 는 Full dump 입니다.
CustomDumpFlags 는 CumpType 이 0 인 경우 사용됩니다. 이 값은 MINIDUMP_TYPE 열거형 값의 비트 조합입니다. (자세한 내용은 6번 사이트를 참고하세요)

이렇게 레지스트리를 수정한 후 (재부팅 필요없습니다) CrashTest.exe 를 실행합니다. CrashTest.exe 의 작동이 중지되면 "프로그램 닫기"를 선택해서 그냥 닫아 버립니다. 


그냥 닫은 후 DumpFolder 에 지정한 폴더로 이동하면 (여기서는 d:\temp) 아래 그림처럼 덤프파일이 자동으로 생성되어 있습니다. 


생성된 파일 역시 첨부하겠습니다.

이 파일은 CrashTest.exe 와 pdb 파일이 있는 위치로 이동후 위에서 소개한 방법대로 디버깅을 하면 원하는 정보를 얻게 됩니다.


아후~,, 생각보다 글이 길어 졌네요. (파고들면..내용이 점점 늘어나는군요 ㅡㅡ;)
이 내용을 마지막으로 minidump 에 대한 내용은 일단락 하겠습니다. 

2009. 6. 9. 12:30

자동화 Automation 디버깅 팁 : DebugBreak 활용

COM 자동화인 Automation 을 작성할 때, 스크립트를 호출하거나, IE에서 호출되는 스크립트를 디버깅하거나 할 때, 디버깅이 걸리지 않아 애먹을때는................. ㅡㅡ+

DebugBreak();

를 활용하자.

스크립트에서 이 함수가 호출된 자동화 함수를 호출하면 해당 함수에서 중단점이 걸리고 그 문장에서부터 디버깅이 가능하다.

아주 괜찮다~~ 더 좋은 방법이 있다면... 추천좀여..

2009. 5. 18. 08:40

[Visual Studio 2005] 시스템 함수에 중단점 설정하기

시스템 함수에 중단점 설정하는 방법이 2005에서는 2003보다 훨신 쉬워졌다.

시스템 함수 SetParent 에 중단점을 설정한다고 하자.

우선 아래 처럼 설정을 변경한다.

Tools > Options > Debugging Native 에서 "Load Dll exports" 를 체크한 후 "OK"를 클릭한다.


중단점 창을 열고 New를 클릭한 후, "Break at function"을 선택하거나, 단축키 Ctrl+B를 누른다.


그러면 아래처럼 중단점이 정상적으로 설정되고, 함수 수행시마다 중단될 것이다.

2009. 3. 27. 14:31

메모리 릭 디버깅 하기

Detected memory leaks!
Dumping objects ->
f:\sp\vctools\vc7libs\ship\atlmfc\src\mfc\strcore.cpp(141) : {128} normal block at 0x003BDF90, 50 bytes long.
 Data: < 9Px            > AC 39 50 78 10 00 00 00 10 00 00 00 01 00 00 00


위와 같은 메시지가 Output 창에 출력되었을 때 어디에서 메모리 누수가 발생하였는지를 발견하는 것은 쉽지 않다. 이런 경우 디버깅하는 방법에 대해서 소개한다.

F10 (Debug > Step Over) 를 눌러서 디버그를 시작한다. F5가 아니라 F10으로 디버깅을 시작하면 main 함수에서부터 시작한다.

이때 중단점 창을 연후 위 출력창에서 출력된 메시지에서 Normal Block 주소(0x003BDF90) 에다가 중단점을 건다.


중단점을 만든 후에, F5를 눌러 다시 디버깅을 재개하면 해당 데이터 주소에 접근을 시도할 때에 아래 메시지박스가 뜨면서 중단점이 걸린다.


확인을 눌러 디버깅 모드로 진입하면, 콜 스택을 볼 수 있는데 콜 스택을 주의깊게 살펴보면 어디에서 메모리로 할당하였는지를 알 수 있다.


이 예에서는 CUIThread::InitInstance에서 new 를 이용하여 할당했던 메모리를 delete 하지 않았음을 알 수 있다.

이렇게 직접 디버깅하는 방법도 있지만 Visual C++에서 제공하는 다른 방법도 있다. 아래 글 참고

Detecting and Isolating Memory Leaks Using Microsoft Visual C++
2008. 9. 2. 10:34

Detecting and Isolating Memory Leaks Using Microsoft Visual C++

번역 : codemuri (codemuri.tistory.com)

 

Edward Wright

Microsoft Corporation

 

May 1999

 

Summary

C/C++ 프로그래밍 언어로 개발된 애플리케이션에서 문제가 되는 행위를 일으키는 감지하기 어려운 버그인, 메모리 누수(memory leaks)의 위치를 알아내고, 격리시키기 위한 마이크로소프트 비쥬얼 C++ 디버거에 의해 제공되는 툴을 사용하는 방법을 설명한다.

 

Introduction. 1

Enabling Memory Leak Detection. 2

Using _CrtSetDbgFlag. 3

Setting a Breakpoint on a Memory Allocation Number. 4

Comparing Memory States. 5

Additional Information. 7

 

Introduction

 

메모리를 동적으로 할당하고 해제하는 능력은 C/C++ 프로그래밍 언어의 가장 강력한 기능중의 하나이다, 하지만 중국인 철학자 Sun Tzu 가 지적했듯이, 가장 위대한 힘은 가장 취약한 약점일 수 있다. 메모리-제어 실수가 가장 일반적인 버그의 원인이기에 C/C++ 애플리케이션에서 이것은 확실히 진리이다. 가장 미묘하고 감지하기 어려운 버그중의 하나는 메모리 누수이다 이전에 할당된 메모리를 철저히 해제해야 하는 것에 대한 불이행. 딱 한번만 일어나는 작은 메모리 누수는 발견되지 않을 지도 모른다, 하지만 대량의 메모리가 누수되거나, 또는 점진적으로 누수되는 프로그램은 메모리 고갈의 결과로서 실패(failure)로 끝나는 빈약한 ( 그리고 서서히 감소되는) 성능으로 이어지는 징후를 나타낼 것이다. 더욱 나쁘게는, 누수되는 프로그램은 너무 많은 메모리를 소진시켜서, 어느 프로그램이 실제로 문제를 일으키는지에 대한 어떠한 실마리도 없이 사용자를 내버려둔채, 다른 프로그램을 종료시킨다. 덧붙여서, 무해한 메모리 누수조차도 다른 문제들의 전조가 될 수 있다.

 

다행히, 비쥬얼 C++ 디버거와 CRT 라이브러리는 메모리 누수를 발견하고, 식별하기 위한 효과적인 도구들의 집합을 제공한다. 이 글은 효과적으로 그리고 체계적으로 메모리 누수를 격리시키기 위한 이러한 도구들에 대한 사용법을 설명한다.

 

Enabling Memory Leak Detection

 

메모리 누수를 감지하기 위한 근본적인 도구들은 디버거와 CRT 디버그 heap 함수들이다. 디버그 heap 함수를 활성화하기 위해서는, 아래의 문장을 프로그램내에 포함시켜야 한다.

 

#define _CRTDGB_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

 

#include 문은 위에 보여지는 순서대로 정렬되어야만 한다. 만일 순서를 바꾸면, 당신이 사용할 함수들은 올바르게 동작하지 않을지도 모른다. crtdbg.h malloc free 함수를 그들의 디버그 버전, _malloc_dbg _free_dbg 로 맵핑하는 것을 포함하여, 메모리 할당과 해제를 추적한다. 이러한 맵핑은 디버그 빌드(, _DEBUG 가 정의되었을때에만)에서만 일어난다. 릴리즈 빌드는 보통의 malloc free 함수를 사용한다.

 

#define 문은 CRT heap 함수의 버전을 디버그 버전으로 맵핑한다. 이 문장은 꼭 필요한 것은 아니지만, 그것이 없을 경우, 메모리 누수 덤프(dump)는 덜 유용한 정보를 포함할 것이다.

 

위에 보여진 문장을 추가한 후에, 프로그램내에 아래에 나타난 문장을 포함시킴으로써 메모리 누수 정보를 덤프할 수 있다.

 

_CrtDumpMemoryLeaks();

 

디버거하에서 프로그램을 실행할 때, _CrtDumpMemoryLeaks 디버그 탭의 Output 윈도우에 메모리 누수 정보를 표시한다. 메모리 누수 정보를 아래와 같이 보여준다.

 

Detected memory leaks!

Dumping objects ->

C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18}

   normal block at 0x00780E80, 64 bytes long.

 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

Object dump complete.

 

#define _CRTDBG_MAP_ALLOC 문을 사용하지 않았다면, 메모리 누수 덤프는 아래와 같이 보여질 것이다.

 

Detected memory leaks!

Dumping objects ->

{18} normal block at 0x00780E80, 64 bytes long.

 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

Object dump complete.

 

보는 바와 같이, _CrtDumpMemoryLeaks _CRTCBG_MAP_ALLOC 이 정의되어 있을 경우에는 더욱 유용한 정보를 제공한다. _CRTDBG_MAP_ALLOC 이 정의되어 있지 않은 경우, 디스플레이는 아래의 것들을 보여준다.

 

l       메모리 할당 숫자({} ) – the memory allocation number

l       블락 타입(normal, client, or CRT) – the type of block

l       16진수 형태의 메모리 위치 – the memory location in hexadecimal form

l       바이트 단위의 블락 사이즈 (the size of the block in bytes)

l       첫번째 16 바이트의 내용(역시 16진수 형태) – the contents of the first 16 btyes

 

_CRTDBG_MAP_ALLOC 이 정의되어 있는 경우, 디스플레이는 또한 누수된 메모리가 할당되었던 파일을 보여준다. 파일 이름뒤에 괄호안에 있는 수(아래 예에서는 20)는 그 파일안의 라인 넘버이다. 라인 넘버와 파일 이름을 포함하는 출력 라인을 더블클릭하면,

 

C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18}

   normal block at 0x00780E80, 64 bytes long.

 

커서는 소스파일 내의 메모리가 할당된 라인으로 점프할 것이다 (위 예의 경우, leaktest.cpp의 라인넘버 20). 출력된 라인을 선택하는 것과 F4를 누르는 것은 같은 효과를 가질 것이다.

 

Using _CrtSetDbgFlag

 

_CrtDumpMemoryLeaks 를 호출하는 것은 프로그램이 항상 같은 곳에서 종료한다면 무난할 것이다. 그러나, 프로그램이 다수의 위치에서 종료될 수 있다면? 각각의 가능성 있는 종료에다가 _CrtDumpMemoryLeaks의 호출을 놓는 대신에, 당신의 프로그램의 시작 부분에 아래의 호출을 포함시킬 수 있다.

 

_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

 

다른 위치로 출력을 보내기 위한 _CrtSetReportMode를 사용하는 것에 대한 정보는, Visual C++ 문서의 _CrtSetReportMode를 보라 (Visual C++ Programmer’s Guide, Run-Time Library Reference, Debug Function Reference).

 

Setting a Breakpoint on a Memory Allocation Number

 

메모리 누수 리포트내의 파일 이름과 라인 넘버는 누수된 메모리가 할당된 위치를 가르쳐 준다, 하지만 메모리가 어디에서 할당되었는 지를 아는 것은 문제를 식별하기 위해서 항상 충분한 것은 아니다. 종종 할당은 프로그램이 실행되는 동안에 여러 번 호출될 것이다, 그러나 특정 호출에 대해서만 메모리가 누수될 지도 모른다. 문제를 식별하기 위해서, 누수된 메모리가 어디에서 할당되었는 지뿐만 아니라, 누수가 일어나는 조건(conditions)도 역시 알고 있어야만 한다. 이것을 가능하게 하는 정보는 메모리 할당 넘버이다. 이것은 결과가 표시될 때 파일이름과 라인넘버의 뒤에서, { } 괄호안에 나타나는 수이다. 예를 들면, 아래 출력에서, “18”은 메모리 할당 넘버이다. 누수된 메모리는 프로그램에서 할당된 메모리의 18번째 블락(the 18th block of memory)이라는 것을 의미한다.

 

Detected memory leaks!

Dumping objects ->

C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {18}

   normal block at 0x00780E80, 64 bytes long.

 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD

Object dump complete.

 

CRT 라이브러리는 CRT 라이브러리에 의해 할당되는 메모리 또는 MFC와 같은 다른 라이브러리에 의해서 할당되는 메모리를 포함하여, 프로그램이 실행되는 동안에 할당되는 모든 메모리 블락들을 카운트 한다. 그리하여, 할당 넘버 n인 객체(object)는 프로그램에서 할당된 n번째 객체일 것이다 그러나 당신의 코드에 의해 할당된 n번째 코드는 아닐 수 있다. (대부분의 경우에, 그렇지 않을 것이다.)

 

메모리가 할당된 위치에 중단점을 설정하기 위해서 할당 넘버를 사용할 수 있다. 이렇게 하기 위해서는, 프로그램의 시작 근처에 중단점을 설정하라. 그 지점에서 프로그램이 멈추었을 때, QuickWatch 대화 상자나 Watch 윈도우를 통해서 그러한 메모리 할당 중단점을 설정할 수 있다. 예를 들면, Watch 윈도우에서 이름 칼럼(Name column)안에 아래의 문장을 입력하라.

 

_crtBreakAlloc

 

만일 multithreaded DLL 버전의 CRT 라이브러리 (/MD 옵션)를 사용하고 있다면, 아래 보여지는 바와 같은 컨텍스트 연산자(context operator)를 반드시 포함해야 한다.

 

{,,msvcrtd.dll}_crtBreakAlloc

{,,msvcr71d.dll}_crtBreakAlloc=89 (.Net 2003)

{,,msvcr80d.dll}_crtBreakAlloc=1993 (.NET 2005)


위에 dll 파일은 모듈창에서 확인가능 함.

 

이제, RETRUN을 눌러라. 디버거는 그 호출을 평가(evaluate)하고 Value 칼럼안에 그 결과를 둔다. 이 값은 메모리 할당에 대한 어떠한 중단점도 설정하지 않았다면 -1일 것이다. 중단점을 설정하고 싶은 메모리 할당의 할당 넘버를 Value 칼럼의 값에다가 입력하라 예를 들면, 앞의 출력에서 보여준 할당에 중단점을 설정하기 위해서 18을 입력)

 

관심이 있는 메모리 할당에 대한 중단점을 설정한 후에, 디버깅을 계속할 수 있다. 할당 순서가 바뀌지 않도록 이전 실행과 똑 같은 상태하에서 프로그램을 실행하는 것에 주의하라. 프로그램이 지정된 메모리 할당에서 중단되었을 때, 그 메모리가 할당된 상태를 결정하는 Call Stack 윈도우와 다른 디버그 정보를 볼 수 있다. 필요하다면, 그 객체에 일어나는 것을 보기 위해 그 지점으로부터 프로그램의 실행을 계속하거나 어쩌면 왜 그것이 올바르게 해제 되지 않았는 지를 측정할 수 있을 것이다 (그 객체에 대한 데이터 중단점(Data breakpoint)을 설정하는 것은 유용할 것이다.)

 

디버거에서 메모리 할당 중단점을 설정하는 것이 보통 더 쉬울지라도, 기호에 따라 그것들을 당신의 코드에서 설정할 수 있다. 코드 안에 메모리 할당 중단점을 설정하기 위해서는, 아래와 같은 라인을 추가하라 ( 18번째 메모리 할당의 경우)

 

_crtBreakAlloc = 18;

 

다른 방법으로는, 같은 효과를 가진 _CrtSetBreakAlloc 함수를 사용할 수 있다.

 

_CrtSetBreakAlloc(18);

 

Comparing Memory States

 

메모리 릭의 소재를 파악하기 위한 또 다른 기법은 키 포인트에다가 애플리케이션의 메모리 상태에 대한 스냅샷(snapshots)을 포함시키는 것이다. CRT 라이브러리는 메모리 상태의 스탭샷을 저장하기 위해 사용할 수 있는 구조체형, _CrtMemState 를 제공한다.

 

_CrtMemState s1, s2, s3;

 

주어진 지접에서 메모리 상태의 스냅샷을 찍기 위해서는, _CrtMemCheckpoint 함수에다가_CrtMemState 구조체를 전달하라. 이 함수는 구조체 안에다가 현재 메모리 상태의 스냅샷을 채운다.

 

_CrtMemCheckpoint( &s1 );

 

_CrtMemDumpStatistics 함수에다가 그 구조체를 전달함으로써 어떤 지점에서든 _CrtMemState 구조체의 내용을 덤프할 수 있다.

 

_CrtMemDumpStatistics( &s3 );( &s1 );

 

이 함수는 아래와 같이 보이는 메모리 할당 정보의 덤프를 출력한다.

 

0 bytes in 0 Free Blocks.

0 bytes in 0 Normal Blocks.

3071 bytes in 16 CRT Blocks.

0 bytes in 0 Ignore Blocks.

0 bytes in 0 Client Blocks.

Largest number used: 3071 bytes.

Total allocations: 3764 bytes.

 

메모리 누수가 코드의 어떤 코드 섹션에서 일어났는지를 측정하기 위해서는, 그 섹션의 앞과 뒤에다가 메모리 상태의 스냅샷을 찍고, 그러고나서 두개의 상태를 비교하기 위해서 _CrtMemDifference 를 사용할 수 있다.

 

_CrtMemCheckpoint( &s1 );

// memory allocations take place here

_CrtMemCheckpoint( &s2 );

 

if ( _CrtMemDifference( &s3, &s1, &s2) )

   _CrtMemDumpStatistics( &s3 );

 

이름이 암시하듯이, _CrtMemDifference 는 두개의 메모리 상태 (처음 두개의 파라미터들)를 비교하고 두개의 상태에 대한 차이점의 결과를 (세번째 파라미터) 만들어 낸다. 프로그램의 시작과 끝에다가 _CrtMemCheckpoint 호출을 두는 것과 그 결과를 비교하기 위해서 _CrtMemDifference 를 사용하는 것은 메모리 누수를 체크하기 위한 또 다른 수단을 제공한다. 누수가 발견되면, 프로그램을 분할하기 위해 _CrtMemCheckpoint 호출을 사용할 수 있다. 그리고 이진 검색 기법(binary search technique)를 사용하여 그 누수의 소재를 알아낼 수 있다.

 

Additional Information

 

아래의 글은 MFC 애플리케이션에서 특수한 강조 기법(special emphasis)을 가진 메모리 누수 디버깅에 대해서 논의한다.

 

l       Blasczak, Mike. Stunt Debugging: Diagnosing memory Leaks, Visual C++ Developer. Pinnacle Publishing, 1997.

 

아래 책은 메모리 누수 디버깅에 대한 어떠한 정보도 담고 있지 않다, 그러나 만일 당신이 백지(white paper)를 작성하고 있고 고대의 중국 철학자로부터 인용 어구(quote)를 원한다면 유용할 지도 모른다.

 

l       Tzu, Sun. The Art of War. Various publishers.