通过 PTRACE_SINGLESTEP 实现单步调试
单步调试原理
单步调试可以让程序运行一条指令 / 语句后就停下。GDB 中常用的命令有 next, step, nexti, stepi。单步跟踪又常分为语句单步 (next, step) 和指令单步 (如 nexti, stepi)。
在 Linux 上,指令单步可以通过 ptrace 来实现。通过系统调用 ptrace (PTRACE_SINGLESTEP,pid,…) 可以使被调试的进程在每执行完一条指令后就触发一个 SIGTRAP 信号,让 GDB 运行。
测试用例
下面来看一个例子:
1 |
|
运行结果:
1 | lhx@ubuntu:~/test/test_ptrace/test_singlestep$ ./test-singlestep |
这段程序比较简单,子进程调用 execve 执行 HelloWorld, 而父进程则先调用 ptrace (PTRACE_ATTACH,pid,…) 建立与子进程的跟踪关系。然后调用 ptrace (PTRACE_SINGLESTEP, pid, …) 让子进程一步一停,以统计子进程一共执行了多少条指令 (你会发现一个简单的 HelloWorld 实际上也执行了好几万条指令才完成)。当然你也完全可以在这个时候查看 EIP 寄存器中存放的指令,或者某个变量的值,当然前提是你得知道这个变量在子进程内存镜像中的位置。
指令单步可以依靠硬件完成,如 x86 架构处理器支持单步模式 (通过设置 EFLAGS 寄存器的 TF 标志实现),每执行一条指令,就会产生一次异常 (在 Intel 80386 以上的处理器上还提供了 DRx 调试寄存器以用于软件调试)。也可以通过软件完成,即在每条指令后面都插入一条断点指令,这样每执行一条指令都会产生一次软中断。
语句单步基于指令单步实现,即 GDB 算好每条语句所对应的指令,从什么地方开始到什么地方结束。然后在结束的地方插入断点,或者指令单步一步一步的走到结束点,再进行处理。