本文包含原理图、PCB、源代码、封装库、中英文PDF等资源
您需要 登录 才可以下载或查看,没有账号?注册会员
×
全文请见附件
教你如何找到导致程序跑飞的指令.pdf
(276.96 KB, 下载次数: 29)
在word里写好了,在论坛里不好排版,请下载附件观看全文
调试嵌入式程序时,你是否遇到过程序跑飞最终导致硬件异常中断的问题?遇到这种问
题是否感觉比较难定位?不知道问题出在哪里,没有办法跟踪?尤其是当别人的程序踩了自
己的内存,那就只能哭了:(
今天在论坛上看有同学求助这种问题,正好我还算有一点办法,就和大家分享一下。
解决办法非常非常简单,本文将以Aduc7026(ARM7 内核)和LM3S8962(Cortex 内核,
STM32 也是cortex 内核,同理)为例,讲讲解如何定位此种问题。
先说ARM7 内核,cortex 内核稍微有一点复杂,后面再说。
ARM7 内核有多种工作模式,每种模式下有R0~R15 以及CPSR 共17 个寄存器可以使
用,有关这些寄存器的细节我就不详细介绍了,详细的介绍请参考“底层工作者手册之嵌入
式操作系统内核”中的2.2~2.3 节,这里只介绍与本文相关的寄存器。
其中R14 又叫做LR 寄存器,它被用来保存函数、中断调用时的返回地址,看到了吧,
它保存了“返回地址”!这不就是我们需要的么?就这么简单,发生异常中断时,LR 寄存器
中保存的地址附近就会有导致异常的指令。
接下来我们再先了解一下相关的知识,然后再通过一个例子构造一个指令异常,然后再
反推找到产生异常的这条指令,做一个实例演练!
当程序跑飞时,绝大部分情况都会触发硬件异常中断,硬件异常中断的中断服务函数在
中断向量表中有定义,我们来看看ARM7 的中断向量表,在keil 开发环境里(以下例子是
在keil 环境下介绍的),这个文件一般叫startup.s,如下:
Vectors: LDR PC, Reset_Addr
LDR PC, Undef_Addr
LDR PC, SWI_Addr
LDR PC, PAbt_Addr
LDR PC, DAbt_Addr
NOP /* Reserved Vector */
LDR PC, IRQ_Addr
LDR PC, FIQ_Addr
Reset_Addr: .word Reset_Handler
Undef_Addr: .word ADI_UNDEF_Interrupt_Setup
SWI_Addr: .word ADI_SWI_Interrupt_Setup
PAbt_Addr: .word ADI_PABORT_Interrupt_Setup
DAbt_Addr: .word ADI_DABORT_Interrupt_Setup
IRQ_Addr: .word ADI_IRQ_Interrupt_Setup
FIQ_Addr: .word ADI_FIQ_Interrupt_Setup
ARM7 的中断向量表比较简单,只有7 种中断,它把所有正常的中断都放到了SWI、IRQ
和FIQ 中了,那么本文所介绍的异常情况将会触发Undef、PAbt 或者DAbt 异常中断,至于
是哪种就需要看具体的原因了。
指令A //触发异常
指令B
比如说当指令A 无法执行时,它就会触发异常中断,硬件就会自动将这条指令后面的
指令的所在地址,也就是指令B 的地址保存到LR 寄存器中,然后就跳转到与这种异常相关
的中断向量表中,假如指令A 触发了Undef 异常中断,那么硬件就会跳转到中断向量表的
第二个中断向量Undef_Addr,从中断向量表可知,这个中断向量对应的中断服务函数就是
ADI_UNDEF_Interrupt_Setup,这个函数一般是一个死循环,这样单板就死了,当我们停下
程序时,就会发现程序停在了这个函数里面。
我们来看下面这个实例,我把定位过程的每一步都记录下来,一起来看下:
14 S32 main(void)
15 {
16 U8* pucAddr;
17
18 /* 初始化硬件*/
19 DEV_HardwareInit();
20
21 /* 创建任务*/
22 WLX_TaskInit(1, TEST_TestTask1, TEST_GetTaskInitSp(1));
23 WLX_TaskInit(2, TEST_TestTask2, TEST_GetTaskInitSp(2));
24
25 /**********此指令会触发异常中断**********/
26 pucAddr = (U8*)0;
27 *pucAddr = 0;
28 /****************************************/
29
30
31 /* 开始任务调度*/
32 WLX_TaskStart();
33
34 return 0;
35 }
上面这段测试代码是我在我写的一个小型嵌入式操作系统上改的(有兴趣的话可以访问
我的博客O(∩_∩)O),只需要关注26 和27 行即可,其余的只是陪衬,以使这段程序看起
来稍微复杂一些。这两行指令将0 地址清0,0 地址是中断向量表,向这个地址写数据会导
致异常的,但——这正是我们所需要的。
然后,为了方便,我们在中断向量表里把上面的3 个异常中断向量都修改一下,如下:
Vectors: LDR PC, Reset_Addr
LDR PC, FaultIsr
LDR PC, SWI_Addr
LDR PC, FaultIsr
LDR PC, FaultIsr
NOP /* Reserved Vector */
LDR PC, IRQ_Addr
LDR PC, FIQ_Addr
这样,只要发生异常中断就都会进入FaultIsr 函数,FaultIsr 函数如下:
void FaultIsr()
{
while(1)
{
;
}
}
可以看到FaultIsr 函数是个死循环,所以当程序发生异常跑飞时就会死在这里了。
准备工作完成,准备实战演练!在这之前还有一点需要注意,那就是最好将编译选项设
置为不优化,这样方便我们定位问题。当然,实际情况也许不允许我们这么做,这样的话就
需要你有比较高的汇编语言水平了,这不在本文讨论之内,先不管了。我们在这个例子里将
编译选项设置为不优化。
我们将上面改动后的代码重新编译,然后加载到单板里,进入仿真状态,然后全速运行,
然后再停止运行,我们就可以发现程序死在FaultIsr 函数里了,如下图所示:
图1
从图1 可以看到程序停在了42 行,这与我们的设计是一致的。在图1 的左侧显示了此
时各个寄存器内的数值,注意到LR 寄存器了吧,这里保存的就是返回地址,出错的指令就
在这附近。但,还有一点需要注意,FaultIsr 函数是C 语言函数,它运行时可能会修改LR
寄存器,如果是这样的话,那么此时LR 寄存器内的数值就不是发生异常时的值了,为解决
此问题,我们可以找到FaultIsr 函数的起始地址,将断点打在FaultIsr 函数的起始地址,这
样当异常发生时就会停在断点的地方,也就是FaultIsr 函数的起始地址,这样就可以保证LR
寄存器的值就是发生异常时的值了。
如果你的汇编语言足够好,那么你可以在图1 右上角的汇编窗口里向上找,找到FaultIsr
函数的起始地址。另外,我们还可以通过一个简单的方法找到FaultIsr 函数的起始地址。我
们在keil 的选项中选择生成map 文件,代码编译后就会生成一个map 文件,我们可以从这
个文件里找到FaultIsr 函数的地址。
使用一个文本编辑器打开这个map 文件,然后搜索“FaultIsr”,如下图,我们就找到了
FaultIsr 函数的起始地址:0x80608。
图2
在汇编窗口找到0x80608 的地址,打上断点,如下图所示:
图3
复位程序,再重新全速跑一遍,我们就会发现程序停在了断点上,这时LR 里面的数值
就是程序异常时存入的返回地址,通过这个地址差不多就可以找到出错的指令了。
如图3 所示,LR 的值为0x805ec,我们在汇编窗口里跳到这个地址,如下图所示:
|
|