HardFault定位方法和步驟


轉載:https://aijishu.com/a/1060000000234578

1. Cortex-M0 處理器內核異常中斷簡介

在Cortex‐M0內核上搭載了一個異常響應系統,支持眾多的系統異常和外部中斷。其中,編號為1-15的對應系統異常,大於等於16的則全是外部中斷,優先級的數值越小,則優先級越高。除了個別異常的優先級被定死外,其它異常的優先級都是可編程的。

因為芯片設計可以修改內核的硬件描述源代碼,所以做成芯片后,支持的中斷源數目常常不到240 個,並且優先級的位數也由芯片廠商最終決定。

表1.png

類型編號為 1-15 對應系統異常,在《ARM Cortex-M0權威指南》一書中的第12章節<錯誤處理>章節中有描述:對於ARM處理器,架構采用錯誤異常的機制來檢測問題,當一個程序產生了錯誤並且被處理器檢測到時,異常中斷會被觸發,並且核心會跳轉到相應的異常終端處理函數執行,錯誤異常的中斷有如下:

Reset
在上下電、NRST拉低、看門狗復位或軟復位時啟動復位。當復位產生時,處理器停止一切操作,並將復位當做一種特殊形式的異常來執行,進入到對應的中斷函數。當復位撤銷時,從向量表中復位項提供的地址處重新啟動執行,芯片重新開始執行。

NMI
不可屏蔽中斷(NMI),可以由外設產生,也可以由軟件來觸發。這是除復位之外優先級最高的異常中斷,NMI永遠使能,優先級固定為-2,CSS的時鍾安全機制使能判定時鍾失效后就會進入到該中斷。NMI 不能:
1、被屏蔽,它的執行也不能被其他任何異常中止;
2、被除復位之外的任何異常搶占。

HardFault
HardFault 是由於在正常操作過程中或在異常處理過程中出現錯誤而出現的一個異常。HardFault的優先級固定為-1,表明它的優先級要高於任何優先級可配置的異常。

SVCall
管理程序調用(SVC)異常是一個由SVC指令觸發的異常。在OS環境下,應用程序可以使用 SVC指令來訪問OS內核函數和器件驅動。

PendSV
PendSV是一個中斷驅動的系統級服務請求。在OS環境下,當沒有其它異常有效時,使用 PendSV 來進行任務切換。

SysTick
SysTick是一個系統定時器到達零時產生的異常,軟件也可以產生一個SysTick異常。在OS環境下,處理器可以將這個異常用作系統節拍。

中斷(IRQ)
中斷(或 IRQ)是外設發出的一個異常,或者由軟件請求產生的一個異常。在系統中,外設使用中斷來與處理器通信,在中斷函數中可以查詢和清除標志操作。

2. HardFault異常

HardFault (硬件錯誤,也有譯為硬錯誤)是在MCU上編寫程序中所產生的錯誤,硬件錯誤處理幾乎是最高優先級,它的優先級為-1,只有復位和不可屏蔽中斷(NMI)可以對其進行搶占。當它發生時,表示處理器出現了問題,需要采取緊急修復措施。

造成HardFault錯誤的可能原因較多,如何在代碼量較大的情況下,快速定位造成的HardFault的問題代碼,就成為比較關鍵的問題。

本文將以MM32F0130系列MCU為例,Keil-MDK開發環境,總結HardFault的調試、定位方法。在其它Cortex-M0 (M3,M4)內核處理器,和其它開發環境下,也可作為參考。

2.1 可能的原因

《ARM Cortex-M0權威指南》中提到,關於 Cortex M0內核主要有以下幾點引起HardFault的原因:

  • 非法存儲器訪問
  • 非對齊數據訪問
  • 從總線返回錯誤
  • 異常處理中的棧被破壞
  • 程序在某些 C 函數中崩潰
  • 意外地試圖切換至 ARM 狀態
  • 在錯誤的優先級上執行系統服務調用指令(SVC)

從軟件角度,產生HardFault的可能原因有:

  • 數組越界
  • 野指針
  • 未初始化硬件卻開始操作,或無中斷服務函數等
  • 任務堆棧溢出
  • 中斷服務函數設置錯誤
  • 時鍾異常

注意:只有復位和NMI可以搶占優先級固定的 HardFault 處理程序。HardFault可以搶占除復位、NMI 或其它硬故障之外的任何異常。

2.2 可能出現的異常

如果在執行NMI或HardFault處理程序時,或者在一個使用MSP的異常返回時出棧的卻是PSR的時候系統產生一個總線錯誤,處理器進入一個鎖定狀態。當處理器處於鎖定狀態時,它不執行任何指令。處理器保持處於鎖定狀態,直到下面任何一種情況出現:

  • 出現復位
  • 調試器將鎖定狀態終止,出現中止仿真的現象
  • 出現一個NMI,以及當前的鎖定處於HardFault處理程序中

