基于rtthread了解C语言函数调用栈

在系统运行或调试过程中,程序总是出现一些出乎意料的现象(例如程序异常或者某个任务运行不是预料的)。其中我们比较常见的是Hard Fault 错误,一般因为硬件造成 Hard Fault 错误的可能性较低,大多数都是软件原因造成的。所以遇到硬件中断错误,基本就是通过软件来排查。比较常见的错误有以下几种。

  1. 使用了空指针;
  2. 对地址偏移量的计算有误;
  3. 数组越界导致程序出错;
  4. 动态内存使用不当,导致访问了已释放的内存地址;
  5.  通过地址访问了已失效的局部变量; 

当出现这样的错误时,那么我们怎么能快速定位问题所在处,其实是我们可以通过调用栈定位问题。 大家都知道函数调用是通过栈来实现的,而且知道在栈中存放着该函数的局部变量。但是对于栈的实现细节可能不一定清楚,有人觉得没必要了解的这么深入,其实不然,了解系统深层次的原理对分析疑难问题有很好的帮助。就像熟悉抓包是解决网络通信问题一样,熟悉函数调用栈则是分析程序内存问题的高级武器。我们先从整体上来看一下函数调用栈的主要内容,如图所示。在函数栈中主要包括函数参数表、局部变量表、栈的基址和函数返回地址。这里栈的基址是上一个栈帧的基址,因为在本函数中需要使用该基址访问栈中的内容,因此需要首先将上一个栈帧中的基址(LR)压栈。

本文以rtthread开发为例,介绍应用程序调用栈的实现原理。在RT-Thread的异常处理函数中,可以打印出”发生错误瞬间”的所有寄存器。我们调试时,可以根据这些寄存器,知道发生错误的位置。但是,光知道发生错误的位置还不够!比如,根据打印信息知道在C函数里发生错误,但是你无法确定是在哪个调用链上出错:

  1. A -> B ->C时出错?
  2. D > C时出错?

我们希望得到:出错时的函数调用关系怎么办?我们知道C语言函数的返回地址会保存在栈里,我们可以分析函数调用栈来查找调用关系

        在RT-Thread 中发生错误时会打印发生错误瞬间寄存器的值(context)。

我们同时增加将当前线程的线程栈内容打印出来,运行示例程序后打印

下面根据程序段错误发生时保存的寄存器值以及程序的反汇编代码分析程序的调用栈。

根据PC(0x08000794)值在反汇编文件中找到发生错误的位置。

发现函数C太简单,它根本没有使用栈,函数C执行完,直接返回到LR(在rtthread打印的信息中),LR=0x0800076f,去掉bit0,就是:0x0800076e根据这个值,在反汇编文件中找到函数C的调用者,是函数B,在函数B的入口处,发现使用了8字节的栈,并且保存了R4,LR的值

根据线程的线程栈打印,确定函数B入栈时的LR,R4的值,返回地址LR=0x08000741,bit0清零后就是0x08000740。

在反汇编中搜0x08000740,确定函数B的返回地址是函数A,同样在函数A的入口处,发现使用了8字节的栈,并且保存了LR,确定返回地址LR=0x080099a1,bit0清零后就是0x080099a0这是thread1_entry函数

根据以上分析,得到的最终调用结果(thread1_entry->A->B->C)与我们程序调试打印调用关系一致 。

大家对函数调用栈有个整体的了解,这样对以后程序的疑难杂症就有更多的解决思路。因为在实际生产环境中与栈相关的问题也是比较多的,比如局部变量太多导致的栈溢出,或者踩内存问题引起的栈破坏等等。因此,了解了函数栈的原理,在遇到所谓的莫名其妙问题的时候就会有新的思路,本文部分使用了韦东山老师的文档。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值