IA-32 CPU的调试支持

断点和单步执行是debugger最核心功能,这里介绍IA-32 CPU如何支持断点和单步执行的功能。

回顾一下基本概念:

  1. 中断和异常最根本的差别是:异常来自于CPU本身,是CPU主动产生的。而中断来自于外部设备,是中断源发起的,CPU是被动的。

  2. IA-32 CPU将异常分为3类:错误(Fault)、陷阱(Trap)和中止(Abort)。

断点的基本功能就是CPU执行到断点指令时,可以中断到debugger,查看保存的程序上下文等信息,在调试结束后,又可以回到程序继续运行。这个功能最好的实现方式就是使用操作系统的中断和异常处理机制,当程序执行到断点指令时产生异常,异常处理将异常分发到debugger并等待返回。其实这就是debugger断点功能的最基本原理。

IA-32 CPU提供了一条专门用来支持调试的指令:INT 3。

CPU执行INT 3指令时,产生#BP异常,并转去执行异常处理例程。在跳转到处理例程之前,CPU会保存当前执行上下文,包括段寄存器、程序指针寄存器等内容。INT 3异常的处理函数是内核函数KiTrap03。因此执行INT 3导致CPU执行nt!KiTrap03例程。CPU从用户模式转入内核模式,经过几个内核函数的分发和处理,内核例程会把这个#BP异常通过调试子系统以调试事件的形式分发给用户模式debugger,并等待debugger回复。当收到回复后,调试子系统函数会层层返回,最后返回到异常处理例程,然后异常处理例程通过IRET/IRETD指令触发一个异常返回动作,使CPU恢复执行上下文,从发生异常位置继续执行。

可以看出,debugger断点功能使用了中断描述符表IDT来查找对应异常处理函数,所以一种反调试技术就是修改IDT,使所有INT 3断点失效。

值得注意的是,debugger在设置断点时,会将指令的首字节保存,然后替换成0xCC(INT 3)并执行。而此时eip指向的是下一条指令的地址。为了保持程序正常执行被替换的那一条指令,KiTrap03异常处理例程中进行了特殊处理,从debugger返回时eip指向的是INT 3恢复后的指令。

IA-32 CPU的调试支持_第1张图片

调试器的断点功能图示

除了断点,还有一类常用的方法使CPU中断到调试器,这便是调试陷阱标志。可把陷阱标志想像成一面“令旗”,当有陷阱标志置起时,CPU一旦检测到符合陷阱条件的事件发生,就会报告调试异常通知调试器。IA-32处理器所支持的调试陷阱标志共有3种:

  1. 单步执行标志(标志寄存器EFLAGS的TF位)

  2. 任务状态陷阱标志(任务状态段TSS的T标志)

  3. 分支到分支单步执行标志(DebugCt1寄存器中的BTF标志)

CPU在即将执行完一条指令时会检测TF位,如果该位是1,那么CPU就会先清除此位,并产生调试异常(#DB),中断到异常处理程序,调试异常的向量号是1。调试器的单步执行功能大多依靠这一机制来实现的。

高级语言的一条语句可能包含多条指令,这里以15条指令为例,其单步调试实现过程是:用TF标志一步步地走过每条汇编指令,这种方法意味着会产生15次调试异常,CPU中断到调试器15次,不过中间的14次都是简单的重置起TF标志,便恢复调试程序执行,不中断给用户。


你可能感兴趣的:(IA-32 CPU的调试支持)