--use_frame_pointer
选项会保留一个寄存器来存储帧指针R11
R11
和 Thumb 代码中的 R7
不建议对 Cortex-M 控制器使用选项 --use_frame_pointer
此选项不会为执行时间或代码密度带来好处,μVision 调试器也不需要此选项来正确显示应用程序的调用关系。
其基本原理就是在每个函数开始入栈R11(FP)和LR
PUSH {r11,lr}
这里的FP会指向上一个函数的LR,那么我们就能知道每一个函数的LR,就能很快推出程序的调用关系
入栈之后会更新FP的值为当前函数的LR的入栈地址,方便下一级别函数直接入栈
在Keil5中Options(魔术棒)> C/C++ > Misc Controls 加上启用帧指针FP(Frame Pointer)(默认不启用)的编译选项:
--use_frame_pointer
附:在GCC中启用
-fno-omit-frame-pointer -fno-optimize-sibling-calls
调用关系main() > test2() > test1() > fault_test()
void fault_test()
{
volatile int *SCB1 = (volatile int *) 0xFEEEEEEE;
// 非对齐访问 会挂掉
*SCB1 |= 0x10;
printf("SCB1: %d\r\n", *SCB1);
}
void test1()
{
printf("test 1 start\r\n");
fault_test();
printf("test 1 end\r\n");
}
void test2()
{
printf("test 2 start\r\n");
test1();
printf("test 2 end\r\n");
}
int main(void)
{
//......
test2();
//......
}
0x08008A6C E92D0810 PUSH {r4,r11}
20: volatile int *SCB1 = (volatile int *) 0xFEEEEEEE;
0x08008A70 4806 LDR r0,[pc,#24] ; @0x08008A8C
0x08008A72 F10D0B04 ADD r11,SP,#0x04
21: *SCB1 |= 0x10;
22:
0x08008A76 6801 LDR r1,[r0,#0x00]
0x08008A78 F0410110 ORR r1,r1,#0x10
0x08008A7C 6001 STR r1,[r0,#0x00]
23: printf("SCB1: %d\r\n", *SCB1);
0x08008A7E 6801 LDR r1,[r0,#0x00]
0x08008A80 A003 ADR r0,{pc}+0x10 ; @0x08008A90
0x08008A82 E8BD0810 POP {r4,r11}
0x08008A86 F7FEBE03 B.W __0printf (0x08007690)
0x0800A994 E92D4800 PUSH {r11,lr}
0x0800A998 F10D0B04 ADD r11,SP,#0x04
28: printf("test 1 start\r\n");
0x0800A99C A004 ADR r0,{pc}+0x14 ; @0x0800A9B0
0x0800A99E F7FCFE77 BL.W __0printf (0x08007690)
29: fault_test();
0x0800A9A2 F7FEF863 BL.W fault_test (0x08008A6C)
0x0800A9A6 E8BD4800 POP {r11,lr}
30: printf("test 1 end\r\n");
0x0800A9AA A005 ADR r0,{pc}+0x18 ; @0x0800A9C0
0x0800A9AC F7FCBE70 B.W __0printf (0x08007690)
0x0800A9D0 E92D4800 PUSH {r11,lr}
0x0800A9D4 F10D0B04 ADD r11,SP,#0x04
35: printf("test 2 start\r\n");
0x0800A9D8 A004 ADR r0,{pc}+0x14 ; @0x0800A9EC
0x0800A9DA F7FCFE59 BL.W __0printf (0x08007690)
36: test1();
0x0800A9DE F7FFFFD9 BL.W test1 (0x0800A994)
0x0800A9E2 E8BD4800 POP {r11,lr}
37: printf("test 2 end\r\n");
0x0800A9E6 A005 ADR r0,{pc}+0x18 ; @0x0800A9FC
0x0800A9E8 F7FCBE52 B.W __0printf (0x08007690)
66: test2();
0x08008E9A F001FD99 BL.W test2 (0x0800A9D0)
83: HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
0x08008E9E F8DFA0B4 LDR.W r10,[pc,#180] ; @0x08008F56
如何打印栈空间和FP不是本文的重点,可参考我的另一篇博客《栈回溯之CmBacktrace》
基于CmBacktrace
修改,其原打印栈空间的函数原型为
void cm_backtrace_fault(uint32_t fault_handler_lr, uint32_t fault_handler_sp)
修改为
void cm_backtrace_fault(uint32_t fault_handler_lr, uint32_t fault_handler_sp, uint32_t fault_handler_fp)
原HardFault
为
HardFault_Handler PROC
MemManage_Handler
MOV r0, lr ; get lr
MOV r1, sp ; get stack pointer (current is MSP)
BL cm_backtrace_fault
Fault_Loop
BL Fault_Loop ;while(1)
ENDP
END
增加第三个参数后为
HardFault_Handler PROC
MemManage_Handler
MOV r0, lr ; get lr
MOV r1, sp ; get stack pointer (current is MSP)
MOV r2, r11 ; get frame pointer (if enabled)
BL cm_backtrace_fault
Fault_Loop
BL Fault_Loop ;while(1)
ENDP
END
=========== 线程堆栈信息 ===========
addr: 20001040 data: deadbeef
addr: 20001044 data: 2000104c
addr: 20001048 data: 20001054
addr: 2000104c data: 0800a9e3
addr: 20001050 data: 200010cc
addr: 20001054 data: 08008e9f
addr: 20001058 data: 23232323
addr: 2000105c data: 23232323
addr: 20001060 data: 23232323
addr: 20001064 data: 23232323
addr: 20001068 data: 23232323
addr: 2000106c data: 23232323
addr: 20001070 data: 23232323
addr: 20001074 data: 23232323
addr: 20001078 data: 23232323
addr: 2000107c data: 23232323
addr: 20001080 data: 23232323
addr: 20001084 data: 23232323
addr: 20001088 data: 23232323
addr: 2000108c data: 00000000
addr: 20001090 data: deadbeef
addr: 20001094 data: deadbeef
addr: 20001098 data: deadbeef
addr: 2000109c data: deadbeef
addr: 200010a0 data: deadbeef
addr: 200010a4 data: deadbeef
addr: 200010a8 data: deadbeef
addr: 200010ac data: deadbeef
addr: 200010b0 data: 200010c4
addr: 200010b4 data: 08009247
addr: 200010b8 data: deadbeef
addr: 200010bc data: deadbeef
addr: 200010c0 data: 200010cc
addr: 200010c4 data: 08008f81
addr: 200010c8 data: deadbeef
addr: 200010cc data: 08008155
====================================
========================= 寄存器信息 =========================
PSP: 20001040 FP : 20001044
R0 : feeeeeee R1 : f0000000 R2 : 00000000 R3 : 08008a0a
R12: 0000c000 LR : 0800a9a7 PC : 08008a76 PSR: 61000000
==============================================================
这里选择了在HardFault的第一现场获取了FP并打印,分析不同的优化等级和代码结构
发现FP的值有三种指向可能:(如不认同,非常欢迎留言)
观察fault_test()的汇编可知,符合这里的第二种情况,开发分析:
这里的PC表示出问题的地方,对应到汇编文件中可知, fault_test()
中的如下内容出问题了
*SCB1 |= 0x10;
LR表示函数返回地址,由于LR的LSB表示的是Thumb模式和ARM模式,所以返回地址为0800a9a6,对应为 test1()
中的
0x0800A9A6 E8BD4800 POP {r11,lr}
FP : 20001044 指向的值为 addr: 20001044 data: 2000104c
那么2000104c 指向的值addr: 2000104c data: 0800a9e3为上一个函数 test1()
的LR,对应为 test2()
中的
0x0800A9E2 E8BD4800 POP {r11,lr}
addr: 2000104c data: 0800a9e3的上一个数据 addr: 20001048 data: 20001054为 test1()
中的FP
那么它指向的值 addr: 20001054 data: 08008e9f为test2()
的LR,对应为 main()
中的
83: HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
0x08008E9E F8DFA0B4 LDR.W r10,[pc,#180] ; @0x08008F56
addr: 20001054 data: 08008e9f的上一个数据 addr: 20001050 data: 200010cc为栈底,表示回溯完毕
综上可以函数的调用关系为main() > test2() > test1() > fault_test() ,符合预期
好像这里并不能像CmBackTrace一样回溯到线程的入口函数
使用带有选项 –use_frame_pointer 的 ARMCC 5,在某些情况下可能会为 Cortex-M 微控制器生成错误代码。此错误代码可能会导致偶发的运行时崩溃或硬故障,下面是一个示例:
int foo(void)
{
int w = 4;
int x = 5;
int y = 6;
int z = 7;
return w + x + y + z;
}
这将生成以下代码:
foo
0x00000000: e92d4810 -..H PUSH {r4,r11,lr}
0x00000004: f10d0b08 .... ADD r11,sp,#8
0x00000008: 2404 .$ MOVS r4,#4
0x0000000a: 2105 .! MOVS r1,#5
0x0000000c: 2206 ." MOVS r2,#6
0x0000000e: 2307 .# MOVS r3,#7
0x00000010: 1860 '. ADDS r0,r4,r1
0x00000012: 4410 .D ADD r0,r0,r2
0x00000014: 4418 .D ADD r0,r0,r3
0x00000016: 46dd .F MOV sp,r11
0x00000018: b082 .. SUB sp,sp,#8
0x0000001a: e8bd8810 .... POP {r4,r11,pc}
如果地址 0x00000016 处的 MOV 指令和地址 0x00000018 处的 SUB 指令之间发生中断,则中断堆栈可能会损坏地址 0x0000001a处的 POP 指令将加载到 r4 和 r11 的值。这是因为当中断发生时,它们位于 SP 寄存器中的地址下方。
此编译器选项不能在μVision中通过复选框进行选择。必须在Options(魔术棒)> C/C++ > Misc Controls对话框中手动指定它。
这是 Arm 编译器版本 5.06u6 及更低版本 (armcc.exe) 的代码生成中的错误。
不建议对 Cortex-M 控制器使用选项 --use_frame_pointer。此选项不会为执行时间或代码密度带来好处。μVision 调试器也不需要此选项来正确显示应用程序的调用堆栈。
这是 5.06u6 之前的已知问题。Arm 编译器 5.06u7 修复了此问题,MDK 版本 5.32 及更高版本也将包含此编译器版本。