打印

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

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

使用特权

评论回复
评论
xiaoqi000 2023-1-15 14:56 回复TA
———————————————— 版权声明:本文为CSDN博主「木子芯双」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_31446727/article/details/125709280 
沙发
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异常中断。

使用特权

评论回复
5
xiaoqi000|  楼主 | 2023-1-15 15:15 | 只看该作者
主函数代码如下:
#include <stdio.h>
#include "gd32c10x.h"
#include "main.h"
/* 异常中断栈内容结构体*/
typedef struct
{
        uint32_t LR_hardfault;
        uint32_t R4;
        uint32_t R5;
        uint32_t R6;
        uint32_t R7;
        uint32_t R8;
        uint32_t R9;
        uint32_t R10;
        uint32_t R11;
        uint32_t R0;
        uint32_t R1;
        uint32_t R2;
        uint32_t R3;
        uint32_t R12;
        uint32_t LR;
        uint32_t PC;
        uint32_t xPSR;
}Hard_Fault_Typedef;
uint8_t c[1024]={0};
uint32_t *p=(uint32_t*)0x20ffffff;

/*hardfault stack information output function*/
void hw_fault_printf(Hard_Fault_Typedef* hard_fault)
{
        printf("LR_hardfault: 0x%x\n",hard_fault->LR_hardfault);
        printf("R4: 0x%x\n",hard_fault->R4);
        printf("R5: 0x%x\n",hard_fault->R5);
        printf("R6: 0x%x\n",hard_fault->R6);
        printf("R7: 0x%x\n",hard_fault->R7);
        printf("R8: 0x%x\n",hard_fault->R8);
        printf("R9: 0x%x\n",hard_fault->R9);
        printf("R10: 0x%x\n",hard_fault->R10);
        printf("R11: 0x%x\n",hard_fault->R11);
        printf("R0: 0x%x\n",hard_fault->R0);
        printf("R1: 0x%x\n",hard_fault->R1);
        printf("R2: 0x%x\n",hard_fault->R2);
        printf("R3: 0x%x\n",hard_fault->R3);
        printf("R12: 0x%x\n",hard_fault->R12);
        printf("LR: 0x%x\n",hard_fault->LR);
        printf("PC: 0x%x\n",hard_fault->PC);
        printf("xPSR: 0x%x\n",hard_fault->xPSR);
}
/*usart configure function*/
void USART_init(void)
{
        rcu_periph_clock_enable(RCU_USART0);
  /*enable COM GPIO clock USART0*/
  rcu_periph_clock_enable(RCU_GPIOA);
  /* connect port to USARTx_Tx */
  gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_9);
  /* connect port to USARTx_Rx */
  gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,GPIO_PIN_10);
  usart_deinit(USART0);
  usart_baudrate_set(USART0, 115200U);
  usart_receive_config(USART0, USART_RECEIVE_ENABLE);
  usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
  usart_enable(USART0);
}

/*taskA function*/
void task_A(void)
{
        task_B();       
}

/*taskB function*/
void task_B(void)
{
        task_C(1);
}
uint8_t task_C(uint8_t data)
{
        *p=2;
        c[10]=data;
        return 100/data;
}

/*taskC function*/

int main(void)
{
  USART_init();
  task_A();       
        while(1)
  {
  }
}

int fputc(int ch, FILE *f)
{
    while (RESET == usart_flag_get(USART0, USART_FLAG_TBE));
    usart_data_transmit(USART0, (uint8_t) ch);
    return ch;
}

使用特权

评论回复
6
xiaoqi000|  楼主 | 2023-1-15 16:15 | 只看该作者
异常中断函数汇编代码如下:
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler                 [WEAK]  
                                IMPORT  hw_fault_printf
                                mrs     r0, msp                           ;R0保存进入异常中断时,栈的地址
                                stmdb   r0!, {r4-r11}                     ;依次将R11~R4寄存器值入栈
                                stmdb   r0!, {lr}                         ;因为后面调用C函数可能会改变异常中断LR的地址,故将链接地址入栈
                                msr     msp, r0                           ;将此时栈顶的实际地址赋值给msp主堆栈指针     
                                push    {lr}                              ;将异常lr链接地址入栈
                                bl      hw_fault_printf                   ;调用C函数打印异常中断栈内容信息
                                pop     {lr}                              ;出栈恢复异常lr的值
                        bx      lr                               ;返回线程模式并使用主堆栈指针         
                B       .
                ENDP

使用特权

评论回复
7
xiaoqi000|  楼主 | 2023-1-15 16:17 | 只看该作者
串口助手打印信息如下:

使用特权

评论回复
8
xiaoqi000|  楼主 | 2023-1-15 16:18 | 只看该作者
由上图我们可以看出进入异常时LR的地址为0xfffffff9,查阅内核手册可知此时SP指针使用的是主堆栈指针MSP,程序发生崩溃时PC指针的地址为0x8000b12,崩溃时下一条指令的地址为0x80008f7。知道以上信息后,我们可以在程序编译输出的反汇编代码中查找这两个地址信息,可以迅速定位程序崩溃地址。

使用特权

评论回复
9
xiaoqi000|  楼主 | 2023-1-15 16:20 | 只看该作者

使用特权

评论回复
10
xiaoqi000|  楼主 | 2023-1-15 16:21 | 只看该作者
由上图可知可迅速定位出程序崩溃地址发生在C函数,我们只需要分析C函数中的代码,找出程序进入异常中断的原因。这里说一个大家容易产生疑惑的知识点哈,由上面内容可知崩溃时下一个指令地址为0x80008f7,我们会发现在反汇编代码中找不到这个地址,当时当我们将它二进制最低位清0,查找0x80008f6这个地址就可以找到。这是什么原因呢?因为ARM内核是Thumb 16bit和32bit 指令混用,此时是Thumb 16bit指令,所以地址一般是2 byte 对齐,所以需要将最低位清零,就和ARM PC指针地址每次都是加4原理一样。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

49

主题

731

帖子

0

粉丝