STM32的backtrace深度讲解(cortex-m的栈布局与栈回溯的原理和方案)

发布时间:2024-01-04  
  • 1.说明

  • 2.cortex-m上的栈布局

    • 2.1 cortex-m上的寄存器

    • 2.2 cortex-m上的自动压栈

    • 2.3 cortex-m上的函数执行流程

  • 3.cmbacktrace原理分析

    • 3.1 问题分析

  • 4.实际应用

  • 5.总结

1.说明

对于一个嵌入式产品的开发流程来说,一般都需要经过如下几个阶段:

1.方案预研

2.产品功能设计

3.开发调试

4.工厂测试

5.产品上线售后

一般来说,1,2,3板子都是在开发者手上,一旦遇到bug,只要可以复现,基本上都可以排查出来,然后修复或者规避。但一旦进入到4,5阶段,产品已经成型之后,再想排查BUG就比较麻烦了。例如工厂测试阶段,有可能连续运行好几天或者好几个星期才能复现的问题,排查起来就十分的复杂。对于这种情况,backtrace是十分必要的。可以在离线的状态下分析系统的关键信息,通过函数的栈回溯,从而找到出错的对应的执行函数,然后结合程序设计,基本上大部分的bug基本上也可以找到。我之前写过一篇文章arm上backtrace的分析与实现原理。分析了在cortex-a上的分析情况。但是对于cortex-m来说,问题就会复杂许多,因为cortex-m对于固件的体积的限制以及特殊的架构,让backtrack的方案占用了过大的flash。这是设计者所不能接受的,而且更加难受的是cortex-m并没有栈回溯指针。这就让栈的深度的计算变的十分复杂。本文主要分析cortex-m的栈布局以及一些栈回溯的底层原理和方案。

2.cortex-m上的栈布局

在cortex-m上弄清楚栈的布局,就必须理解cortex-m上的压栈入栈的机制和原理。下面从该体系架构上说说cortex-m上比较重要的细节。

2.1 cortex-m上的寄存器

一旦涉及到C语言函数,必须要考虑到的问题就是函数的入栈出栈的问题,也就是SP指针的增加或者减少。下面还是来复习一下arm cortex-m上的寄存器。

按照arm cortex-m的设计,一共有32个寄存器。

  • 13个通用寄存器,r0-r12
  • 2个不同模式下使用的SP, PSP(SP_process) 和MSP(SP_main)
  • 1个链接寄存器LR(r14)
  • 1个程序计数器(PC)
  • 1个程序状态寄存器(xPSR)

在不同的模式下,R0-R12、SP、LR是各有一份的,所以这样算下来,总共是32个寄存器,但是在不同的模式下,并不能完全看到这32个寄存器的状态,只能看到其中的一部分。

通用寄存器R0-R12

上图将通用寄存器分为low register和high registers就是根据指令集来说的,对于thumb指令,是16位的,只能访问到low register,也就是R0-R7,而对于32位的arm指令,是所有的指令都可以访问到。所以有这样的划分。

栈指针SP

一旦涉及到参数的压栈与入栈,或者函数的执行返回的时候,必须会涉及到栈指针的变化。在cortex-m由于涉及到两种不同的sp的切换,所以在使用SP的时候要格外的小心。

程序链接寄存器LR

程序的链接寄存器在函数返回的时候会被使用到,比如一个函数A中执行的另外一个函数B,如下

void fun_A()
{
 fun_B()
}

那么当执行到fun_B的时候,首先编译器编译的汇编代码会将func_A的地址自动存放LR压栈,然后压入其他的参数。待func_B执行完成之后,会弹出LR到PC,此时就会返回到fun_A函数去执行了。

程序计数寄存器

该寄存器会自动指向当前指向的程序地址。

2.2 cortex-m上的自动压栈

不同于其他的处理器架构,cortex-m的定位一开始就是为实时性、小体积容量的设计考虑的,所以在中断处理这一块,也做了一个十分有意思的设计--自动压栈处理。

