当前位置: 首页 > article >正文

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 不再发生。

相关文章:

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

目录 步骤 1: 实现一个有效的 HardFault 处理程序 步骤 2: 复现 HardFault 并使用调试器分析 步骤 3: 解读故障信息 步骤 4: 定位并修复源代码 HardFault 是 ARM Cortex-M 处理器中的一种异常。当处理器遇到无法处理的错误&#xff0c;或者配置为处理特定类型错误&#xff…...

黑马 redis面试篇笔记

redis主从 version: "3.2"services:r1:image: rediscontainer_name: r1network_mode: "host"entrypoint: ["redis-server", "--port", "7001"]r2:image: rediscontainer_name: r2network_mode: "host"entrypoint:…...

Docker端口映射与容器间DNS发现:打通服务通信的任督二脉

Docker端口映射与容器间DNS发现&#xff1a;打通服务通信的任督二脉 一、端口映射深度解析1.1 端口映射核心机制映射规则语法&#xff1a; 1.2 高级映射技巧批量端口映射&#xff1a;查看端口绑定状态&#xff1a; 二、容器间服务发现机制2.1 自定义网络DNS体系DNS解析特性&…...

DBdriver使用taos数据库

首先创建连接 连接后比如数据库里有三个库 选择其中的hypon 选中localhost&#xff0c;右键sql编辑器&#xff0c;打开sql控制台 就插入了一条数据...

观成科技:摩诃草组织Spyder下载器流量特征分析

一、概述 自2023年以来&#xff0c;摩诃草组织频繁使用Spyder下载器下载远控木马&#xff0c;例如Remcos。观成安全研究团队对近几年的Spyder样本进行了深入研究&#xff0c;发现不同版本的样本在数据加密、流量模式等方面存在差异。基于此&#xff0c;我们对多个版本样本的通…...

AIGC实战之如何构建出更好的大模型RAG系统

一、RAG 系统核心架构解析 1. 检索模块深度优化 1.1 混合检索技术实现 技术原理&#xff1a;结合稀疏检索&#xff08;BM25&#xff09;与密集检索&#xff08;DPR&#xff09;&#xff0c;通过动态权重分配提升检索精度。例如&#xff0c;在医疗领域&#xff0c;BM25 负责精…...

C++入门小馆: 深入了解STLlist

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…...

【Git】Fork和并请求

当你在 GitHub 或其他代码托管平台上 Fork 了一个项目后&#xff0c;你可以基于你的 Fork 进行开发&#xff0c;并通过 Pull Request&#xff08;PR&#xff09; 的方式将你的更改提交给原始项目&#xff08;也称为上游仓库&#xff09;。以下是完整的流程和步骤&#xff1a; 1…...

小白学习java第15天:JDBC

1.数据库驱动 想一下我们之前是怎么操作数据库&#xff0c;是不是使用SQL语句对其mysql数据库管理系统&#xff0c;然后管理系统在进行数据库&#xff08;硬盘文件里面的&#xff09;进行操作。那么我现在想使用应用程序对其数据库进行操作&#xff0c;应该怎么办呢&#xff1…...

大模型应用开发(PAFR)

Prompt问答 特征:利用大模型推理能力完成应用的核心功能 应用场景&#xff1a; 文本摘要分析 舆情分析 坐席检查 AI对话 AgentFunction Calling 特征&#xff1a;将应用端业务能力与AI大模型推理能力结合&#xff0c;简化复杂业务功能开发 应用场景: 旅行指南 数据…...

Go 剥离 HTML 标签的三把「瑞士军刀」——从正则到 Bluemonday

1 为什么要「剥皮」&#xff1f; 安全&#xff1a;去掉潜在的 <script onload…> 等恶意标签&#xff0c;防止存储型 XSS。可读性&#xff1a;日志、消息队列、搜索索引里往往只需要纯文本。一致性&#xff1a;不同富文本编辑器生成的 HTML 五花八门&#xff0c;统一成「…...

