Retrieving return address of an exception on ARM Cortex M0

让人想犯罪 __ 提交于 2019-12-04 10:12:48

You can't rely on the stack pointer inside of the C handler because of two reasons:

  1. Registers are always pushed to the active stack for the preempted code. Handlers always use the main stack (MSP). If the interrupt preempts thread-mode code that's running from the process stack (PSP) then the registers will be pushed to the PSP and you'll never find them in the handler stack;
  2. The C routine will probably reserve some stack space for local variables, and you don't know how much that is, so you won't be able to locate the registers.

This is how I usually do it:

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"
    );
}

The trick here is that the handler is a small piece of assembly (I used a naked function with GCC asm, you can also use a separate asm file) that passes the stack pointer to the real handler. Here's how it works (for M3/4):

  • The initial value of LR in an exception handler is known as EXC_RETURN (more info here). Its bits have various meaning, we're interested in the fact that EXC_RETURN[2] is 0 if the active stack was the MSP and 1 if the active stack was the PSP;
  • TST LR, #4 checks EXC_RETURN[2] and sets condition flags;
  • MRSEQ R0, MSP moves the MSP into R0 if EXC_RETURN[2] == 0;
  • MRSNE R0, PSP moves the PSP into R0 if EXC_RETURN[2] == 1;
  • Finally, LDR/BX jumps to the real function (R0 is the first argument).

The M0/1 variant is similiar but uses branches since the core does not support IT blocks.

This solves the MSP/PSP issue and, since it runs before any compiler-generated stack operation, it will provide a reliable pointer. I used a simple (non-linked) branch to the function because I don't have to do anything after it and LR is already good to go. It saves a few cycles and an LR push/pop. Also all registers used are in the R0-R3 scratch range, so there's no need to preserve them.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!