一次内存错误调试总结(平台Cortex-M3)

芯片:STM32F103CBT6
IDE:Keil
调试器:J-Link
操作系统:uCOS

从一开始怀疑硬件问题,到最后定义到内存错误的思路,
有些思维定式。
首先从最开始发现程序有一个地方通不过时,通过断点的逐步调试,
很快发现了是哪里,导致程序没有运行过去。
但是这里没有再继续缩小断点的范围,
从而漏掉了在最短的时间内,将问题解决的时机。
如果这个时候,再逐步缩小断点设置的范围,将程序通不过的地方,有几行代码,
缩小到一到两行代码,这时就比较容易去分析是什么问题,是什么原因导致了这个问题。

但是这时自己没有再去缩小断点的范围,而是将其运行通不过的范围,放在了9行代码上,
但是由于造成的这个通不过,是导致程序直接复位,这样就很难断定是什么问题。
虽然自己当时也常识分析,是什么原因导致代码,通不过这段程序,但是一直没有考虑到程序复位,
这个,应该是调试时的一个思维惯性,

为什么没有考虑到程序复位的情况?
应该是以为程序像其他步骤一样,没有接收到数据,或者接收到的数据不对,没有进行处理,
而是又回到了最初的步骤,开始执行。这点是在使用断点调试时很难发现的,因为没有在程序的运行开始处
设置断点,这样很难判断程序是否复位。
后来之所以发现这个复位的情况,是自己设置的一个用于调试使用的全局变量,这个值
在程序中有的地方进行赋值,这个调试使用的全局变量,只有在程序复位时才会是零。
由此,发现了程序复位的情况。
这个时候,使用串口调试可能更快的发现复位的情况。
或者就是,在程序的起始处设置一断点,如果程序复位就可以检测到。

于此得出一经验,在调试时最好在程序的起始端添加一断点,这样如果程序复位就可以马上检测到。
虽然一般,用不上,但是如果遇到了,可以节省我们很多的调试时间。

当检测到复位时,首先怀疑的是硬件原因。
因为程序已经在以前一个板子中跑了将近一年,而这次使用的是刚焊好的板子,
依照程序不会在两个板子中表现差异很大,因为毕竟这些都是数字电路。
没有模拟那么玄乎。所以首先怀疑是硬件问题,而在这一通不过的代码前,程序正常执行,
只有在这一段代码内出错,所以怀疑,是这一代码导致了电源不稳定导致了MCU复位。
但是金哥用示波器测试发现电源出来一只很稳定,即使是在程序通不过的地方,也没有出现什么异常。

这时,调试就有些陷入僵局。

于是,当程序运行到通不过的那一段代码时,暂停程序运行,查看代码在哪里停住,
发现程序运行到了
App_Fault_ISR       B       App_Fault_ISR
对于这个问题,以前并没有遇到过,于是在百度上搜了下,发现很多人碰到过这个问题。但是没有人说,是硬件引起的,
反而很多说是内存操作失误,或者栈溢出。
这样硬件导致复位的因素,可以暂时放一放,于是又回到了最早的那个断点的地方。

这个时候,终于想起来了,为什么不进一步缩小程序通不过的范围呢?
于是将第一个断点不变,将第二个断点,向前设置一行,发现依然没有运行到第二个断点,
于是在一次将第二个断点向前设置几行,直到程序运行到第二个断点处为止。
找到这一行后,
将第一个断点设置到这一行,
然后将第二个断点向后设置一行,发现这个时候,程序在第一个断点处开始运行后,没有走到第二个断点,
于是得出结论,程序在第一个断点处的代码导致了程序发生
App_Fault_ISR       B       App_Fault_ISR
于是在分析这一段代码,发现
是用的是strcat函数,这个函数是有可能造成内存错误(栈溢出的),因为如果它的第二个参数没有以'\0'结尾,
它就会以第二个参数的地址开始将内存中的内容复制到第一个参数中,直到遇到以第一个'\0',如果没有遇到就继续向第一个参数
写入。
于是这时在断点处查看,strcat的第二个参数的值,发现这个参数中的值全都是0XFF,这就导致了将第一个参数所在的栈内存破坏掉了。
至此找到了,程序复位的原因。


导致在新板子上出现问题的原因是,刚焊好的板子中Flash没有写入过参数,
而strcat的第二个参数的值,是从Flash中读取的,由于Flash默认读出的是0xFF,
所以造成了这个错误。
关于这个bug,有三个地方可以避免,
1、设置初始化默认参数,
2、在读出数据后,进行判断,如果不正确,将其设置为默认的参数。
3、在复制时,采用strncat,防止数据出错时,不影响堆栈。

在这里,分析下使用断点调试和串口调试
串口调试:
需要一定的技巧,
设置打印输出的地方,输出的打印信息,
在分析时,串口调试更直观的看到程序运行出现什么现象,
但是它很难判断到程序具体运行到什么地方,
所以串口调试的方法,有助于很快发现问题,
但是对于解决问题,它显得力不从心,
因为它很难判断程序运行到的具体代码行。

对于断点调试,
需要技巧,设置断点的位置,引入的调试的变量。
这一点上,断点调试显得没有串口调试那么直观,
因为对于程序运行的状态只能从设置的断点处去推测,
这时引入的调试变量,以及此时在断点处涉及到的变量的值就显得尤为重要,
调试变量的作用相当于串口调试中的打印输出,它的引入与设置,很关键,
直接影响到我们看到程序运行的轨迹。
当前的变量的值,指示了现在断点处的程序状态。
对于此,断点调试更适合在细节上使用,它有助于缩小问题的范围,
找到问题所在的代码行,能很快解决问题。

综上,
对于串口调试与断点调试,最好的方法是结合使用,
在宏观的调试上使用串口调试,在微观的调试上,
使用断点调试。
当然,在不支持断点调试的情况下,使用串口,led,液晶等显示
我们需要的信息,也是一种方法,判断的思路是一致的。
从宏观到微观。

Bug导致的原因分析:
程序在ETCPIP处复位(GPRS激活PDP)。
1、是否因为拨号时,电流过大,造成单片机电流出现问题。
2、最后发现,是程序内存溢出造成的。

因为,新板子中Flash没有配置参数,而原来程序中没有默认的初始化参数,
导致当从Flash中读取错误的数据时,读到的错误数据没有进行处理,
同时在复制数据时,使用的是strcat,由于数据错误,结尾没有'\0'
结果就一直在复制,造成了内存错误。
这里有三个地方可以,防止这个bug,
但是都没有做,
1、设置初始化默认参数,
2、在读出数据后,进行判断,如果不正确,进行相应的处理
3、在复制时,采用strncat,防止数据出错时,不影响堆栈。

由此知,在程序中,关于读出数据,和写入数据要进行Assert,
保证数据正确。


你可能感兴趣的:(一次内存错误调试总结(平台Cortex-M3))