Tutorial 30: Win32 Debug API part 3
win32 디버그에 관한 API의 마지막 장이다. 이 설명서에서는 디버그 대상 프로그램의 트레이스 방법(실행추적)에 대한것을 설명한다.
메인 소스 | 실행 결과 |
|
디버거를 사용했던 적이 있다면, 트레이스에 대해 대충 들어 보았을 것이다. 프로그램을 트레이스 하면, 명령어마다 프로그램은 정지하게 되고, 레지스터, 메모리의 값을 변경할 수 있게 된다. 덧붙여서 트레이스를 정식으로는 「싱글스텝」이라 부른다.
싱글스텝의 특징은, CPU 차원에서 지원하고 있다는 것이다. 플래그 레지스터의 8번째를 트랩 플래그라고 부르며,트랩 플래그가 설정되면, CPU는 싱글스텝모드에 돌입한다. CPU는 각 명령어마다 디버그 예외를 발생시키고, 트랩 플래그를 자동적으로 클리어 한다.
디버그 대상 프로그램을 싱글스텝으로 동작시켜서, win32 디버그 API를 사용하는 방법은, 다음과 같다.
- ContextFlags에 CONTEXT_CONTROL을 지정하고, GetThreadContext함수를 호출해서, 플래그 레지스트를 얻는다.
- CONTEXT구조체의 regFlag에 트랩 비트를 설정한다.
- SetThreadContext함수를 호출한다.
- 평소와 같이 디버그 이벤트를 기다린다. 디버그 대상 프로그램은 싱글스텝모드로 동작하게 되고, 각 명령마다 EXCEPTION_DEBUG_EVENT를 받게되고, 동시에 u.Exception.pExceptionRecord.ExceptionCode의 값이 EXCEPTION_SINGLE_STEP으로 되게 된다.
- 다음 명령을 트레이스 하고 싶다면, 트랩 비트를 다시 설정한다.
|
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .data ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .code start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL mov hwnd,eax jmp $ invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd .while TRUE invoke GetMessage, ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax,msg.wParam ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start
|
이 프로그램을 실행하면, 파일열기 대화상자가 표시된다. 유저가 실행 파일을 선택하면, 그 프로그램이 싱글스텝모드로 동작하게되고 디버그 대상 프로그램이 종료할 때까지 명령어수를 카운트 한다.
.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT . if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT 여기서, 디버그 대상 프로그램을 싱글스텝모드로 동작시키도록 설정하게 된다. Windows는 프로그램을 실행하기 직전에, EXCEPTION_BREAKPOINT를 전송한다.
mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context GetThreadContext함수를 호출하는 것으로, 현재 디버그 대상 프로그램의 레지스터값을 CONTEXT구조체에 설정한다. 구체적으로는 현재 플래그레지스터의 값이 필요하다.
or context.regFlag, 100h 플래그 레지스터의 트랩 비트(8번째의 비트)를 설정한다.
invoke SetThreadContext, pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE . continue 그리고,CONTEXT구조체의 값을 적용시키기 위해, SetThreadContext함수를 호출하고, 디버그 대상 프로그램의 처리를 재개시키기 위해, DBG_CONTINUE플래그를 설정하고, ContinueDebugEvent함수를 호출한다.
.elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP inc TotalInstruction 디버그 대상 프로그램의 명령코드가 실행될 때, EXCEPTION_DEBUG_EVENT를 받게된다. 그 때, u.Exception.pExceptionRecord.ExceptionCode의 값을 조사해야 한다.
그 값이 EXCEPTION_SINGLE_STEP이라면, 디버그 이벤트가 발생한 원인은 싱글스텝모드로 작동된지를 알수 있게 된다. 이 때, 디버그 대상 프로그램은 한개의 명령을 실행했으므로, TotalInstruction을 증가시켜(increment) 둔다.
invoke GetThreadContext, pi.hThread, addr context or context.regFlag, 100h invoke SetThreadContext, pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE . continue 디버그 예외가 일어난 후에는 트랩 플래그가 클리어 되므로, 계속 싱글스텝모드로 실행하고 싶다면 다시 트랩 플래그를 설정 할 필요가 있다.
※ 이 샘플을 큰 프로그램에 사용해서는 안 된다. 트레이스는 매우 늦게 처리되므로, 자칫하면 프로그램이 종료할 때까지 오랫동안 기다려야 할지도 모르기 때문이다.