ARM Cortex-M (STM32)如何调试HardFault

目录

步骤 1: 实现一个有效的 HardFault 处理程序

步骤 2: 复现 HardFault 并使用调试器分析

步骤 3: 解读故障信息

步骤 4: 定位并修复源代码


HardFault 是 ARM Cortex-M 处理器中的一种异常。当处理器遇到无法处理的错误,或者配置为处理特定类型错误(如总线错误、内存管理错误、用法错误)的异常处理程序被禁用,或者在处理这些特定错误的过程中又发生了其他错误时,就会触发 HardFault。它是一个“兜底”的异常,表明系统遇到了严重问题。

调试 HardFault 需要耐心和系统的方法。关键在于:

  • 实现一个能捕获足够信息的 HardFault_Handler。
  • 利用调试器获取故障状态寄存器和异常堆栈帧的值。
  • 仔细解读这些值,特别是 CFSR, HFSR, MMFAR, BFAR 以及堆栈中的 PC。
  • 结合反汇编和源代码,定位到触发故障的具体指令和代码行。
  • 分析常见原因(指针、越界、堆栈、对齐、MPU 等)并修复。

发生 HardFault 时,处理器会自动将一些关键的寄存器压入当前使用的堆栈(MSP 或 PSP),并跳转到 HardFault 处理程序。我们的首要任务就是编写一个有效的 HardFault 处理程序,从中提取有用的信息。

步骤 1: 实现一个有效的 HardFault 处理程序

默认的 HardFault_Handler 通常是一个无限循环 while(1);。我们需要替换它,使其能够捕获并报告故障信息。

在你的项目中(通常在 stm32xxxx_it.c 或类似文件中)找到 HardFault_Handler 函数,并用以下代码替换或修改:

// 定义一个结构体来存储从堆栈中提取的寄存器值
typedef struct {uint32_t r0;uint32_t r1;uint32_t r2;uint32_t r3;uint32_t r12;uint32_t lr; // Link Registeruint32_t pc; // Program Counteruint32_t psr;// Program Status Register
} HardFaultRegs_t;// 全局变量,用于在调试器中查看
volatile HardFaultRegs_t stacked_regs;
volatile uint32_t cfsr_val;
volatile uint32_t hfsr_val;
volatile uint32_t dfsr_val;
volatile uint32_t afsr_val;
volatile uint32_t mmfar_val;
volatile uint32_t bfar_val;
volatile uint32_t stacked_sp; // 保存堆栈指针本身的值// HardFault 处理函数
// 使用 __attribute__((naked)) 避免编译器生成额外的栈操作代码
void HardFault_Handler(void) __attribute__((naked));
void HardFault_Handler(void)
{// 获取当前使用的堆栈指针 (MSP 或 PSP)// TST LR, #4 测试 LR 的 bit 2 (EXC_RETURN 的 bit 2)// 如果 bit 2 为 1,表示异常返回时使用 PSP;否则使用 MSP__asm volatile (" TST LR, #4\n"          // Test bit 2 of LR: 0 = MSP, 1 = PSP" ITE EQ\n"             // If-Then-Else based on EQ flag (result of TST)" MRSEQ R0, MSP\n"      // EQ=1 (bit 2 is 0): Use MSP, move MSP to R0" MRSNE R0, PSP\n"      // NE=0 (bit 2 is 1): Use PSP, move PSP to R0" MOV %0, R0\n"         // Move the selected stack pointer to the C variable 'stacked_sp': "=r" (stacked_sp)    // Output operand: stacked_sp C variable:                      // Input operands: none: "r0"                 // Clobbered registers: R0 is used internally);// 从获取的堆栈指针处加载寄存器值到结构体// stacked_sp 现在指向 R0 的位置stacked_regs.r0 = *((volatile uint32_t*)(stacked_sp + 0));stacked_regs.r1 = *((volatile uint32_t*)(stacked_sp + 4));stacked_regs.r2 = *((volatile uint32_t*)(stacked_sp + 8));stacked_regs.r3 = *((volatile uint32_t*)(stacked_sp + 12));stacked_regs.r12= *((volatile uint32_t*)(stacked_sp + 16));stacked_regs.lr = *((volatile uint32_t*)(stacked_sp + 20));stacked_regs.pc = *((volatile uint32_t*)(stacked_sp + 24));stacked_regs.psr= *((volatile uint32_t*)(stacked_sp + 28));// 读取故障状态寄存器cfsr_val = (*((volatile uint32_t*)0xE000ED28));hfsr_val = (*((volatile uint32_t*)0xE000ED2C)); // 注意:HFSR 地址是 0xE000ED2Cdfsr_val = (*((volatile uint32_t*)0xE000ED30));afsr_val = (*((volatile uint32_t*)0xE000ED3C));// 检查 MMFAR 和 BFAR 是否有效并读取if (cfsr_val & (1 << 7)) { // MMARVALID bit in MMFSRmmfar_val = (*((volatile uint32_t*)0xE000ED34));} else {mmfar_val = 0xFFFFFFFF; // 无效}if (cfsr_val & (1 << 15)) { // BFARVALID bit in BFSRbfar_val = (*((volatile uint32_t*)0xE000ED38));} else {bfar_val = 0xFFFFFFFF; // 无效}// 在这里可以添加代码将这些变量的值通过串口、SWO 或其他方式打印出来// printf("HardFault!\n");// printf("SP = 0x%08X\n", stacked_sp);// printf("R0 = 0x%08X\n", stacked_regs.r0);// printf("R1 = 0x%08X\n", stacked_regs.r1);// ... (打印其他寄存器)// printf("PC = 0x%08X\n", stacked_regs.pc); // 出错指令的下一条地址// printf("LR = 0x%08X\n", stacked_regs.lr);// printf("PSR= 0x%08X\n", stacked_regs.psr);// printf("CFSR=0x%08X\n", cfsr_val);// printf("HFSR=0x%08X\n", hfsr_val);// printf("MMFAR=0x%08X\n", mmfar_val);// printf("BFAR=0x%08X\n", bfar_val);// 设置一个断点在这里,或者进入无限循环等待调试器连接__asm volatile("BKPT #0\n"); // Software breakpoint// 或者// while(1);
}

