是的,x86 上有一个架构单步标志。从内核返回到用户空间使内核有机会同时设置 RIP/RFLAGS,因此它可以为用户空间设置单步,而无需在内核指令上触发。
由于某种原因,陷阱旗 https://en.wikipedia.org/wiki/Trap_flag有自己的维基百科文章!也可以看看维基百科的 EFLAGS 文章 https://en.wikipedia.org/wiki/FLAGS_register.
See the x86 /questions/tagged/x86tag wiki 以获取英特尔架构手册的链接,该手册记录了所有这些内容。
也许您可以执行后跟“ud2”操作码的指令来触发信号
然后,您需要代码来确定 x86 指令长度,以了解在哪里设置软件断点。而你不会使用ud2
,你会用int3为此目的而存在。 https://www.felixcloutier.com/x86/intn:into:int3:int1
x86 还具有调试寄存器 (dr0..7),可以在不修改代码的情况下设置硬件断点,或者可以监视对给定数据地址的访问或写入。 (GDBhbreak
使用这些,就像常量地址上的 GDB 观察点一样)
但对于跳转/调用/ret 和其他可能对 RIP 有特殊影响的指令,您需要解码和模拟以找出放置指令的目的地int3
在目的地。使用类似寻址模式的内存间接跳转jmp qword [fs: rax]
需要调试器知道 FS 段基址,甚至知道将从哪个地址加载指针。 (我假设您可以像实际寄存器值一样轻松地使用 ptrace 获取此值,这与来宾程序本身内部不同rdfsbase
是一个新的扩展。)因此,只要您的调试器已停止所有其他线程,就可以避免与另一个线程在读取跳转目标指针和继续执行之间修改跳转目标指针的 TOCTOU 竞争条件。
Fun fact:并非所有 ISA 都有硬件支持PTRACE_SINGLESTEP
.
举个例子,Linux 内核曾经为 ARM 模拟它,但这需要内核中的 ARM 反汇编程序在下一条指令处放置断点,即使是分支目标也是如此。它于 2011 年被删除;现在ptrace(PTRACE_SINGLESTEP)
回报-ENOSYS
on ARM.
他们只是去掉了所有的复杂性,而不是试图使其成为 SMP 安全并支持每个新指令,如 Thumb-2 等。 (http://lists.infradead.org/pipermail/linux-arm-kernel/2011-February/041324.html http://lists.infradead.org/pipermail/linux-arm-kernel/2011-February/041324.html)
因此调试器必须在此类 ISA 上手动使用断点,而不是让内核为它们执行此操作。如果这意味着其他线程暂时注意到内存中的调试中断操作码,那么这不是内核的问题。 (通常是像 GDB 这样的调试器do单步执行时停止所有线程。)
这意味着调试器必须解码分支指令才能找出断点的位置。包括寄存器间接和/或谓词分支。