HardFault 錯(cuò)誤信息的解釋和可采用的處理方法

1.發(fā)生中斷時(shí)處理器的行為

不考慮其他細(xì)節(jié),M3內(nèi)核在發(fā)生中斷時(shí)首先自動(dòng)將如下8個(gè)寄存器壓棧。因此在中斷處理函數(shù)中,發(fā)生中斷時(shí)正常執(zhí)行時(shí)的寄存器數(shù)值已經(jīng)被壓入了堆棧中。在中斷處理函數(shù)開始執(zhí)行時(shí),除了PC,LR,SP等控制寄存器,從r0-r12等這些通用寄存器的數(shù)據(jù)是沒有變化的。下圖描述了M3內(nèi)核將寄存器壓棧的順序:

地址 寄存器 被保存的順序
舊SP(N-0) 原先已壓入的內(nèi)容 -
(N-4) xPSR 2
(N-8) PC 1
(N-12) LR 8
(N-16) R12 7
(N-20) R3 6
(N-24) R2 5
(N-28) R1 4
新SP(N-32) R0 3

2.編譯器通過棧來實(shí)現(xiàn)函數(shù)調(diào)用

C編譯器通過棧來實(shí)現(xiàn)函數(shù)的調(diào)用,即在棧中記錄程序執(zhí)行的軌跡并輔助寄存器進(jìn)行參數(shù)傳遞。具體如何實(shí)現(xiàn)C函數(shù)的調(diào)用,歷史上有很多的規(guī)范,這些規(guī)范叫做調(diào)用慣例。
對(duì)于ARM處理器來說,有一個(gè)官方的規(guī)范AAPCS(Procedure Call Standard for the ARM? Architecture)詳細(xì)描述了進(jìn)行函數(shù)調(diào)用時(shí)如何進(jìn)行參數(shù)的傳遞和調(diào)用路徑的記錄等。
如下僅對(duì)使用棧記錄調(diào)用路徑的行為進(jìn)行簡(jiǎn)單描述:查看編譯器生成的匯編代碼可以得知,大多數(shù)的函數(shù)調(diào)用通過BL語句實(shí)現(xiàn),BL語句將當(dāng)前程序下一條指令的地址存入LR寄存器,并跳轉(zhuǎn)到指定的地方(子函數(shù)開始的地方)開始執(zhí)行。子函數(shù)中如果還需要調(diào)用孫子函數(shù),就會(huì)在函數(shù)的入口處將LR的值壓棧,以便函數(shù)執(zhí)行結(jié)束后能夠返回父函數(shù)。因此依次找到棧中LR的數(shù)值,就能找到調(diào)用路徑中各個(gè)函數(shù)的地址。最后根據(jù)map文件翻譯出各函數(shù)的名稱,就可以得到函數(shù)的調(diào)用路徑了。
如下是一個(gè)簡(jiǎn)單函數(shù)匯編代碼的例子,函數(shù)OnPowerOff調(diào)用了函數(shù)FS_Deinit,函數(shù)FS_Deinit調(diào)用了SPIFFS_unmount??梢钥闯鯫nPowerOff函數(shù)的入口如將LR壓入棧中(此時(shí)LR中保存的是函數(shù)OnPowerOff的返回地址,也就是調(diào)用OnPowerOff的父函數(shù)中的某條指令的地址),然后調(diào)用了FS_Deinit。同樣FS_Deinit也在入口處將LR壓入棧中(此時(shí)LR中保存的是OnPowerOff函數(shù)中POP指令的地址),然后再調(diào)用SPIFFS_unmount。返回的過程,依次將棧中保存的返回地址直接出棧到PC寄存器,完成函數(shù)的返回。這樣,如果某個(gè)函數(shù)將棧中的返回地址寫壞,則函數(shù)在返回時(shí)就會(huì)跳轉(zhuǎn)到某個(gè)隨機(jī)的地方,這就是常說的“程序跑飛了”。

OnPowerOff PROC
;;;202 void OnPowerOff(void)
0001b2 b510 PUSH {r4,lr}
;;;203 {
;;;204 FS_Deinit();
0001b4 f7fffffe BL FS_Deinit
;;;205 }
0001b8 bd10 POP {r4,pc}
;;;206
ENDP
FS_Deinit PROC
;;;166 void FS_Deinit(void)
000158 b510 PUSH {r4,lr}
;;;167 {
;;;168 SPIFFS_unmount(&g_fs);
00015a 4810 LDR r0,|L1.412|
00015c f7fffffe BL SPIFFS_unmount
;;;169 return;
;;;170 }
000160 bd10 POP {r4,pc}
;;;171
ENDP

