PTRACE_SINGLESTEP是如何实现的?

How is PTRACE_SINGLESTEP implemented?

据我所知(我可能是错的),没有办法在 x86-64 系统上只执行 one 指令。也许相反,您可以执行指令后跟 'ud2' 操作码来触发信号——但是您必须担心指令修改控制流并转到其他地方。

但是,如果我理解正确的话,ptrace() 系统调用有一个 SINGLESTEP 选项,它将只执行一条指令。这是如何实施的?我无法想象内核有某种反汇编程序来识别指令和原因。那么,它是否使用了某种我不知道的架构特性?还是完全不同的东西?

是的,x86 上有架构单步标志。从内核返回到 user-space 使内核有机会同时设置两个 RIP/RFLAGS,因此它可以为 user-space 设置单步而不触发它一条内核指令。

由于某种原因,Trap Flag has its own wikipedia article! See also wikipedia's EFLAGS article

请参阅 标签 wiki,获取记录所有这些内容的英特尔架构手册的链接。


Perhaps instead you could execute the instruction followed by the 'ud2' opcode to trigger a signal

然后您需要代码来确定解码 x86 指令的长度。你不会使用 ud2,你会使用 int3 which exists for this purpose.

IIRC,也有debug寄存器,不用修改代码就可以设置硬件断点


有趣的事实:并非所有 ISA 都支持 PTRACE_SINGLESTEP

例如,Linux 内核曾经为 ARM 模拟它,但这需要内核中的 ARM 反汇编器在下一条指令处放置断点,即使是分支目标。它于 ~2011 年被删除;现在 ptrace(PTRACE_SINGLESTEP) returns -ENOSYS 在 ARM 上。

他们只是去掉了所有的复杂性,而不是试图使其成为 SMP 安全的并支持每条新指令,如 Thumb-2 等等。 (http://lists.infradead.org/pipermail/linux-arm-kernel/2011-February/041324.html)

因此调试器必须在此类 ISA 上手动使用断点,而不是让内核为他们做断点。如果这意味着其他线程暂时注意到内存中的调试中断操作码,那不是内核的问题。