打印
[其他ST产品]

(GCC)STM32进阶详解之栈回溯

[复制链接]
1002|76
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
接上一篇:

函数调用

由上一篇大概了解了函数是如何被调用,中断或者说异常又是如何被调用,而这一篇相当于上一篇知识的一个应用,也是上一篇遗留的思考,即在hardfault中如何判断是从何处触发这个异常的。本来打算自己写demo,但是查到github上有一个开源的CmBacktrace,既然有大牛已经写了开源的库,就直接拿来分析印证吧。项目地址:

CmBacktrace

硬件我使用的是STM32F103ZET6最小系统板,demo是项目中提供的,直接下载即可。

使用特权

评论回复
评论
大鹏2365 2023-1-15 16:52 回复TA
———————————————— 版权声明:本文为CSDN博主「我我我只会printf」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qwe5959798/article/details/122835924 
沙发
大鹏2365|  楼主 | 2023-1-15 16:53 | 只看该作者
原理分析

从串口1输出如下:

使用特权

评论回复
板凳
大鹏2365|  楼主 | 2023-1-15 16:54 | 只看该作者
实际使用项目中提供的addr2line运行打印出来的地址时,输出如下:

使用特权

评论回复
地板
大鹏2365|  楼主 | 2023-1-15 16:55 | 只看该作者
我们回过头来看一下demo,在app.c中的main里有调用fault_test_by_div0()这个函数,它里面故意做了除0运算:

使用特权

评论回复
5
大鹏2365|  楼主 | 2023-1-15 16:56 | 只看该作者

使用特权

评论回复
6
大鹏2365|  楼主 | 2023-1-15 16:57 | 只看该作者
仔细对比addr2line的输出,我们可以看到,输出结果是完全正确的,包括行号(有空行的情况可能会有点偏差)。其实addr2line这个程序的作用是通过你输入的地址和.elf文件,输出这个地址在.elf文件中表示的函数。原理不难理解,elf文件中完美保留了每个函数的地址,以及每个函数对应的.c文件,自然可以去对应.c中查看该函数行号。

使用特权

评论回复
7
大鹏2365|  楼主 | 2023-1-15 16:57 | 只看该作者
所以这里的关键其实在于我们给addr2line传入的三个地址:

addr2line -e CmBacktrace.axf -a -f 08001da6 08001dfc 08000268

打开.axf文件可以看到以上三个地址分别对应的函数:

使用特权

评论回复
8
大鹏2365|  楼主 | 2023-1-15 16:59 | 只看该作者

使用特权

评论回复
9
大鹏2365|  楼主 | 2023-1-15 17:00 | 只看该作者

使用特权

评论回复
10
大鹏2365|  楼主 | 2023-1-15 17:13 | 只看该作者

使用特权

评论回复
11
大鹏2365|  楼主 | 2023-1-15 17:22 | 只看该作者

使用特权

评论回复
12
大鹏2365|  楼主 | 2023-1-15 17:24 | 只看该作者
可以看到最后一个地址指向的08000268其实在源文件中是找不到的而且它保存的也不是某行代码,而是一个全局变量,所以addr2line打印的是问号,表示查找不到。

使用特权

评论回复
13
大鹏2365|  楼主 | 2023-1-15 17:24 | 只看该作者
从这里可以看出,我们的关键就在于找到出错误代码的地址,以及调用这个出错误函数的地方,当然,实际可能有很多个函数层层嵌套调用,而只需要依次找到上一层调用地址,就可以一层一层的递进。

使用特权

评论回复
14
大鹏2365|  楼主 | 2023-1-15 17:25 | 只看该作者
这里,我会再次分析整个函数调用流程,是对上一节内容的一个印证。请注意栈中保存数据在整个流程中的变化!

使用特权

评论回复
15
大鹏2365|  楼主 | 2023-1-15 17:26 | 只看该作者
首先我们把断点打在main函数的起始位置,此时现场如下:

使用特权

评论回复
16
大鹏2365|  楼主 | 2023-1-15 17:27 | 只看该作者

由上图我们可以知道几点:

1.调用mian函数结束后, 我们返回到调用main函数的地方,并执行下一个指令,它的地址是0x08000268(为什么减一我前几章有说过),这个地址实际对应

使用特权

评论回复
17
大鹏2365|  楼主 | 2023-1-15 17:28 | 只看该作者

实际这个地址保存的根本不是可以执行的代码,那为什么还要指明从main执行完后回来执行它?

由上图可以看到,我们实际跳转main是0x08000264,这里使用的指令是BL,这个指令会自动装载下一个指令地址到LR(即执行时PC指向的地址),保证调用完函数后,可以很方便的直接用汇编指令:B LR返回到上层函数,然而实际这个工程中,main函数根本不会返回,所以这个地址即使是错的,也无所谓。

使用特权

评论回复
18
大鹏2365|  楼主 | 2023-1-15 17:29 | 只看该作者
.因为我们main函数中还要再使用汇编指令BL和寄存器R4,所以我们需要把它们保存在栈里,防止被覆盖,所以可以看到main函数第一行汇编就是把LR和R4保存在了栈里。而此时的栈顶是SP所指向的0x2000 0560,打开.map文件可以看到:

使用特权

评论回复
19
大鹏2365|  楼主 | 2023-1-15 17:35 | 只看该作者
我们分配给栈的空间是从0x2000 0160开始,大小为0x400的空间,因为栈是从上到下生长的,所以栈顶一开始就是0x2000 0560。

使用特权

评论回复
20
大鹏2365|  楼主 | 2023-1-15 17:36 | 只看该作者
我们再次推进断点位置,这次把断点打在进入fault_test_by_div0函数前,具体现场如下:

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

50

主题

659

帖子

0

粉丝