3.對(duì)信息的繼續(xù)挖掘

  • 通用寄存器
    通用寄存器中可供挖掘的信息并不多,通常情況下r0-r3寄存器保存著函數(shù)的前四個(gè)參數(shù)(其余的參數(shù)在棧中保存),需要注意的是:這四個(gè)寄存器的數(shù)值僅在函數(shù)開始執(zhí)行的時(shí)候是可靠的,在函數(shù)執(zhí)行的過程中可能被改變。在函數(shù)返回時(shí),寄存器r0和r1用于保存返回值(根據(jù)返回?cái)?shù)據(jù)的大小,決定僅使用r0還是同時(shí)使用r0和r1)。同樣這兩個(gè)寄存器僅在子函數(shù)剛返回時(shí)數(shù)值才是可靠的。
  • 特殊功能寄存器
    特殊功能寄存器就是PC、LR和SP了。
    SP指向當(dāng)前的棧頂,在知曉棧的結(jié)構(gòu)時(shí),可以根據(jù)SP訪問棧中的數(shù)據(jù)。
    在中斷處理函數(shù)中LR有特殊用法,其中保存了返回被中斷地點(diǎn)的方法,而不是通常情況下的返回地址。因此在Hardfault處理函數(shù)中寄存器LR和PC的值沒有太多參考意義,被處理器自動(dòng)壓棧的LR和PC最有用,PC記錄了被中斷打斷前正在執(zhí)行的指令地址(也是正在執(zhí)行的函數(shù)地址),LR記錄了被中斷打斷前,正在執(zhí)行的函數(shù)的父函數(shù)的地址。根據(jù)這兩個(gè)地址,可以找到引發(fā)Hardfault異常的函數(shù)和語句,以及其父函數(shù)(如果輔以匯編代碼繼續(xù)對(duì)棧的內(nèi)容進(jìn)行分析,則可以回溯整個(gè)調(diào)用路徑)。
    而具體引發(fā)Hardfault異常的原因,可以根據(jù)下面章節(jié)介紹的SCB寄存器來查看。
  • SCB寄存器
    在M3/M4處理器標(biāo)準(zhǔn)外設(shè)中,有一個(gè)叫做SCB(System Control Block)的部分,其中有6個(gè)寄存器記錄了發(fā)生Hardfault異常的原因。
    CMSIS規(guī)范中對(duì)SCB寄存器的定義:
typedef struct
{
    __I uint32_t CPUID;
    __IO uint32_t ICSR;
    __IO uint32_t VTOR;
    __IO uint32_t AIRCR;
    __IO uint32_t SCR;
    __IO uint32_t CCR;
    __IO uint8_t SHP[12];
    __IO uint32_t SHCSR;
    __IO uint32_t CFSR;  //主要關(guān)注
    __IO uint32_t HFSR; //主要關(guān)注
    __IO uit32_t DFSR;
    __IO uint32_t MMFAR; //主要關(guān)注
    __IO uint32_t BFAR; //主要關(guān)注
    __IO uint32_t AFSR;
    __I uint32_t PFR[2];
    __I uint32_t DFR;
    __I uint32_t ADR;
    __I uint32_t MMFR[4];
    __I uint32_t ISAR[5];
    uint32_t RESERVED0[5];
    __IO uint32_t CPACR;
} SCB_Type;

CFSR、HFSR、MMFAR、BFAR幾個(gè)寄存器是我們需要關(guān)注的,AFSR是平臺(tái)相關(guān)的暫時(shí)忽略。上述寄存器中CFSR又可以分為三個(gè)寄存器分別是:UFSR,BFSR,MFSR。上述寄存器的內(nèi)存分布如下表所示:

地址 寄存器 全名 尺寸
0xE000 ED28 MFSR MemManage fault 狀態(tài)寄存器 字節(jié)
0XE000 ED29 BFSR 總線 fault 狀態(tài)寄存器 字節(jié)
0XE000 ED2A UFSR 用法 fault 狀態(tài)寄存器 半字
0XE000 ED2C HFSR 硬 fault 狀態(tài)寄存器
0XE000 ED30 DFSR 調(diào)試 fault 狀態(tài)寄存器
0XE000 ED3C AFSR 輔助 fault 狀態(tài)寄存器

各寄存器數(shù)據(jù)的描述如下:
MFSR 中可能出現(xiàn)的錯(cuò)誤及原因:

可能的原因
MSTKERR 入棧時(shí)發(fā)生錯(cuò)誤(異常響應(yīng)序列開始時(shí))
1.堆棧指針的值被破壞
2.堆棧容易過大,已經(jīng)超出MPU允許的region范圍
MUNSTKERR 出棧時(shí)發(fā)生錯(cuò)誤(異常響應(yīng)序列終止時(shí)),入棧時(shí)沒有發(fā)生錯(cuò)誤,出棧時(shí)卻出錯(cuò),總令人有些匪夷所思,可能的原因是
1.異常服務(wù)例程破壞的堆棧指針
2. MPU配置被異常服務(wù)例程更改
DACCVIOL 內(nèi)存訪問保護(hù)違例。這是MPU發(fā)揮作用的體現(xiàn)。常常是用戶應(yīng)用程序企圖訪問特權(quán)級(jí)region所致
IACCVIOL 1.內(nèi)存訪問保護(hù)違例。常常是用戶應(yīng)用程序企圖訪問特權(quán)級(jí)region。入棧的PC給出的地址,就是產(chǎn)生問題代碼之所在
2.跳轉(zhuǎn)到不可執(zhí)行指令的regions
3.異常返回時(shí),使用了無效的EXC_RETURN值
4.向量表中有無效的向量。例如,異常在向量建立之前就發(fā)生了,或者加載的是用于傳統(tǒng)ARM內(nèi)核的可執(zhí)行映像