一般的CPU进入中断后都会去进行压栈操作,因为栈就是函数的现场,保护了栈内容,中断退出的时候只需要恢复栈数据就可以恢复到程序执行的状态了。以往这个阶段都是通过人工操作写程序完成的,在cortex-m上,将部分栈由硬件自动压入。其压入栈的顺序一般如下:

xPSR->PC(返回地址)->LR->R12->R3->R2->R1->R0

这些寄存器硬件自动压入,效率上应该有较大的提升。另外的一些寄存器可以手动处理。

2.3 cortex-m上的函数执行流程

在分析函数的执行的时候,主要是想弄清楚底层的硬件寄存器做了哪些操作,这就需要进行汇编翻译进行。此处我们用arm gcc编译出cortex-m的elf固件,通过objdump随便看一个函数体的执行。

对于一个arm函数的汇编代码,基本上都是上面的执行逻辑。根据指令机器码,得到对应的指令。

我们知道,在函数执行的时候,保存在内存上的都是机器码,只有在通过objdump工具的时候,才会将这些机器码变成程序。也就是说,在程序执行时,如果此时查看0x8004794这个地址,看到的数据是80b5 84b0这样的内容。那么这些又该如何进行翻译呢?该函数的sp指针到底该如何计算。

PUSH指令分析

PUSH指令所对应的机器码如下:

1011 010R rrrr rrrr -- PUSH reg_list

按照解析,R表示的是LR寄存器,后面的是R0-R7寄存器的列表。所以解释起来机器码b580翻译成二进制b1011 0101 1000 0000。对应的实际含义就是压入LR与R7寄存器,当执行PUSH后,SP指针会自动减去两个寄存器的大小,也就是8个字节。

SUB指令分析

SUB指令对应的机器码如下:

1011 0000 1vvv vvvv -- SUB Sp,#immed_7*4

根据含义,v表示分别乘以4。也就是最低位为4,第二位是8,第三位是16,第四位为32,以此类推,得到其偏移的立即数。目前的机器码为b084 翻译成二进制为b1011 0000 1000 0100,所以表示的立即数为16.

两者结合,得到当前函数会使得sp指针的值减少16+8=24。

3.cmbacktrace原理分析

在做cortex-m上的backtrace的时候,查阅了一些资料,其中发现一个CmBacktrace。

https://github.com/armink/CmBacktrace

设计的目的:针对 ARM Cortex-M 系列 MCU 的错误代码自动追踪、定位,错误原因自动分析的开源库。

其实现的机理是利用cortex-m的压栈特性所决定的。当指定好栈地址后,sp指针就会在自己的栈空间内进行偏移。函数入栈的时候,会压入参数,也会压入lr寄存器,利用lr寄存器的值就可以找到是谁调用该函数的。

对于裸机情况,栈地址指向一个

当程序出现异常的时候,只需知道当前的栈顶以及当前的sp的偏移量,这些在程序中是很好得到的。然后开始便利栈中的数据,每四个字节遍历一次得到地址,该地址不一定是函数地址,有可能是参数的地址,人工去审阅这些地址的时候,只要细心一点是可以找到线索的。在CmBacktrace上通过判断地址的前面2个字节的thumb指令的机器码是否为BL或者bLx来进行判断该地址是否为函数。这样也是可以的。

如果在cortex-m上使用了操作系统,原理上基本上是类似的,由于每个线程都会有自己的线程栈,所以会有多个线程栈的情况。要想得到当前运行的线程栈的backtrack,原理上是和裸机一样。但是如果想要分析其他的线程的栈的backtrace,则需要注意操作系统的压栈问题。

例如在rt-thread中,进行线程切换的时候,会调用pendsv进行自动压栈一次,然后在手动压栈其他的寄存器。如果要做解析,首先去掉前面操作系统压栈的部分。rt-thread操作系统前面压栈的数据

#  xPSR->PC->LR->R12->R3->R2->R1->R0#  R11 R10 R9 R8 R7 R6 R5 R4 FLAG

一共压了16个寄存器,如果不做处理,解析到的PC为rt_hw_interrupt_enable,解析到的LR为rt_schedule。

3.1 问题分析

