debug break point
断点
硬断点
Debug Register
硬件断点是通过位于 CPU 上的一组特殊寄存器来实现的,称为调试寄存器。比如 x86 架构的 CPU 上有 8 个调试寄存器(DR0-DR7),分别用于设置和管理硬件断点。
- DR0-DR3 负责存储硬件断点的Linear Address。所以最多只能同时使用 4 个硬件断点。
- DR4 和 DR5 保留使用。
- DR6 为调试状态寄存器,保存调试异常产生后显示的一些信息
- DR7 是硬件断点的激活开关,存储着各个断点的触发信息条件。 与软断点不同的是,硬件断点使用 1 号中断(INT1)实现,INT1 一般被用于硬件断点和单步事件。
1 | typedef struct _DBG_REG7 |
保存DR0-DR3地址所指向位置的断点类型(RW0-RW3)与断点长度(LEN0-LEN3),状态描述如下:
- RW: 00:执行 01:写入 11:读写
- LEN: 00:1字节 01:2字节 11:4字节
设置硬件执行断点时,长度只能为1(LEN0-LEN3设置为0时表示长度为1)
设置读写断点时,如果长度为1,地址不需要对齐,如果长度为2,则地址必须是2的整数倍,如果长度为4,则地址必须是4的整数倍。
如果我们想开启一个硬断点
DBG_REG7
- L0 = 0b1
- LE = 0b1 在局部exception触发断点
- RW0 = 0b11
- LEN0 = 0b11
可以做一下CTF题目:SCTF 2023 Kernel Pwn Sycrop
软断点
需要中断指令 INT3。
当我们在调试器中对代码的某一行设置断点时,调试器会先把这里的本来指令的第一个字节保存起来,然后写入一条 INT 3 指令。因为 INT 3 指令的机器码为 11001100b(0xCC),仅有一个字节,所以设置和取消断点时也只需要保存和恢复一个字节,这是设计这条指令时须考虑好的。
当 CPU 执行到 INT 3 指令时,由于 INT 3 指令的设计目的就是中断到调试器,因此,CPU 执行这条指令的过程也就是产生断点异常(breakpoint exception,简称#BP)并会保存当前的执行上下文,转去执行异常处理例程的过程。
在调试器下,我们是看不到动态替换到程序中的 INT 3 指令的。大多数调试器的做法是在被调试程序中断到调试器时,会先将所有断点位置被替换为INT 3 的指令恢复成原来的指令,然后再把控制权交给用户。
当用户结束分析希望恢复被调试程序执行时,调试器通过调试 API 通知调试子系统,这会导致系统内核的异常分发函数返回到异常处理例程,然后异常处理例程通过IRET/IRETD 指令触发一个异常返回动作,使 CPU 恢复执行上下文,从发生异常的位置继续执行。
gdb
x86处理器引入的PSW寄存器,有一个陷阱标志位,名为Trap Flag,简称TF。
当TF为1时,CPU每执行一条指令便会产生一个调试异常,中断到调试异常处理程序。
调试器的单步执行功能大多是依靠这一机制来实现的。