如果你以前使用过调试器,那么你应对跟踪比较熟悉。当"跟踪"一个程序时,
程序在每执行一条指令后将会停止,这使你有机会去检查寄存器/内存中的值。这种单步运
行的官方定义为跟踪(tracing)。
单步运行的特色是由CPU本身提供的。标志寄存器的第8位称为陷阱标志trap flag。
如果该位设置,则CPU运行于单步模式。CPU将在每条指令后产生一个debug异常。当debug
异常产生后,陷阱标志自动清除。利用win32调试api,我们也可以单步运行被调试程序。
方法如下:
.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
该程序先显示一个打开文件对话框,当用户选择了一个可执行文件,它将单步执行该程序, 并记录执行的指令数,直到被调试程序退出运行。
.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
然后调用SetThreadContext去覆盖CONTEXT的值。再以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加一,因为我们确切地知道此时被调试程序执行了一条指令。
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
由于陷阱标志在debug异常后自动清除了,如果我们需要继续保持单步运行模式,则
必须设置陷阱标志位。
警告: 不要用本教程中的此例子来调试大程序: 跟踪是很慢的。你或许需要等待10
多分钟才能关闭被调试程序。