检索ARM Cortex M0上异常的返回地址 [英] Retrieving return address of an exception on ARM Cortex M0

查看:309
本文介绍了检索ARM Cortex M0上异常的返回地址的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在我的代码中检索IRQ处理程序的返回地址. 我的目标是使用WDT_IRQHandler()在看门狗定时器到期之前和在出于调试目的而进行复位之前保存PC的值.我还正在与其他IRQ一起测试这种方法,以检查我是否理解了这个想法. 但似乎我还没有.

I am trying to retrieve the return address of an IRQ handler in my code. My aim is to save the value of the PC just before the watchdog timer expires and before the reset for debug purposes, using WDT_IRQHandler(). I am also testing this approach with other IRQs to check if I grasped the idea. But it seems I haven't.

我已阅读文档可用. 我知道发生异常时,会将8个寄存器压入堆栈: R0,R1,R2,R3,R12,LR,PC和XPSR.

I have read the documentation available. I understood that when an exception happens, 8 registers are pushed to the stack: R0, R1, R2, R3, R12, LR, PC and XPSR.

我还读过,堆栈是自动双字对齐的.因此,在我看来,检索寄信人地址非常简单:

I have also read that the stack is automatically double word aligned. So in my mind, retrieving the return address is as easy as:

  • 使用__builtin_frame_address(0)检索sp地址;
  • 向其添加堆叠PC的偏移量(0x18),并读取该值,该值应该是在处理程序返回时将恢复到PC的值.

在连接调试器的情况下进行检查,似乎不是这种情况,该内存地址中的内容并不总是指向闪存区域,甚至不指向有效区域,在任何情况下,PC永远都不会提供该值假设在POP指令之后.

Checking with the debugger attached, this seems not the case, the content at that memory address doesn't always point to a flash area, or even to a valid area, and in any case it is never the value that PC will assume after the POP instruction.

代码工作正常,所以我认为这是我理解代码工作方式的问题.

The code works fine, so I think it's a problem I have in understanding how it works.

如果我检查反汇编,则在某些IRQ中,在POP(?)之前将常量添加到sp

If I check the disassembly, in some IRQs a constant is added to the sp before POPping (?)

00001924: 0x000009b0 ...TE_IRQHandler+280   add     sp, #36 ; 0x24
00001926: 0x0000f0bd ...TE_IRQHandler+282   pop     {r4, r5, r6, r7, pc}

在其他IRQ中不会发生这种情况.

In other IRQs this doesn't happen.

我了解可能会发生更多寄存器被压入堆栈的情况,那么如何确定在哪个偏移量处检索PC?

I understand that it can happen that more registers are pushed to the stack, so how can I be sure at which offset to retrieve the PC?

如果在代码仍位于IRQ处理程序中的情况下检查SP周围的内存转储,我可以发现返回地址,但返回地址始终处于怪异的位置,与SP相比偏移量为负.我不明白如何获取正确的地址.

If I check the memory dump around the SP when the code is still in the IRQ handler, I can spot the return address, but it's always at a weird location, with a negative offset compared to the SP. I can't understand how to obtain the right address.

推荐答案

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

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

  1. 寄存器始终被推入活动堆栈以获取被抢占的代码.处理程序始终使用主堆栈(MSP).如果该中断抢占了从进程堆栈(PSP)运行的线程模式代码,则寄存器将被推送到PSP,并且您将永远不会在处理程序堆栈中找到它们;
  2. C例程可能会为局部变量保留一些堆栈空间,并且您不知道它有多少,因此您将无法找到寄存器.
  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.

这是我通常的做法:

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):

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):

  • 异常处理程序中LR的初始值称为EXC_RETURN(更多信息
  • 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).

M0/1变体类似,但由于其核心

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

这解决了MSP/PSP问题,并且由于它在任何编译器生成的堆栈操作之前运行,因此它将提供可靠的指针. 我对函数使用了一个简单的(非链接的)分支,因为在此之后我无需执行任何操作,并且LR已经可以使用了.它节省了几个周期,并节省了LR推入/弹出操作.另外,所有使用的寄存器都在R0-R3暂存范围内,因此无需保留它们.

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.

这篇关于检索ARM Cortex M0上异常的返回地址的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