当前位置 : 主页 > 网络推广 > seo >

stack – 检索ARM Cortex M0上的异常的返回地址

来源:互联网 收集:自由互联 发布时间:2021-06-16
我试图在我的代码中检索IRQ处理程序的返回地址. 我的目标是在看门狗定时器到期之前和复位之前使用WDT_IRQHandler()保存PC的值以进行调试.我也在用其他IRQ测试这种方法来检查我是否掌握
我试图在我的代码中检索IRQ处理程序的返回地址.
我的目标是在看门狗定时器到期之前和复位之前使用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划痕范围内,因此无需保留它们.

网友评论