注意:

  • __attribute__((naked)) 告诉编译器不要生成函数入口和出口代码(如压栈、出栈),因为我们需要精确控制堆栈指针。
  • volatile 关键字确保编译器不会优化掉对这些变量的读写。
  • 代码中包含了读取 MSP 或 PSP 的汇编指令。
  • 你需要根据你的项目配置(如串口初始化)来添加打印信息的代码。
  • 最后使用 BKPT #0 可以在 HardFault 发生时触发一个软件断点,让调试器停在 HardFault_Handler 中,方便查看变量值。

步骤 2: 复现 HardFault 并使用调试器分析

编译并下载 包含上述 HardFault_Handler 的代码到目标板。

连接调试器 (如 ST-Link, J-Link)。

运行代码 直到 HardFault 发生。如果设置了 BKPT #0,程序会自动停在断点处。如果没有设置断点,并且处理函数最后是 while(1);,则在 HardFault 发生后手动暂停程序,程序计数器应该停在 while(1); 循环内。

检查变量值: 在调试器的 Watch 窗口或 Memory 窗口中查看 stacked_regs, cfsr_val, hfsr_val, mmfar_val, bfar_val 等变量的值。

步骤 3: 解读故障信息

分析 CFSR:

  • MMFSR (位 [7:0]):

    • IACCVIOL (位 0): 指令访问冲突 (如从 XN 区域取指)。

    • DACCVIOL (位 1): 数据访问冲突 (如写入只读区)。

    • MUNSTKERR (位 3): MemManage Fault 在异常返回时出栈错误。

    • MSTKERR (位 4): MemManage Fault 在异常进入时压栈错误。

    • MLSPERR (位 5): MemManage Fault 发生在浮点惰性状态保存期间。

    • MMARVALID (位 7): MMFAR 中的地址有效。

  • BFSR (位 [15:8]):

    • IBUSERR (位 8): 指令预取导致的总线错误。

    • PRECISERR (位 9): 精确的数据总线错误。BFAR 有效。

    • IMPRECISERR (位 10): 不精确的数据总线错误。BFAR 无效。通常由写缓冲区或缓存引起,错误点与报告点有延迟。

    • UNSTKERR (位 11): BusFault 在异常返回时出栈错误。

    • STKERR (位 12): BusFault 在异常进入时压栈错误。

    • LSPERR (位 13): BusFault 发生在浮点惰性状态保存期间。

    • BFARVALID (位 15): BFAR 中的地址有效。

  • UFSR (位):

    • UNDEFINSTR (位 16): 执行了未定义指令。

    • INVSTATE (位 17): 尝试进入无效状态(如执行 ARM 指令)。

    • INVPC (位 18): 无效的 PC 加载(如尝试跳转到 LSB=0 的地址)。

    • NOCP (位 19): 尝试执行协处理器指令。

    • UNALIGNED (位 24): 发生了未对齐访问(需要 CCR.UNALIGN_TRP 位使能)。

    • DIVBYZERO (位 25): 执行了除以零的操作(需要 CCR.DIV_0_TRP 位使能)。

