查看: 690|回复: 1

比特世界病虫害防治指南(二)

[复制链接]
  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3303

    主题

    6550

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32067
    最后登录
    2024-4-30
    发表于 2022-2-11 17:49:31 | 显示全部楼层 |阅读模式
    比特世界病虫害防治指南(二)
    有了上次的开场,(错过开场白的小伙伴请点击这里)相信大家对HardFault的设计初心,外在表现都打量了一番。这一次,咱们就从分析CPU给抓到的事故现场开始,踏上和HardFault的相爱相杀之旅吧。

    找到事故现场
    在发生Fault后,Cortex-M CPU按中断/异常通用流程,把关键的寄存器压入发生fault时正在使用的栈中,并且随后切换到由MSP指向的栈区。
    在Cortex-M CPU上,栈是向低地址使用的,并且栈指针总是指向最新的已经压入栈中的数据,这也被称为“向下生长的满栈”,长得大概像下图这个样子:
    15.png
    对于判定事发时的上下文,自动压栈的寄存器提供了非常重要的分析线索。之前提到的通用print打印到串口或SWI那里,都是显示这些压栈的值。


    除此之外,小编还非常推荐使用IDE所带有的调试会话界面。现在所有IDE中都提供了观察存储器的方法,一般被称为"Memory"窗口。
    KEIL允许我们直接使用寄存器的名字来输入待观察的地址,非常方便。比如,回顾一下下面这张图吧:
    16.png
    从图中可见,入栈的现场寄存器按照入栈地址,依次为 R0, R1, R2, R3, R12 (IP), R14 (LR) R15 (PC), XPSR。所以在进入fault处理程序后,原来正在使用的栈指针指向的就是地址最低的R0寄存器,也就是“刚刚入栈”的那里。
    现在所有IDE中都提供了观察存储器的方法,一般被称为"Memory"窗口。KEIL允许我们直接使用寄存器的名字来输入待观察的地址,非常方便。
    细心的读者可能会发现压栈的寄存器中有好几个奇怪的“0x8”开头的地址,并且有奇数还有偶数。其实,这些都是代码区的地址,刚好是因为这款实验用的MCU把SDRAM区域映射到了0x8开头的地址中。如果是换了您在用的MCU,还请对应成您的程序地址区。
    奇数地址的指针,一般都是函数指针,包括存储在LR寄存器中的函数返回地址,末位的奇数只是表明函数要运行在Thumb状态。而PC这个寄存器比较特殊,总是真实地反映当时的地址,而Cortex-M CPU上的指令长度必为2字节或4字节,所以总是偶数。
    备注:IDE的Memory窗口一般是把小地址放在上部,所以“向下生长的满栈”在这里看上去反而像是向上生长的。备注:事实上,CPU在压栈时,寄存器保存的时间顺序和空间顺序是不同的,但对于分析现场,看空间顺序就够了。


    分析自动压栈的寄存器 – PC
    找到了入栈的寄存器,就好比到达了翻车现场。发生交通事故时,首先就是要看现场当事车辆。同样道理,分析入栈的寄存器,首先也是要看入栈的PC内容,这直接给出了发生fault时程序执行到了哪里,通常来说,对应的指令就是肇事者。
    不过,凡事皆有例外,比如后面会介绍的”ImpreciseBusErr”类型的错误就不能通过PC找到准确的指令,需要额外动些脑筋。
    拿到PC的值后,最需要的是通过这个地址找到对应的源代码在哪里。这里介绍两种常用的方法。第一种,如果是在IDE的调试会话中,有源代码。这时,可以打开IDE的反汇编窗口,右键点击弹出上下文菜单,一般有类似“显示某处的反汇编”的菜单项。比如,下图显示了在KEIL下的例子:
    17.png
    点击此项,会弹出一个对话框要我们输入目的地址。
    18.png
    这样,反汇编窗口就会显示以肇事者指令为中心的前后指令和对应的源代码了。


    第二种,用于无法使用调试会话的场合。比如,在已上线的产品上出了问题,从dump出的log来分析。这时,就需要找到程序所对应的“map文件”,从中找出此地址对应的函数。Map文件其实是一个变量与函数的布局报告,最关键的就是给出了各变量和函数都被安置在何处:既包括烧写时的位置,也包括运行时的位置。


    对于一般的程序代码,烧写的位置就是运行的位置,这种代码也俗称为“XIP”代码。但对于全局变量的初值表,以及开发人员手工指定搬到内存里运行的关键代码段,它们的运行时位置和烧写时位置就不一样了。不同工具链的链接器在map文件中写入的数据格式不同。


    在使用map文件查看对应的函数时,有一个小坑,那就是map文件记录的都是函数入口地址,而肇事地址却位于函数内部,所以常常在map中找不到对应的地址。所以咱们在查找时,需要去掉最后两、三位再查。这可能导致查出多个结果,咱们再耐心筛选出真正的函数体。

    分析自动压栈的寄存器 – LR
    除了压栈的PC,压栈的LR也常常给出了锦上添花的线索,那就是“程序是怎么样走到这一步的”——函数调用链的回溯。


    在Cotex-M CPU中,LR的主要用途是保存函数的返回地址,比如,在前面的入栈现场例子中,LR处压入的地址是0x80012573,表明函数返回后执行0x80012572处的指令。


    于是,LR的线索,在缺失源代码等原因导致IDE的call stack窗口无法正常显示调用链,或者无法使用调试会话时格外有用,能帮助我们顺藤摸瓜,追根溯源。


    不知是否有用心的读者跳出来challenge一下:你就这么确定LR存储的一定是返回地址?倘若是遇到复杂的子程序,很可能把LR先PUSH进去后,临时挪用存别的东西呢!


    首先,如果您想到了这里,先容小编为您点个赞!确实,不能确保LR处存储的就一定是返回地址。因此,我们需要进一步确认。这时调试会话中的反汇编窗口就又大有裨益了,我们可以查看LR地址前面的那条指令,是不是“BL”或者“BLX”。在Cortex-M CPU中,这两条指令都用来调用子程序,那么可想而知,子程序返回的位置就应该在BL或BLX紧挨着的后面。所以,只要压栈的LR地址之前的指令是BL或BLX,就可以断定这个LR确实是要返回的地方了。


    不过,这个的方法也只能找到直接的主调函数,如何才能回溯出完整的调用链呢?或者如果不幸的是LR存储的并非返回地址呢?遇到这种困难,咱们就需要拿出筛查疫情的精神来了。


    咱们需要回看栈区中的内容,都按32位整数来仔细观察:凡是能“莫须有”成代码段地址的整数,都先假设它可能就是返回地址,再按上文说“查看在这之前是否是BL或BLX指令”的方法一一确认。


    不得不说,即使是有条件使用IDE的调试会话,这也是个脏活累活;如果是手动查map文件那就太苦逼了。所以,如果您有这个需求,小编还是推荐使用armink大侠提供的cmbacktrace库。只要告诉它代码段的地址范围,这个库就会自动逐一筛查并判定出调用链来。

    分析自动压栈的寄存器 – XPSR
    XPSR作为Cortex-M CPU中的状态寄存器,有时也会给出一些有用的线索,但是用途不如PC和LR广泛。这里就举两个例子。
    XPSR里面有一个状态位叫“T”位,指示当前是否在thumb状态。倘若为零,显然表示了程序错误地试图把它改成arm状态。
    再如,它里面还有ICI/IT字段用于记录LDM/STM (一次在内存和寄存器之间传送多个数据字)指令的执行进度,倘若修改了返回地址(比如使用longjmp函数)使得不再是原来的LDM/STM指令,也会导致HardFault。


    小结
    在本篇中,咱们介绍了Cortex-M CPU为我们自动保存的“事故现场”,并且重点分析了如何寻找肇事指令,以及顺藤摸瓜找回来龙去脉。


    对于Cortex-M0, M0+, M23这样的CPU,这就是主要的手段了。但是在更高端的Cortex-M3, M4, M7, M33,以及最新的M55上,CPU还提供了发生Hard Fault时的更多细节(还记得前一篇提到的15种异常状态吗)?千万不要小看这些信息,它们常常帮我们立即缩小包围圈,有的放矢地分析问题。那么后面,就容小编一一为大家道来吧,一定要看哦!

    签到签到
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    9 小时前
  • 签到天数: 1483 天

    [LV.10]以坛为家III

    203

    主题

    2万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    92962
    最后登录
    2024-5-6
    发表于 2022-2-12 10:55:18 | 显示全部楼层
    最近抓虫快让我发狂了……
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-5-6 22:59 , Processed in 0.117066 second(s), 21 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表