SWD接口:探索&泄密&延伸

2023-02-12,,,

  http://bbs.21ic.com/icview-871133-1-1.html
文买了个JLINKV9,以为神器,拿到手发现根本不是,完全没必要替换V8,想自己做个另类的调试器,当然想只是想而已。又过了一阵,在网上居然发现ARM的开源调试器CMSIS DAP代码,回想起来,KL25小板子貌似不就是这个么,以前居然没注意。代码量很小于是很有兴趣,修改引脚编译了下到片子里,发现设备,飞线到别的板子,进入MDK顺利调试。有了这么好的一个参考样本,琢磨着这次得研究下SWD,又因为各种事没动工。时间到了上周末,周六睡懒觉到10点起床,想起有这回事,于是终于开工。好了要进入正题了。
搜了一通网页,找到隔壁论坛一个好帖子:“也谈SWD接口协议分析”,感谢原作者,这个给了我起步帮助。除了SWD一点基本了解,还从中知道要玩这个要看ADIV5等等ARM原作,英文大部头有木有,英文不好有木有,读这些不如杀了我。让正规军去啃大部头吧,游击队必须另辟蹊径才能在周末搞定这事了。
年的STM32中文参考手册,CM3权威指南中文版,CM3技术参考手册中文版(这三本好歹以前读过有印象)。
理,分别有DP和AP寄存器,无非是通过多级级联后访问就像指针间接访问变量。但是参考手册只讲了寄存器地址和名字并没有详细位定义内容,又但是,望文会意,名字就很清楚告知了用途和大概使用方法,英文虽然差,好歹几个基本单词还认识。比如ADD,DAT,SELECT ID神马的。基本协议也对照“也谈SWD接口协议分析”看了一下。
  迫不及待开始敲代码试一把,先不管寄存器内容定义了。读DP_ID顺利通过,操作别的DP出错。只好回来找寄存器定义,计数参考手册派上用场鸟,一通的#DEFINE,再次上马读AP_ID,还是失败,百思不得其解啊,好歹咱悟性不至于这么愚钝吧,最后放弃治疗了,逼我回头练第二招--仿造。
先示波器抓了一把JLINK和CMSIS DAP的时序,看着波形也是醉了,一个平时CLK为高,一个平时CLK为低。我的是平时CLK为低跟JLINK一样。人眼解码了几个数,照做还是错误,总感觉能读对DP_ID应该时序不会错,一定是我软件哪里有问题,彻底较劲钻牛角尖了,闹了整整半天天黑了又一次放弃治疗,继续本招第二手,没有JLINK源代码好歹有个DAP源代码在手,做不出了只能抄。细读了底层时序,在DAP工程里临时修改测试读AP_ID没问题,于是将DAP代码照猫画虎过来。还真药到病除,读到AP_ID了。顺便将DAP通讯协议分析了下,上BUSHOUND,然后写程序将BUSHOUND记录文本解析,就不用人眼解码示波器了。第一天就这样完了。
来条USB帧数据,丢掉一大堆花里胡哨读信息帧,到读ROMTAB地址了,继续看文档,明白了,AP地址固定,由AP得到ROMTAB地址,由ROMTAB地址得到各调试组件地址,TAB顾名思义当然是一大串了,也就是有很多调试组件。结果就是不管IC公司怎么折腾,可以由ARM定的一个死地址找到一大堆IC公司定的活动地址,是不是又跟指针间接访问对上了。然后为了方便像我这样的菜鸟不把自己搞晕,每个组件都在自己那一块地址的最后放了个ID号,跟GPS一样好使,不会随便迷航,DP和AP不就有ID么。插一句,大家读过ARM类单片机手册就知道,每个模块都是占一块地址的,不像51那样乱七八糟所有模块地址都挤在一起。当然ARM是不可能未仆先知IC公司会做多少ROM,RAM,多少外设等等,所以ROMTAB并不能提供普通用户资源的地址信息,这个要看芯片手册。自我感觉到这可以上岸洗白不要逆向分析别人东西,可以“自主研发”单飞自由发挥,事实上结果也如此。遍历调试组件以及ID一次过没问题。
位摊平了就是一张4G存储卡,通过地址访问,看起来整个单片机只要给地址任你蹂躏位地址的,调试器为何能在IDE上显示值?WHY?HOW?那么问题来了,访问PC指针哪家强? 继续读文档,明白了,再级联一次就解决,通过内核调试模块访问,在这里给PC神马的做了地址映射,看到没,还是遵守真理啊地址+指针永远伟光正。再次访问,依旧失败,想了下原因果然猜对了-----要先使能调试,这些玩意灰常重要的部件,不让你随便乱碰的,使能调试后才允许访问,至此,基本上全通了,剩下就是组合使用以及调试功能了。上权威指南研究,继续测试了停止CPU,释放CPU,单步运行等都通过。
下一步准备干什么是很显然的----脱机下载!搞了个BIN提取数组,将从机烧写文件合并到自己工程文件里。分别测试了RAM下载和ROM下载都顺利。无非就是上面知识的组合应用。至于细节,下载前当然要将CPU停住,下载后重新设置PC指针后再跑。你不会希望程序下到一半PC指针跑飞了各种奇葩意外等着你吧。想到安全问题,绞尽脑汁做了个有意思的实验,加入第三片单片机监听通信并控制DIO脚通断,抓到读DP_ID后将从机断开伪造一个ID给主机然后迅速接回从机。
年以上的电工应该很少会用到单步调试,断点之类的侵入式调试了,而会更多地采用串口打印之类的非侵入调试。非侵入调试的优点是显而易见的--------不干扰正常程序的执行,效率很高。当然了要解决诡异问题时候侵入式调试也会偶尔成为杀手锏。JLINK的RTT和SCOPE就是以这个为基础的,其中RTT的实现是开环形缓冲,CPU和RTT一个拿读指针一个拿写指针,就跟串口FIFO方式一样,缺点是只能通过超级终端交互,虽然JLINK手册也说了两种方法与自己程序对接,感觉终究还是不归自己控制。SCOPE更简单了,通过MAP文件确定变量地址,定时读回指定地址数据绘图。缺点大大的,不支持复杂定义比如数组和结构体(因为MAP只给地址嘛),不支持采样率设置,更不能自己随心所欲显示和记录。正因为如此才有了自己做个特色调试器想法。
好啦,折腾到周日晚上搞定收工,脑子彻底浆糊了。最后,当然是上艳照了,所谓无图无真相。整理了个DEMO运行打印信息。

 
总结陈词。。。

上面已经给出了完整过程和详细分析,以及重要的艳照。当然我是故意没放源码,这么大篇幅文字比源码有用的太多了,自我认为这算是赤裸裸的SWD接口个人开源贴了,打破核垄断人人都能玩转SWD,不算之前对STM32的了解和对JLINK之类的使用,其实我也就两天摸入门了,比几年前从零开始读OBD好太多了。如果什么都不说丢一堆源码有意思么,这篇文章已经把肉送到嘴巴了,只要自己再嚼两口就能吃,文中我略过了一些小细节比如时序图AP传递性之类,我相信一个熟悉STM32的电工看过我文章只用不到一天就能重走一遍我的路。自己重走一遍找到这些小细节更有利于自己的理解,我相信各位不用抄能做出来。SO,不回复不讨论任何SWD细节技术问题,请自行验证自己的疑问和猜想。不然,你还真对不起我码这么长的字,大家尊重一下我哈,下面几段则热烈欢迎大家踊跃讨论,各种讨论。

相信各位电工不用一天已经搞定DEMO了,有完美癖的接下来自行啃ARM文档去了解细节问题,加入各种细节处理追求完美。然后比如位段操作,全局变量,内联函数等等提高速度到极致。比如扩展到ST其他芯片,ATMEL,NXP,FREESCALE等等。

对脱机下载,很显然想到两种,一是直接代替CPU操作,进行ROM烧写,简单直接。另一种就是先下载一个BOOTLOAD到RAM,然后用环形缓冲与BOOTLOAD交互,由BOOTLOAD来烧写FLASH,高级一些通用一些,MDK的芯片烧写算法估计就是个BOOTLOAD。又涉及保密问题了,别人可以监听时序。由此引入UID问题,BOOTLOAD也得是动态,先读UID回来,修改BOOTLOAD再下载BOOTLOAD,BOOTLOAD运行后读UID校验,校验OK才能与下载器交互,下载的ROM也是UID修改过的。具体如何修改和加密各有想法。当然别人可以抓出BOOTLOAD然后跑虚拟机研究或者反汇编研究。又当然对商品下载器可以每个客户给一个加密算法的BOOTLOAD,烧写员是老板自己心腹,屌丝电工产品没好到对方愿意不惜一切代价反汇编破解。

