标题:
教你如何找到导致程序跑飞的指令(2)
[打印本页]
作者:
yuyang911220
时间:
2015-4-29 16:44
标题:
教你如何找到导致程序跑飞的指令(2)
这样,只要发生异常中断就都会进入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
,我们在汇编窗口里跳到这个地址,如下图所示:
图
4
ARM7
内核有
2
级流水线,存入
LR
的地址一般会多
+8
个字节,因此
0x805ec-8=0x805e4
,如图
4
所示,
0x805e4
地址是一条
STRB R2
,
[R3]
指令,这条指令的意思是将
R2
寄存器里的数值保存到
R3
寄存器所指向的地址(一个字节)内。从图
3
左侧可以看到
R2
寄存器的数值为
0
,
R3
寄存器的数值也为
0
,那么这条指令的意思就是将
0
这个数值写入
0
地址这个字节内,这不是正好对应上述
main
函数中
27
行的
C
指令么?
看到这里我们就应该明白了,向
0
地址写
0
,这条
C
指令有问题,那么这个跑飞的问题也就找到原因了,是不是很简单?
当然,实际情况可能要比上述介绍的情况复杂的多。实际使用的程序几乎都是经过优化的,这样从汇编指令找到
C
指令就会比较麻烦。还有可能
FaultIsr
函数的指令或者堆栈被破坏了,那么
FaultIsr
函数运行都会出问题。还有可能出错的指令不会象
27
行这么明显,可能是经过了前面很多步骤的积累才在这里触发异常的,最典型的就是别人的程序踩了你的内存,结果错误在你的程序里表现出来了,如果遇到这种情况你就先哭一顿吧。对于这种踩内存的情况也是可以通过这种方法定位的,但这相当复杂,需要从出错点开始到触发异常点为止,这之间所有的堆栈信息,然后从最后的堆栈开始,结合反汇编的代码,从最后一条指令向前推,直到发现问题的根源。这种方法相当于是我们用我们的大脑模拟
CPU
的反向运行过程,如果程序是经过优化的,那么这个过程就更麻烦了。我准备在“底层工作者手册之嵌入式操作系统内核”
6.1
节实例讲解一个这种情况(现在是
2012.02.28
,手册暂时只写到了
5.4
节)。
好了,先不说这么复杂的了,接着上面的继续说。
有时候出现问题的单板并不在我们手边,问题也许不能复现,那么我们就可以预先在
FaultIsr
函数里做一个打印功能——将出现异常时的寄存器、堆栈、软件版本号等信息打印出来,编写这样的
FaultIsr
函数需要注意,
FaultIsr
函数开始的代码一定要用汇编语言来写,以防止调用
FaultIsr
函数时的寄存器、堆栈信息被
C
语言破坏。
如果我们的单板有这样的功能,那么当单板跑死时,一般情况都会向外打印信息,比如上面的例子,就会打印出
LR
的值为
0x805ec
。但我们似乎又遇到了一个问题,我们如何知道
0x805ec
这个地址是哪个函数的?别忘了,我们在一个版本发布时会将软件所有的信息归档(什么?没归档!这样的公司我劝你还是走了吧),根据软件版本号找到出问题的软件的归档文件,取出
map
文件,利用上面讲述的方法通过
map
文件我们就可以找到出问题的函数了。再通过软件版本从归档文件中找到这个函数最终编译链接生成的目标文件,一般为
.o
、
.axf
、
.elf
等文件(必须是静态链接的文件,需要有各种段信息的),不能是
bin
、
hex
等文件,
windows
、
linux
等动态链接的文件已经超出了我目前的知识范围,也不再其中。
然后使用
objdump
程序进行反汇编,将目标文件与
objdump
程序放到同一个目录,在
cmd
窗口下进到这个目录,执行下面命令:
objdump -d wanlix.elf >> uncode.txt
这行命令的意思是将
wanlix.elf
目标程序进行反汇编,反汇编的结果以文本格式存入
uncode.txt
文本文件。
我们用文本编辑器打开
uncode.txt
文件,找到
0x805ec
地址,如下图所示:
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/)
Powered by Discuz! 7.0.0