当前位置:网站首页>基于GD32 C10x MCU栈回溯调试原理实现
基于GD32 C10x MCU栈回溯调试原理实现
2022-07-16 02:56:00 【木子芯双】
在嵌入式软件开发调试过程中,经常会出现程序在运行过程中莫名其妙崩溃进入HardFault异常中断。因为GD 或者ST 芯片对于hardfault异常中断的处理是跳进一个函数死循环,不利于程序进入异常问题的定位分析。平时我们通过调试器进入Debug模式,设置断点然后一步步定位发生异常的位置,这种方法只适用于代码量比较小的情况。当代码量比较大时,而且函数调用关系又复杂时,这种方法很费时间。本文基于GD32 C10x芯片实现一种栈会回溯调试方法,用于当程序因为数组越界、野指针、堆栈溢出或者非法地址访问时,快速定位发生异常的位置。
1.栈回溯调试原理
当MCU发生异常中断时,硬件会自动将xPSR、PC、LR、R12、R3~R0入栈,PC指针是程序发生崩溃的位置,LR是发生崩溃程序的下一条指令地址,R12、R3 ~R0是发生崩溃时内部寄存器的值。栈回溯调试原理就是程序发生崩溃后,在进入异常中断后,通过汇编程序,将R11 ~ R4 手动入栈,并将当前栈指针值保存在R0中,通过汇编调用C函数,C函数的入口参数即为栈的地址,在C函数中按照先进后出原则通过串口依次打印栈中信息。程序进入异常时,硬件自动保存栈内容如下:
2. 栈回溯原理程序实现
本文通过定义一个指针,指向非法的sram地址,通过向非法地址赋值,从而进入hardfault异常中断。主函数代码如下:
#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;
}
异常中断函数汇编代码如下:
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
串口助手打印信息如下:
由上图我们可以看出进入异常时LR的地址为0xfffffff9,查阅内核手册可知此时SP指针使用的是主堆栈指针MSP,程序发生崩溃时PC指针的地址为0x8000b12,崩溃时下一条指令的地址为0x80008f7。知道以上信息后,我们可以在程序编译输出的反汇编代码中查找这两个地址信息,可以迅速定位程序崩溃地址。
由上图可知可迅速定位出程序崩溃地址发生在C函数,我们只需要分析C函数中的代码,找出程序进入异常中断的原因。这里说一个大家容易产生疑惑的知识点哈,由上面内容可知崩溃时下一个指令地址为0x80008f7,我们会发现在反汇编代码中找不到这个地址,当时当我们将它二进制最低位清0,查找0x80008f6这个地址就可以找到。这是什么原因呢?因为ARM内核是Thumb 16bit和32bit 指令混用,此时是Thumb 16bit指令,所以地址一般是2 byte 对齐,所以需要将最低位清零,就和ARM PC指针地址每次都是加4原理一样。
边栏推荐
- Kotlin compose bottom bar
- 5g applications are accelerated, and cool Lehman VR live broadcast is born at the right time.
- JS array most complete method interpretation!!!
- MySQL基础——数据库约束与表的设计
- MySQL Basic - add and Advanced Query
- 印度独角兽再添一员:5ire以15亿美元估值获1亿美元A轮融资
- QT writing IOT management platform 43 alarm SMS forwarding
- ReversingKr-wp(7)
- 2022.07.06 group a summary
- Uncover the origin of metamask: a tool set for getting started with Web3 culture
猜你喜欢

如何免費系統化入門數據科學?
![leetcode:1760. The least number of goal balls in the bag [the most valuable value + the scoreboard]](/img/f3/f7b038ef70ee0f1851dfda9bb793a9.png)
leetcode:1760. The least number of goal balls in the bag [the most valuable value + the scoreboard]

【Golang | gRPC】gRPC-Client Streaming客户端流实战

Software testing - learning notes 3

数字藏品系统开发,助力企业元宇宙场景营销

Typescript之常量与对象冻结

Classic examples of C language: 11-20 examples: finding the maximum and minimum values of two-dimensional arrays, finding prime numbers in arrays, compiling perpetual calendars, sorting array elements

Resume of sleeper - is there a boss who needs to recruit? Your success is just as bad as mine

编程老司机带你玩转 CompletableFuture 异步编程

沉睡者的简历-有老板需要招兵买马吗?您的成功就差一个我
随机推荐
ORA-00322&ORA-00312
Slave during MySQL master-slave configuration_ IO_ Running: connecting solution
报错解决——台式机与U盘数据传输报错
《论文阅读》A Survey on Dialogue Systems: Recent Advances and New Frontiers
解压报错——error: Entry too big to split, read, or write (Poor compression resulted in unexpectedly large
Taking advantage of the trend, cool Lehman continued to set out with many pioneers
Data structure in redis (I): String
CRS-2674,CRS-4000
DINO&PAWS
[QT introduction] Introduction to three window classes
Software testing - learning notes 3
还在用命令行看日志?快用Kibana吧,可视化日志分析YYDS ~
In depth analysis of multiple knapsack problem (Part 1)
2022.07.06 group a summary
聊聊数据溢出的事
【Golang | gRPC】使用TLS/SSL认证的gRPC服务
Word——设置Tab键宽度
[QT入门篇]三大窗口类介绍
编程老司机带你玩转 CompletableFuture 异步编程
MySQL基础——新增与进阶查询