基于GD32 C10x MCU栈回溯调试原理实现

[复制链接]
734|10
 楼主| xiaoqi000 发表于 2023-1-15 14:56 | 显示全部楼层 |阅读模式
在嵌入式软件开发调试过程中,经常会出现程序在运行过程中莫名其妙崩溃进入HardFault异常中断。因为GD 或者ST 芯片对于hardfault异常中断的处理是跳进一个函数死循环,不利于程序进入异常问题的定位分析。平时我们通过调试器进入Debug模式,设置断点然后一步步定位发生异常的位置,这种方法只适用于代码量比较小的情况。当代码量比较大时,而且函数调用关系又复杂时,这种方法很费时间。本文基于GD32 C10x芯片实现一种栈会回溯调试方法,用于当程序因为数组越界、野指针、堆栈溢出或者非法地址访问时,快速定位发生异常的位置。

评论

———————————————— 版权声明:本文为CSDN博主「木子芯双」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_31446727/article/details/125709280  发表于 2023-1-15 14:56
 楼主| xiaoqi000 发表于 2023-1-15 15:02 | 显示全部楼层
栈回溯调试原理
当MCU发生异常中断时,硬件会自动将xPSR、PC、LR、R12、R3~R0入栈,PC指针是程序发生崩溃的位置,LR是发生崩溃程序的下一条指令地址,R12、R3 ~R0是发生崩溃时内部寄存器的值。栈回溯调试原理就是程序发生崩溃后,在进入异常中断后,通过汇编程序,将R11 ~ R4 手动入栈,并将当前栈指针值保存在R0中,通过汇编调用C函数,C函数的入口参数即为栈的地址,在C函数中按照先进后出原则通过串口依次打印栈中信息。程序进入异常时,硬件自动保存栈内容如下:
 楼主| xiaoqi000 发表于 2023-1-15 15:14 | 显示全部楼层
 楼主| xiaoqi000 发表于 2023-1-15 15:15 | 显示全部楼层
栈回溯原理程序实现
本文通过定义一个指针,指向非法的sram地址,通过向非法地址赋值,从而进入hardfault异常中断。
 楼主| xiaoqi000 发表于 2023-1-15 15:15 | 显示全部楼层
主函数代码如下:
  1. #include <stdio.h>
  2. #include "gd32c10x.h"
  3. #include "main.h"
  4. /* 异常中断栈内容结构体*/
  5. typedef struct
  6. {
  7.         uint32_t LR_hardfault;
  8.         uint32_t R4;
  9.         uint32_t R5;
  10.         uint32_t R6;
  11.         uint32_t R7;
  12.         uint32_t R8;
  13.         uint32_t R9;
  14.         uint32_t R10;
  15.         uint32_t R11;
  16.         uint32_t R0;
  17.         uint32_t R1;
  18.         uint32_t R2;
  19.         uint32_t R3;
  20.         uint32_t R12;
  21.         uint32_t LR;
  22.         uint32_t PC;
  23.         uint32_t xPSR;
  24. }Hard_Fault_Typedef;
  25. uint8_t c[1024]={0};
  26. uint32_t *p=(uint32_t*)0x20ffffff;

  27. /*hardfault stack information output function*/
  28. void hw_fault_printf(Hard_Fault_Typedef* hard_fault)
  29. {
  30.         printf("LR_hardfault: 0x%x\n",hard_fault->LR_hardfault);
  31.         printf("R4: 0x%x\n",hard_fault->R4);
  32.         printf("R5: 0x%x\n",hard_fault->R5);
  33.         printf("R6: 0x%x\n",hard_fault->R6);
  34.         printf("R7: 0x%x\n",hard_fault->R7);
  35.         printf("R8: 0x%x\n",hard_fault->R8);
  36.         printf("R9: 0x%x\n",hard_fault->R9);
  37.         printf("R10: 0x%x\n",hard_fault->R10);
  38.         printf("R11: 0x%x\n",hard_fault->R11);
  39.         printf("R0: 0x%x\n",hard_fault->R0);
  40.         printf("R1: 0x%x\n",hard_fault->R1);
  41.         printf("R2: 0x%x\n",hard_fault->R2);
  42.         printf("R3: 0x%x\n",hard_fault->R3);
  43.         printf("R12: 0x%x\n",hard_fault->R12);
  44.         printf("LR: 0x%x\n",hard_fault->LR);
  45.         printf("PC: 0x%x\n",hard_fault->PC);
  46.         printf("xPSR: 0x%x\n",hard_fault->xPSR);
  47. }
  48. /*usart configure function*/
  49. void USART_init(void)
  50. {
  51.         rcu_periph_clock_enable(RCU_USART0);
  52.   /*enable COM GPIO clock USART0*/
  53.   rcu_periph_clock_enable(RCU_GPIOA);
  54.   /* connect port to USARTx_Tx */
  55.   gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_9);
  56.   /* connect port to USARTx_Rx */
  57.   gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,GPIO_PIN_10);
  58.   usart_deinit(USART0);
  59.   usart_baudrate_set(USART0, 115200U);
  60.   usart_receive_config(USART0, USART_RECEIVE_ENABLE);
  61.   usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
  62.   usart_enable(USART0);
  63. }

  64. /*taskA function*/
  65. void task_A(void)
  66. {
  67.         task_B();       
  68. }

  69. /*taskB function*/
  70. void task_B(void)
  71. {
  72.         task_C(1);
  73. }
  74. uint8_t task_C(uint8_t data)
  75. {
  76.         *p=2;
  77.         c[10]=data;
  78.         return 100/data;
  79. }

  80. /*taskC function*/

  81. int main(void)
  82. {
  83.   USART_init();
  84.   task_A();       
  85.         while(1)
  86.   {
  87.   }
  88. }

  89. int fputc(int ch, FILE *f)
  90. {
  91.     while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));
  92.     usart_data_transmit(USART0, (uint8_t) ch);
  93.     return ch;
  94. }

 楼主| xiaoqi000 发表于 2023-1-15 16:15 | 显示全部楼层
