软件断点
X86系列处理器从其第一代产品8086开始就提供了一条专门用来支持调试的指令,即INT3。简单的说,这条指令的目的就是使CPU中断到调试器,以供调试者对执行现场进行各种分析。调试程序时,我们可以在可能有问题的地方插入一条INT3指令,使CPU执行到这一点时停下来。这便是软件调试中经常用到的断点功能,因此INT3指令又称为断点指令。
早期的ARM架构中没有断点指令,使用未定义指令来替代,在./arch/arm/include/asm/kgdb.h文件中可以看到内核工程师对ARM硬件工程师留下的一条建议,ARMV5为了弥补这个不足引入了BKPT指令,ARMV8又引入了BRK指令。
ARMV5引入的断点指令叫做BKPT,在THUMB指令集和ARM指令集中都有,CPU执行该指令时会产生prefetch abort异常。
ARMV8引入的64位指令集中,新增了一条名为BRK的断点指令,在32位指令集中,则保留了BKPT指令。
在x86系统上,在连接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中的实现
SR bit0清除的目的是关闭中断:
ARM中的实现:
arm实现使用的是bkpt指令,关于bkpt 指令,arm ISA 文档花了两页进行介绍
实现:
RISCV的实现
在连接调试器和不连接调试器两种情况下,执行软件断点指令,CPU的行为:
这个问题我们问以下chatgpt:
蜂鸟E203 RTL中的逻辑,看起来连接调试器情况下,确实会影响硬件的执行逻辑从而作出不同的执行流程。
总结
因为使用INT3指令产生的断点是依靠插入指令和软件中断机制工作的,所以习惯上被称为软件断点,软件断点具有如下局限性:
1.属于代码类断点,即可以让CPU执行到代码段内的某个地址时停下来,不适用于数据段和IO空间,这里需要用到观察点。
2.对于只读存储器中执行的程序,无法添加软件断点,因为目标内存无法插入INT3指令,这种情况需要用到硬件断点。
3.在调试异常和硬件条调试器都没有安装的情况下,软件断点是不能正常工作的。
所以软件断点有一些不足,但是因为它使用方便,而且没有数量限制(硬件断点需要寄存器记录断点地址,有数量限制)。
软件断点指令的应用内核KPROBE机制:
Linux tracing之基于uprobe/kprobe的调试调优浅析_papaofdoudou的博客-CSDN博客
不同架构的软件调试断点指令,可以从arch_kgdb_breakpoint的实现找到线索,下图是ARM AARCH64的实现:
结束!