程序跑飞是软件开发常见的问题,无论是在产品开发过程中,还是在批量量产后都可能会出现,本文探讨如何快速查找导致程序跑飞的代码,分析程序跑飞几种原因以及提出相关的解决方法。
程序跑飞通常代码会进入非法中断,首先需要定位到运行哪行代码会导致程序跑飞,通过RPC寄存器的值可以查看非法中断的返回地址,即在代码跑飞之后,在CCS寄存器窗口查看Core Register的RPC的值。
需要注意RPC只保存了上次由LCR指令跳转过来的返回地址,对于其他跳转指令如"LB #22bit address"等,RPC则提供不了有效信息。
如果是LB指令,则需要从堆栈里查找,如TMS320C28x CPU and Instruction Set (Rev. F)手册所说,堆栈指针SP往回偏移7和8个字节的地址保存了上次跳到非法中断的返回地址。
例如下图的C代码 “(*(void (*)(void))0x0090000)()”的汇编代码包含了LCR指令,当该代码从地址0x81f7b执行LCR跳转指令时,会跳到0x90000继续运行,而0x90000是只有0xFF内容的无效指令的Flash地址,因此必然会跳到非法中断。
这时可以从RPC寄存器得到地址为0x81F7E,也就是指向"(*(void (*)(void))0x0090000)()"代码的地址。
而堆栈0xC022~0xC023(当前堆栈指针0xC02A偏移7~8个值)保存了0x0090000的地址信息,也就是执行地址为0x0090000的非法指令FFFF导致了异常。
因此,RPC寄存器只保存了LCR跳转指令的返回地址,而堆栈则是始终保存了上一个跳到非法中断的返回地址,但如果堆栈被破坏了或者返回地址存的指令本身是非法的,则RPC指针有时候可以提供更为有效的信息,在实际调试中,两者可以结合起来,帮助找到程序跑飞的原因。
不过有时候即使找到了具体哪行代码导致程序跑飞,也未必知道根本的原因,根据C28x Interrupt FAQ (ti.com)提供的原因,有下面几种情况下会导致非法中断。
下面从方便调试的角度对可能导致非法中断的原因进行分类,以提供进一步解决该类问题的思路。
1. 堆栈溢出导致程序跑飞
cmd文件里面会定义分配给堆栈的RAM空间大小,但实际堆栈所占的空间是动态的,只有程序跑起来后才知道堆栈的实际占用的空间有多大,因此如果cmd文件里面定义堆栈太小,编译器是不会报错提醒的,这个时候运行代码,堆栈数据会溢出,侵占了其他RAM的空间,刷掉RAM程序或者全局变量的内容。
Stack_Data : origin = 0x008030, length = 0x000070
RAM_Code : origin = 0x0080A0, length = 0x000600
.stack : > Stack_Data, fill=0xAA
例如下面的函数updateDisplay()定义了很多临时变量,运行的时候需要动态分配大量的内存空间。
在运行updateDisplay()之前,如下面0x0080A0地址所示存放的是RAM的代码,这是初始化之后就应该固定不变的。
但跑完这个函数后,堆栈空间数据溢出,RAM代码被堆栈数据所刷新,导致程序跑飞。
解决方法:
通过在cmd文件中初始化stack堆栈空间,如初始化填充了0xAA,在代码运行起来的时候,观察有多少0xAA的值被动态更新了,这样大致能够确定代码所需要的堆栈空间,一般设置需要留一定的裕量,如果不够,可以把下面分配给堆栈的空间Stack_Data设大一些。
Stack_Data : origin = 0x008030, length = 0x000070
.stack : > Stack_Data, fill=0xAA
另外还需要注意的是堆栈的指针是16位的,因此存储堆栈区域的地址是不能超过0xFFFF的,否则也可能会出现程序跑飞的情况。
2. 在RAM运行的函数没有从Flash中拷贝成功
如果函数声明在RAM里面运行,但在应用程序中并没有用memcpy函数从Flash拷贝到RAM,这时候程序运行就会跑飞。
例如下面sampleADC()函数声明放在段.TI.ramfunc里面,而.TI.ramfunc在cmd文件中的定义是从Flash装载,然后在RAM里面运行,这个时候编译器会自动分配RAM空间给sampleADC函数,但不会自动把代码从Flash拷贝到RAM,
#pragma CODE_SECTION(sampleADC, ".TI.ramfunc")
//将函数存在段.TI.ramfunc所在的RAM空间
如下图所示函数sampleADC()分配的起始RAM地址为0x0080E8,但这部分RAM空间的值都是0,即还没有从对应Flash程序中拷贝内容,运行该函数将会导致程序跑飞。
解决方法:
确保所有在RAM运行的函数调用之前,必须先用memcpy函数把代码从Flash拷贝到RAM。
memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t)&RamfuncsLoadSize);
DELAY_US(1000);
InitFlash();
3. 代码所占Flash空间靠近无效内存边界
对于代码空间,需要注意存储的代码在flash或者RAM的空间不能太靠近边界,如F28003x的Errata所说,例如下面所说的0x0AFFF0~0x0AFFFF是不能用的,否则有可能出现异常。
解决方法:
在cmd配置中,不使用地址为0x0AFFF0~0x0AFFFF的Flash空间。
4. 非加密区程序访问加密区的数据
如果对代码使用了DCSM/CSM模块进行加密,需要注意的是非加密区的程序不能访问加密区的数据。
另外以下几种情况容易被忽视,需要特别注意:
解决方法:
确保非加密的程序不能访问加密区数据,特别注意ROM和部分RAM空间是不加密的,调用了这里的函数不能访问加密区的数据。
通过RPC的值或者堆栈保存下来的返回地址,可以找到具体哪行代码导致程序跑飞,以帮助进一步分析原因。
而程序跑飞的原因有很多,比如堆栈大小不够导致溢出,运行了没有被成功初始化的RAM程序,代码跑到Errata所说的禁用内存区间,或者用了DCSM加密模块而有非加密程序访问了加密的数据等等。
当然还有其他因为软件bug导致的原因,如果CCS编译之后有warning的提示,一般都要尽可能消掉,否则会存在难以排查的潜在风险。