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.pswp.cn/pingmian/78216.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/78216.shtml
英文地址,請注明出處:http://en.pswp.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也能執…