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

时间:2016-07-27 16:23:02

标签: stack arm watchdog cortex-m 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相比具有负偏移。我无法理解如何获得正确的地址。

1 个答案:

答案 0 :(得分:5)

由于两个原因,你不能依赖C处理程序内的堆栈指针:

  1. 寄存器总是被推送到抢占代码的活动堆栈。处理程序始终使用主堆栈(MSP)。如果中断抢占了从进程堆栈(PSP)运行的线程模式代码,则寄存器将被推送到PSP,并且您永远不会在处理程序中找到它们堆叠;
  2. C例程可能会为局部变量保留一些堆栈空间,并且您不知道它有多少,因此您无法找到寄存器。
  3. 这就是我通常的做法:

    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],则0MSP,如果活动堆栈为1,则我们感兴趣PSP TST LR, #4;
    • EXC_RETURN[2]检查MRSEQ R0, MSP并设置条件标记;
    • MSPR0移至EXC_RETURN[2] == 0 MRSNE R0, PSP;
    • PSPR0移至EXC_RETURN[2] == 1 LDR;
    • 最后,BX / R0跳转到真实函数(MSP是第一个参数)。

    M0 / 1变体类似,但从核心does not support IT blocks开始使用分支。

    这解决了PSP / LR问题,因为它在任何编译器生成的堆栈操作之前运行,它将提供可靠的指针。 我在函数中使用了一个简单的(非链接的)分支,因为我不必在它之后做任何事情,LR已经很好了。它节省了几个周期和R0-R3推/弹。此外,所有使用的寄存器都在while (index != -1) { var select = document.getElementById("recipes"); var opt = document.createElement("li"); opt.className = "list-group-item"; var link = document.createElement("a"); link.id = response.substring(0, index); link.onclick = function () { alert(link.id); }; link.textContent = response.substring(0, index); select.appendChild(opt); opt.appendChild(link); response = response.substring(index + 4); index = response.indexOf("~,;~"); } 临时范围内,因此无需保留它们。