分析 HFSR:

  • VECTTBL (位 1): 读取向量表时发生总线错误(通常发生在异常处理启动阶段)。

  • FORCED (位 30): 表明 HardFault 是由一个可配置的故障(MemManage, BusFault, UsageFault)升级而来的,因为其处理程序被禁用或在处理时发生新故障。此时应重点查看 CFSR

  • DEBUGEVT (位 31): 表明 HardFault 是由调试事件引起的(例如,在 Halting 调试模式下)。

分析 MMFAR 和 BFAR:如果 MMARVALIDBFARVALID 置位,这两个寄存器会告诉你导致内存或总线错误的确切地址。检查这个地址是否在你预期的内存范围内,是否需要特殊访问权限(如 MPU 设置),或者是否指向了一个无效的外设地址。

分析堆栈帧中的 PC 和 LR:

  • stacked_regs.pc: 这是导致故障的指令的下一条指令的地址。在调试器的反汇编 (Disassembly) 窗口中跳转到 PC - 2PC - 4(取决于故障指令是 16 位还是 32 位 Thumb 指令)附近,查看是哪条汇编指令触发了错误。

  • stacked_regs.lr: 链路寄存器。如果是一般函数调用导致的 HardFault,LR 包含返回地址。如果 HardFault 发生在中断/异常处理程序内部,LR 会包含一个特殊的 EXC_RETURN 值(例如 0xFFFFFFF9, 0xFFFFFFFD 等),指示处理器状态和返回后使用的堆栈。这可以帮助判断 HardFault 是否发生在中断上下文中。

步骤 4: 定位并修复源代码

根据反汇编窗口中定位到的指令地址,结合 .map 文件或调试器的符号信息,找到对应的 C 源代码行。

分析原因:

  • 空指针/野指针: 检查 MMFARBFAR 指向的地址,或者出错指令访问的指针变量是否为 NULL 或指向了无效/已释放的内存区域。
  • 数组越界: 检查数组索引是否超出了边界,导致访问了非法内存。
  • 堆栈溢出: 如果 stacked_sp 的值非常接近或超出了定义的堆栈区域的边界,或者 PC 指向了堆栈区域,则很可能是堆栈溢出。检查函数调用深度、局部变量大小、中断嵌套。可以尝试增大堆栈空间 (startup_stm32xxxx.s 文件中定义)。
  • 未对齐访问: 检查代码中是否有对 uint16_t, uint32_t 等多字节类型的指针进行强制类型转换和解引用,而该指针的地址不是 2 或 4 的倍数。例如:uint32_t* p = (uint32_t*)0x20000001; val = *p;。可以修改数据结构或使用 memcpy 来避免。
  • 除零错误: 检查代码中是否存在除数为零的情况。
  • MPU 配置错误: 如果使用了 MPU,检查 MPU 区域的配置是否正确,是否允许了必要的读/写/执行权限。
  • 访问无效外设地址: 检查 BFAR 是否指向了一个未启用时钟或不存在的外设寄存器地址。
  • 中断/RTOS 问题: 如果 HardFault 发生在中断处理或 RTOS 任务切换期间,问题可能更复杂,可能涉及中断优先级配置错误、临界区保护不足、任务堆栈太小等。检查 LREXC_RETURN 值有助于判断上下文。

