c - 如何从 Cortex M4 中的异常处理程序中保存调用堆栈?
问题描述
这是我想要实现的目标:每当我收到硬故障或看门狗中断时,我都会将前一条指令的地址保存到某个 RAM 位置,以便在复位后仍然存在。
Kinetis M64 看门狗在它发出复位之前给了我 256 个 CPU 周期,这应该有足够的时间来保存一些东西。问题是我在哪里可以找到这些地址?当一个 IRQ 发生时,LR 持有一个异常值而不是实际的返回地址。
我想在没有附加 SWD 探针的情况下执行此操作,因此设备可以在出现问题时自行报告。
解决方案
我认为,你能做的最好的事情就是找到处理程序返回的指令的地址,如果它被允许返回的话。这通常是导致故障的指令之后的指令,尽管不能保证(例如,如果故障是由分支指令引起的)。
在进入处理程序时,链接寄存器包含一个代码,除其他外,该代码告诉您异常发生时哪个堆栈正在使用中。参见此处的 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 作为第二个参数。
推荐阅读
- android - 如何解决无法获取提供程序 androidx.lifecycle.ProcessLifecycleOwnerInitializer
- django - DRF:调用时会发生什么验证
。节省()? - cassandra - Cassandra Appender - 如何仅记录错误消息?
- git - 现实世界中的 git 回滚,在工作环境中
- python - 将月份和年份转换为日期时间
- django-rest-framework - Django Rest Framework 中的用户特定序列化程序
- android - 管理后台堆栈中的片段
- amazon-web-services - 来自 Windows 网络的 AWS EC2 服务器连接错误:连接超时错误
- r - 在 tibble 变量名中使用特殊字符,例如上标
- javascript - 是什么导致javascript函数不运行它的回调