如何解开ARM Cortex M3堆栈

3
ARM Coretex STM32的HardFault_Handler在发生崩溃时只能获取几个寄存器的值,r0、r1、r2、r3、lr、pc和xPSR。但是堆栈中没有FP和SP。因此,我无法解开堆栈。 有没有解决方案?非常感谢。
[更新] 按照网上的指导,让ARMGCC(Keil uvision IDE)通过添加编译选项"--use_frame_pointer"生成FP,但是我在堆栈中找不到FP。我是一个真正的新手。以下是我的演示代码:
int test2(int i, int j)
{
    return i/j;
}

int main()
{
    SCB->CCR |= 0x10;
    int a = 10;
    int b = 0;
    int c;
    c = test2(a,b);
}

enum { r0 = 0, r1, r2, r3, r11, r12, lr, pc, psr};
void Hard_Fault_Handler(uint32_t *faultStackAddress)
{
    uint32_t r0_val = faultStackAddress[r0]; 
    uint32_t r1_val = faultStackAddress[r1];
    uint32_t r2_val = faultStackAddress[r2]; 
    uint32_t r3_val = faultStackAddress[r3];
    uint32_t r12_val = faultStackAddress[r12]; 
    uint32_t r11_val = faultStackAddress[r11]; 
    uint32_t lr_val =  faultStackAddress[lr];
    uint32_t pc_val =  faultStackAddress[pc];
    uint32_t psr_val = faultStackAddress[psr];
}

我这里有两个问题:
1. 我不确定FP(r11)在堆栈中的索引位置,或者它是否被推入堆栈。我假设它在r12之前,因为我比较了添加选项"--use_frame_pointer"之前和之后的汇编源代码。我还比较了从Hard_Fault_Handler读取的值,似乎r11不在堆栈中。因为我读取的r11地址指向的地方不是我的代码。 [更新] 我已确认FP被推入堆栈。第二个问题仍需回答。

请参考下面的代码片段:

没有使用选项"--use_frame_pointer"

test2 PROC
        MOVS     r0,#3
        BX       lr
        ENDP

main PROC
        PUSH     {lr}
        MOVS     r0,#0
        BL       test2
        MOVS     r0,#0
        POP      {pc}
        ENDP

使用选项“--use_frame_pointer”
test2 PROC
        PUSH     {r11,lr}
        ADD      r11,sp,#4
        MOVS     r0,#3
        MOV      sp,r11
        SUB      sp,sp,#4
        POP      {r11,pc}
        ENDP

main PROC
        PUSH     {r11,lr}
        ADD      r11,sp,#4
        MOVS     r0,#0
        BL       test2
        MOVS     r0,#0
        MOV      sp,r11
        SUB      sp,sp,#4
        POP      {r11,pc}
        ENDP

2. 似乎FP不在Hard_Fault_Handler()的输入参数faultStackAddress中,我应该从哪里获取调用者的FP以解开堆栈?
[再次更新] 现在我明白了最后的FP(r11)没有存储在堆栈中。我只需要读取r11寄存器的值,然后就可以解开整个堆栈。
所以现在我的最终问题是如何使用C内联汇编来读取它。 我尝试了下面的代码,但未能根据 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0472f/Cihfhjhg.html 的参考读取正确的值。

volatile int top_fp;
__asm
{
    mov top_fp, r11
}

r11的值为0x20009DCC top_fp的值为0x00000004


[更新3] 以下是我的整个代码。

int test5(int i, int j, int k)
{
    char a[128] = {0} ;
    a[0] = 'a';
    return i/j;
}
int test2(int i, int j)
{
    char a[18] = {0} ;
    a[0] = 'a';
    return test5(i, j, 0);    
}


int main()
{
    SCB->CCR |= 0x10;
    int a = 10;
    int b = 0;
    int c;
    c = test2(a,b); //create a divide by zero crash
}

/* 故障处理程序的实现调用一个名为Hard_Fault_Handler()的函数。 */
#if defined(__CC_ARM)
__asm void HardFault_Handler(void)
{
   TST lr, #4
   ITE EQ
   MRSEQ r0, MSP
   MRSNE r0, PSP
   B __cpp(Hard_Fault_Handler)
}
#else
void HardFault_Handler(void)
{
   __asm("TST lr, #4");
   __asm("ITE EQ");
   __asm("MRSEQ r0, MSP");
   __asm("MRSNE r0, PSP");
   __asm("B Hard_Fault_Handler");
}
#endif

void Hard_Fault_Handler(uint32_t *faultStackAddress)
{
   volatile int top_fp;
   __asm
   {
       mov top_fp, r11
   }
   //TODO: use top_fp to unwind the whole stack.

 }

[更新4] 终于,我成功解决了。我的解决方案是:
注意:要访问r11,我们必须使用嵌入式汇编器,请参考这里,这花费了我很多时间来弄清楚。
//we have to use embedded assembler.     
__asm int getRegisterR11()
{
    mov r0,r11
    BX LR
}

