Tutorial 30: Win32 Debug API part 3
第三十课:win32调试API第三部分
________________________________________
In this tutorial, we continue the exploration of win32 debug api. Specifically, we will learn how to trace the debuggee.
Download the example. 
在这一课中,我们继续探索win32调试api。特别的,我们将学习如何跟踪被调试进程。
Theory:
If you have used a debugger before, you would be familiar with tracing. When you "trace" a program, the program stops after executing each instruction, giving you the chance to examine the values of registers/memory. Single-stepping is the official name of tracing.
The single-step feature is provided by the CPU itself. The 8th bit of the flag register is called trap flag. If this flag(bit) is set, the CPU executes in single-step mode. The CPU will generate a debug exception after each instruction. After the debug exception is generated, the trap flag is cleared automatically.
如果你曾经使用过调试器,你将会更加熟悉跟踪。当你跟踪一个程序时,在执行了每一条指令后程序都会停止运行,以便给你一个机会,让你能分析寄存器和内存。单步执行官方命名为跟踪。单步执行的特征由CPU自己提供。标志寄存器的第8位叫做陷阱标志。如果该位被设置,CPU的运行就是单步执行模式。CPU将在每条指令执行后产生一个调试异常。在调试异常产生之后,陷阱标志被自动清除。
We can also single-step the debuggee, using win32 debug api. The steps are as follows:
使用win32调试API,我们同样能单步执行被调试进程,步骤如下:
1.  Call GetThreadContext, specifying CONTEXT_CONTROL in ContextFlags, to obtain the value of the flag register. 
调用GetThreadContext,在ContextFlags中指定CONTEXT_CONTROL标志,来获取标志寄存器的值。
2.  Set the trap bit in regFlag member of the CONTEXT structure 
在CONTEXT结构的regFlag成员中设置陷阱位。
3.  call SetThreadContext   
调用SetThreadContext
4.  Wait for the debug events as usual. The debuggee will execute in single-step mode. After it executes each instruction, we will get EXCEPTION_DEBUG_EVENT with EXCEPTION_SINGLE_STEP value in u.Exception.pExceptionRecord.ExceptionCode 
像通常一样等待调试事件。被调试进程将运行于单步执行模式。在它执行一条指令之后,u.Exception.pExceptionRecord.ExceptionCode中得到的值为EXCEPTION_SINGLE_STEP和EXCEPTION_DEBUG_EVENT
5.  If you need to trace the next instruction, you need to set the trap bit again. 
如果你需要单步执行下一条指令,你需要在一次的设置陷阱标志位。
Example:
例子: 
.386
.model flat,stdcall 
option casemap:none 
include \masm32\include\windows.inc 
include \masm32\include\kernel32.inc 
include \masm32\include\comdlg32.inc 
include \masm32\include\user32.inc 
includelib \masm32\lib\kernel32.lib 
includelib \masm32\lib\comdlg32.lib 
includelib \masm32\lib\user32.lib 

.data 
AppName db "Win32 Debug Example no.4",0 
ofn OPENFILENAME <> 
FilterString db "Executable Files",0,"*.exe",0 
             db "All Files",0,"*.*",0,0 
ExitProc db "The debuggee exits",0Dh,0Ah 
         db "Total Instructions executed : %lu",0 
TotalInstruction dd 0

.data? 
buffer db 512 dup(?) 
startinfo STARTUPINFO <> 
pi PROCESS_INFORMATION <> 
DBEvent DEBUG_EVENT <> 
context CONTEXT <> 

.code 
start: 
mov ofn.lStructSize,SIZEOF ofn 
mov ofn.lpstrFilter, OFFSET FilterString 
mov ofn.lpstrFile, OFFSET buffer 
mov ofn.nMaxFile,512 
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY 
invoke GetOpenFileName, ADDR ofn 
.if eax==TRUE 
    invoke GetStartupInfo,addr startinfo 
    invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi 
    .while TRUE 
       invoke WaitForDebugEvent, addr DBEvent, INFINITE 
       .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT 
          invoke wsprintf, addr buffer, addr ExitProc, TotalInstruction 
          invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION 
          .break 
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT           .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT 
             mov context.ContextFlags, CONTEXT_CONTROL 
             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 
          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP 
             inc TotalInstruction 
             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 
          .endif 
       .endif 
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED 
    .endw 
.endif 
invoke CloseHandle,pi.hProcess 
invoke CloseHandle,pi.hThread 
invoke ExitProcess, 0 
end start 
Analysis:
分析: 
The program shows the openfile dialog box. When the user chooses an executable file, it executes the program in single-step mode, couting the number of instructions executed until the debuggee exits. 
程序显示了一个打开文件对话框。当用户选择一个可执行文件后,它以单步执行模式来运行这个程序,并记录被执行的指令数,直到被调试进程退出。
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT           .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT 
We take this opportunity to set the debuggee into single-step mode. Remember that Windows sends an EXCEPTION_BREAKPOINT just before it executes the first instruction of the debuggee.
我们利用这个机会来设置被调试进程为单步跟踪模式。注意:windows仅在它执行被调试进程的第一条指令之前发送一EXCEPTION_BREAKPOINT消息。
             mov context.ContextFlags, CONTEXT_CONTROL 
             invoke GetThreadContext, pi.hThread, addr context 
We call GetThreadContext to fill the CONTEXT structure with the current values in the registers of the debuggee. More specifically, we need the current value of the flag register.
我们调用GetThreadContext函数,用被调试进程使用的当前寄存器的值来填充CONTEXT结构体。更准确的说,我们需要标志寄存器的当前值。
             or context.regFlag,100h 
We set the trap bit (8th bit) in the flag register image.
我们设置标志寄存器映像的陷阱标志(第八位)
             invoke SetThreadContext,pi.hThread, addr context 
             invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE 
             .continue 
Then we call SetThreadContext to overwrite the values in the CONTEXT structure with the new one(s) and call ContinueDebugEvent with DBG_CONTINUE flag to resume the debuggee.
然后我们调用SetThreadContext函数重写在CONTEXT的值并用DBG_CONTINUE标识调用ContinueDubugEvent函数来恢复被调试进程的执行。
          .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP 
             inc TotalInstruction 
When an instruction is executed in the debuggee, we receive an EXCEPTION_DEBUG_EVENT. We must examine the value of u.Exception.pExceptionRecord.ExceptionCode. If the value is EXCEPTION_SINGLE_STEP, then this debug event is generated because of the single-step mode. In this case, we can increment the variable TotalInstruction by one because we know that exactly one instruction was executed in the debuggee.
当被调试进程中的第一条指令被执行时,我们收到一EXCEPTION_DEBUG_EVENT调试事件,我们必须检查u.Exception.pExceptionRecord.ExceptionCode的值。如果这个值为EXCEPTION_SINGLE_STEP,那么这个调试事件是由于单步执行模式而产生的。在这种情况下,我们将TotalInstruction变量的值增一,因为我们知道在调试进程中确实有一条指令被执行。
             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 
Since the trap flag is cleared after the debug exception is generated, we must set the trap flag again if we want to continue in single-step mode.
Warning: Don't use the example in this tutorial with a large program: tracing is SLOW. You may have to wait for ten minutes before you can close the debuggee. 
在调试异常产生之后,因为陷阱标志被清除,此时,如果我们想继续以单步执行模式运行程序,那么我们必须再一次的设置陷阱标志。
警告:不要将此例用于一个大程序中,这样跟踪是缓慢的。你可能必须等待10分钟才能关闭被调试进程。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于2008-5-2 日 晨

上传的附件 tut30.zip