☼
原创
最近打算玩一下STM8, 只为了消化一下我的库存,因为我曾经买过几个型号的STM8单片机,但是一直没用来DIY啥。我对STM8熟悉程度远不如STM32, 后者是流行广泛的ARM核,STM8却是ST独家的架构。
STM8 CPU是在ST7基础上增强,有人说是从6502演变来的,我看倒也不像。学习了一下历史,Motorola的6800演变出来的6805/6811/6809三个分支,以及6502这个与6800有渊源的CPU,从寄存器和指令集上看STM8是和它们有相似之处的,不过差异的地方也很大。作为一个8位MCU,STM8的寻址范围居然达到16M byte(我不信ST会给8位机配上1M以上的ROM或RAM),寻址模式就很多了,间接内存访问比x86都复杂,看惯了RISC的CPU更不能忍。好吧,虽然指令集复杂,STM8的执行速度还快,反正不会纯用汇编来开发。
ST并没有提供STM8的C编译器(汇编器是有的),需要用第三方的。Cosmic C编译器有免费License的版本可以用,这也是ST推荐的,我就装了一个来试。ST官方支持的还有Raisonance的编译器,此外IAR也有STM8的开发环境。
试写了个C程序测试,可以用STVP连接ST-Link下载程序,但我觉得还需要个能反汇编看编译结果的东西。Cosmic工具链里面没有反汇编程序,ST的汇编工具里也没有,STVD既然能跟踪调试应该有,但我没能把它用起来。
干脆自己写一个STM8反汇编工具吧,也练下手怎么写。
先研究下STM8的指令集,这是一种典型变长指令集,除了前缀字节,操作码就在一个字节里面。于是我照着手册统计了一张表出来:
一个字节能表示的范围除了 0x90, 0x91, 0x92, 0x72 用来做指令前缀,其它几乎都用来作操作码了。当然许多指令都有多种寻址模式的(比如加法是谁和谁相加,需要指定),因此用了不止一个操作码。算上寻址模式,256种指令都不够用的,所以STM8靠前面增加前缀字节来扩展。从手册里面截一个例子如下(这是XOR指令的多种编码):
在指令的操作码后面就是提供数据或地址的字节了,长度由操作码加上前缀来决定。
编写反汇编程序就是写一个根据字节数据流的查表过程。上面我做的那个表只是划分了指令的分布,涉及到寻址模式的细节还是得一边写一边查手册。从表上看,操作码的高半字节大概可以把指令划分为几类,再用低半字节去细分指令,于是我的程序解码第一步就是一个 switch-case 结构来划分任务:
代码请点击阅读原文
解码的结果是放到全局变量里面的,返回值只代表了指令是否有效。例如,表格最右边一列的指令我是这样解析的:
代码请点击阅读原文
主要是靠 format() 函数根据当前的指令前缀来翻译操作码:指令名称,寻址的第一操作数、第二操作数。若一共写 256 个 case 分支就太繁琐了,需要抓住共性,像表格中绿色背景的这一组指令我是这么处理的:
代码请点击阅读原文
在给 format() 这个函数的参数中,指令和操作数类型都是用数值来表示的——用 enum 定义:
代码请点击阅读原文我想这么写而不是直接写字符串的原因是,字符串万一写错了很难检查出来。写成常量编译器可以检查,再统一对应到字符串即可。
format() 函数是这么实现的:
代码请点击阅读原文
下面就是解码成功后的翻译过程了,用 get_extra() 函数从代码数据中提取操作数(立即数、地址等),存放到dat1, dat2两个整型数,供后面用 printf() 输出。至于 printf() 需要的格式字符串,实际上是由解码得到的 Str_opr1, Str_opr2 结果提供的。这里还要特殊处理一下带位操作的指令(BCCM, BCPL, BRES, BSET, BTJF, BTJT这几个),其中的位是编码在操作码当中的,我在 format() 函数中并不把这个位编码作为一个操作数,尽管从汇编语言角度它应该是算一个操作数。
代码请点击阅读原文
完整的源程序附在后面。贴一个运行的结果:反汇编内容是我写的LED点灯测试程序,不包括中断向量表。
暂时还不能确定指令有没有遗漏,后面边用边检查吧。
以上图文内容均是EEWORLD论坛网友cruelfox 原创,在此感谢。
欢迎微博@EEWORLD
如果你也写过此类原创干货请公众号:EEWORLD(电子工程世界)回复“投稿”,也可将你的原创发至:bbs_service@eeworld.com.cn,一经入选,我们将帮你登上头条!
与更多行业内网友进行交流请登陆EEWORLD论坛。