最近的工作还是改那坨代码……维护这摊东西也快要2年了,好几次想重构它,顺便整理一下,不过我还是缺乏那种毅力。在这段时间里我还加了一些功能模块,估计如果以后有新人接手这摊东西,会抱怨这么多垃圾,呵呵。但不管量产有多少垃圾,我还是有些“经验”需要总结一下。
先说下目标系统的特点:一,硬件资源丰富:对于我们简单的应用来说,处理器的频率够快了,内存够大了,FlashROM也有富余;二,对外接口简单:多数情况下,只有一两个串口与外设交互。目前,我们的目标系统的开发和使用有几个问题:软件需求变动比较频繁、软件bug很多;平时我们是通过仿真器调试和下载程序,对于结构和线路紧凑的设备,频繁连接仿真器对设备的状态有不利影响。所以,通过串口进行软件在线升级以及进行在线调试可以大大方便了开发和维护,提高效率。
现时我们已有不少设备具备串口软件在线升级功能;但串口在线调试还没有,针对我们目标系统的硬件环境和使用情况,串口在线调试功能的可行性和实用性还是挺大的。经过简单的调研,用仿真器进行调试是处理器芯片提供硬件级的支持,而用串口在线调试则是在软件内提供对外调试接口这种“插桩式”的软件级支持,显然性能相比用串口会差很远,而且前提是串口部分代码要正常工作,不过从易用性来说用串口还是很有优势的。
我打算分几步实现串口在线调试。首先是对变量内存数据的读取。例如输入x 0x400,输出0xFACEDEAD这样。由于我们的目标系统用的是平坦的内存模型,这样很好办,读取内存变量都不需要权限、不需要重定位等,直接把指定地址开始的一定数量字节从串口送出来,唯一需要注意的是串口传输速率一般都不高,读大数据可能会有延时,这里可能会影响程序实时性和有同步问题。
第二步是对指定地址内存的修改。其实这和读内存的实现差不多,就是data=*addr和*addr=data的差别;但因为如果操作不慎,会影响程序正常运行,如果和固件存储相关甚至会影响设备配置(即不但影响了这次程序的运行,还影响到关机重启以后的程序运行)。所以如何实现好这个接口的确挺费脑筋的。
第三步就是对程序的运行控制了,模拟仿真器设断点、暂停、恢复等调试功能。这个我原理上还没弄懂,不知道实现方法;大概的方向就是以第二步为基础,把断点对应代码的内存数据改成会使CPU停下来的指令,例如中断/异常/死循环等等,涉及到寄存器的修改,好像挺麻烦的。
第四步就是实现单步、连续。。。等等其他常用调试功能。此外,除了目标系统软件要提供接口实现,上位机(调试主机)利用这些接口开发辅助程序也很有必要,尤其是对于第三第四步,需要保存/恢复断点位置内容,纯手工效率太低。
目前,第一步目标系统基本的接口已经实现,上位机方面我打算写个小程序,从可执行文件或者映射文件把符号翻译成地址,从工程头文件获得结构体信息,从而使内存数据意义的可读性更好。此外,我还留了几个“功能强大”的接口,其一是uCOS的OSQPost %addr %msg,在我们的那坨代码里很多消息传递,这个可以改变软件的执行流程;其二是更一般的%func_addr %arg1 %arg2 ...,这个可以直接执行任意函数,其中参数传递方面我还研究得不多,似乎多数函数都是用寄存器传参数而不是通过栈,所以我直接传int型,具体函数的具体参数会根据类型截取长度,可能这种做法通用性欠缺,超过8个参数会有问题,不过一般参数太多的函数不太好吧。当然,这两个接口危险性都很大,调用不慎都会崩掉。不过,现在我用这几个接口调试感觉很爽。
总而言之,我的这些所谓“经验”,可说是公司的制度、开发水平和具体应用需求的产物,可能换了个环境就会变成“鸡肋”,又或者有更科学的方法我还没接触到。不过无论如何,这都是我工作中一个实实在在的脚印,是我在开发的思路的一种体现,务必整理好记录下来。