异常中断函数汇编代码如下:
  1. HardFault_Handler\
  2.                 PROC
  3.                 EXPORT  HardFault_Handler                 [WEAK]  
  4.                                 IMPORT  hw_fault_printf
  5.                                 mrs     r0, msp                           ;R0保存进入异常中断时,栈的地址
  6.                                 stmdb   r0!, {r4-r11}                     ;依次将R11~R4寄存器值入栈
  7.                                 stmdb   r0!, {lr}                         ;因为后面调用C函数可能会改变异常中断LR的地址,故将链接地址入栈
  8.                                 msr     msp, r0                           ;将此时栈顶的实际地址赋值给msp主堆栈指针     
  9.                                 push    {lr}                              ;将异常lr链接地址入栈
  10.                                 bl      hw_fault_printf                   ;调用C函数打印异常中断栈内容信息
  11.                                 pop     {lr}                              ;出栈恢复异常lr的值
  12.                         bx      lr                               ;返回线程模式并使用主堆栈指针         
  13.                 B       .
  14.                 ENDP
 楼主| xiaoqi000 发表于 2023-1-15 16:17 | 显示全部楼层
串口助手打印信息如下:
9652063c3b6a6102f1.png
 楼主| xiaoqi000 发表于 2023-1-15 16:18 | 显示全部楼层
由上图我们可以看出进入异常时LR的地址为0xfffffff9,查阅内核手册可知此时SP指针使用的是主堆栈指针MSP,程序发生崩溃时PC指针的地址为0x8000b12,崩溃时下一条指令的地址为0x80008f7。知道以上信息后,我们可以在程序编译输出的反汇编代码中查找这两个地址信息,可以迅速定位程序崩溃地址。
 楼主| xiaoqi000 发表于 2023-1-15 16:20 | 显示全部楼层
 楼主| xiaoqi000 发表于 2023-1-15 16:21 | 显示全部楼层
由上图可知可迅速定位出程序崩溃地址发生在C函数,我们只需要分析C函数中的代码,找出程序进入异常中断的原因。这里说一个大家容易产生疑惑的知识点哈,由上面内容可知崩溃时下一个指令地址为0x80008f7,我们会发现在反汇编代码中找不到这个地址,当时当我们将它二进制最低位清0,查找0x80008f6这个地址就可以找到。这是什么原因呢?因为ARM内核是Thumb 16bit和32bit 指令混用,此时是Thumb 16bit指令,所以地址一般是2 byte 对齐,所以需要将最低位清零,就和ARM PC指针地址每次都是加4原理一样。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

67

主题

821

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部