U-Mail邮件加速服务:全球链路加速,安全稳定收发

由于跨国网络拥堵、带宽不稳定等因素&#xff0c;导致海外用户在使用企业邮箱收发邮件时&#xff0c;经常出现邮件收发不畅的问题。针对这种情况&#xff0c;U-Mail正式推出了邮件加速服务&#xff0c;U-Mail邮件加速服务依托全球优质加速链路和转发集群服务器&#xff0c;为海…...

实战交易策略 篇十七:翻倍黑马交易策略

文章目录 系列文章设置指标判断大盘买入的条件判断大盘卖出的条件精选个股,挖掘明天能上涨的黑马熊市选股牛市选股短线最佳买点短线最佳卖点“翻倍”的核心秘籍系列文章 实战交易策略 篇一:奥利弗瓦莱士短线交易策略 实战交易策略 篇二:杰西利弗莫尔股票大作手操盘术策略 实…...

反爬策略应对指南:淘宝 API 商品数据采集的 IP 代理与请求伪装技术

一、引言​ 在电商数据驱动决策的时代&#xff0c;淘宝平台海量的商品数据极具价值。然而&#xff0c;淘宝为保障平台安全和用户体验&#xff0c;构建了严密的反爬体系。当采集淘宝 API 商品数据时&#xff0c;若不采取有效措施&#xff0c;频繁的请求极易触发反爬机制&#x…...

论文精读:大规模MIMO波束选择问题的量子计算解决方案

论文精读&#xff1a;大规模MIMO波束选择问题的量子计算解决方案 概要&#xff1a; 随着大规模多输入多输出系统&#xff08;MIMO&#xff09;在5G及未来通信技术中的应用&#xff0c;波束选择问题&#xff08;MBS&#xff09;成为提升系统性能的关键。传统的波束选择方法面临计…...

精益数据分析(13/126):洞察数据关系,灵活调整创业方向

精益数据分析&#xff08;13/126&#xff09;&#xff1a;洞察数据关系&#xff0c;灵活调整创业方向 大家好&#xff01;在创业和数据分析的探索之路上&#xff0c;每一次的学习都是成长的宝贵机会。今天&#xff0c;咱们接着深入学习《精益数据分析》&#xff0c;一起探索相…...

uniapp-商城-37-shop 购物车 选好了 进行订单确认3 支付栏

支付栏 就是前面用的 car-Layout 在shop也用来这个组件 只是在那里用来的是购物车。 1、 样式 我们开始进入这个页面是点击的shop的购物篮 到这里就变成了支付栏 其实他们是同一个组件 只是做了样式区分 2、具体看看样式和代码 2.1 消失了购物车和改变了按钮名字 如何…...

【LLM+Code】Claude Code Agent 0.2.9 版本PromptTools最细致解读

一、Claude Code 是anthropic团队开发的一个code agent bash工具 具体使用文档&#xff1a;https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview 1.1 安装/使用Claude Code 自行安装 npm install -g anthropic-ai/claude-code cd your-project-dire…...

ISCTF2024-misc(部分)

前言 之前写的&#xff0c;一直没发&#xff0c;留个记录吧&#xff0c;万一哪天记录掉了起码在csdn有个念想 1.少女的秘密花园 打开是个图片 随波逐流binwalk一下分离得到一个zip&#xff0c;解压得到base_misc发现是zip 爆破得到密码 解压得到一个txt&#xff0c;将里面的…...

U8G2在PC端模拟(C语言版本)

前提&#xff1a; 电脑已经准备好mingw编译器环境&#xff0c;已经加入环境变量. 测试方法&#xff1a; window下打开cmd,输入gcc -v 会有信息打印. u8g2 u8g2官方支持sdl2接口&#xff0c;已经做好了适配. 所以只需要在使用的开发环境配置好SDL2路径即可. sdl2和u8g2的适配…...

【计算机视觉】CV实战项目 - 深入解析基于HOG+SVM的行人检测系统:Pedestrian Detection

