ARM死机(HardFault)调试技巧详解(栈回溯,不破坏现场)

目录

Keil调试技巧:

一.不破坏现场连接仿真器与进入debug

二.栈回溯

死机调试示例

J-Link调试方法

示例:空指针异常

不能连接烧录器或者读取内存怎么办?


在日常开发中,经常会遇到单片机卡死机等问题,经常很难定位到问题代码在哪里。

常见的会导致单片机跑飞卡死的原因比如说死循环、数组越界、野指针等,尤其野指针最难调试,一般调试只能看见程序掉进 HardFault_Handler 死循环,无从得知是什么导致程序掉进了HardFault_Handler,连什么问题卡死都不知道,更别提上哪找了。

(一些硬件问题也会导致HardFault_Handler,了解就好) 

Keil调试技巧:

一.不破坏现场连接仿真器与进入debug

假如死机触发比较难,好不容易触发一次想连接仿真器,连完debug自动把单片机reset了,又要等它再触发。

Keil只需按如下设置即可:

1.不要进入debug自动回到startup

2.Jlink连接仿真器自动reset

STlink也一样

3.连接仿真器,点击debug,但发现C语言代码未发生关联,无从得知现在运行到哪了

解决办法:在debug的command窗口输入 LOAD %L INCREMENTAL

然后就会发现C语言关联上了,可以定位到当前代码。

二.栈回溯

首先看调试页面左侧,会有一列寄存器在Core中,它们是ARM的内核寄存器

在ARM中,R0到R15是通用寄存器,除此之外还有特殊寄存器。

R0到R11也叫通用目的寄存器,可以存数值进行运算,

其中R0到R3可以存函数参数, 所以设计函数传参最好不要超过4个,超过4个的话,CPU会有额外负担。

另外函数结束返回时,R0和R1也可以存函数返回值。

我们的关注点在于R13 R14 R15,也叫SP LR PC, 用来存储地址(内存或flash)

SP保存内存中的栈指针地址;

LR保存函数的返回地址;

PC保存当前程序指令的地址。

所以根据上图可知,发生异常中断如 HardFault_Handler 时,通过SP寄存器的栈指针即可找到保存现场的栈结构,

再通过其中的PC和LR上的地址即可对应到当前程序发生问题的指令代码。

死机调试示例

int32_t Caculate(int32_t val1, int32_t val2)
{
    return val1 / val2;
}
 
int32_t Process(void)
{
    volatile int32_t aVal = 10;
    volatile int32_t bVal = 0;
    return Caculate(aVal, bVal);
}
//... ... .... ...
int main() {
    //... ...
    volatile int *SCB_CCR = (volatile int *)0xE000ED14;
    *SCB_CCR |= (1<<4);
    volatile int32_t mVal = Process();
    //... ...
}

以上代码是个除数为0的问题,正常来说除数为0可能会导致死机,但单片机有时并不会

例如STM32中出现除以零的操作时,会进入异常处理,而导致程序出现异常,但是进入Usage Fault 是有前提条件的,即 只有在 DIV_0_TRP 置位时才会发生。

 

所以在以上代码中我们加入对CCR寄存器的操作,让除数为0进入异常中断。

现在单片机已经死机,我们按上述不破坏现场方法进入调试。

程序进入异常中断,PC也指向异常中断所在地址。

如何找到问题代码呢?程序是执行问题代码之后进入中断,所以我们要向前回溯。

根据上文说的栈回溯,观察此时SP指向 0x20000448 即栈空间中保存的问题现场。

栈空间在内存中,要查看内存数据,需要点击:

View →  Memory windows →  Memory 1

打开一个内存视窗,默认为1字节显示,但是我们是32位单片机

所以在视窗中右键→  unsigned →  long , 选择4字节显示,方便我们查看

在其中输入SP的地址

找到了对应的栈,根据上文栈结构的介绍,要找到这个栈内的PC LR地址

R0 R1 R2 R3 R12 LR PC PRS , 其中LR为08001415 , PC为08001046。

找到08001046所在指令:

return出现了问题?肯定不是,所以看汇编代码。

MOVS是往寄存器里装数值

SDIV是除法指令

    71:         volatile int32_t aVal = 10;
0x08001040 210A      MOVS          r1,#0x0A
    72:         volatile int32_t bVal = 0;
0x08001042 2000      MOVS          r0,#0x00
    73:         return Caculate(aVal, bVal);
