首页 > 解决方案 > 如何从 Cortex M4 中的异常处理程序中保存调用堆栈?

问题描述

这是我想要实现的目标:每当我收到硬故障或看门狗中断时,我都会将前一条指令的地址保存到某个 RAM 位置,以便在复位后仍然存在。

Kinetis M64 看门狗在它发出复位之前给了我 256 个 CPU 周期,这应该有足够的时间来保存一些东西。问题是我在哪里可以找到这些地址?当一个 IRQ 发生时,LR 持有一个异常值而不是实际的返回地址。

我想在没有附加 SWD 探针的情况下执行此操作,因此设备可以在出现问题时自行报告。

标签: cinterrupt-handlingcortex-mthumb

解决方案


我认为,你能做的最好的事情就是找到处理程序返回的指令的地址,如果它被允许返回的话。这通常是导致故障的指令之后的指令,尽管不能保证(例如,如果故障是由分支指令引起的)。

在进入处理程序时,链接寄存器包含一个代码,除其他外,该代码告诉您异常发生时哪个堆栈正在使用中。参见此处的 Cortex-M4 示例。

就在跳转到异常处理程序之前,CPU 会将r0-r3, r12, LR( r14), PC( r15)推xPSR送到活动堆栈。如果您的设备具有 FPU 并且已启用,则浮点上下文也可能会被推送,或者为它留出空间。ARM堆栈是完全降序的,寄存器按寄存器编号升序存储在升序的内存地址中,因此在进入异常处理程序时,无论哪个堆栈指针正在使用,都将指向堆栈的值r0;因此,上面的 6 个字(24 个字节)将是 的堆叠值PC,这是异常处理程序的返回地址,这是理所当然的。

因此,假设它不是由分支引起的,在导致错误的指令之后查找指令的过程是:

  • 检查LR以找出正在使用的堆栈
  • 将适当的堆栈指针加载到空闲寄存器中(r0-r3全部可用,因为它们在进入处理程序时被推送)
  • 读取此堆栈指针上方 24 字节的字以找到处理程序的返回地址

导致故障的指令是位于该返回地址之前的 2 个字节还是 4 个字节,当然取决于指令,Thumb-2 指令集是 16 位和 32 位混合的。当然,它可能完全位于其他地方!

请记住,如果在MSP发生故障之前正在使用 ,则处理程序将使用相同的堆栈,并且只有在处理程序函数的序言中没有将任何内容推送到堆栈时,所有这些才会起作用。最简单的事情可能是用汇编语言编写处理程序。它总是可以在处理完堆栈后调用一个 C 函数来完成您想到的任何终止过程。

最后一件事,可能还值得保存LR. 如果 的堆叠值PC告诉您没有任何用处(例如,因为它为零,代码试图跳转到无效地址),那么 的堆叠值LR至少会告诉您BL遇到最后一条指令的位置,如果您'幸运的是,这将是导致故障的分支。即使您没有那么幸运,它也可以帮助您缩小搜索范围。

代码

这里有一些(未经测试的)代码可能会做你想做的事。它是用 ARMASM 语法编写的,因此如果您使用不同的工具链,则需要更改奇怪的东西:

    IMPORT cHandler

    TST   lr, 0x4       ; Is bit 2 of LR clear?
    ITE   eq
    MRSEQ r3, MSP       ; If so, MSP was in use
    MRSNE r3, PSP       ; Otherwise, PSP was in use
    LDR   r0, [r3, #24] ; Load the stacked PC into r0
    LDR   r1, [r3, #20] ; Load the stacked LR into r1
    B     cHandler      ; Tail-call a C function to finish the job

如果 C 函数cHandler有原型

void cHandler(void * PC, void * LR);

那么上面汇编语言处理程序的最后一行将调用此函数,将恢复的堆叠 PC 作为第一个参数,将恢复的堆叠 LR 作为第二个参数。


推荐阅读