对调试器,你能做到JLINK的兼容性?你能做到JLINK的功能细节?你愿意花多少精力去嵌入IDE(搜了下COOCOX是实现了嵌入IDE)?你有多少时间闲折腾?你做好了能卖钱不会为别人做嫁衣?回答全是NO.但是屌丝电工们还是很容易满足的,第一咱们可以将DAP嵌入工程内,满足嵌入IDE的要求,满足单步和断点这种侵入式调试的要求。第二咱有白菜TFT+FLASH,脱机下载+调试PRINTF记录搞定。第三咱有物美价廉480M USB的高速CM4芯片,一头USB通电脑,一头通被调试芯片,读写内存刷刷刷。以内存并行读写为基础做非侵入调试功能。对SCOPE功能,支持多种类型变量。任意指定每个变量的采样间隔,这就变成了一个每个通道都可以分别设置采样率的片上示波器&逻辑仪,我们的实体示波器还只能统一采样率呢。如果上位机还支持逻辑仪解码插件呢?只给出接口DLL和范例程序,支持范例程序脚本编程,支持自己任意设计,支持分享优秀设计呢?屌丝电工们有木有高大上感觉。对于RTT功能,没啥好说的,相当于将串口打印从串口&USB转移到SWD.好处依然是非侵入,比如单片机本来就是USB与上位机通信的,不用在USB通信中插入调试交互去挤占或影响正常通信,咱另修一条高速通道做调试交互。黑白超级终端滚粗,早看你不顺眼了,调试交互软件想怎么任性就怎么设计。依然给出接口DLL给出范例程序带脚本编程分享优秀设计。当然别忘了还能脱机记录调试信息,睡个觉抽根烟再来分析现场故障哦,对啦,脱机记录还能预设与被调试系统的应答哦,而非傻乎乎只知道抄写记录。最后,AVR的ARDUINO火的一塌糊涂,CCDEBUG可以SPI连接无线芯片,USBEE虚拟示波器也有意思。我们为毛不可以结合他们做个类似的傻瓜化工具,调试器将CPU 停掉接管资源,电脑简单控制语言控制调试器对被调试芯片的访问,比如类C代码ad_get(1,3);  指挥调试器去写被调试芯片的AD模块,并返回结果到电脑。 如果我用个WHILE(1)  AD_GET(1,3);呢,那不就是USBEE一样了。  如果我用个 if(AD_GET(1,3) > 300)  led_on();  呢,那不就是ARDUINO类似了。  总之,对现有ARM板子,小朋友们可以不会单片机不装编译器也能玩,具体实现方法很多。最后的最后,我们是单纯的屌丝电工,乐于吃着地沟油分享技术,不是想到个东西就考虑钱,这个设计显然是开源共享的。

JTAG接口还没研究,等年后哪天有时间,得来个姊妹篇 JTAG接口:探索&泄密&延伸

对啦,说起JLINK,又说几句,JLINKV9出来后翻找了一些JLINK的帖子,得知APP是明码放在DLL文件里,直接提取的。BOOT是通过木马APP搞出来的,木马APP的关键是通过BOOT或上位机的验证才能下进去。已经有不少人搞定了,SO,SEGGER应该是对加密没太上心,大家可以讨论下对JLINK BOOT的分析,以及如何防范别人窃取。

 
下午用电脑上了,发现这位兄弟真的是一个人看着我的贴摸索了好几个月了,赞一个。发现写的贴能帮到几个这样的兄弟还是挺开心的。又自己回翻了下自己文章,大概理解了一下,给你讲解下,时间太久,我也不确定自己讲解的对不对了,要是讲的不对就抱歉了,大家互相学习。
看样子你前面磕磕碰碰完成了SWD接口基本时序-》读写DP接口--》读写AP接口,现在卡在读取单片机普通寄存器阶段了,过了普通寄存器阶段还有一个阶段就是内核和调试模块。至此基本就全部完了。加油,马上就要搞定了。
摘录几句 希望能帮到你 
        //AP寄存器  APREG比较多 地址包括A[3:2]以及DPREG_SELECT即A[7:4]组成 32位地址所以低两位无效
        #define apreg_dgromaddr                (0xf8>>2)//只读 存放ROM_TAB绝对地址的寄存器                    这个一个AP寄存器地址定义