在对栈的解析过程中,我们往往会涉及到一些脏数据来破坏我们的分析。比如,参数中传递东西是函数的地址,这是读到的可能会误以为这是LR,这样分析起来会有一定的风险,虽然说在大多数情况下CmBacktrace的解析可以做的很好,但是遇到参数是函数地址的时候,就很难去做分析了,此时可能会借助人工来做分析。需要一定的工作量。那么有没有比较想的办法,不需要便利,直接跳转到下一个LR去执行呢?

根据在《2.3 cortex-m上的函数执行流程》的分析,我们基本上可以算出来一个函数的栈数据偏移,这样就可以顺利的解决这个问题了。每次都会跳转到固定的函数中,结合当前的数据栈的内容,从而得到想要的结果。

4.实际应用

上述的分析是有实际应用的价值的,在每次出错的情况下,我们可以保存栈的数据到掉电非易失性存储介质的某个特定的地址处,因为栈的大小并不会很大,一般512字节或者1k或者2k等等数据量,问题出现后,取出栈里面的内容,然后通过外部工具例如python脚本进行分析,与对应的elf文件结合起来,就能很准确的定位函数的backtrace了。然后对于问题的查询也会变得有迹可循,大大减少后期调试工作的复杂性。

5.总结

未雨绸缪是设计中必须考虑的问题,做出的产品都不能保证一点问题都不会出现,当出现问题的时候,也不用怕,因为有了分析的手段和数据。这样也能够减少产品设计的风险,做出更好用的嵌入式产品。


文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    方案的功能,因而可大幅降低成本。或者现在就可以开始采用我们的最大容量FPGA进行原型设计和构建系统仿真器,和通常的更新换代速度相比, 至少可以提前一年时间。” 从历史上看,FPGA 产品系列中的最大......
    储能大容量电芯能否标准化?;市场上,储能大容量电芯技术更新时间越来越短、速度越来越快。电芯容量的快速升级,将给市场和业主带来哪些影响?面对种类繁多,规格不一的产品,为引导行业有序竞争,又能否推进电芯容量......
    Flash系列产品将凭借自身超大容量、高速读取性能与高可靠性等优势大放异彩。 此次推出的GD25/55 B/T/X系列产品代表了SPI NOR Flash行业的最高水准,提供512Mb、1Gb和2Gb的容量......
    、中容量和大容量之分。 小容量IC指的是FLASH容量在16KByte到32KByte。 中容量IC指的是FLASH容量在64KByte到128KByte。 大容量IC指的是FLASH容量......
    第四代产品HBM3,速率则提高到了6.4Gbps,最大容量则增加到了24GB。HBM3所提供的内存带宽对实现下一代高性能计算、人工智能和百万兆级系统至关重要。基于过去两年存储行业低迷的行情,生成式AI的横......
    则在相同封装尺寸下提供了36GB的最大容量和1.18TB/s的带宽。3月份SK海力士宣布了HBM3E开始量产,将与台积电合作开发HBM4产品。【】 三星的HBM3E内存采用12层堆叠技术,提供......
    -E CoXProcessor架构,延迟更低、随机读写性能更好,可搭配2D/3D TLC NAND闪存,最大容量256GB,这也是当今智能手机的最大容量。 它支持StrongECC纠错技术,相比......
     BGA SSD搭配PCIe Gen4x2接口,NVMe 1.4协议,单颗最大容量为1TB,而尺寸仅为11.5x13(mm),最大顺序读写速度分别达到3500MB/s、3300MB/s。   在半......
    数字万用表电容档如何测电容好坏;如有说明书请参考说明书是最详细的,该表档用的是电容专用测试口(有电容标志)不是用表笔,档位选择要略大于电容容量,如不明容量则先用最大容量档后用接近该容量......
    型系列时钟频率达到72MHz,是同类产品中性能最高的产品;基本型时钟频率为36MHz,以16位产品的价格得到比16位产品大幅提升的性能,是32位产品用户的最佳选择。两个系列都内置32K到128K的闪存,不同的是SRAM的最大容量......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>