BFSR 中可能出現(xiàn)的錯(cuò)誤及原因:

可能的原因
STKERR (自動(dòng))入棧期間出錯(cuò)
1.堆棧指針的值被破壞
2.堆棧容易太大,到達(dá)了未定義存儲(chǔ)器的區(qū)域
3.PSP未經(jīng)初始化就使用
UNSTKERR (自動(dòng))出棧器件出錯(cuò)。如果沒有發(fā)生過STKERR,則最可能的就是異常處理器件把SP的值破壞了
IMPRECISERR 與設(shè)備傳送數(shù)據(jù)的過程中發(fā)生總線錯(cuò)誤??赡芤?yàn)樵O(shè)備未經(jīng)初始化而引起:在用戶級(jí)訪問了特權(quán)級(jí)的設(shè)備,或者傳送的數(shù)據(jù)單位尺寸不能為設(shè)備所接受。此時(shí),有可能是LDM/STM指令造成了非精確總線fault。
PRECISERR 在數(shù)據(jù)訪問期間的總線錯(cuò)誤。通過BFAR可以獲取具體的地址。發(fā)生fault的原因同上。
IBUSERR 同MFSR中的IACCVIOL

UFSR 中可能出現(xiàn)的錯(cuò)誤及原因:

可能的原因
DIVBYZERO 當(dāng)DIV_0_TRP置位時(shí)發(fā)生除數(shù)為零。導(dǎo)致此fault的指令可以從入棧的PC讀取
UNALIGNED 當(dāng)UNALIGN_TRP置位時(shí)發(fā)生未對(duì)齊訪問。導(dǎo)致此fault的指令可以從入棧的PC讀取
NOCP 企圖執(zhí)行一個(gè)協(xié)處理器指令。導(dǎo)致此fault的指令可以從入棧的PC讀取
INVPC 1.異常返回時(shí)使用了無效的 EXC_RETURN,例如:
1)當(dāng) EXC_RETURN = 0xFFFF FFF1 時(shí)卻要返回線程模式
2)當(dāng) EXC_RETURN = 0xFFFF FFF9 時(shí)卻要返回 handler 模式
2.無效的異?;顒?dòng)狀態(tài),例如:
1)當(dāng)前異常的活動(dòng)狀態(tài)已經(jīng)清除了,卻在此時(shí)執(zhí)行異常返回。往往是因?yàn)闉E用 VECTCLRACTIVE 或清除了 SHCSR 中活動(dòng)狀態(tài)所致
2)在還有其他異常的活動(dòng)位置位時(shí),卻要返回線程模式
3.由于堆棧指針錯(cuò)誤導(dǎo)致了 IPSR 的值不正確。對(duì)于 INVPC fault ,入棧的 PC 指出了該 fault 服務(wù)例程在何處搶占了其他的代碼。這個(gè)問題往往是比較隱晦的程序錯(cuò)誤造成的,欲詳細(xì)調(diào)查該問題的原因,最好使用ITM的跟蹤功能。
4.ICI/IT 位對(duì)當(dāng)前指令無效。當(dāng)LDM/STM 指令被異常打斷后,在異常服務(wù)例程中又更改了入棧的 PC。結(jié)果在中斷返回時(shí),非零的 ICI 位段作用到了不是用 ICI 位段的指令上。如果是其他原因破壞了 PSR 的值,也可能導(dǎo)致此 fault。
INVSTATE 1.加載到 PC 中的跳轉(zhuǎn)地址值是偶數(shù)(LSB=0)。通過檢查入棧 PC 的值,一下子就可以查出該問題。
2.向量地址的 LSB=0,診斷方法同上。
3.入棧的 PSR 在異常處理過程之中被破壞,使得在返回時(shí)內(nèi)核嘗試進(jìn)入 ARM 狀態(tài)
UNDEFINSTR 1.使用了 CM3 不支持的指令
2.代碼段中的數(shù)據(jù)被破壞
3.連接時(shí)加載了 ARM 目標(biāo)碼。請(qǐng)檢查編譯階段的位置
4.指令對(duì)其的問題。例如,在使用 GNU 工具鏈時(shí),忘記了 .ascii后使用 .align,就有可能導(dǎo)致下一條指令沒有對(duì)齊

解讀SCB寄存器時(shí)應(yīng)首先根據(jù)HFSR寄存器判斷產(chǎn)生Hardfault的原因,如果確認(rèn)是fault上訪的情況,則依次檢查BFSR、UFSR和HFSR確定具體的錯(cuò)誤原因和地址。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容