//各种寄存器读写操作宏定义 START   DP/AP  wr/RD   A3:2            PAR   STOP   FREE  
       #define code_regr_rom                        (( bv(1)| bv(2)|(2<<3))>>1 )        //读取ROM_TAB地址 命令         读取这个AP寄存器的操作编码

status = swd_transfer(code_regr_rom  ,dat);//        选择存放有ROM_TAB绝对地址的AP寄存器               本次读是读不回本次结果的 每次只能读回上一次结果 所以作为单独测试的DEMO 这次读回来的值扔掉

status = swd_transfer(code_regr_rbuf ,dat);//读取ROM_TAB的绝对地址              这次读到了上一条操作的结果        注意通过AP寄存器去读其他寄存器时候有延迟一拍,下一拍读上一拍的结果 最后一次用dpreg_readbuf读取最后一拍结果

rom_addr = dat[0]&0xfffffffc;                      //获取到的ROM_TAB绝对地址编码

status = swd_transfer(code_regw_addr ,&rom_addr);          //选择读ROM_TAB绝对地址

status = swd_transfer(code_regr_dat  ,dat);//读数据         依然本次操作要下一拍读回          本次读回的数据没用

status = swd_transfer(code_regr_rbuf ,dat);//读数据  都会了ROM_TAB绝对地址里面装的内容了

总结的说,AP和DP模块用的相对地址,可以调试器直接访问的。但各家单片机的地址映射表不同,包括FLASH  RAM  REG的位置都不同,需要绝对地址访问,这些都没法摸瞎访问。有个ROM_TAB记录了各个调试模块的绝对地址位置,但是这个ROM_TAB本身也是放在FLASH里的,也要通过绝对地址访问,各家也不同。于是就在AP里有个寄存器保存了这个ROM_TAB的绝对地址, 这样就可以一步步按图索骥。 先由AP的某个寄存器找到ROM_TAB的绝对地址,再访问ROM_TAB的绝对地址找到各个调试模块的绝对地址,然后就可以去访问各个调试模块了。   另外AP有个特性,通过它去读别的东西,本次只发出指令去操作,操作结果要等一下才有返回,你得下一拍去读回结果,估计写也是一样,但是写你不用管写完,所以感受不到。如果要读一串N个,每个都顺带取回前一个的结果,倒是不碍事,总共也只多费一个操作。如果是读一个,也是多费一个操作,效率就相当于折半了    N/(N+1)  VS   1/ (1+1)

注意了这两点应该不难了,祝你成功   @tgwfcc

 
  http://bbs.21ic.com/icview-906224-1-1.html

过年最后一天提早来长沙,花了一天研究了下JTAG接口,也码上来。 
书接上回,说到SWD顺便七七八八已经说完了,这下JTAG就没多少东西了,只是简单记录一下过程。 
说起JTAG,普通电工跟它的相遇基本都是大学上FPGA课发现下载那个并口线叫JTAG,或者买并口头子和牛角插头在松烟中DIY古老的并口单片机下载器。文艺电工有些可能没上大学就跟JTAG早恋了,有些毕业后才遇到JTAG。遗憾的是,认识这么多年我也从没深入了解JTAG,就知道是神马IEEE神马边界扫描标准神马测试集成电路的。好吧,现在开始了解也为时不晚。
 
不管最初设计者对JTAG的期望是神马,现实就是现在JTAG已经满大桌了,各种芯片的下载和仿真几乎清一色的JTAG,估计超出了设计者的期望。也不知道这些JTAG协议是不是已经衍生出很多不兼容的版本了,估计除了硬件接口,二进制流和基本状态定义,其他都改了。别的芯片先不管,反正我们现在只看ARM的JTAG接口。
 
