我的目标是在看门狗定时器到期之前和复位之前使用WDT_IRQHandler()保存PC的值以进行调试.我也在用其他IRQ测试这种方法来检查我是否掌握了这个想法.
但似乎我没有.
我已经阅读了documentation.
我明白当异常发生时,8个寄存器被推送到堆栈:
R0,R1,R2,R3,R12,LR,PC和XPSR.
我还读过堆栈自动双字对齐.所以在我看来,检索返回地址就像这样简单:
>使用__builtin_frame_address(0)检索sp地址;
>添加堆叠PC的偏移量(0x18),并读取值,该值应该是处理程序返回时将恢复到PC的值.
检查附加的调试器,似乎不是这种情况,该内存地址的内容并不总是指向闪存区域,甚至不是指向有效区域,并且在任何情况下它都不是PC在POP指令.
代码工作正常,所以我认为这是一个问题,我理解它是如何工作的.
如果我检查反汇编,在某些IRQ中,在POPping(?)之前向sp添加一个常量
00001924: 0x000009b0 ...TE_IRQHandler+280 add sp, #36 ; 0x24 00001926: 0x0000f0bd ...TE_IRQHandler+282 pop {r4, r5, r6, r7, pc}
在其他IRQ中,这不会发生.
我知道可能会发生更多的寄存器被推送到堆栈,所以我怎么能确定在哪个偏移量来检索PC?
如果我在代码仍在IRQ处理程序中时检查SP周围的内存转储,我可以发现返回地址,但它始终位于一个奇怪的位置,与SP相比具有负偏移.我无法理解如何获得正确的地址.
您不能依赖C处理程序内部的堆栈指针,原因有两个:>寄存器总是被推送到抢占代码的活动堆栈.处理程序始终使用主堆栈(MSP).如果中断抢占了从进程堆栈(PSP)运行的线程模式代码,则寄存器将被推送到PSP,您将永远不会在处理程序堆栈中找到它们;
> C例程可能会为局部变量保留一些堆栈空间,而您不知道它有多少,因此您将无法找到寄存器.
这就是我通常这样做的方式:
void WDT_IRQHandler_real(uint32_t *sp) { /* PC is sp[6] (sp + 0x18) */ /* ... your code ... */ } /* Cortex M3/4 */ __attribute__((naked)) void WDT_IRQHandler() { asm volatile ( "TST LR, #4\n\t" "ITE EQ\n\t" "MRSEQ R0, MSP\n\t" "MRSNE R0, PSP\n\t" "LDR R1, =WDT_IRQHandler_real\n\t" "BX R1" ); } /* Cortex M0/1 */ __attribute__((naked)) void WDT_IRQHandler() { asm volatile ( "MRS R0, MSP\n\t" "MOV R1, LR\n\t" "MOV R2, #4\n\t" "TST R1, R2\n\t" "BEQ WDT_IRQHandler_call_real\n\t" "MRS R0, PSP\n" "WDT_IRQHandler_call_real:\n\t" "LDR R1, =WDT_IRQHandler_real\n\t" "BX R1" ); }
这里的诀窍是处理程序是一小段程序集(我使用了一个带有GCC asm的裸函数,你也可以使用一个单独的asm文件),它将堆栈指针传递给真正的处理程序.这是它的工作原理(适用于M3 / 4):
>异常处理程序中LR的初始值称为EXC_RETURN(更多信息here).它的位有各种含义,我们感兴趣的是EXC_RETURN [2]如果活动堆栈是MSP则为0,如果活动堆栈是PSP则为1;
> TST LR,#4检查EXC_RETURN [2]并设置条件标志;
> MRSEQ R0,如果EXC_RETURN [2] == 0,MSP将MSP移动到R0;
> MRSNE R0,如果EXC_RETURN [2] == 1,PSP将PSP移动到R0;
>最后,LDR / BX跳转到实际函数(R0是第一个参数).
M0 / 1变体类似,但从核心does not support IT blocks开始使用分支.
这解决了MSP / PSP问题,因为它在任何编译器生成的堆栈操作之前运行,它将提供可靠的指针.我在函数中使用了一个简单的(非链接的)分支,因为在它之后我不需要做任何事情并且LR已经很好了.它节省了几个周期和LR推/弹.此外,所有使用的寄存器都在R0-R3划痕范围内,因此无需保留它们.