这是我在15PB做的一个项目
简易调试器的实现
调试器实现的依赖.
1.CPU支持调试功能
1.1CPU中,有标志寄存器EFLAGS的IF,TF标志位用于开启调试功能,如果一个进程是以调试状态开启,且其线程环境(CONTEXT)中的EFLSGS的TF标志位是1,那么这个进程执行一条指令后,将会产生一个异常,异常被处理后,TF自动被重置为0
1.2CPU有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:读写断点()读取指令不算)
2.操作系统提供的功能
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_EVENTstcDeEvent={0};//这是保存调试子系统发来的信息的结构体
WaitForDebugEvent(&stcDeEvent,//保存异常信息的结构体
INFINIT//等待时间
);
2.1关于解读DEBUG_EVENT结构体的一些方法和注释
typedefstruct_DEBUG_EVENT{
DWORDdwDebugEventCode;//发生异常的是什么事
DWORDdwProcessId;//触发异常的进程ID(如果被调试进程有多个进程,这个ID有可能是其子进程的)
DWORDdwThreadId;//触发异常的线程ID(如果被调试进程有多个线程,这个ID有可能是其中的一个线程的
union{
EXCEPTION_DEBUG_INFOException;//异常类型信息
CREATE_THREAD_DEBUG_INFOCreateThread;//创建线程时得到的信息结构体(有可能会创建多个线程)
CREATE_PROCESS_DEBUG_INFOCreateProcessInfo;//创建进程时得到的信息结构体,有可能会得到多个
EXIT_THREAD_DEBUG_INFOExitThread;//线程退出的信息结构体
EXIT_PROCESS_DEBUG_INFOExitProcess;//进程退出的信息结构体
LOAD_DLL_DEBUG_INFOLoadDll;//加载模块的信息结构体
UNLOAD_DLL_DEBUG_INFOUnloadDll;//卸载模块的信息结构体
OUTPUT_DEBUG_STRING_INFODebugString;//输出调试字串的信息结构体
RIP_INFORipInfo;//系统调试错误时的信息结构体
}u;//这是一个联合体,dwDebugEventCode决定联合体中哪个字段是有用的.
}DEBUG_EVENT,*LPDEBUG_EVENT;
CREATE_PROCESS_DEBUG_EVENT创建进程之后发送此类调试事件,这是调试器收到的第一个调试事件。
CREATE_THREAD_DEBUG_EVENT创建一个线程之后发送此类调试事件。
EXCEPTION_DEBUG_EVENT发生异常时发送此类调试事件。
EXIT_PROCESS_DEBUG_EVENT进程结束后发送此类调试事件。
EXIT_THREAD_DEBUG_EVENT一个线程结束后发送此类调试事件。
LOAD_DLL_DEBUG_EVENT装载一个DLL模块之后发送此类调试事件。
OUTPUT_DEBUG_STRING_EVENT被调试进程调用OutputDebugString之类的函数时发送此类调试事件。
RIP_EVENT发生系统调试错误时发送此类调试事件。
UNLOAD_DLL_DEBUG_EVENT卸载一个DLL模块之后发送此类调试事件。
if(stcDeEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT)
{
//做点事情
}
ContinueDebugEvent(
stcDeEvent.dwProcessId,//指明是哪个被调试进程发的消息
stcDeEvent.dwThreadId,//指明是哪个被调试进程下的线程发的消息
DBG_CONTINUE
);
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!