MCU HardFault問題查找和破解方法
- 心里明白徒手分析法
- CmBacktrace 天龍大法
一、HardFault產(chǎn)生原因和常規(guī)分析方法
在嵌入式開發(fā)中,偶爾會(huì)遇到Hard Fault死機(jī)的異常,常見產(chǎn)生Hard Fault的原因大致有以下幾類:- 數(shù)組越界和內(nèi)存溢出,譬如訪問數(shù)組時(shí),動(dòng)態(tài)訪問的數(shù)組標(biāo)號(hào)超過數(shù)組長度或者動(dòng)態(tài)分配內(nèi)存太小等;
- 堆棧溢出,例如在使用中,局部變量分配過大,超過棧大小,也會(huì)導(dǎo)致程序跑飛;
- 在外設(shè)時(shí)鐘開啟前,訪問對(duì)應(yīng)外設(shè)寄存器,例如Kinetis中未打開外設(shè)時(shí)鐘去配置外設(shè)的寄存器;
- 不當(dāng)?shù)挠梅ú僮鳎绶菍?duì)齊的數(shù)據(jù)訪問、除0操作(默認(rèn)情況下M3/M4/M7,除0默認(rèn)都不會(huì)觸發(fā)Fault,因?yàn)锳RM內(nèi)核CCR寄存器DIV_0_TRP位復(fù)位值為0,而對(duì)M0來說DIV_0_TRP位是reserved的,也不會(huì)產(chǎn)生Fault錯(cuò)誤)、強(qiáng)行訪問受保護(hù)的內(nèi)存區(qū)域等;
盡管本測試是針對(duì)NXP KW36芯片的,但該步驟和方法也適用于其他的Arm Cortex-M內(nèi)核MCU;
二、HardFault解決方法分析
筆者在實(shí)際支持客戶過程中也遇到這種困惑,網(wǎng)上的介紹資料比較零散,理論很多,很少詳細(xì)描述實(shí)戰(zhàn)操作的步驟,借助同事的點(diǎn)撥,摸索出兩種定位Hard Fault問題的方法,在實(shí)際使用中操作性也很強(qiáng),此處分別做一介紹。- 第一種:心里明白徒手分析法,就是在了解Hard Fault出錯(cuò)原理以及程序調(diào)用壓棧出棧原理的基礎(chǔ)上(當(dāng)然按照本文的練就心法,心里不明白也可以),在Debug仿真模式下徒手去回溯分析CPU通用寄存器(LR/MSP/PSP/PC),然后結(jié)合調(diào)試IDE去定位到產(chǎn)生Hard Fault的代碼位置;
- 第二種:CmBacktrace 天龍大法,該方法是朱天龍大神針對(duì) ARM Cortex-M系列MCU開發(fā)的一套錯(cuò)誤代碼自動(dòng)追蹤、定位、錯(cuò)誤原因自動(dòng)分析的開源庫,已開源在Github上,該方法支持在非Debug模式下,自動(dòng)分析定位到出錯(cuò)的行號(hào),無需了解復(fù)雜的壓棧出棧過程。
三、HardFault回溯的原理
為了找到Hard Fault 的原因和觸發(fā)的代碼段,就需要深刻理解當(dāng)系統(tǒng)產(chǎn)生異常時(shí) MCU 的處理過程: 當(dāng)處理器接收一個(gè)異常后,芯片硬件會(huì)自動(dòng)將8個(gè)通用寄存器組中壓入當(dāng)前??臻g里(依次為 xPSR、PC、LR、R12以及 R3~R0),如果異常發(fā)生時(shí),當(dāng)前的代碼正在使用PSP,則上面8個(gè)寄存器壓入PSP,否則就壓入MSP。那問題來了,如何找到這個(gè)??臻g的地址呢?答案是SP, 但是前面提到壓棧時(shí)會(huì)有MSP和PSP,如何判斷觸發(fā)異常時(shí)使用的MSP還是PSP呢?答案是LR。到此確定完SP后,用戶便可以通過堆棧找到觸發(fā)異常的PC 值,并與反匯編的代碼對(duì)比就能得到哪條指令產(chǎn)生了異常。總結(jié)下來,總體思路就是:首先通過LR判斷出異常產(chǎn)生時(shí)當(dāng)前使用的SP是MSP還是PSP,接著通過SP去得到產(chǎn)生異常時(shí)保存的PC值,最后與反匯編的代碼對(duì)比就能得到哪條指令產(chǎn)生了異常。回到前面的第二個(gè)問題,如何通過LR判斷當(dāng)前使用的MSP還是PSP呢?參見如下圖,當(dāng)異常產(chǎn)生時(shí),LR 會(huì)被更新為異常返回時(shí)需要使用的特殊值(EXC_RETURN),其定義如下,其高 28 位置 1,第 0 位到第3位則提供了異常返回機(jī)制所需的信息,可見其中第 2 位標(biāo)示著進(jìn)入異常前使用的棧是 MSP還是PSP。
四、操作分析流程:
理解了以上的Hard Fault回溯的原理,下面按以上提到的兩種思路來實(shí)操一下。1. ?心里明白徒手分析法
前面提到,為了清晰的展現(xiàn)這個(gè)過程以及每個(gè)參數(shù)之間的關(guān)系,盡量把整個(gè)流程按照順序整理到一張圖中,如下圖1。示例中使用的是KW36 temp_sensor_freeRTOS例子(什么例子不重要,該方法也適用于其他的MCU系列),在main函數(shù)中通過非對(duì)齊地址訪問故意制造Hard Fault錯(cuò)誤,代碼如圖中序號(hào)1,當(dāng)程序試圖訪問讀取非對(duì)齊地址0xCCCC CCCC位置時(shí)程序就會(huì)跳入到Hard Fault Handler中,那具體是如何通過堆棧分析定位到出錯(cuò)代碼是在n=*p這一行呢?具體步驟如下:Step1:判斷SP是MSP還是PSP,找出SP地址。在產(chǎn)生Hard Fault異常后,首先在序號(hào)2中選擇“ CPU register”,不要使用默認(rèn)的 “CPU register ”,否則默認(rèn)只會(huì)顯示MSP,不會(huì)顯示PSP。然后查看序號(hào)3中LR寄存器的值表示判斷當(dāng)前程序使用堆棧為MSP主進(jìn)程或PSP子進(jìn)程堆棧,顯然LR=0xFFFFFFF9 的bit2=0,表示使用的是主棧,于是得到SP=序號(hào)4中的SP_main=0x20005620;Step2:找出PC地址。如序號(hào)5演示,打開memory串口,輸入SP的地址可以找到異常產(chǎn)生前壓棧的8個(gè)寄存器,依次為 xPSR、PC、LR、R12以及 R3~R0,序號(hào)6中便可以找到出錯(cuò)前PC的地址位0x00008a06;Step3:找出代碼行數(shù)。如序號(hào)7演示,打開匯編窗口,在“go to”串口輸入PC地址,便可以找到具體出錯(cuò)時(shí)代碼的位置,如序號(hào)8演示,可以發(fā)現(xiàn),輕松愉快的找到了導(dǎo)致Hard Fault的非對(duì)齊訪問的代碼行;
2. CmBacktrace 天龍大法
Step1: 從天龍大神的Github下載CmBacktrace的源代碼包,拷貝cm_backtrace目錄下的4個(gè)文件以及cmb_fault.s文件到KW36 IAR工程中,如下圖序號(hào)2標(biāo)識(shí),并添加相應(yīng)的搜索路徑;Step2: 根據(jù)應(yīng)用修改cmb_cfg.h的配置,需要配置的選項(xiàng)包括print打印信息的重定義,是否需要支持OS,OS的類型(RTT、uCOS以及FreeRTOS),ARM內(nèi)核的類型,打印輸出語言類型等;本實(shí)例中使用了錯(cuò)誤信息中文打印以及FreeRTOS,所以配置如下圖序號(hào)2標(biāo)識(shí)。Step5: 配置打印信息的輸出位置,建議的做法是輸出到物理串口,可以方便的離線分析記錄log, 但實(shí)驗(yàn)中為了簡化以及通用(有些時(shí)候硬件設(shè)計(jì)上可能沒有留硬件串口),直接把打印信息輸出到IAR的Terminal IO進(jìn)行顯示(Kinetis SDK如何修改代碼,使能打印信息輸出到IAR的Terminal IO的做法詳見另外一篇文檔)。Step6: 運(yùn)行代碼,觀察打印結(jié)果,可以看到打印信息中包含出錯(cuò)的任務(wù)名稱、出錯(cuò)前的任務(wù)壓棧的8個(gè)通用寄存器名稱和內(nèi)容,從圖中可以一目了然的找出出錯(cuò)的PC指針,如果進(jìn)一步去結(jié)合匯編代碼可以清晰的看到其能夠準(zhǔn)確定位到代碼出錯(cuò)的位置。
到此,使用CmBacktrace大法不輕松但很愉悅的定位到問題點(diǎn)了。