调试器实现的依赖
- CPU支持调试功能
- CPU中,有标志寄存器EFLAGS的IF,TF标志位用于开启调试功能,如果一个进程是以调试状态开启,且其线程环境(CONTEXT)中的EFLSGS的TF标志位是1,那么这个进程执行一条指令后,将会产生一个异常,异常被处理后,TF自动被重置为0
- CPU有DRx(DebugRegister)系列的寄存器可用于断点功能.
- DR0~DR3这四个寄存器是断点地址存储器,用于保存断点的地址
DR6是调试状态寄存器用于指明DR0~DR3寄存器中哪一个产生了调试异常
DR6寄存器使用比特位B0~B3来指明DR0~DR3中哪个产生了调试异常
DR7是断点属性控制器
DR7寄存器分别保存着DR0~DR3的断点地址对应的断点的属性,属性有如下几种:
- L0~L3:这个比特位等于1,则断点为本地断点.
G0~G3:这个比特位等于1,则断点为全局断点.
R/W0-R/W3:
00:执行断点
01:数据写入断点
10:I/0读写断点
11:读写断点()读取指令不算)
- L0~L3:这个比特位等于1,则断点为本地断点.
- DR0~DR3这四个寄存器是断点地址存储器,用于保存断点的地址
- 操作系统提供的功能
- Windows有一个调试子系统,所有的异常(包括CPU产生的异常)都会中断到调试子系统中,进程产生异常后,调试子系统会捕捉到这个异常,如果这个进程是以被调试状态创建,那么,调试子系统会将这个异常派发到产生异常的进程的父进程.如果其父进程的代码用有函数WaitForDebugEvent(),那么,函数将会从等待状态中被唤醒,返回到其父进程的调用地点.并将异常信息保存到DEBUG_EVENT结构体中.
调试器实现的原理
程序在运行起来后,其执行指令的速度是光速的.由于人类无法从光速中看清真相,程序出错也是光速的,常会在人无法预知的时候发生了无法预知的错误,让程序变慢,或者停下来是一种能够查明程序出错,或者程序运行机制的重要技能.
程序的运行,是通过CPU对指令流的处理,如果CPU能够听人指挥,一直在指令流的指定地点处执行,那么,程序的流程不会接着往下走,程序就自然而然的停下来了.
前面说的异常,其实就是CPU告诉操作系统,大佬,我看到了一条让我停下来的指令(0xcc,遇到了DRx寄存器中保存的地址,除0等),现在该怎么办.
操作系统遇到了这个情况,赶紧回想,自己没有发布这样的指令,于是就让它的马仔调试子系统去解决这个问题,调试子系统会查看进程是否被人调试了,一看,真是被调试了,于是找到了调试这个进程的人的电话(PrentProcessID),打了个电话给那个人.
如果那个人装了电话(WaitForDebugEvent),那么就能从这个电话中得到信息,调试子系统会跟那个人约定,你先去解决吧,我就在这等着,于是那个人就可以针对调试子系统给的信息做出对应的处理.最后就答复调试子系统有没有处理成功.
调试子系统就会拍拍屁股回去,让被调试的进程继续执行或是一直执行那条指令.
调试器在刚才说的作品中扮演的角色就是被调试进程的父进程.
下面就是调试器要完成的流程:
具体的代码实现
1. 以调试创建一个进程或附加到一个进程
/*创建调试线程*/
CreateProcess(pszFilePath,//可执行模块路径
NULL,//命令行
NULL,//安全描述符
NULL,//线程属性是否可继承
FALSE,//否从调用进程处继承了句柄
DEBUG_ONLY_THIS_PROCESS,//启动方式
NULL,//新进程的环境块
NULL,//新进程的当前工作路径(当前目录)
&stcStartupInfo,//指定进程的主窗口特性
&stcProcInfo//接收新进程的识别信息
);
2. 创建完进程后,需要安装个电话,静静的等待调试子系统找上门来
DEBUG_EVENT stcDeEvent={
0};//这是保存调试子系统发来的信息的结构体
WaitForDebugEvent(&stcDeEvent,//保存异常信息的结构体
INFINIT//等待时间
);
2.1 关于解读DEBUG_EVENT结构体的一些方法和注释
typedefstruct_DEBUG_EVENT{
DWORDdwDebugEventCode;//发生异常的是什么事<