深入解析基于HOGSVM的行人检测系统&#xff1a;从理论到实践 技术核心&#xff1a;HOGSVM检测框架HOG特征原理SVM分类器 项目架构与数据准备INRIA Person数据集目录结构 实战指南&#xff1a;从零构建检测系统环境配置完整训练流程检测应用 关键技术问题与解决方案1. 难例挖掘不…...

如何借助全球动态IP实现多平台账号的批量注册?

无论是社交网络、在线购物平台还是专业应用软件&#xff0c;账号的创建和使用都是必不可少的。然而&#xff0c;在面对不同平台各自的注册限制和策略时&#xff0c;如何高效、安全且合法地进行账号批量注册成为了亟待解决的问题。本文将探讨全球动态IP在这一过程中的作用及其如…...

PR第二课--混剪

1.音乐打点 1.1 手动打点 按钮(如图),或者,快捷键M(如果在已有打点处,再次按M键会进入对标记点的设置界面,如下下图) 1.2 插件打点 一段音乐中,有明显的鼓点时,可以使用打点插件,快捷打点;如果鼓点不明显的话,最好还是手动打点,用插件打点会打出大量的标记点,…...

网页不同渲染方式的应对与反爬机制的处理——python爬虫

文章目录 写在前面爬虫习惯web 网页渲染方式服务器渲染客户端渲染 反爬机制使用session对象使用cookie让请求头信息更丰富使用代理和随机延迟 写在前面 本文是对前两篇文章所介绍的内容的补充&#xff0c;在了解前两篇文章——《爬虫入门与requests库的使用》和《BeautifulSou…...

高级电影感户外街拍人像摄影后期Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色介绍 高级电影感户外街拍人像摄影后期 Lr 调色&#xff0c;是运用 Adobe Lightroom 软件&#xff0c;对户外街拍的人像照片进行后期处理&#xff0c;以塑造出具有电影质感的独特视觉效果。此调色过程借助 Lr 丰富的工具与功能&#xff0c;从色彩、光影、对比度等多维度着手…...

JAVA设计模式——(三)桥接模式

JAVA设计模式——&#xff08;三&#xff09;桥接模式&#xff08;Bridge Pattern&#xff09; 介绍理解实现武器抽象类武器实现类涂装颜色的行为接口具体颜色的行为实现让行为影响武器修改武器抽象类修改实现类 测试 适用性 介绍 将抽象和实现解耦&#xff0c;使两者可以独立…...

类加载器与jvm的内存

1. 类加载器与内存的关系 类加载器的字节码放在方法区&#xff08;元空间&#xff09;中&#xff0c;同时类加载器加载类后类的信息&#xff08;成员变量、成员方法及修饰符等&#xff09;存放在方法区中。类的信息所占内存的回收要同时满足两个条件&#xff1a;类的实例被回收…...

docker 配置代理

说明&#xff1a;该方法仅对 docker 程序本身拉取镜像的时候有效&#xff0c;对命令行无效。 docker 配置代理有 2 中方法 1.Daemon configuration 直接在 /etc/docker/daemon.json 文件中配置 {"proxies": {"http-proxy": "http://proxy.example.…...

【硬核干货】JetBrains AI Assistant 干货笔记

快进来抄作业&#xff0c;小编呕心沥血整理的 JetBrains AI Assistant 超干货笔记&#xff01; 原文链接&#xff1a;【硬核干货】JetBrains AI Assistant 干货笔记 关于晓数神州 晓数神州坚持以“客户为中心”的宗旨&#xff0c;为客户提供专业的解决方案和技术服务&#xff…...

Linux部署ragflow,从安装docker开始~

安装docker https://download.docker.com/linux/static/stable/x86_64/docker-28.0.1.tgz #首先创建一个文件夹&#xff0c;存放我们需要的各类文件,并切换到该目录 mkdir /project && cd /project #此时我们的工作目录已经切换到刚刚创建的文件夹下了&#xff0c;接…...