win-调试原理
X86的主要调试设施
INT 3 指令 软件断点基础
追踪标志TF 单步追踪的基础
调试寄存器 硬件寄存器的基础
分支监视和记录 按分支单步的基础
软件断点
0xCC int3指令
机器码为1字节,无数量限制,只支持代码段(可执行的段),且不支持在只读存储器中使用
将一个int 3 写入,触发异常break之后会被恢复,再执行后会再次写入
硬件断点
通过调试寄存器实现,DR0到DR3 的四个寄存器中存放目标地址
DR7有8组设置标志位,每组分别有2个2位标志,其中一个标志表示R/W,另一个标志表示长度
CPU每执行1步就进行一次匹配,如果是这个地址,模式正确,就会写入DR6的标志位,然后触发断点(产生1号异常),操作系统通过检查标志位知道哪一个命中了断点
由于每个线程的寄存器信息独立保存,所以每个线程都可以设4个地址
陷阱标志
标志寄存器efl(ag)的TF位
单步异常在efl的最低位。每当efl最后一位是1,系统会自动置0,然后触发单步异常
异常
前面几节已经差不多说明了,这里就不赘述了。不过在程序中,尤其是VS生成的代码,一般来说低级的异常往往会封装成Cxx异常。
中断向量表
1 | 0 除零异常 |
来源
CPU产生 执行指令检测到的错误,机器异常,调试异常等
程序产生,RaiseException win32api
C++ throw E
JTAG
硬件调试标准
用户态调试模型XP
断点命中时,CPU立即切换到内核执行一系列异常处理函数。其中KISystemService调用KiRaiseException()产生异常KiDebugService和KiTrap04~00做分发准备。KiDispatchException()是分发中枢,会通知用户态调试子系统Dbgk,然后触发DbgkForwardExcption(),如果有调试器执行DbgkpSendApiMessages()继续传递,到DbgkQueueMessage()生成调试事件并将产生的调试对象挂到内核调试对象队列中。调试器一直等待这个队列,有调试对象时就会取出并处理。
调试器的载入程序
一般情况下,会在目标进程创建一个线程执行int 3,但是系统在分发异常时会冻结所有线程
KiDisPatchException
用户态异常内含2轮异常分发:
第一轮:
如果没有用户态调试器,尝试分发给内核调试器
DbgkForwardException尝试交给用户态调试器
复制栈帧然后改程序指针到KeUserExceptionDispatcher到用户态
第二轮:
DbgkForwardException尝试交给用户态调试器
然后尝试交给服务进程,让服务进程做最后处理,一般直接杀死
如果还是不处理,直接调用ZwTerminateProcess在内核态杀死
产生硬件异常通过 IDT调用异常处理例程, 产生软件异常通过 API的层层调用产地异常信息。而异常又由于发生位置不同,分为内核异常和用户态异常,二者最后都会靠
kiDispathException
函数来进行异常分发;
当内核产生异常时,程序处理流程进入到KiDispatchException
函数,在该函数内备份当前线程 R3 的TrapFrame
(即栈帧的基址)。异常处理首先判断这是否是第一次异常,判断是否存在内核调试器,如果有内核调试器,则把当前的异常信息发送给内核调试器;如果没有内核调试器或者内核调试器没有处该异常 , 则进入步骤3,调用RtlDispatchException
。
内核异常进入RtlDispatchException
函 数, 如果RtlDispatchException
函数没有处理该异常,那么将再次尝试将异常发送到内核调试器,如果此时内核调试器仍然不存在或者没有处理该异常,那么此时系统会直接蓝屏;
如果是用户态异常则经过KiDispatchException
进行用户态异常分发和处理。如果是第一次分发异常,则调用DbgKForwardException
将异常分发到内核调试器;如果内核调试器不存在或没有处理异常,则尝试将异常分发给用户态调试器;如果异常被处理,则进入步骤10;如果用户态调试器不存在或未处理异常,则检测是否是第一次处理异常,如果是第一次处理异常则进入第5步中的异常数据准备;
准备一个返回ntdll!KiUserExceptionDispatcher
函数的应用层调用栈,结束本次KiDispatchException
函数的运行,调用KiServiceExit
返回用户层。此时函数栈帧是ntdll!KiUserExceptionDispatcher
的执行环境,用户态线程从执行 ntdll!KiUserExceptionDispatcher
开始执行。该函数调用 ntdll!RtlDispatchException进行异常的分发,进入第 6 步;
通过RtlCallVectoredExceptionHandlers
遍历 VEH链表尝试查找异常处理函数;如果 VEH未处理异常。则从 fs[0]读取 ExceptionList并开始执行 SEH 函数处理,进入步骤7;
如果SEH没有处理函数处理该异常,则检查用户是否通过SetUnhandledExceptionFilter
函数注册过进程的异常处理函数,如果用户注册过异常处理函数,调用该异常处理函数,如果异常没有被成功处理或没有自定义的异常处理函数,则进入步骤3;
如果最后仍没有处理该异常,便会主动调用NtRaiseException
将该异常重新跑出来,但是此时不是第一次分发,此时NtRaiseException
流程重新调用了 ntdll!KiDispatchException
,并再次进入用户态异常的处理分支,进入步骤9;
第二次进入用户态异常处理时,不会再尝试发送到内核调试器,也不会再进行异常分发,而是直接尝试发送到用户态体异常调试器,如果最后异常仍未被处理则进入步骤11;
异常被处理,调用 NtContine,将之前保存的 TrapFrame还原,程序继续从异常处正常运行;
异常不能被处理,系统调用 ntdll!KiDispatchException
调用 ZeTerminateProcess结束进程。
WinDbg常见指令
1 | bp addr 下软件断点 |
以Noninvasive模式可以只读的形式附加