看到有朋友遇到Hard Fault 異常錯(cuò)誤,特地找到一篇飛思卡爾工程師寫(xiě)的一片經(jīng)驗(yàn)帖,定位Hard Fault 異常。
Kinetis MCU 采用 Cortex-M4 的內(nèi)核,該內(nèi)核的 Fault 異?梢圆东@非法的內(nèi)存訪問(wèn)和非法的編程行為。Fault異常能夠檢測(cè)到以下幾類非法行為:· 總線 Fault: 在取址、數(shù)據(jù)讀/寫(xiě)、取中斷變量、進(jìn)入/退出中斷時(shí)寄存器堆棧操作(入棧/出棧)時(shí)檢測(cè)到內(nèi)存訪問(wèn)錯(cuò)誤。 · 存儲(chǔ)器管理 Fault: 檢測(cè)到內(nèi)存訪問(wèn)違反了內(nèi)存保護(hù)單元(MPU, MemoryProtection Unit)定義的區(qū)域。 · 用法 Fault: 檢測(cè)到未定義的指令異常,未對(duì)其的多重加載/存儲(chǔ)內(nèi)存訪問(wèn)。如果使能相應(yīng)控制位,還可以檢測(cè)出除數(shù)為零以及其他未對(duì)齊的內(nèi)存訪問(wèn)。
· 硬 Fault: 如果上述的總線 Fault、存儲(chǔ)器管理 Fault、用法 Fault 的處理程序不能被執(zhí)行(例如禁能了總線 Fault、存儲(chǔ)器管理Fault、用法Fault 的異常或者在這些異常處理程序中又出現(xiàn)了新的Fault)則觸發(fā)硬Fault。 在 MQX 操作系統(tǒng)啟動(dòng)的時(shí)候會(huì)安裝上默認(rèn)的異常中斷處理函數(shù),當(dāng)系統(tǒng)異常時(shí)會(huì)產(chǎn)生一個(gè)“unexpected”中斷,內(nèi)核就會(huì)自動(dòng)調(diào)用異常處理函數(shù),同時(shí)也將運(yùn)行用戶自定義的處理函數(shù),來(lái)實(shí)現(xiàn)特殊故障的定位方法。 默認(rèn)情況下,MQX把出現(xiàn)異常的任務(wù)掛起,避免故障進(jìn)一步擴(kuò)大。通過(guò)TAD 任務(wù)感知調(diào)試插件的Task summary 功能,我們可以觀察到出現(xiàn)異常的任務(wù)情況。
220231ka89dmda9a9la2ad.png.thumb.jpg (273.83 KB, 下載次數(shù): 207)
下載附件
2015-10-29 17:22 上傳
開(kāi)發(fā)人員在調(diào)試期間,需要弄清楚系統(tǒng)異常觸發(fā)了哪類Fault,由什么原因觸發(fā)了Fault 以及定位觸發(fā)Fault 的代碼。在這種情況下,可以利用自定義的Fault 中斷處理程序來(lái)分析 Fault 出錯(cuò)原因。 為了解釋所述的 Fault 中斷處理程序的原理,這里重述一下當(dāng)系統(tǒng)產(chǎn)生異常時(shí) MCU 的處理過(guò)程: · 有一個(gè)壓棧的過(guò)程,若產(chǎn)生異常時(shí)使用 PSP(進(jìn)程棧指針),就壓入到 PSP 中,若產(chǎn)生異常時(shí)使用MSP(主棧指針),就壓入MSP 中。 · 會(huì)根據(jù)處理器的模式和使用的堆棧,設(shè)置 LR 的值(當(dāng)然設(shè)置完的LR 的值再壓棧)。 · 異常保存,硬件自動(dòng)把 8 個(gè)寄存器的值壓入堆棧(8 個(gè)寄存器依次為 xPSR、PC、LR、R12以及 R3~R0)。如果異常發(fā)生時(shí),當(dāng)前的代碼正在使用PSP,則上面8 個(gè)寄存器壓入PSP; 否則就壓入MSP。 當(dāng)系統(tǒng)產(chǎn)生異常時(shí),我們需要兩個(gè)關(guān)鍵寄存器值,一個(gè)是 PC ,一個(gè)是 LR (鏈接寄存器),通過(guò) LR找到相應(yīng)的堆棧,再通過(guò)堆棧找到觸發(fā)異常的PC 值。將產(chǎn)生異常時(shí)壓入棧的 PC 值取出,并與反匯編的代碼對(duì)比就能得到哪條指令產(chǎn)生了異常。 這里解釋一下關(guān)于 LR 寄存器的工作原理。如上所述,當(dāng) Cortex-M4 處理器接受了一個(gè)異常后,寄存器組中的一些寄存器值會(huì)被自動(dòng)壓入當(dāng)前?臻g里,這其中就包括鏈接寄存器(LR )。這時(shí)的 LR 會(huì)被更新為異常返回時(shí)需要使用的特殊值(EXC_RETURN)。關(guān)于 EXC_RETURN 的定義如下,其為 32 位數(shù)值,高 28 位置 1,第 0 位到第三位則提供了異常返回機(jī)制所需的信息,如下表所示?梢(jiàn)其中第 2 位標(biāo)示著進(jìn)入異常前使用的棧是 MSP還是PSP。在異常處理過(guò)程結(jié)束時(shí),MCU 需要根據(jù)該值來(lái)分配 SP 的值。這也是本方法中用來(lái)判斷所使用堆棧的原理,其實(shí)現(xiàn)方法可以從后面_init_hardfault_isr 中看到。
220232v9gw5v22awlaabza.jpg.thumb.jpg (14.05 KB, 下載次數(shù): 203)
下載附件
2015-10-29 17:22 上傳
另外,我們可以利用 MQX 的控制臺(tái)串口輸出Fault 異常信息來(lái)幫助調(diào)試。編寫(xiě)Fault 處理程序時(shí),將啟動(dòng)代碼中默認(rèn)的Fault 處理程序跟換成自己需要的Fault 處理程序。需要注意的是,由于是在中斷中進(jìn)行打印輸出,MQX的控制臺(tái)串口只能使用POLL 輪詢模式的驅(qū)動(dòng),不能使用中斷模式的驅(qū)動(dòng)。 用戶可以編寫(xiě)自定義的硬 Fault 處理程序_int_hardfault_isr,修改 MQX 的中斷向量定義vector.c,把里面的DEFAULT_VECTOR 代碼段換成下面的代碼。當(dāng)系統(tǒng)出現(xiàn)硬Fault 異常時(shí),將會(huì)調(diào)用自定義的Fault 處理_int_hardfault_isr函數(shù)。在這個(gè)函數(shù),我們可以通過(guò)StackTrace-back 回溯出現(xiàn)問(wèn)題的代碼。
220232ixlrzbjt8fhlllxy.png.thumb.jpg (230.03 KB, 下載次數(shù): 187)
下載附件
2015-10-29 17:22 上傳
我們可以在_int_hardfault_isr 函數(shù)里將出現(xiàn)異常時(shí)的寄存器、堆棧、狀態(tài)寄存器等信息打印出來(lái)。如果系統(tǒng)出現(xiàn)異常時(shí),一般情況都會(huì)通過(guò)串口控制臺(tái)打印出LR,PC的值。然后根據(jù)編譯器生成的map 文件,找到出現(xiàn)問(wèn)題的具體函數(shù)。
220233nz2l2eiixxelvbcb.png.thumb.jpg (168.56 KB, 下載次數(shù): 211)
下載附件
2015-10-29 17:22 上傳
從上圖的串口輸出我們可以看到 PC 和 LR 寄存器值,PC 的值為 0x56c6,我們根據(jù)匯編代碼可以找到出現(xiàn)問(wèn)題的指令。從而大大縮小了查找出現(xiàn)問(wèn)題的范圍,可以幫助開(kāi)發(fā)人員快速定位問(wèn)題的根本原因。
附錄Fault異常中斷處理代碼: - // hard fault handler in C,
- // with stack frame location as input parameter
- void hard_fault_handler_c (unsigned int * hardfault_args)
- {
- unsigned int stacked_r0;
- unsigned int stacked_r1;
- unsigned int stacked_r2;
- unsigned int stacked_r3;
- unsigned int stacked_r12;
- unsigned int stacked_lr;
- unsigned int stacked_pc;
- unsigned int stacked_psr;
- stacked_r0 = ((unsigned long)hardfault_args[0]);
- stacked_r1 = ((unsigned long)hardfault_args[1]);
- stacked_r2 = ((unsigned long)hardfault_args[2]);
- stacked_r3 = ((unsigned long)hardfault_args[3]);
- stacked_r12 = ((unsigned long)hardfault_args[4]);
- stacked_lr = ((unsigned long)hardfault_args[5]);
- stacked_pc = ((unsigned long)hardfault_args[6]);
- stacked_psr = ((unsigned long) hardfault_args[7]);
- printf ("\n\n[Hard faulthandler - all numbers in hex]\n");
- printf ("R0 = %x\n",stacked_r0);
- printf ("R1 = %x\n",stacked_r1);
- printf ("R2 = %x\n",stacked_r2);
- printf ("R3 = %x\n",stacked_r3);
- printf ("R12 = %x\n",stacked_r12);
- printf ("LR [R14] = %x subroutine call return address\n",stacked_lr);
- printf ("PC [R15] = %x program counter\n", stacked_pc);
- printf ("PSR = %x\n",stacked_psr);
- /******************* Add yourdebug trace here ***********************/
- _int_kernel_isr();
- }
- /* hard fault interrupt handler */
- void _int_hardfault_isr( )
- {
- __asm("TST LR, #4");
- __asm("ITE EQ");
- __asm("MRSEQ R0,MSP");
- __asm("MRSNE R0,PSP");
- __asm("Bhard_fault_handler_c");
- }
復(fù)制代碼
|