使用 INT 3 (0xCC) 软件断点时,即使已修补指令,调试器如何保证正确性?

Posted

技术标签:

【中文标题】使用 INT 3 (0xCC) 软件断点时,即使已修补指令,调试器如何保证正确性?【英文标题】:How do debuggers guarantee correctness when using INT 3 (0xCC) software breakpoint even though an instruction was patched? 【发布时间】:2010-09-19 23:09:59 【问题描述】:

我了解到 INT 3 (0xCC) 用于软件断点。

它由(例如)调试器通过覆盖内存中的实际程序代码来设置。

我还读到 INT 3 是“陷阱”而不是“故障”异常,这意味着压入堆栈的地址是 INT3 指令之后的指令地址。

如果修补后的指令没有重新执行,调试器如何保证正确性?

【问题讨论】:

有人可以引用GDB源代码吗? :-) 【参考方案1】:

当您想在断点触发后继续执行时,您有两种可能性:断点应该只触发一次,或者它应该是持久的。如果它只应该触发一次,则恢复用断点指令覆盖的原始值,手动将地址调整为该指令的地址(请记住,无论那里有什么指令,执行是你的单字节断点,因此调整总是微不足道的)。然后继续执行。

如果它应该是一个持久断点,还有一个额外的问题:在继续执行之前,在堆栈上的标志中设置单步(也称为陷阱)位。这意味着只有设置断点的一条指令将执行,然后您将再次获得断点中断。您可以通过将刚刚修补的 int 3 字节恢复到原始指令的第一个字节来对此做出响应,然后(再次)继续执行。

【讨论】:

在GDB internals wiki上也有提到:“当用户说要继续时,GDB会恢复原来的指令,单步执行,重新插入trap,继续。”【参考方案2】:

我已经有一段时间没有研究这类东西了,但假设你是正确的,以下地址被压入堆栈,调试器可以弹出返回地址并使用它来确定断点在哪里(返回地址减一,因为 INT 3 指令是一个字节长)[已编辑]。

换句话说,调试器不一定需要返回堆栈上的地址。它可以恢复原来的指令,然后在原来的位置执行。如果断点保持设置,它可以使用标志中的“陷阱位”仅执行一条指令 - 被覆盖的原始指令 - 在生成另一个陷阱之前(我认为再次为 INT 3);然后可以重新建立 INT 3 指令,然后才能继续正确执行。

不过,大多数情况下,调试器都在一个不直接处理陷阱的系统下运行。例如,他们可能会收到一个信号,告诉他们陷阱发生在哪里。他们很可能仍然需要从陷阱地址中找出“真实”地址(即 INT 3 指令的地址),因为操作系统无法做到这一点。

如果涉及多个线程,事情也会变得复杂;在这种情况下,“就地”恢复原始指令可能会导致断点被另一个线程命中时丢失。一种解决方案可能是在恢复指令之前停止所有其他线程(然后再次启动它们)。

【讨论】:

单步是 INT 1 IIRC【参考方案3】:

通常的解决方案是让调试器修改堆栈上的地址(并恢复被陷阱覆盖的指令),因此它确实执行修补后的指令。

【讨论】:

以上是关于使用 INT 3 (0xCC) 软件断点时,即使已修补指令,调试器如何保证正确性?的主要内容,如果未能解决你的问题,请参考以下文章

断点之软件断点的一些基本知识(INT3)

int3断点指令的原理和示例

常用断点设置

为什么有时候程序出问题会打印出“烫烫烫烫...

如何强制 NASM 将 int3 编码为 0xCC

VC++设置软件断点和“XXX已停止工作“对话框