栈回溯之手动分析栈空间

说明

本文重点在于如何分析栈空间,不在于如何获取栈空间,所以栈空间的打印本文不作描述。

关于如何打印栈空间,可参考我的另一篇博客《栈回溯之CmBacktrace》

测试源码

调用关系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();
	//......
}



对应汇编

void fault_test(void)

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)

void test1()

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)

void test2()

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)

int main(void)

    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



日志

这里的日志是在HardFault中打印的

=========== 线程堆栈信息 ===========
  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
====================================
========================= 寄存器信息 =========================
  R0 : feeeeeee  R1 : f0000000  R2 : 00000000  R3 : 08008a0a
  R12: 0000c000  LR : 0800a9a7  PC : 08008a76  PSR: 61000000
==============================================================



分析

Cortex‐M 使用的是“向下生长的满栈”模型。堆栈指针 SP 指向最后一个被压入堆栈的 32位数值。在下一次压栈时,SP 先自减 4,再存入新的数值。

这里的PC表示出问题的地方,对应到汇编文件中可知, fault_test() 中的如下内容出问题了

*SCB1 |= 0x10; 

观察其上的堆栈操作,仅有如下操作,没有对LR进行操作,说明LR即为函数返回地址

0x08008A6C E92D0810  PUSH          {r4,r11}

由于LR的LSB表示的是Thumb模式和ARM模式,所以返回地址为0800a9a6,对应为 test1()中的

0x0800A9A6 E8BD4800  POP           {r11,lr}

观察其上的堆栈操作,有如下操作

0x0800A994 E92D4800  PUSH          {r11,lr}

所以 test1()的LR想较于sp有3个偏移,即 addr: 2000104c,所以返回地址为0800a9e2,对应为 test2()中的

0x0800A9E2 E8BD4800  POP           {r11,lr}

观察其上的堆栈操作,有如下操作

0x0800A9D0 E92D4800  PUSH          {r11,lr}

所以 test2()的LR想较于sp有5个偏移,即 data: 08008e9f,所以返回地址为08008e9e,对应为 main()中的

    83:                 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); 
0x08008E9E F8DFA0B4  LDR.W         r10,[pc,#180]  ; @0x08008F56

综上可以函数的调用关系为main() > test2() > test1() > fault_test() ,符合预期



总结

上一节中还可以继续分析找到线程的入口,不过main()数据略多,不利于手动分析。

本文也是旨在加深理解栈回溯的原理,实际中可能会使用BackTrace自动分析,可参考我的另一篇博客《栈回溯之CmBacktrace》

你可能感兴趣的:(C语言,嵌入式,单片机,STM32,栈回溯)