在x86系统上,有一条调试指令"int3",在连接GDB的情况下,执行int3指令,被调试的程序便可以和GDB主动连接,之后就可以通过GDB命令观察被调程序的运行环境,如下所示:
代码:
#include <stdio.h>
int main(void)
{
printf("%s line %d.\n", __func__, __LINE__);
asm("int3");
printf("%s line %d.\n", __func__, __LINE__);
return 0;
}
Makefile
all:
gcc -g -O0 main.c -o main
调试情况:
如果不通过gdb运行被调程序,而是直接运行,则会由于无法连接调试器而coredump
之所以可以这样做,是因为Linux内核会检测当前陷入异常的指令是否是调试指令int3,如果是的话,会给被调试进程发送信号,在有GDB启动的情况下,这个调试信号会被发送给父进程,也就是GDB进程进行处理,所以可以和gdb成功建立连接。
但是在直接运行待调程序的情况下,内核产生的调试信号无法发送给调试器,而前者又不知道怎么处理,最后的结果就是被内核杀死。只留下coredump一声哀嚎。
那么,不禁要问,在Linux中,内核作为一个超然的独立,客观,第三方存在,可以检测调试条件,控制调试过程,如果对于RTOS裸机程序呢?所有的内容都编译,链接在一起,没有一个独立观察者存在,这个时候,可以达到这种执行到调试指令便立刻和调试器建立成功连接的效果吗?
答案是肯定的,如下是架构无关的实现流程,通过beak指令触发异常,然后再异常中再次返回break指令,再次触发异常,再次返回。。。。。,通过这样一个指令序列,可以保证CPU循环执行break调试请求调试指令,这样,调试器再任何时刻侵入调试,都可以使CPU进入调试状态:
这个流程和上面X86上的试验有所差异,再上面的实验情况中,内核如果发现待调试的用户进程没有parent父进程处理调试信号,会直接杀死被调试的进程。而不会返回用户程序int3指令处继续执行。这是一点差异(不过应该可以通过修改内核在这个时候让子进程返回到int3指令处,周而复始执行上述过程).
我们以MIPS和ARM 两个架构为例,来分析如何实现上述流程。
MIPS处理器架构方框图的简化版,Sequencer代表流水线Pipeline.
实现此功能需要用到MIPS的"SDBBP"指令,关于这条指令,MIPS官方ISA Spec有详细说明,整个过程可以这样理解,sdbbp之于裸机debugger,就相当于int3之于GDB,ICE仿真器就相当于Linux内核。
MIPS中的实现
ARM中的实现:
arm实现使用的是bkpt指令,关于bkpt 指令,arm ISA 文档花了两页进行介绍
实现:
结束!