注意:如果鎖定狀態出現在NMI處理程序中,后面的NMI就無法使處理器離開鎖定狀態。

在應用程序中,處理器處於鎖定狀態,會一直在void HardFault\_Handler(void)函數中執行。

void HardFault_Handler(void)
{  /* Go to infinite loop when Hard Fault exception occurs */
  while (1)
  {
  }
}

下面將在MM32F0130上運行的數組越界代碼為例,具體闡述定位步驟:

void StackTest(void)
{    int data[3],i;
    for(i=0; i<10000; i++)
    {
    data[i]=1;
    }
}
3. 查找HardFault方法和步驟

實際環境中,由於測試高壓等產品常常無法連接調試器,故需要代碼來定位目標語句地址,並通過一定手段保存:

在MM32F0130中,需先修改啟動文件startup\_mm32f013x.s:

HardFault_Handler\
            PROC
            IMPORT  hard_fault_handler_c;函數聲明
            MOVS r0, #4                   ;判斷主棧指針還是進程棧指針
            MOV r1, LR
            TST r0, r1
            BEQ stacking_used_MSP        ;如果是主棧指針
            MRS R0, PSP              ;否則是進程棧指針,把進程棧指針地址付給 R0
            B get_LR_and_branch          ;跳轉到 HardFault 中斷程序
            stacking_used_MSP
            MRS R0, MSP                   ;把主棧指針地址賦給 R0
            get_LR_and_branch
            MOV R1, LR

            BL hard_fault_handler_c
            ENDP

該段代碼會判斷當前堆棧使用的是MSP或PSP,然后將堆棧參數傳遞給hard\_fault\_handler\_c函數,該函數定義如下:

void hard_fault_handler_c(unsigned int * hardfault_args, unsigned lr_value)
{    unsigned int stacked_r0; //壓棧的 r0
    unsigned int stacked_r1; //壓棧的 r1
    unsigned int stacked_r2; //壓棧的 r2
    unsigned int stacked_r3; //壓棧的 r3
    unsigned int stacked_r12; //壓棧的 r12
    unsigned int stacked_lr; //壓棧的 lr
    unsigned int stacked_pc; //壓棧的 pc
    unsigned int stacked_psr; //壓棧的 psr

    stacked_r0 = ((unsigned int) hardfault_args[0]);
    stacked_r1 = ((unsigned int) hardfault_args[1]);
    stacked_r2 = ((unsigned int) hardfault_args[2]);
    stacked_r3 = ((unsigned int) hardfault_args[3]);
    stacked_r12 = ((unsigned int)hardfault_args[4]);
    stacked_lr = ((unsigned int) hardfault_args[5]);
    stacked_pc = ((unsigned int) hardfault_args[6]);
    stacked_psr = ((unsigned int) hardfault_args[7]);

    while(1)
    {
        printf("[Hard fault handler]\r\n");
        printf("R0 = %x\r\n", stacked_r0);
        printf("R1 = %x\r\n", stacked_r1);
        printf("R2 = %x\r\n", stacked_r2);
        printf("R3 = %x\r\n", stacked_r3);
        printf("R12 = %x\r\n", stacked_r12);
        printf("Stacked LR = %x\r\n", stacked_lr);
        printf("Stacked PC = %x\r\n", stacked_pc);
        printf("Stacked PSR = %x\r\n", stacked_psr);
        printf("SCB_SHCSR=%x\r\n",SCB->SHCSR);
        printf("Current LR = %x\r\n", lr_value);
    }
}

處理器進入到HardFault,將R0\~R3、R12、LR、PC信息通過串口打印,根據寄存器信息排查問題代碼。

當處理器處理異常時,除非異常是一個末尾連鎖異常或遲來的異常,否則,處理器把信息都壓入到當前堆棧中入棧(stacking),8個數據字的結構被稱為棧幀(stack frame),棧按照雙字地址對齊方式。

image1.png

入棧后,堆棧指針立刻指向棧幀的最低地址單元。棧包含返回地址,這是被中止的程序中下條指令的地址。這個值在異常返回時返還給 PC,使被中止的程序恢復執行。

如下圖連接仿真器查看匯編的地址可以找到是程序問題,根據PC指針地址,在程序生成的.map中查找出問題函數。

image2.png

4. 處理建議

根據上述的定位手段可以查找是哪一種情況造成的異常,在編程過程中需要避免出現上述異常情況,但是在惡劣復雜的環境下,可能會小概率觸發HardFault中斷,可以在函數中添加復位或者跳轉指令,具體的實現方式需根據應用和使用環境來評估。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM