调试寄存器
- DR0~DR3:调试地址寄存器,用于设置硬件断点
- DR4~DR5:保留,未公开具体作用
- DR6:调试寄存器组状态寄存器
- DR7:调试寄存器组控制寄存器
硬件断点的原理是使用DR0~DR3设置地址,使用DR7设置状态,因此最多设置4个。
<1> L0/G0 ~ L3/G3: L0/G0对应DR0,L1/G1对应DR1,以此类推…,用于控制Dr0~Dr3是否有效,Lx是局部的仅影响当前线程,Gx全局的影响当前进程中所有线程。
每次异常后,Lx都被清零,Gx不清零。
<2> 断点长度(LENx):x的下标为几就对应那个控制寄存器,00(1字节) 01(2字节) 11(4字节)
<3> 断点类型(R/Wx):x的下标为几就对应那个控制寄存器,00(执行断点) 01(写入断点) 11(访问断点)
被调试进程
- CPU执行时检测到调试寄存器(Dr0~Dr3)
- 查IDT表找到对应的断处理函数nt!KiTrap01(1号门)
- CommonDispatchException
- KiDispatchException।
- DbgkForwardException收集并发送调试事件
DbgkpSendApillessage (x, x)
1)第一个参数:消息结构每种消息都有自己的消息结构共有7种类型
2)第二个参数,要不要把本进程内除了自己之外的其他线程挂起.有些消息需要把其他线程挂起,比如异常
这个过程到第3步时,所有断点都一样执行流程都一样。
处理硬件断点:
- 硬件调试断点产生的异常是 STATUS_SINGLE_STEP(单步异常)
- B0~B3:哪个调试地址寄存器触发的异常
设置硬件断点要设置到所属线程的上下文。
单步异常
不单只有硬件断点能触发单步异常,EFLAGS寄存器也可以
设置单步运行:
TF位置1
处理单步异常:
单步产生的异常是 STATUS_SINGLE_STEP(单步异常)
TF位置1的时候每走完一步就会触发单步异常。
单步步入:
遇到CALL指令会跟进去
单步步过:
遇到CALL不会跟进去,实现原理是计算当指令长度,当前指令长度+当前eip的地方下断(也就是call指令的下一行)。
如果写代码修改返回地址,单步步入没事,单步步过就会跑飞。
下面代码只有单步步入
//被调试进程句柄
HANDLE hDebuggeeProcess = NULL;
//被调试线程句柄
HANDLE hDebuggeeThread;
//系统断点
BOOL bIsSystemInt3 = TRUE;
//被INT 3覆盖的数据
char OriginalCode = 0;
//线程上下文
CONTEXT Context = {
0 };
BOOL WaitForUserCommand()
{
BOOL bRet = FALSE;
char cContext = 0;
printf("COMMAND> ");
scanf("%c",&cContext);
switch (cContext)
{
case 't':
bRet = TRUE;
//1.获取线程上下文
Context.ContextFlags = CONTEXT_FULL || CONTEXT_DEBUG_REGISTERS;
GetThreadContext(hDebuggeeThread, &Context);
//2.设置单步执行
Context.EFlags |= 0x100;
//3.设置线程上下文
SetThreadContext(hDebuggeeThread, &Context);
break;
case 'p':