note:本文是我的原始文章的中文重写版本,语义及内容有一定出入
DSP JTAG探索
这个DSP宣称是有JTAG调试功能的。来看看是否有可能用上它。
平台相关: DSP的“软件”方式JTAG
在安卓内核源码里可以看到, 这颗SoC有一个MMIO接口允许ARM直接控制DSP的JTAG信号,如同GPIO bit-bang一样:
#define BIT_CEVA_SW_JTAG_ENA ( BIT(8) )
#define BIT_STDO ( BIT(4) )
#define BIT_STCK ( BIT(3) )
#define BIT_STMS ( BIT(2) )
#define BIT_STDI ( BIT(1) )
#define BIT_STRTCK ( BIT(0) )
这个默认是关着的(应该是连接到了物理的JTAG引脚上):
# devmem 0x20900280
0x00000000
把它打开
# devmem 0x20900280 32 0x100
0x00000100
我们知道RTCK是JTAG从机接收到TCK电平状态的反馈信号,因此RTCK应该等于我们写入TCK的值:
# devmem 0x20900280 32 0x108
# devmem 0x20900280
0x00000109
的确如此,这样就可以确定这个接口能用了接下来我们就可以通过bitbang这个寄存器来操作DSP的JTAG了。我手动(狗头)发送JTAG时序,尝试读取了下IDCODE:
T-L-R -> R-T-I
0 1 0 0 0 0x8
0 0 0 0 0 0x0
R-T-I -> S-D-S
0 1 1 0 0 0xC
0 0 1 0 0 0x4
S-D-S -> C-D-R
0 1 0 0 0 0x8
0 0 0 0 0 0x0
C-D-R -> S-D-R
0 1 0 0 0 0x8
0 0 0 0 0 0x0
S-D-R
0 1 0 ? 0 0x8
0 0 0 ? 0 0x0
TDO上得到这样一串数:
1 0 1 0 0 1 0 1 0 0 1 0 0 1 0 0 0 1 0 0 0 1 1 0 1 0 0 0 0 0 0 0
是0x016224A5
,而刚好这个DSP叫X1622,所以我们已经成功和DSP JTAG say hello world了。
JTAG接口扫描
其实一开始我对JTAG是不抱希望的,因为JTAG的移位寄存器特性(读取的同时必须写入),使得它不能直接实现一个像总线一样的地址空间,也就很难“直接”实现调试逻辑的接口。很多厂商选择在JTAG层实现另一层协议,以便间接访问一系列的调试寄存器,解决移位的问题;想要猜出这个协议就很不容易了。不过我还是硬着头皮试了试,找了个JTAG协议层的实现,穷举一下全部的DR值(实际上会往里面写0):
First try:
IR = 0, DR = 0
...
IR = 1f, DR = 0
IR = 20, DR = 1
IR = 21, DR = 0
IR = 22, DR = 0
IR = 23, DR = 0
IR = 24, DR = 1
IR = 25, DR = 0
...
IR = 33, DR = 0
IR = 34, DR = c00a87d8
IR = 35, DR = 0
...
IR = 5f, DR = 0
IR = 60, DR = 4a
IR = 61, DR = 0
...
IR = 6f, DR = 0
IR = 70, DR = ffffffff
IR = 71, DR = 0
IR = 72, DR = 16220401
IR = 73, DR = 0
...
IR = 87, DR = 0
IR = 88, DR = 1
IR = 89, DR = 0
IR = 8a, DR = 1
IR = 8b, DR = 1
IR = 8c, DR = 0
IR = 8d, DR = 0
IR = 8e, DR = 0
IR = 8f, DR = 0
IR = 90, DR = 0
IR = 91, DR = 0
IR = 92, DR = 30023
IR = 93, DR = 0
...
IR = 9f, DR = 0
IR = a0, DR = 16224a5
IR = a1, DR = 0
...
IR = ff, DR = 0
十分幸运的是,这个DSP貌似是在JTAG层直接实现的各个调试寄存器。那么他们是如何解决移位访问的问题的呢?猜测有几种可能:可能把寄存器分成了只读和只写的,也可能每个寄存器有特定的位需要置1才能写入,也有可能每个寄存器的初始值都是确定的。
再试一次:
Second try:
IR = 0, DR = 0
...
IR = 1f, DR = 0
IR = 20, DR = 1
IR = 21, DR = 0
IR = 22, DR = 0
IR = 23, DR = 0
IR = 24, DR = 1
IR = 25, DR = 0
...
IR = 33, DR = 0
IR = 34, DR = c00a65ac
IR = 35, DR = 0
...
IR = 5f, DR = 0
IR = 60, DR = 68
IR = 61, DR = 0
...
IR = 6f, DR = 0
IR = 70, DR = ffffffff
IR = 71, DR = 0
IR = 72, DR = 16220401
IR = 73, DR = 0
...
IR = 87, DR = 0
IR = 88, DR = 1
IR = 89, DR = 0
IR = 8a, DR = 1
IR = 8b, DR = 1
IR = 8c, DR = 0
IR = 8d, DR = 0
IR = 8e, DR = 0
IR = 8f, DR = 0
IR = 90, DR = 0
IR = 91, DR = 0
IR = 92, DR = 30023
IR = 93, DR = 0
...
IR = 9f, DR = 0
IR = a0, DR = 16224a5
IR = a1, DR = 0
...
IR = ff, DR = 0
没有太大变化,但是可以发现IR = 0x34
对应的DR似乎一直落在代码空间的位置上(0xC0000000是DDR的位置)。它也许就是PC的值?
加载一个程序测试一下:
devmem 0x20080 32 0x3CA03CA0 # SQ.nop SQ.nop
devmem 0x20084 32 0x3CA03CA0 # SQ.nop SQ.nop
devmem 0x20088 32 0x3CA03CA0 # SQ.nop SQ.nop
devmem 0x2008C 32 0x3CA03CA0 # SQ.nop SQ.nop
devmem 0x20090 32 0x3CA03CA0 # SQ.nop SQ.nop
devmem 0x20094 32 0x3CA03CA0 # SQ.nop SQ.nop
devmem 0x20098 32 0x3CA03CA0 # SQ.nop SQ.nop
devmem 0x2009C 32 0x3CA03CA0 # SQ.nop SQ.nop
devmem 0x200A0 32 0x9ABFF81F # SQ.brr{t} 0xFFFFFFE0 (-0x20, to 0x20080)
IR = 0x34
的值现在一直落在0xC0020080 - 0xC00200A4了。可以确定这就是保存PC的寄存器,只不过它似乎不是直接的PC值,而是PC+4或者PC-4,这可能和VLIW之类的有关系。
收获
尽管继续分析这个JTAG接口并不怎么现实,但我们至少知道了如何获得PC的值,这实际上足够我们跟踪DSP的执行情况了,也许对分析指令集大有帮助。