嵌入式系统的调试往往很复杂,可用的手段并不像PC编程那么多,开发成本较PC系统也要大很多。嵌入式系统调试主要手段只有JTAG为代表的单步追踪、printf夹杀大法等。
这两种调试方法在嵌入式中也不尽然全部能解决问题。Jtag需要调试者有一个调试设备(有可能很昂贵),和目标系统相连。使用类似GDB Client等软件登录调试设备,跟踪运行程序。说实话,这个方法对嵌入式来讲是终极的调试办法,也是比较好的调试方法。但仍然有几个不足,当断点过多时,超出硬件的限制,某些低档的CPU不支持更多的断点,就需要JTAG利用软件模拟,或采用软件陷阱(软中断或异常)等办法实现断点。机理比较复杂,简单点说,1.不能进行长时间调试,不太稳定; 2.有可能影响程序的运行时刻的行为,通过时序影响。挂接JTAG系统后,利用硬件实现的断点不会影响系统运行的速度,但是软件实现的断点是必定牺牲一些性能的。可靠性也要打折扣的。当断点太多,而系统又进入临界区域,可能会造成断点不起作用。因为嵌入式实现全局临界区域往往需要关闭中断,有些CPU没有非屏蔽中断,当断点超过一定数量,使用软件断点,而软件断点又需要在中断工作的情况下使用……

特别调试时序问题和高速通信类的代码,JTAG帮助并不大。通信过程往往很快,通信包也是接二连三,才能完成一个完整的动作。如果是高速通讯,断点是无法让程序完成工作的。所以只能使用printf夹杀的办法,printf夹杀办法很好。但是也要注意几个问题:嵌入式系统往往没有屏幕,printf输出是通过串口输出。而串口工作模式有两种,一种是查询,另外一种是中断,或DMA。不管哪种,调试输出的printf只能使用查询的办法输出,千万不要使用中断或DMA的办法。不管是前后台程序也好,还是操作系统也好,都有不方便的时候,也许在全局临界内需要打印(关闭了中断),也许需要在中断里打印(不允许嵌套中断),也许要在一些驱动里打印(很多配合的设备没有初始化,内存分配和中断并不能很好的工作)。在这些情况下,利用Uart中断输出字符是不明智的。所以调试输出只能使用查询的办法。不要幻想着使用什么牛叉的办法,不必了。一句话,不可靠!既然做调试,那可靠的输出结果是第一要求。也就是因为如此,printf也会影响代码的工作效率,串口最高的波特率115200bps,越快速的CPU越是浪费时间,因为需要等待上一个字符输出完毕,这段时间完全是通过空转消耗这部分时间。所以使用printf要有一些技巧,在不影响一些关键时序的位置下再打印,而不是随意烂打……淹没了bug。

以上这两种办法并不能很好的解决全部的问题,在实际中如果嵌入式系统有一两个LED灯,尝试用IO口将其在特殊的情况下点亮熄灭的办法,也可表示程序的状态。这种办法适合调试中断、临界区域这些问题。点量LED灯需要的时间是非常短的,基本上是一条内存读写命令,如果IO口寄存器是CPU统一编址的话。基本上造成的影响微乎其微。在调试一些复杂的时序的时候,还可以使用空闲的IO口,将其在特殊的情况下拉低,拔高,然后利用数字示波器或者逻辑分析仪抓取再具体分析。特别是分析一段代码的执行频度,执行时间,优化效果等。对整体的性能提升等,有非常大的意义。对于简单的单片机,厂商开发软件都有个时序统计的功能。但对于有cache和MMU的单片机,时序统计并不准,往往不如用示波器测得的准。如果没有示波器利用CPU内部的时间计数器也可以实现时间的统计,需要结合printf使用。

我一个同事,调试飞利浦的ARM7,由于飞利浦ARM7外扩的RAM全部是静态RAM,即使在CPU死机情况下,只要不断电,SRAM里的数据也不会丢失,由于SRAM和内部的SRAM统一编址,所以,访问起来也就是一条读写指令,速度很快。利用这个特性,他把程序的模块和点全部标记上,当系统运行不正常,将ARM7复位以后,ARM7上电第一个工作就是取出复位前的数据打印出来。由此可调试ARM7的代码,非常巧妙的办法。如果只有SDRAM的朋友们是不能用这种办法的,因为只要系统复位,SDRAM没有刷新,数据即会丢失。

使用哪种办法,都有利有弊,在嵌入式系统里权衡的东西比PC平台多得多……欢迎和我讨论嵌入式系统的复杂性的问题,谢谢。