2007. 2. 20. 14:16
DevX Win32 Assembly Tutorial 30: Win32 Debug API part 3

Tutorial 30: Win32 Debug API part 3

win32 디버그에 관한 API의 마지막 장이다. 이 설명서에서는 디버그 대상 프로그램의 트레이스 방법(실행추적)에 대한것을 설명한다.
 메인 소스    실행 결과  

Theory:

디버거를 사용했던 적이 있다면, 트레이스에 대해 대충 들어 보았을 것이다. 프로그램을 트레이스 하면, 명령어마다 프로그램은 정지하게 되고, 레지스터, 메모리의 값을 변경할 수 있게 된다. 덧붙여서 트레이스를 정식으로는 「싱글스텝」이라 부른다.

싱글스텝의 특징은, CPU 차원에서 지원하고 있다는 것이다. 플래그 레지스터의 8번째를 트랩 플래그라고 부르며,트랩 플래그가 설정되면, CPU는 싱글스텝모드에 돌입한다. CPU는 각 명령어마다 디버그 예외를 발생시키고, 트랩 플래그를 자동적으로 클리어 한다.

디버그 대상 프로그램을 싱글스텝으로 동작시켜서, win32 디버그 API를 사용하는 방법은, 다음과 같다.

  1. ContextFlagsCONTEXT_CONTROL을 지정하고, GetThreadContext함수를 호출해서, 플래그 레지스트를 얻는다.
  2. CONTEXT구조체의 regFlag에 트랩 비트를 설정한다.
  3. SetThreadContext함수를 호출한다.
  4. 평소와 같이 디버그 이벤트를 기다린다. 디버그 대상 프로그램은 싱글스텝모드로 동작하게 되고, 각 명령마다 EXCEPTION_DEBUG_EVENT를 받게되고, 동시에 u.Exception.pExceptionRecord.ExceptionCode의 값이 EXCEPTION_SINGLE_STEP으로 되게 된다.
  5. 다음 명령을 트레이스 하고 싶다면, 트랩 비트를 다시 설정한다.

Example:

.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
            

Analysis:

이 프로그램을 실행하면, 파일열기 대화상자가 표시된다. 유저가 실행 파일을 선택하면, 그 프로그램이 싱글스텝모드로 동작하게되고 디버그 대상 프로그램이 종료할 때까지 명령어수를 카운트 한다.

.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

디버그 예외가 일어난 후에는 트랩 플래그가 클리어 되므로, 계속 싱글스텝모드로 실행하고 싶다면 다시 트랩 플래그를 설정 할 필요가 있다.

※  이 샘플을 큰 프로그램에 사용해서는 안 된다. 트레이스는 매우 늦게 처리되므로, 자칫하면 프로그램이 종료할 때까지 오랫동안 기다려야 할지도 모르기 때문이다.



Posted by openserver