根据分析出的原因修改代码,重新编译、下载并运行代码,确保 HardFault 不再发生。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于归纳共形预测的大型视觉-语言模型中预测集的**数据驱动校准**

摘要 本研究通过分离共形预测&#xff08;SCP&#xff09;框架&#xff0c;解决了大型视觉语言模型&#xff08;LVLMs&#xff09;在视觉问答&#xff08;VQA&#xff09;任务中幻觉缓解的关键挑战。虽然LVLMs在多模态推理方面表现出色&#xff0c;但它们的输出常常表现出具有…

LangChain4j 搭配 Kotlin:以协程、流式交互赋能语言模型开发

Kotlin 支持 | LangChain4j Kotlin 是一种面向 JVM&#xff08;及其他平台&#xff09;的静态类型语言&#xff0c;能够实现简洁优雅的代码&#xff0c;并与 Java 库无缝互操作。 LangChain4j 利用 Kotlin 扩展和类型安全构建器来增强 Java API&#xff0c;为其增添特定于 Ko…

正大模型视角下的市场结构判断逻辑

正大模型视角下的市场结构判断逻辑 在多数交易策略中&#xff0c;结构识别往往先于方向判断。以正大的数据研判风格为例&#xff0c;其核心逻辑是&#xff1a;价格行为不能孤立解读&#xff0c;必须结合时间与成交效率来判断当前结构的有效性。 例如&#xff0c;一个上涨过程&…

Django 入门实战:从环境搭建到构建你的第一个 Web 应用

Django 入门实战&#xff1a;从环境搭建到构建你的第一个 Web 应用 恭喜你选择 Django 作为你学习 Python Web 开发的起点&#xff01;Django 是一个强大、成熟且功能齐全的框架&#xff0c;非常适合构建中大型的 Web 应用程序。本篇将通过一个简单的例子&#xff0c;带你走完…

Unity 打包后 无阴影 阴影不显示

在项目设置里面->质量 这里面显示的是打包之后的质量 PS:注意运行质量 点击左键选择运行质量,这俩不一致就会导致,运行有阴影但是打包出来的平台没有阴影,原因就在这. 质量等级选择好之后 往下滑,在这里打开阴影,如果距离过远不显示阴影,就增加阴影距离.

python——面向对象编程

一、编程思想 面向过程编程&#xff08;典型&#xff1a;c语言&#xff09;&#xff1a;是一种以过程为中心的编程思想。它强调流程化、线性化、步骤化的思考方式&#xff0c;实现思路就是函数。 面向对象编程&#xff1a;强调整体性和差异性。它将任何事物看做一个统一整个&…

宿主机和容器 ping 不通域名解决方法

目录 一、问题描述 二、宿主机解决方法 三、容器解决办法 一、问题描述 宿主机是Ubuntu&#xff0c;在宿主机上 ping 不通域名&#xff1a;xxxx.cn&#xff0c;但是个人电脑能 ping 通。 同时宿主机上的启动的k8s容器也无法ping通。 二、宿主机解决方法 ①编辑文件&#xff…

windows作业job介绍

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、作业job是什么&#xff1f;二、使用步骤1.代码示例 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; winapi网站&#xff1a; h…

ESG跨境电商如何为国内的跨境电商企业打开国外的市场

现在不管是国内还是国外&#xff0c;做电商的企业都非常的多&#xff0c;那么既然有这么多大电商公司&#xff0c;就要有为这些电商公司提供服务的公司&#xff0c;这就是ESG&#xff0c;它是专门为跨境电商服务的公司&#xff0c;那么这家公司的主要业务是什么呢&#xff1f;它…

