• 栈回溯之手动分析栈空间


    说明

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

    关于如何打印栈空间,可参考我的另一篇博客《栈回溯之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();
    	//......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29



    对应汇编

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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
    
    • 1
    • 2
    • 3
    • 4



    日志

    这里的日志是在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
    ==============================================================
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42



    分析

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

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

    *SCB1 |= 0x10; 
    
    • 1

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

    0x08008A6C E92D0810  PUSH          {r4,r11}
    
    • 1

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

    0x0800A9A6 E8BD4800  POP           {r11,lr}
    
    • 1

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

    0x0800A994 E92D4800  PUSH          {r11,lr}
    
    • 1

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

    0x0800A9E2 E8BD4800  POP           {r11,lr}
    
    • 1

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

    0x0800A9D0 E92D4800  PUSH          {r11,lr}
    
    • 1

    所以 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
    
    • 1
    • 2

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



    总结

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

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

  • 相关阅读:
    Bridge Champ与Ignis公链:探索Web3游戏的新未来
    HarmonyOS之从网络获取数据测试题
    CentOS中实现基于Docker部署BI数据分析
    Go语学习笔记 - gorm使用 - 数据库配置、表新增 Web框架Gin(七)
    x86 汇编中的 “lock“ 指令详解
    计算机毕业设计Java迎新管理系统演示录像2020(系统+程序+mysql数据库+Lw文档)
    WPF 设置全局静态参数
    深度学习中softmax激活函数的用法
    Flink Window&Time 原理
    医院陪诊小程序源码 陪诊陪护小程序源码
  • 原文地址:https://blog.csdn.net/qq_36973838/article/details/134291557