如何展开 ARM Cortex M3 堆栈
How unwind ARM Cortex M3 stack
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”前后的 assemble 源。我还比较了从 Hard_Fault_Handler 读取的值,好像 r11 不在堆栈中。因为我读到的r11地址指向了一个代码不是我代码的地方。
[update] 已经确认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。 Hard_Fault_Handler()的入参faultStackAddress中好像没有FP,请问调用者的FP去哪里取栈?
[再次更新]
现在我明白最后一个 FP(r11) 没有存储在堆栈中。我需要做的就是读取 r11 寄存器的值,然后我可以展开整个堆栈。
所以现在我的最后一个问题是如何使用 C 的内联 assembler 来读取它。 我尝试了下面的代码,但未能读取r11 的正确值遵循 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.
}
[update 4] 终于搞定了。我的解决方案:
注意:要访问 r11,我们必须使用嵌入式 assembler,参见 ,这让我花了很多时间来弄清楚。
//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);
}
在这种情况下展开堆栈的非常原始的方法是读取在 HardFault_Handler 时看到的所有高于 SP 的堆栈内存,并使用 arm-none- 对其进行处理eabi-addr2line。保存在堆栈上的所有 link 寄存器条目将被转换为源代码行(请记住,实际代码路径在 行 LR 指向之前)。请注意,如果使用 branch 指令 (b) 而不是 branch 和 link (bl) 调用函数,您将不会使用此方法查看它们。
(我没有足够的声望点来写评论,所以我正在编辑我的答案):
问题 2 的更新:
为什么你认为 Hard_Fault_Handler 有任何论据? Hard_Fault_Handler 通常是地址存储在向量中的函数(例外)table。当处理器异常发生时,Hard_Fault_Handler 将被执行。没有任何论据涉及这样做。但仍然会保留故障发生时的所有寄存器。具体来说,如果您在没有 omit-frame-pointer 的情况下编译,您可以只读取 R11 的值(或 Thumb-2 模式下的 R7)。 但是,要确保在你的代码中 Hard_Fault_Handler 实际上是一个 real 硬错误处理程序,查看 startup.s 代码并查看 Hard_Fault_Handler 是否在 third 向量中的条目 table。如果有其他函数,则意味着 Hard_Fault_Handler 只是从该函数显式调用。看这个 article for details. You can also read my blog :) 有一个关于堆栈的章节是基于 Android 例子的,但是很多东西一般都是一样的。
另请注意,很可能在 faultStackAddress 中存储的是堆栈指针,而不是帧指针。
更新 2
好的,让我们澄清一些事情。首先,请粘贴您调用 Hard_Fault_Handler 的代码。其次,我猜你是从真正的 HardFault 异常处理程序中调用它的。在那种情况下,您不能期望 R11 位于 faultStackAddress[r11]。您已经在问题的第一句话中提到了它。将 只有 r0-r3、r12、lr、pc 和 psr。
您还写过:
But there is no FP and SP in the stack. Thus I could not unwind the
stack. Is there any solution for this?
SP 不是 "in the stack",因为您已经在其中一个堆栈寄存器(msp 或 psp)中拥有它。再看THIS ARTICLE。此外,FP 对于展开堆栈并不重要,因为您可以在没有它的情况下执行此操作(通过 "navigating" 通过保存的 Link 寄存器)。另一件事是,如果你将内存转储到你的 SP 下面,如果你真的需要它,你可以期望 FP 紧挨着保存的 LR。
回答你的最后一个问题:我现在不知道你是如何验证这段代码以及你是如何调用它的(你需要粘贴完整的代码)。您可以查看该函数的汇编,看看幕后发生了什么。您可以做的另一件事是遵循 this post 作为模板。
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”前后的 assemble 源。我还比较了从 Hard_Fault_Handler 读取的值,好像 r11 不在堆栈中。因为我读到的r11地址指向了一个代码不是我代码的地方。
[update] 已经确认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。 Hard_Fault_Handler()的入参faultStackAddress中好像没有FP,请问调用者的FP去哪里取栈?
[再次更新]
现在我明白最后一个 FP(r11) 没有存储在堆栈中。我需要做的就是读取 r11 寄存器的值,然后我可以展开整个堆栈。
所以现在我的最后一个问题是如何使用 C 的内联 assembler 来读取它。 我尝试了下面的代码,但未能读取r11 的正确值遵循 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.
}
[update 4] 终于搞定了。我的解决方案:
注意:要访问 r11,我们必须使用嵌入式 assembler,参见
//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);
}
在这种情况下展开堆栈的非常原始的方法是读取在 HardFault_Handler 时看到的所有高于 SP 的堆栈内存,并使用 arm-none- 对其进行处理eabi-addr2line。保存在堆栈上的所有 link 寄存器条目将被转换为源代码行(请记住,实际代码路径在 行 LR 指向之前)。请注意,如果使用 branch 指令 (b) 而不是 branch 和 link (bl) 调用函数,您将不会使用此方法查看它们。
(我没有足够的声望点来写评论,所以我正在编辑我的答案):
问题 2 的更新:
为什么你认为 Hard_Fault_Handler 有任何论据? Hard_Fault_Handler 通常是地址存储在向量中的函数(例外)table。当处理器异常发生时,Hard_Fault_Handler 将被执行。没有任何论据涉及这样做。但仍然会保留故障发生时的所有寄存器。具体来说,如果您在没有 omit-frame-pointer 的情况下编译,您可以只读取 R11 的值(或 Thumb-2 模式下的 R7)。 但是,要确保在你的代码中 Hard_Fault_Handler 实际上是一个 real 硬错误处理程序,查看 startup.s 代码并查看 Hard_Fault_Handler 是否在 third 向量中的条目 table。如果有其他函数,则意味着 Hard_Fault_Handler 只是从该函数显式调用。看这个 article for details. You can also read my blog :) 有一个关于堆栈的章节是基于 Android 例子的,但是很多东西一般都是一样的。
另请注意,很可能在 faultStackAddress 中存储的是堆栈指针,而不是帧指针。
更新 2
好的,让我们澄清一些事情。首先,请粘贴您调用 Hard_Fault_Handler 的代码。其次,我猜你是从真正的 HardFault 异常处理程序中调用它的。在那种情况下,您不能期望 R11 位于 faultStackAddress[r11]。您已经在问题的第一句话中提到了它。将 只有 r0-r3、r12、lr、pc 和 psr。
您还写过:
But there is no FP and SP in the stack. Thus I could not unwind the stack. Is there any solution for this?
SP 不是 "in the stack",因为您已经在其中一个堆栈寄存器(msp 或 psp)中拥有它。再看THIS ARTICLE。此外,FP 对于展开堆栈并不重要,因为您可以在没有它的情况下执行此操作(通过 "navigating" 通过保存的 Link 寄存器)。另一件事是,如果你将内存转储到你的 SP 下面,如果你真的需要它,你可以期望 FP 紧挨着保存的 LR。
回答你的最后一个问题:我现在不知道你是如何验证这段代码以及你是如何调用它的(你需要粘贴完整的代码)。您可以查看该函数的汇编,看看幕后发生了什么。您可以做的另一件事是遵循 this post 作为模板。