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

查看:19
本文介绍了在 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 中,一个常量会在 POPping (?) 之前添加到 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(更多信息此处).它的位有多种含义,我们感兴趣的是 EXC_RETURN[2]0 如果活动堆栈是 MSP1 如果活动堆栈是 PSP;
  • TST LR, #4 检查 EXC_RETURN[2] 并设置条件标志;
  • MRSEQ R0, MSP如果EXC_RETURN[2] == 0,则将MSP移动到R0
  • MRSNE R0, PSP如果EXC_RETURN[2] == 1,则将PSP移动到R0中;
  • 最后,LDR/BX 跳转到真正的函数(R0 是第一个参数).
  • 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 变体是类似的,但使用了自核心的分支不支持 IT 块.

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

这解决了 MSP/PSP 问题,并且由于它在任何编译器生成的堆栈操作之前运行,因此它将提供可靠的指针.我对函数使用了一个简单的(非链接的)分支,因为在它之后我不需要做任何事情并且 LR 已经很好了.它节省了几个周期和一个 LR push/pop.此外,所有使用的寄存器都在 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天全站免登陆