ARM调试端口简介
ARM的调试是基于DAP实现的,ARM的调试端口一般要实现以下一些功能:
- 更改处理器的状态
- 读取处理器状态
- 设定调试事件,比如搞个断点什么的
- 强制处理器进入或者离开Debug状态
- 查看当前处理器是不是在Debug状态
- 追踪程序执行状态
DAP的基本结构
一般DAP的结构如下图所示:
Debug Port(DP)是直接和外部接口的,可以通过JTAG和SWD两种形式实现。
Access Port(AP)是位于DP和芯片之间的一个桥梁,一般AP会有一个识别标准,当Debugger扫描AP时候,如果碰到不支持的AP可以直接跳过。
下图是一个详细的DAP的结构图
对于DP和AP,debugger在访问的时候都会涉及到两个地址,一个是公用的A[3:2],对于DP来讲A[3:2]和SELECT.DPBANKSEL共同决定了目标DP寄存器的地址;对于AP则是通过 SELECT.APSEL 选择一个AP寄存器,A[3:2]则是选择不同的bank,比如要访问AP 0 的0x14寄存器:
- 首先通过DP写相应的寄存器SELECT.APSEL写成0x00,SELECT.APBANKSEL 写 0x1.
- 访问AP,访问的时候要将A[3:2]写为0x01
DAP的读写操作
DAP的结构看起来还挺复杂的,不过JLINK工具里面其实已经集成这个工具了,比如Jlink Commander
工具就支持以下一些命令
- writedp <index> <data>
- writeap <index> <data>
- readdp <index>
- readap <index>
比如要读AP[0] BANK[15] 的IDR,就可以通过如下的命令执行:
WriteDP 2, 0x000000F0 // Select AP[0] bank 15
ReadAP 3 // Read AP[0] IDR
上面语句中WriteDP
是写DP的指令,index=2代表写SELECT
寄存器,后面的数据代表AP[0] BANK 15,SELECT寄存器的定义如下:
- APSEL代表AP的序号,如果访问了一个没有连接的AP序号,那么后续的读操作是全零,而写操作会直接忽略。
- APBANKSEL代表AP BANK的标号
- DPBANKSEL代表DP BANK的标号,这个具体含义和DAP实现的版本有关,DPv0中要保持写0,新一点的版本中会有特殊含义,但是我做测试的时候一般直接写0。
后面的ReadAp 3则是表示读AP的第四个寄存器(寄存器序号从0开始),所以对应的就是IDR寄存器了。
另外在读DP,AP的时候最好多读几遍,因为里面的扫描链一般会有滞后,所以读AP的时候至少要读两次,一般第一次读的结果是不可靠的,这里建议读三次,当后面两次结果相同时认为结果可靠。
通过DP,AP访问Memory
作为一个调试端口,读写Memory的功能是必不可少的,DAP形式的结构中,读写Memory可以直接通过MEM-AP
实现。
MEM-AP定义如下图所示:
图中红框中就是相应寄存器的定义,可以看到里面有地址和数据寄存器,我们就是通过对这两个寄存器的读写实现对memory的访问的。
下面就是一个Memory访问的例子
# 首先通过正常命令读memory, 实际这块memory都写了0xA5A5A5A5
J-Link>mem32 20000000 1
20000000 = A5A5A5A5
# 更改第一个值,方便后面验证读操作
J-Link>w4 20000000 12345678
Writing 12345678 -> 20000000
J-Link>mem32 20000000 1
20000000 = 12345678
# 写DP,选AP[0] Bank[0]
J-Link>writedp 2 0
Writing DP register 2 = 0x00000000 (0 write repetitions needed)
# 写AP[0] BANK[0] Address寄存器
J-Link>writeap 1 20000000
Writing AP register 1 = 0x20000000 (0 write repetitions needed)
# 读操作代表读memory, 可以看到结果已经变化
J-Link>readap 3
Reading AP register 3 = 0x12345678 (0 read repetitions needed)
# 连续读操作的时候,地址会自动累加
J-Link>readap 3
Reading AP register 3 = 0xA5A5A5A5 (0 read repetitions needed)
J-Link>readap 3
Reading AP register 3 = 0xA5A5A5A5 (0 read repetitions needed)
# 写操作时,我们再把地址改回来
J-Link>writeap 1 20000000
Writing AP register 1 = 0x20000000 (0 write repetitions needed)
J-Link>readap 3
Reading AP register 3 = 0x12345678 (0 read repetitions needed)
# 写AP就代表写memory
J-Link>writeap 3 87654321
Writing AP register 3 = 0x87654321 (0 write repetitions needed)
J-Link>readap 3
Reading AP register 3 = 0xA5A5A5A5 (0 read repetitions needed)
J-Link>writeap 1 20000000
Writing AP register 1 = 0x20000000 (0 write repetitions needed)
J-Link>readap 3
Reading AP register 3 = 0x12345678 (0 read repetitions needed)
J-Link>readap 3
Reading AP register 3 = 0x87654321 (0 read repetitions needed)
# 可以看到Memory内容已经改变
J-Link>mem32 20000000 6
20000000 = 12345678 87654321 A5A5A5A5 A5A5A5A5
20000010 = A5A5A5A5 A5A5A5A5
J-Link>
最后的一点说明,Jlink Commander这个工具虽然支持了这些指令,但是如果使用这些命令过程中一旦出现错误,后续的命令都不能正常执行,只能通过USB命令或者connect命令重新连接之后才能正常工作。
最后更新于 2019-10-06 22:14:27 并被添加「调试 ARM 脚本」标签,已有 10476 位童鞋阅读过。
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。