0x08001044 9000      STR           r0,[sp,#0x00]
0x08001046 FB91F0F0  SDIV          r0,r1,r0

 可知向 r1 存入 0x0A(10),然后向 r0 存入 0x00 ,然后SDIV r0,r1,r0 ,让 r1 除以 r0 的结果存在 r0 里,

这就能看出问题是除数为0了。

如果汇编看不懂,可以直接丢给GPT让它解释,或者查一下ARM指令手册。

J-Link调试方法

如果不用keil的debug,也可以用Jlink。

但是需要keil中页面的debug的那种地址和汇编指令对应文件。

文件格式为dis,在魔术棒中添加即可

如 fromelf --text -a -c --output .\Arm_test\Arm_test.dis .\Arm_test\Arm_test.axf

再次编译可以得到dis文件,拉到keil里打开就是地址汇编指令的对应

 

示例:空指针异常

这里有一个函数指针的任务调度框架,如果某个任务的执行函数指针设为空,则主循环执行这个任务时会触发空指针异常

打开Jlink 或者 jlink commande,在你jflash安装目录里面有

打开后是个命令行,连接好仿真器之后

输入usb连接设备,输入halt,再选择对应单片机和调试接口以及速度。

得到PC 和SP 的值,在dis文件中搜索PC的地址可以得知又落入了异常中断

下面要根据SP的值在内存中找到栈。

在jlink命令行输入:

如:savebin d:/ram.bin 0x20000000 0x10000

表示将0x20000000开始0x10000大小的内存数据读出来存到D盘,叫ram.bin

然后打开J-Flash,把bin文件拉进来,选择起始地址

选择4字节,按32位显示,搜索SP地址,

顺着往下

找到LR地址,08001417

找到PC地址,发现是00000000

为什么PC地址会为0??

很显然是空指针,这里调用指令的地址指向了空

怎么看在哪里出现的空指针呢?

我们知道LR保存函数的返回地址,查一下LR就知道在哪里调用的空指针

查LR地址要减去1,也就是在dis文件中查08001416

发现在main函数中

(这里main函数中只有一个TaskHandler,汇编直接把这个函数展开了,不同编译器和单片机有所区别,GD32中TaskHandler没有被展开在main里)

这里一大段汇编看起来很难理解,GPT来解释一下

多元素数据结构的便利,刚好就是我们TaskHandler的内容,

BCC      0x8001400;      B        0x80013fc;

都是转到前面的指令,也就是两个循环嵌套,对应了主循环while1和TaskHandler中的for循环

再看问题指令:

        0x08001414:    4780        .G      BLX      r0
        0x08001416:    1c64        d.      ADDS     r4,r4,#1

BLX是加载对应地址的指令,然后再下一步就会出错,

说明这里加载指令有问题,对应到上文就是PC指向了00000000,即一个空指针。
 

不能连接烧录器或者读取内存怎么办?

对于一些企业项目,会有读保护不允许读取内存,或者烧录口被复用无法连接调试器的。
理论上可以用串口把错误信息打印出来,如(公司里大神写的):

void hard_fault_handler_c(unsigned int * hardfault_args)
{
    uint32_t stacked_r0;
    uint32_t stacked_r1;
    uint32_t stacked_r2;
    uint32_t stacked_r3;
    uint32_t stacked_r12;
    uint32_t stacked_lr;
    uint32_t stacked_pc;
    uint32_t stacked_psr;
    stacked_r0 = ((uint32_t) hardfault_args[0]);
    stacked_r1 = ((uint32_t) hardfault_args[1]);
    stacked_r2 = ((uint32_t) hardfault_args[2]);
    stacked_r3 = ((uint32_t) hardfault_args[3]);
    stacked_r12 = ((uint32_t) hardfault_args[4]);
    stacked_lr = ((uint32_t) hardfault_args[5]);
    stacked_pc = ((uint32_t) hardfault_args[6]);
    stacked_psr = ((uint32_t) hardfault_args[7]);
    RTT_Printf("\n\n[Hard fault handler - all numbers in hex]\n");
    RTT_Printf("R0 = 0x%08x\n", stacked_r0);
    RTT_Printf("R1 = 0x%08x\n", stacked_r1);
    RTT_Printf("R2 = 0x%08x\n", stacked_r2);
    RTT_Printf("R3 = 0x%08x\n", stacked_r3);
    RTT_Printf("R12 = 0x%08x\n", stacked_r12);
    RTT_Printf("LR [R14] = 0x%08x subroutine call return address\n", stacked_lr);
    RTT_Printf("PC [R15] = 0x%08x program counter\n", stacked_pc);
    RTT_Printf("PSR = 0x%08x\n", stacked_psr);
    RTT_Printf("BFAR = 0x%08x\n", (*((volatile uint32_t *)(0xE000ED38))));
    RTT_Printf("CFSR = 0x%08x\n", (*((volatile uint32_t *)(0xE000ED28))));
    RTT_Printf("HFSR = 0x%08x\n", (*((volatile uint32_t *)(0xE000ED2C))));
    RTT_Printf("DFSR = 0x%08x\n", (*((volatile uint32_t *)(0xE000ED30))));
    RTT_Printf("AFSR = 0x%08x\n", (*((volatile uint32_t *)(0xE000ED3C))));
    RTT_Printf("SCB_SHCSR = 0x%08x\n", SCB->SHCSR);
    while (1) __NOP();
}
 
/* Cortex M0/1 */
__attribute__((naked)) void HardFault_Handler()
{
    asm volatile (
        "MOVS R0, #4\n\t"
        "MOV R1, LR\n\t"
        "TST R0, R1\n\t"
        "BEQ WDT_IRQHandler_call_real\n\t"
        "MRS R0, PSP\n\t"
        "LDR R1, =hard_fault_handler_c\n\t"
        "BX  R1\n"
    "WDT_IRQHandler_call_real:\n\t"
        "MRS R0, MSP\n\t"
        "LDR R1, =hard_fault_handler_c\n\t"
        "BX  R1"
    );
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/912753.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

nodejs 020: React语法规则 props和state

props和state 在 React 中&#xff0c;props 和 state 是管理数据流的两种核心机制。理解它们之间的区别和用途是构建 React 应用程序的基础。 一、props 和 state的区别 特性propsstate定义方式由父组件传递给子组件的数据组件内部管理的本地数据是否可修改不可变&#xff…

【开源免费】基于SpringBoot+Vue.JS水果购物网站(JAVA毕业设计)

博主说明&#xff1a;本文项目编号 T 065 &#xff0c;文末自助获取源码 \color{red}{T065&#xff0c;文末自助获取源码} T065&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

python可视化进阶

引用&#xff1a; 首先需要安装 plotnine from plotnine import* import joypy数据可视化进阶操作 3.1 类别数据可视化 【例3-1】——绘制简单条形图 【代码框3-1】——绘制简单条形图 # 图3-1的绘制代码 import pandas as pd import matplotlib.pyplot as plt from cvxpy …

大模型入门自学资源汇总,很难找到比这还全的大模型学习资源总结了!

接触各种AI工具到现在也快两年了&#xff0c;今年和同学陆续做了一些AI应用的科普宣讲&#xff0c;在这过程中收集了不少自学资源&#xff0c;特地挑出一部分整理成以下的内容。 书籍 大模型应用开发极简入门&#xff1a;基于GPT-4和ChatGPT 首推今年年初出版的《大模型应用开…

为何选择Spring AI Alibaba开发智能客服平台?

0 前言 本文来看如何使用Spring AI Alibaba构建Agent应用。 1 需求 智能客服平台&#xff0c;可帮助用户完成机票预定、问题解答、机票改签、取消等动作&#xff0c;具体要求&#xff1a; 基于 AI 大模型与用户对话&#xff0c;理解用户自然语言表达的需求支持多轮连续对话…

Python学习从0到1 day27 第三阶段 Spark ② 数据计算Ⅰ

人总是会执着于失去的&#xff0c;而又不珍惜现在所拥有的 —— 24.11.9 一、map方法 PySpark的数据计算&#xff0c;都是基于RDD对象来进行的&#xff0c;采用依赖进行&#xff0c;RDD对象内置丰富的成员方法&#xff08;算子&#xff09; map算子 功能&#xff1a;map算子…

数据结构合并两个有序链表

数据结构 1.合并两个有序数组代码&#xff1a; 1.合并两个有序数组 这里我们可以创建一个新的对象作为合并后的新链表newHead&#xff0c;而NewHead.next就是我们要返回的头部的位置 在创建一个对象来获取nextHead.next下一个节点来作为我们新链表的起始位置防止我们的头部位置…

动态规划 —— dp 问题-买卖股票的最佳时机含手续费

1. 买卖股票的最佳时机含手续费 题目链接&#xff1a; 714. 买卖股票的最佳时机含手续费 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/ 2. 算法原理 状态表示&#xff1a;以某一个…

利用pythonstudio写的PDF、图片批量水印生成器,可同时为不同读者生成多组水印

现在很多场合需要将PDF或图片加水印&#xff0c;本程序利用pythonstudio编写。 第一步 界面 其中&#xff1a; LstMask:列表框 PopupMenu:PmnMark LstFiles:列表框 PopupMenu:PmnFiles OdFiles:文件选择器 Filter:PDF文件(.PDF)|.PDF|图像文件(.JPG)|.JPG|图像文件(.png…

基于python深度学习技术矩阵分解的推荐系统,通过学习隐含特征,实现推荐

实现了一个基于矩阵分解的推荐系统&#xff0c;用于预测用户对电影的评分。具体来说&#xff0c;该程序通过TensorFlow构建和训练一个模型&#xff0c;来学习用户和电影之间的隐含特征&#xff0c;并根据这些特征预测评分。以下是代码的主要功能和步骤的详细描述&#xff1a; …

[vulnhub] DarkHole: 1

https://www.vulnhub.com/entry/darkhole-1,724/ 端口扫描主机发现 探测存活主机&#xff0c;184是靶机 nmap -sP 192.168.75.0/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-08 09:59 CST Nmap scan report for 192.168.75.1 Host is up (0.00027s latency). MA…

4.1 WINDOWS XP,ReactOS对象与对象目录----1

系列文章目录 文章目录 系列文章目录4.1 对象与对象目录OBJECT_HEADERObpLookupEntryDirectory()NtCreateTimer() 4.1 对象与对象目录 “对象(Object)”这个词现在大家都已耳熟能详了&#xff0c;但是对象到底是什么呢?广义地说&#xff0c;对象就是“目标”&#xff0c;行为…

STM32H503开发(2)----STM32CubeProgrammer烧录

STM32H503开发----2.STM32CubeProgrammer烧录 概述硬件准备视频教学样品申请源码下载参考程序自举模式BOOT0设置UART烧录USB烧录 概述 STM32CubeProgrammer (STM32CubeProg) 是一款用于编程STM32产品的全功能多操作系统软件工具。 它提供了一个易用高效的环境&#xff0c;通过…

“双十一”电商狂欢进行时,在AI的加持下看网易云信IM、RTC如何助力商家!

作为一年一度的消费盛会&#xff0c;2024年“双十一”购物狂欢节早已拉开帷幕。蹲守直播间、在主播热情介绍中点开链接并加购&#xff0c;也已成为大多数人打开“双11”的重要方式。然而&#xff0c;在这火热的购物氛围背后&#xff0c;主播频频“翻车”、优质主播稀缺、客服响…

debian系统安装qt的时候 显示xcb相关文件缺失

如果是安装之后的问题 我们可以选择使用ldd的命令查看当前依赖的so那些文件确实 ldd /home/yinsir/Qt/5.15.2/gcc_64/plugins/platforms/libqxcb.so 本人在进行打包的时候 出现则会个报错 ERROR: ldd outputLine: “libxcb-util.so.1 > not found” ERROR: for binary: “/…

A023-基于SpringBoot的冷链物流系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

【数据分析】如何构建指标体系?

有哪些指标体系搭建模型&#xff1f;五个步骤教你从0开始搭建指标体系 一、企业指标体系搭建存在什么问题 许多企业在搭建数据指标体系时遇到了诸多难题&#xff0c;如问题定位不准确、数据采集不完整、目标不一致、报表无序、指标覆盖不全面以及报表价值未充分利用等。 1、…

C++20 概念与约束(1)—— SFINAE

1、从模板说起 众所周知&#xff0c;C在使用模板时&#xff0c;如果有多个模板匹配&#xff0c;则编译器会选择最匹配的一个模板进行实例化&#xff0c;这也正是模板特化和偏特化的依据。 根据上面这张图中的现象&#xff0c;列举下面几个示例&#xff1a; 1、不存在模板的情况…

基于Spring Boot的在线装修管理系统的设计与实现,LW+源码+讲解

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#…

原生鸿蒙应用市场:赋能开发者全生命周期服务体验

文章目录 背景自动化检测前移&#xff1a;早发现&#xff0c;早解决技术细节&#xff1a;静态代码分析与兼容性测试应用场景 按需加载&#xff1a;优化性能&#xff0c;提升用户体验技术细节&#xff1a;模块化与懒加载实现应用场景 应用加密&#xff1a;保护应用代码安全&…