//call it from Hard_Fault_Handler function.
/*
Function call stack frame:
   FP1(r11) ->    | lr |(High Address)
                  | FP2|(prev FP)
                  | ...| 
Current FP(r11) ->| lr |
                  | FP1|(prev FP)
                  | ...|(Low Address)

    With FP, we can access lr(link register) which is the address to return when the current functions returns(where you were).
    Then (current FP - 1) points to prev FP.
    Thus we can unwind the stack.
*/
void unwindBacktrace(uint32_t topFp, uint16_t* backtrace)
{
    uint32_t nextFp = topFp;
    int j = 0;

    //#define BACK_TRACE_DEPTH 5
    //loop backtrace using FP(r11), save lr into an uint16_t array.
    for(int i = 0; i < BACK_TRACE_DEPTH; i++)
    {
        uint32_t lr = *((uint32_t*)nextFp);
        if ((lr >= 0x08000000) && (lr <= 0x08FFFFFF))
        {
            backtrace[j*2] = LOW_16_BITS(lr);
            backtrace[j*2 + 1] = HIGH_16_BITS(lr);
            j += 1;
        }
        nextFp = *((uint32_t*)nextFp - 1);
        if (nextFp == 0)
        {
            break;
        }
    }
}

#if defined(__CC_ARM)
__asm void HardFault_Handler(void)
{
   TST lr, #4
   ITE EQ
   MRSEQ r0, MSP
   MRSNE r0, PSP
   B __cpp(Hard_Fault_Handler)
}
#else
void HardFault_Handler(void)
{
   __asm("TST lr, #4");
   __asm("ITE EQ");
   __asm("MRSEQ r0, MSP");
   __asm("MRSNE r0, PSP");
   __asm("B Hard_Fault_Handler");
}
#endif

void Hard_Fault_Handler(uint32_t *faultStackAddress)
{
       //get back trace
    int topFp = getRegisterR11();

    unwindBacktrace(topFp, persistentData.faultStack.back_trace);
}
1个回答

4
在这种情况下,解开堆栈的非常原始的方法是读取SP上方的所有堆栈内存,并使用arm-none-eabi-addr2line进行处理。保存在堆栈上的所有链接寄存器条目都将转换为源行(请记住,实际代码路径走到LR指向的行之前)。请注意,如果中间调用的函数使用分支指令(b)而不是分支和链接(bl),则使用此方法将无法看到它们。
你为什么认为Hard_Fault_Handler有任何参数?通常,Hard_Fault_Handler是一个函数,其地址存储在向量(异常)表中。当处理器发生异常时,将执行Hard_Fault_Handler。这个过程中没有涉及到参数传递。但是,所有在故障发生时的寄存器都被保留下来。具体而言,如果没有使用omit-frame-pointer编译,您可以读取R11的值(或Thumb-2模式下的R7)。然而,要确保在您的代码中Hard_Fault_Handler实际上是一个真正的硬件故障处理程序,请查看startup.s代码,并查看Hard_Fault_Handler是否位于向量表的第三个条目。如果有其他函数,则意味着Hard_Fault_Handler只是从该函数显式调用的。请参阅article了解详细信息。您还可以阅读我的blog :) 其中有一章关于基于Android示例的堆栈,但是许多内容通常相同。
还要注意的是,faultStackAddress 中存储的很可能是堆栈指针,而不是帧指针。
更新2
好的,让我们澄清一些事情。首先,请粘贴调用 Hard_Fault_Handler 的代码。其次,我猜你从真正的 HardFault 异常处理程序中调用它。在这种情况下,你不能期望 R11 在 faultStackAddress[r11] 处。你已经在问题的第一句话中提到了这一点。只有 r0-r3、r12、lr、pc 和 psr。
你也写道:
“但是堆栈中没有 FP 和 SP。因此,我无法展开堆栈。有什么解决办法吗?”
SP不在堆栈中,因为它已经在堆栈寄存器(msp或psp)之一中。请再次查看本文。此外,FP对于展开堆栈并不重要,因为您可以在没有FP的情况下进行展开(通过“导航”保存的链接寄存器)。另一件事是,如果您在SP下面转储内存,如果您确实需要它,可以期望FP紧挨着已保存的LR。

回答您的最后一个问题:我不知道您如何验证此代码以及如何调用它(您需要粘贴完整的代码)。您可以查看该函数的汇编代码,了解底层发生了什么。您还可以按照此帖子作为模板进行操作。


谢谢@mkmk88的评论。我已经更新了我的问题,你能再看一下吗?谢谢。 - bettermanlu
谢谢你再次帮忙。我在这个问题上取得了一些进展,并且我已经更新了我的问题。你能再次看一下吗?非常感谢。 - bettermanlu
我解决了。谢谢你的建议和帮助。 - bettermanlu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接