EJTAG 调试原理
MIPS 原始设计
MIPS EJTAG 的调试根本思路是,让调试器硬件接管处理器的所有内存读写(指令、数据由调试器提供,而处理器写入的数据由调试器读出),以此控制处理器的执行。以下简略介绍 MIPS EJTAG 调试的流程:
- 写 TAP 的 Control 寄存器中的
EjtagBrk
位,使处理器陷入调试例外; - 处理器会保存当前的指令计数器,然后跳转到 dmseg (Debug Memory Segment,不是 dmesg)执行(32 位:
0xff20_0000 ~ 0xff2f_ffff
;64 位:0xffffffff_ff200000 ~ 0xffffffff_ff2fffff
); - 处理器在 dmseg 中的任何读写操作都将使处理器流水线停止,此时 TAP 的 Control 寄存器中的
PRnW
位表示处理器此次内存访问是读(0)还是写(1),Address 寄存器等于处理器正在读写的字的地址,Data 寄存器等于处理器将写的数值(或者作为处理器将读的数值的接收信箱,等待被调试器改写),并等待调试器向 Control 寄存器中的PrAcc
位写 0 ;检测到写 0 之后,处理器会认为此次内存访问完成(如果是写,则无事发生;如果是读,则将 Data 寄存器中的数值当作读入的数值)。
需要说明的是,CPU 在 dmseg 中读写指令或者数据时,都会需要调试器确认这次内存访问已经完成。CPU 被调试器打断后进入调试模式,此时拥有和内核特权相等同的处理器资源(标准有说)。
CPU 进入调试模式之后,调试器可以向 Data 寄存器写指令,然后向 Control 寄存器的 PrAcc
写 0,这样处理器就会执行我们传入的这个指令,然后将自己的 PC 递增 1 。由这样的方式来实现对处理器的外部控制。
LoongArch 的异同
LoongArch 上的调试方法和 MIPS 大体相同。根据目前实验发现,不同的点主要在于:
- TAP 的 IR 指令与 MIPS 标准不同(见TAP 寄存器)
- dmseg 的起始偏移地址不同(LoongArch 64 位机上,dmseg 的范围是
0xdb000000_00000000 ~ 0xdb000000_000fffff
;由于手头没有使用 LoongArch 32 位机,没有测试 32 位机的情况) - LoongArch 处理器在 dmseg 取指执行后,PC 递增 4 (这才像话嘛)。
此处给出一个实验,可以手动打断处理器执行然后恢复,此处使用了 2024 年 14 周出厂的 LS2K0300 芯片:
- 用 JTAG 调试探头(任何的都可以,只要你能手动向 IR 和 DR 读写数值)连接正在运行的 LoongArch 64 位目标。
- 向 IR 写入 5;然后,向 DR 写入 0x0004d000(向 Control 寄存器写入 0x0004d000,目的是将
EjtagBrk
位置位)。这将使处理器陷入调试例外。此时目标系统应该会暂停运行。 - 向 IR 写入 3;然后,从 DR 读取 64 位数值(从 Address 寄存器读入 64 位数值)。你应当读出 0xdb00000000000000 。
- 向 IR 写入 4;然后,向 DR 写入 0x0000000006483800(向 Data 寄存器写入一条指令:
ERTN
。根据《龙芯架构参考手册卷一》,此指令会使陷入调试例外的 CPU 从之前的位置重新开始执行)。 - 向 IR 写入 5;然后,向 DR 写入 0x0000c000(向 Control 寄存器写入 0x0000c000,目的是将
PrAcc
位置 0)。这将使处理器执行ERTN
指令。 - 如果目标系统没有重新开始运行,请继续下面的步骤。
- 向 IR 写入 3;然后,从 DR 读取 64 位数值(从 Address 寄存器读入 64 位数值)。你应当读出 0xdb00000000000004 。处理器的 PC 递增了 4 。
- 如果你走到了这里,说明你的处理器从调试例外返回时似乎还会从 dmseg 再次取指令。在 LS2K0300 上观察到了这个现象,因为不能保证其他处理器没有这个性质,所以保留了这一段。(注:MIPS 上的从调试例外返回的指令
DERET
*并不存在延迟槽;可能是龙芯制造的芯片 bug,使得 CPU 从 dmseg 执行返回到正常执行状态时会多从 dmseg 取指。又或许是其他问题呢?总之是个小坑*) - 向 IR 写入 5;然后,向 DR 写入 0x0000c000(向 Control 寄存器写入 0x0000c000,目的是将
PrAcc
位置 0)。这将完成 CPU 的这次额外的“取指”操作。 - 此时目标 CPU 应该已经恢复运行了。