龙虎榜——20250425

指数依然在震荡&#xff0c;等待方向选择&#xff0c;整体量能不搞但个股红多绿少。 2025年4月25日龙虎榜行业方向分析 一、核心主线方向 绿色电力&#xff08;政策驱动业绩弹性&#xff09; • 代表标的&#xff1a;华银电力&#xff08;绿电运营&#xff09;、西昌电力&…

大数据学习(112)-HIVE中的窗口函数

&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一…

【MySQL】MySQL索引与事务

目录 前言 1. 索引 &#xff08;index&#xff09; 1.1 概念 1.2 作用 1.3 使用场景 1.4 索引的相关操作 查看索引 创建索引 删除索引 2. 索引背后的数据结构 2.1 B树 2.2 B&#xff0b;树的特点 2.3 B&#xff0b;树的优势 3. 事务 3.1 为什么使用事务 3.2 事…

python21-循环小作业

课程&#xff1a;B站大学 记录python学习&#xff0c;直到学会基本的爬虫&#xff0c;使用python搭建接口自动化测试就算学会了&#xff0c;在进阶webui自动化&#xff0c;app自动化 循环语句小作业 for-in作业斐波那契 for 固定数值计算素数字符统计数字序列range 函数 水仙花…

深度学习小记(包括pytorch 还有一些神经网络架构)

这个是用来增加深度学习的知识面或者就是记录一些常用的命令,会不断的更新 import torchvision.transforms as transforms toPIL transforms.ToPILImage()#可以把tensor转换为Image类型的 imgtoPIL(img) #利用save就可以保存下来 img.save("/opt/data/private/stable_si…

Neo4j 可观测性最佳实践

Neo4j 介绍 Neo4j 是一款领先的图数据库管理系统&#xff0c;采用图数据模型来表示和存储数据。它以节点、关系和属性的形式组织数据&#xff0c;节点代表实体&#xff0c;关系表示节点间的连接&#xff0c;属性则为节点和关系附加信息。Neo4j 使用 Cypher 查询语言&#xff0…

算法训练营第三十天 | 动态规划 (三)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、01背包问题理论基础&#xff08;一&#xff09;动态规划五部曲确定dp数组以及下标的含义确定递推公式初始化dp数组确定遍历顺序 二、01背包问题理论基础&#…

玩机搞机基本常识-------小米OLED屏幕机型怎么设置为永不休眠_手机不息屏_保持亮屏功能 拒绝“烧屏” ?

前面在帮一位粉丝解决小米OLED机型在设置----锁屏下没有永不休眠的问题。在这里&#xff0c;大家要明白为什么有些小米机型有这个设置有的没有的原因。区分OLED 屏幕和 LCD屏幕的不同。从根本上拒绝烧屏问题。 OLED 屏幕的一些优缺点&#x1f49d;&#x1f49d;&#x1f49d; …

PostgreSQL使用LIKE右模糊没有走索引分析验证

建表&数据初始化可参考PostgreSQL 分区表——范围分区SQL实践 背景&#xff1a; 给t_common_work_order_log的handle_user_name新建索引后&#xff0c;使用LIKE右模糊匹配查询时&#xff0c;发现走的全表扫描 CREATE INDEX order_log_handle_user_name_index ON t_commo…

【vue】【element-plus】 el-date-picker使用cell-class-name进行标记,type=year不生效解决方法

typedete&#xff0c;自定义cell-class-name打标记效果如下&#xff1a; 相关代码&#xff1a; <el-date-pickerv-model"date":clearable"false":editable"false":cell-class-name"cellClassName"type"date"format&quo…

《Learning Langchain》阅读笔记8-RAG(4)在vector store中存储embbdings

什么是 vector store&#xff1f; 与专门用于存储结构化数据&#xff08;如 JSON 文档或符合关系型数据库模式的数据&#xff09;的传统数据库不同&#xff0c;vector stores处理的是非结构化数据&#xff0c;包括文本和图像。像传统数据库一样&#xff0c;vector stores也能执…