如果你以前使用过调试器,那么你应对跟踪比较熟悉。当"跟踪"一个程序时,
程序在每执行一条指令后将会停止,这使你有机会去检查寄存器/内存中的值。这种单步运
行的官方定义为跟踪(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
多分钟才能关闭被调试程序。