DrX调试寄存器使用 一
Intel公司自80386以来,在CPU内部引入了Dr0-Dr7八个调试寄存器专门用于程序的调试工作,可以说这8个调试寄存器已经存在了很长时间,可是国内的汇编教科书中鲜有涉及者,导致国内很多朋友对此都不甚了解。我作为一个计算机底层技术的爱好者,从国外的一些网站上阅读了一些此方面的资料,并且写了一些演示用的程序,如今把它们整理出来,希望对那些想了解这方面的技术,却苦于找不到中文资料的朋友提供一些帮助。
如果你的英文过关,你可以参考如下网址获得更详细的资料:
http://www7.informatik.uni-erlangen.de/~msdoerfe/embedded/386html/toc.htm
http://www.anticracking.sk/EliCZ/
调试寄存器的作用就不再多说,相信知道调试是怎么回事情的朋友都能明白调试寄存器对于调试过程的重要性。
下面进入正题:
386体系的调试寄存器的示意图可以表示如下:
Dr0~Dr3用于存放欲设置断点的线性地址。
Dr4和Dr5保留
Dr6保存了调试状态
Dr7是调试控制寄存器
调试寄存器的使用总体来说分为如下几步:
1、 把欲监视的地址放入Dr0~Dr3中的一个寄存器
2、 在Dr7种设置相应的控制位,使得Dr0~Dr3存放的监视地址生效
3、 继续运行程序,接收EXCEPTION_SINGLE_STEP 调试消息,并在消息处理程序中作自己想做的事情。
以下的代码演示bpm最基础的用法,我假设你有Windows Debug API编程的基础知识以及利用32位汇编语言编程的能力。如果你这两方面都不太行,那网上关于这两方面的中文资料已经很多,可以自己参考。
我的编程环境是WinXP,凡是Nt架构的系统,在调试循环收到第一个EXCEPTION_BREAKPOINT调试消息的时候,程序都没有完全载入内存,所以不能对程序地址设断,此时的解决方案是先对Ntdll.dll的引出函数NtContinue设断,然后在第一个EXCEPTION_SINGLE_STEP产生时,再对需要设断的地址设断。
Note:DrX寄存器产生的断点是:EXCEPTION_SINGLE_STEP断点消息
整个程序的分支非常多,流程图如下:
如此多分支的流程图,用ASM实现的确容易出错,所以,一旦搭好一个调试的框架,日后如非必要,就务须修改。
另外,整个程序中使用的CONTEXT结构地址必须4字节对齐,否则得不到正确的结果。请大家记住一个规律:凡是需要和系统内核打交道的数据结构,一般都需要进行4字节对齐。C/C++中,编译器会自动帮你设置好对齐,而在ASM中,数据的内存布局都是由程序员决定的,所以,此处要特别小心。否则,最容易出现的结果就是:整个程序逻辑、编码都正确,可是就是出不来正确的结果。
理论到此为止,下面来看看DrX寄存器的第一个示例代码:
;filename: bpm1.asm
comment /*
演示bpm最基础的用法,在程序的首地址中断
1、CONTEXT结构的地址要4字节对齐
yoda的例子程序里面,CONTEXT结构是.DATA段第一个数据定义,连接器会自动对齐
;2、调试循环结构很复杂
*/
.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
include ../bpm/bpm.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/comdlg32.lib
includelib /masm32/lib/user32.lib
.data
szAppName db "firing's Bpm Example no.1",0
szExitStr db "Debuggee exit...",0
szFormat db "Break at address: %08X",0Dh,0Ah,0
szExeName db "Msg.exe",0
szNtDllName db "ntdll.dll",0
szProcName db "NtContinue",0
DbgState dd 0
TotalInstruction dd 0
dwSSCnt dd 0
dwAddrBuf dd 0
dwBpCnt dd 0
dwBreakAddr dd 401000h
szBuffer db 512 dup(?)
align dword
Regs CONTEXT <CONTEXT_FULL OR /
CONTEXT_DEBUG_REGISTERS> ;这个结构的地址要对齐的
sif STARTUPINFO < SIZEOF STARTUPINFO >
pi PROCESS_INFORMATION < >
DBEvent DEBUG_EVENT < >
WipeContextBPdr0 PROTO
;一些为了编码方便而设置的常量定义
DbgEvent EQU DBEvent.dwDebugEventCode
excCode EQU DBEvent.u.Exception.pExceptionRecord.ExceptionCode
excAddr EQU DBEvent.u.Exception.pExceptionRecord.ExceptionAddress
;pDllName EQU Dev.u.LoadDll.lpImageName
;lpBase EQU Dev.u.LoadDll.lpBaseOfDll
.code
start:
xor eax, eax
mov dwSSCnt, eax
mov dwBpCnt, eax
mov dwBreakAddr, 401000h
invoke GetStartupInfo,addr sif
invoke CreateProcess, addr szExeName, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, /
NULL, NULL, addr sif, addr pi
;以下进入debug循环
.while TRUE
invoke WaitForDebugEvent, addr DBEvent, INFINITE
mov DbgState, DBG_EXCEPTION_NOT_HANDLED
.if DbgEvent == EXCEPTION_DEBUG_EVENT
.if excCode==EXCEPTION_BREAKPOINT
mov DbgState,DBG_CONTINUE
inc dwBpCnt
.if dwBpCnt == 1
invoke GetThreadContext, pi.hThread, addr Regs
invoke GetModuleHandle, addr szNtDllName
invoke GetProcAddress, eax, addr szProcName
mov Regs.iDr7, M_INSTR0 + M_LDR0 ;执行指令中断+Dr0有效
mov Regs.iDr0, eax ;在NtContinue函数设断
invoke SetThreadContext, pi.hThread, addr Regs
jmp @f
.endif
.elseif excCode == EXCEPTION_SINGLE_STEP
inc dwSSCnt
.if dwSSCnt == 1 ;中断在NtContinue
invoke WipeContextBPdr0 ;清除断点
mov eax,Regs.regEsp
add eax,4 ;eax = "esp"+4
invoke ReadProcessMemory,pi.hProcess, eax, addr dwAddrBuf, sizeof DWORD, /
NULL
invoke ReadProcessMemory,pi.hProcess, dwAddrBuf, addr Regs, sizeof CONTEXT, /
NULL
push dwBreakAddr
pop Regs.iDr0
mov Regs.iDr7, M_LDR0+ M_INSTR0
invoke WriteProcessMemory,pi.hProcess,dwAddrBuf,addr Regs,sizeof CONTEXT, /
NULL
jmp @f
.elseif dwSSCnt == 2 ;中断在首地址
invoke WipeContextBPdr0
invoke wsprintf, addr szBuffer, addr szFormat, excAddr
invoke MessageBox, 0, addr szBuffer, addr szAppName, MB_OK+ /
MB_ICONINFORMATION
jmp @f
.endif
.endif
.elseif DbgEvent == EXIT_PROCESS_DEBUG_EVENT ;程序退出消息
invoke MessageBox, 0, addr szExitStr, addr szAppName, MB_OK+
MB_ICONINFORMATION
.break
.endif
@@:
invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
.endw
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
invoke ExitProcess, 0
;****************************************************************************** WipeContextBPdr0 Proc
invoke GetThreadContext,pi.hThread,addr Regs
mov Regs.iDr0,0
mov Regs.iDr7,0
invoke SetThreadContext,pi.hThread,addr Regs
ret
WipeContextBPdr0 Endp
;******************************************************************************
end start
程序运行结果截图如下: