如果在执行指令时,处理器检测到 EFLAGS 寄存器中的 TF 标志被设置,则会生成单步调试异常。该异常属于陷阱类异常,因为异常是在指令执行之后生成的。处理器不会在设置 TF 标志的指令之后立即生成此异常。例如,如果使用 POPF 指令设置 TF 标志,则单步陷阱不会发生,直到 POPF 指令后面的指令执行之后。
来跟踪一下windows的TF异常
- 查看1号向量的处理函数
- 在IDA中断到处理程序,可以看到处理器已经帮我们在栈中压入了参数
- 如果是3环的单步调试,windows切换了栈,rsp从KPCR.Prcb.RspBase 获得
并复制了CPU压入的值 - 进入KxDebugTrapOrFault函数,它清掉了栈上的TF标志,看调试器如何处理,如果没有人处理,那么执行IRET执行后,不会继续执行单步
- 可以看到EFLAGS寄存器从原来的0x306h变成了0x206H ,清除了[8]TF位
- 我们使用P命令,单步执行,按照逻辑,windbg应该要修改这个地址,并重新设置TF标志位.所以在windbg中使用写时中断指令, 希望在写这个地址的时候中断下来,看看是怎么处理的
- 这里可以看到,如果要继续执行使用单步调试, 除了重新设置上TF标志位时,,windows还设置了RF标志位.这是为什么呢?
RF标志位的作用
RF标志的主要功能是允许在指令断点条件导致的调试异常后重新启动指令。在这里,调试软件必须在堆栈上的EFLAGS映像中设置此标志,然后才能使用IRETD返回中断的程序(以防止指令断点导致另一个调试异常)。然后,在返回的指令成功执行后,处理器会自动清除此标志,再次启用指令断点故障。
另请参见:第18.3.1.1节,“指令断点异常条件”
-----来自因特尔白皮书 Vol. 3A 2-11
由于指令断点的调试异常在指令执行前触发,若异常处理程序未移除断点,处理器在重启指令时会再次检测到断点并触发异常。为避免死循环,Intel 架构通过 EFLAGS 寄存器中的 RF 标志(Resume Flag) 控制:当 RF=1
时,处理器忽略指令断点。
调试异常处理程序可通过设置栈中 EFLAGS 镜像的 RF 标志,避免指令断点重复触发。当通过 IRETD/IRETQ
或任务切换返回时,栈中的 RF
值会被复制到 EFLAGS 寄存器,使处理器忽略下一条指令的断点。
这里强调的是 instruction-breakpoint (指令断点,在英特尔白皮书中 instruction-breakpoint 指的是使用DR寄存器的情况)
所以邓志的书中有误导,将RF和TF标志位一起介绍,这是错误的,RF标志位只是为了防止指令断点的重入,因为他是fault类型的异常,可以理解为:指令执行前,CPU的检查到有指令断点.

如果没有设置RF标志位,CPU执行前检查,发现符合指令断点条件,那么将导致循环触发指令断点


RF 标志的管理规则:
清除时机:在指令开始执行时(检查指令断点、代码段限制违规和浮点异常后),RF 被清除。
上下文切换:任务切换和
IRETD/IRETQ
指令会将 RF 从 TSS/栈复制到 EFLAGS 寄存器。异常处理:
非指令断点的故障类异常:压入栈的 EFLAGS 镜像中
RF=1
。指令断点调试异常:压入栈的 EFLAGS 镜像中
RF
保持原值。中断或陷阱类异常:若发生在重复字符串指令的非最后一次迭代中,
RF=1
。
asm
复制
mov ecx, 3 ; 循环次数 ECX=3 lea esi, [src] ; 源地址 lea edi, [dest] ; 目标地址 rep movsb ; 重复复制字节:执行 3 次 MOVSB
场景 1:陷阱类异常发生在非最后一次迭代
迭代过程:
第 1 次迭代(ECX=3 → ECX=2):正常执行
MOVSB
。第 2 次迭代(ECX=2 → ECX=1):执行
MOVSB
时触发 陷阱类异常(如除零异常)。
处理器行为:
压入栈的 EFLAGS 镜像中
RF=1
(因异常发生在非最后一次迭代)。异常处理程序返回后,处理器继续执行 第 3 次迭代(ECX=1 → ECX=0)。
由于
RF=1
,第 3 次迭代的MOVSB
不会触发指令断点(即使存在断点)。
场景 2:陷阱类异常发生在最后一次迭代
迭代过程:
第 3 次迭代(ECX=1 → ECX=0):执行
MOVSB
时触发陷阱类异常。
处理器行为:
压入栈的 EFLAGS 镜像中
RF=0
(因异常发生在最后一次迭代)。异常处理程序返回后,若存在指令断点,仍会触发调试异常。