依然是看STM32中文手册,已经看过SWD部分,剩下JTAG部分就那么两三页。硬件接口上,JTAG一般都不用那根JNTRST脚,庞大的20PIN只要看4根线了,TCK,TMS,TDI,TDO。I和O命名是站在从机角度说的,因为STM32是被调试者。需要注意的是调试器需要将TDO上拉,不然输出一直为零,开始就注意这个,用浮空输入折腾了2分钟一直没数据,改上拉就好了。JTAG跟SPI神似,移位结构,所以可以像595芯片一样级联串成一大串用。在STM32芯片的链路上就串了2个,一个是ARM的CM3,一个是ST公司的TMC,我们要用的就是ARM的CM3,另一个估计是ST公司做芯片质检的?发现一个名词TAP,百度了下叫测试访问接口,估计跟DP,AP差不多意思,一个芯片内有多个器件,这个TAP就是访问各器件的接口模块,每个器件一个TAP挂在JTAG串上。又有两个名词IR和DR。IR用来指定DR,通过DR对器件做读写。两个TAP的IR长度分别为5位和4位。手册提到输入IR时扫描链=5+4,不用的TAP必须输入旁路指令。输入DR时不用的TAP被旁路,需要额外增加一位。这两句话很有用。列出了ARM TAP的IR指令表(即DR地址表),五个指令分别是:旁路  IDCODE   DP访问   AP访问  中止  还给出了每个DR的数据位定义,不同的DR位数不同。再列了一张JTAG DP的寄存器表,比SW 的DP寄存器少了很多,但是地址和数据位定义是相同的。JTAG的AP与SW 的AP是共用的。 看到这里,STM32中文手册用3页纸完整的说清楚了JTAG调试接口的结构和使用:正确连线JTAG的四根线,用旁路指令屏蔽链上其他TAP,目标TAP用IR选择不同的DR寄存器,被屏蔽的TAP在链上只有一位数据,不同DR位数不同。通过DR寄存器访问AP和DP,如何使用AP和DP的介绍在SW部分。中文手册到此结束。
 
尽管ST说的很清楚,我们也看得很仔细,但是依然无从下手。因为:我们只知道在CLK节拍下将数据注入流出,不知道是上升沿还是下降沿。不知道如何区分IR链和DR链,估计TMS就是干这个事的。网上一搜,马上发现一张图叫做TAP状态机的,解了疑惑。每个圈圈都是一个状态,数字表示TMS电平,在时钟上升沿锁存,这么一想,数据估计就是下降沿移位了,因为锁存会引发状态改变,数据必须在状态跳变前送进去。每个状态跳变只有2个分支,清晰简洁,弯箭头处可以停留,也就是可能用来输入数据,直箭头是不上客的小火车站,不可能输入数据。RST和IDLE的弯箭头只是为了提供条件保持当前状态,也不会有数据。PAUSE_DR和PAUSE_IR看名字就不是输入数据地方。DR和IR数据就只能分别在SHIFT_DR和SHIFT_IR处输入了。如果当前忘了自己处于哪个圈了肿么办呢?TMS=1一路走,只要五步保证会到达并停留在RST状态。当然也可以把剪了的那根RST线用起来,直接复位简单粗暴。这下子知道怎么用了。照着这个线路图走,到达SHIFT送数据再返回就对了。 
还是有疑问,对一片或一串已知芯片,链上有多少个器件,以及各自IR长度是知道的,如果是有未知芯片呢?都不知道挂了多少个,也不知道各自IR长度多少(DR只需要知道目标器件,其他器件可以旁路掉),没法用了。想了下所有器件RST后默认状态应该是一致的,旁路?IDCODE?这样可以通过判断注入=流出终止获得设备个数,IR也可以通过注入=流出终止获得IR总长度,各自IR长度依然无法知道,对照IR指令表,除非JTAG协议规定所有器件IDCODE地址必须都是1110格式即除了低位=0其他都为1,可以找零知道每个长度。还是用示波器抓仿真器波形来验证这个想法吧。发现是先在DR发送一长串读回ID,这一步知道了器件个数以及器件ID,然后发送IR,获得的是10001000011111这样格式,这一步知道了每个IR的长度。
 
可以动手测试了。就是将上次SWD的过程照搬了一遍,一切顺利。

JTAG虽然对ARM下载已经不重要,有两线的SWD好用的很。但是在这个遍地JTAG接口的时代,不知道JTAG的电工还真不好意思,没准哪天调试别的东西遇到问题就要这个。问题来了,JTAG没法用硬件SPI,怎么让它速度快。为什么复位后直接读DP是IDCODE,读IR没有被旁路的器件是1000格式,被旁路的器件是1111格式,有相关规定么,读过JTAG正规文档的高手们,给说下呗。

 
 
 
 
 
 
 
 

SWD接口:探索&泄密&延伸的相关教程结束。

《SWD接口:探索&泄密&延伸.doc》

下载本文的Word格式文档,以方便收藏与打印。