Rigo的胡言乱语录

Rigo's Stupid Blogs Archives

View on GitHub

EJTAG 调试原理

MIPS 原始设计

MIPS EJTAG 的调试根本思路是,让调试器硬件接管处理器的所有内存读写(指令、数据由调试器提供,而处理器写入的数据由调试器读出),以此控制处理器的执行。以下简略介绍 MIPS EJTAG 调试的流程:

  1. 写 TAP 的 Control 寄存器中的 EjtagBrk 位,使处理器陷入调试例外;
  2. 处理器会保存当前的指令计数器,然后跳转到 dmseg (Debug Memory Segment,不是 dmesg)执行(32 位:0xff20_0000 ~ 0xff2f_ffff;64 位:0xffffffff_ff200000 ~ 0xffffffff_ff2fffff);
  3. 处理器在 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 大体相同。根据目前实验发现,不同的点主要在于:

此处给出一个实验,可以手动打断处理器执行然后恢复,此处使用了 2024 年 14 周出厂的 LS2K0300 芯片:

  1. 用 JTAG 调试探头(任何的都可以,只要你能手动向 IR 和 DR 读写数值)连接正在运行的 LoongArch 64 位目标。
  2. 向 IR 写入 5;然后,向 DR 写入 0x0004d000(向 Control 寄存器写入 0x0004d000,目的是将 EjtagBrk 位置位)。这将使处理器陷入调试例外。此时目标系统应该会暂停运行。
  3. 向 IR 写入 3;然后,从 DR 读取 64 位数值(从 Address 寄存器读入 64 位数值)。你应当读出 0xdb00000000000000 。
  4. 向 IR 写入 4;然后,向 DR 写入 0x0000000006483800(向 Data 寄存器写入一条指令: ERTN。根据《龙芯架构参考手册卷一》,此指令会使陷入调试例外的 CPU 从之前的位置重新开始执行)。
  5. 向 IR 写入 5;然后,向 DR 写入 0x0000c000(向 Control 寄存器写入 0x0000c000,目的是将 PrAcc 位置 0)。这将使处理器执行 ERTN 指令。
  6. 如果目标系统没有重新开始运行,请继续下面的步骤。
  7. 向 IR 写入 3;然后,从 DR 读取 64 位数值(从 Address 寄存器读入 64 位数值)。你应当读出 0xdb00000000000004 。处理器的 PC 递增了 4 。
  8. 如果你走到了这里,说明你的处理器从调试例外返回时似乎还会从 dmseg 再次取指令。在 LS2K0300 上观察到了这个现象,因为不能保证其他处理器没有这个性质,所以保留了这一段。(注:MIPS 上的从调试例外返回的指令 DERET *并不存在延迟槽;可能是龙芯制造的芯片 bug,使得 CPU 从 dmseg 执行返回到正常执行状态时会多从 dmseg 取指。又或许是其他问题呢?总之是个小坑*)
  9. 向 IR 写入 5;然后,向 DR 写入 0x0000c000(向 Control 寄存器写入 0x0000c000,目的是将 PrAcc 位置 0)。这将完成 CPU 的这次额外的“取指”操作。
  10. 此时目标 CPU 应该已经恢复运行了。