一、問(wèn)題的由來(lái)
在調(diào)試STM32程序時(shí)卖漫,遇到了程序突然運(yùn)行到HardFault中斷的問(wèn)題。之前也遇到過(guò)類似的情況赠群,大多時(shí)憑借猜想解決問(wèn)題羊始,有時(shí)候不能夠很快的定位問(wèn)題來(lái)源。今天在調(diào)試時(shí)乎串,學(xué)到了一個(gè)有效的調(diào)試方法店枣,可以幫助我快速定位問(wèn)題點(diǎn)。
先貼出大神的博客:http://blog.sina.com.cn/s/blog_4aa25f130102v0m8.html
二叹誉、關(guān)于Hardfault
Cortex-M3/4的Fault異常是由于非法的存儲(chǔ)器訪問(wèn)(比如訪問(wèn)0地址、寫只讀存儲(chǔ)位置等)和非法的程序行為(比如除以0等)等造成的闷旧。常見的4種異常及產(chǎn)生異常的情況如下:
Bus Fault:在fetch指令长豁、數(shù)據(jù)讀寫、fetch中斷向量或中斷時(shí)存儲(chǔ)恢復(fù)寄存器棧情況下忙灼,檢測(cè)到內(nèi)存訪問(wèn)錯(cuò)誤則產(chǎn)生Bus Fault匠襟。
Memory Management Fault:訪問(wèn)了內(nèi)存管理單元(MPU)定義的不合法的內(nèi)存區(qū)域,比如向只讀區(qū)域?qū)懭霐?shù)據(jù)该园。
Usage Fault:檢測(cè)到未定義指令或在存取內(nèi)存時(shí)有未對(duì)齊酸舍。
Hard Fault:在調(diào)試程序過(guò)程中,這種異常最常見里初。上面三種異常發(fā)生任何一種異常都會(huì)引起Hard Fault啃勉,在上面的三種異常未使能的情況下,默認(rèn)發(fā)生異常時(shí)進(jìn)入Hard Fault中斷服務(wù)程序双妨。
需要注意的是淮阐,在默認(rèn)復(fù)位初始化時(shí),Hard Fault使能刁品,其它三者不使能泣特,因此當(dāng)程序中出現(xiàn)不合法內(nèi)存訪問(wèn)(一般是指針錯(cuò)誤引起)或非法的程序行為(一般就是數(shù)學(xué)里面常見的除0)時(shí)都將產(chǎn)生Hard Fault中斷。
三挑随、問(wèn)題分析
在網(wǎng)上查找相關(guān)資料状您,發(fā)現(xiàn)這種問(wèn)題主要有以下原因:
1 內(nèi)存溢出或者訪問(wèn)越界,通常為數(shù)組或結(jié)構(gòu)體訪問(wèn)越界兜挨。這個(gè)需要自己寫程序的時(shí)候規(guī)范代碼膏孟,遇到了需要慢慢排查。
2 堆棧溢出暑劝。增加堆棧的大小骆莹。
3 在uCos-III系統(tǒng)中,任務(wù)切換時(shí)要關(guān)中斷担猛。
4 沒有打開相應(yīng)的硬件模塊但操作了相應(yīng)的硬件而導(dǎo)致了錯(cuò)誤
5 Jlink的問(wèn)題幕垦,禁止用Jlink供電就可以了丢氢。在Jlink commander中輸入power off,
6程序在添加全局變量的時(shí)候會(huì)出現(xiàn)sprintf 輸出的浮點(diǎn)數(shù)不正常先改,因此盡量不用sprintf函數(shù)疚察。
7使用了非法的指針,比如說(shuō)空指針
u8 *p = null;
*p = 1; 把0地址的數(shù)據(jù)強(qiáng)制設(shè)置為1仇奶, 不錯(cuò)才怪
8使用 OS_ENTER_CRITICAL();
使用了 OS_ENTER_CRITICAL(); 卻忘了OS_EXIT_CRITICAL(); 退出臨界區(qū)
特別是在這個(gè)函數(shù)OS_ENTER_CRITICAL(); 調(diào)用了子函數(shù) 也有的這類情況貌嫡,很容易忘記關(guān)閉的這樣就造成了“死機(jī)現(xiàn)象”
因此如果調(diào)用的話 建議在函數(shù)中加入OS_CPU_SR cpu_sr = 0u;局部變量 在管理臨界區(qū) os的內(nèi)核程序也是這么用的 ,而且要注意该溯,臨界區(qū)一般用于全局變量的寫操作岛抄,時(shí)間要非常快的狈茉,任務(wù)中的變量可以不用添加 夫椭。
結(jié)合本項(xiàng)目的實(shí)際情況,我認(rèn)為內(nèi)存訪問(wèn)錯(cuò)誤的概率最大氯庆。
四蹭秋、排查過(guò)程
通過(guò)學(xué)習(xí)大神的文章,本次排查問(wèn)題主要利用了MDK中的Call Stack窗口堤撵。
1 在stm32f10x_it.c中仁讨,添加軟件斷點(diǎn),一旦調(diào)試時(shí)出現(xiàn)Hard Fault則會(huì)在停在斷點(diǎn)處实昨。
2調(diào)出Call Stack
3運(yùn)行程序洞豁,直到出現(xiàn)bug
如上圖所示,Call Stack記錄了出現(xiàn)異常前屠橄,單片機(jī)調(diào)用的一些函數(shù)族跛,從下到上,很像是飛機(jī)的黑匣子锐墙。
可以看到礁哄,我的程序運(yùn)行到了0x08004F40地址后,直接跳到了HardFault溪北。而這個(gè)地址的上一步執(zhí)行了Data_frame_parsing這個(gè)函數(shù)桐绒。首先定位到出錯(cuò)的地址,右鍵單擊那個(gè)地址之拨,選擇“Show Caller Code”就可以定位過(guò)去茉继。
定位到了這一行,對(duì)一個(gè)內(nèi)存地址進(jìn)行拷貝操作蚀乔,由于源地址和目標(biāo)地址都是在堆空間上malloc
出來(lái)的烁竭,于是我很快想到會(huì)不會(huì)是在拷貝前,由于某種原因源地址被free掉了吉挣,所以出現(xiàn)了內(nèi)存訪問(wèn)錯(cuò)誤派撕?
于是跑到這個(gè)函數(shù)調(diào)用的地方婉弹,果然發(fā)現(xiàn)緊跟著這個(gè)函數(shù)下面有一個(gè)free。于是改成了
while(Data_frame_parsing(p_wifi_rcv_frame) != 0);
free(p_wifi_rcv_frame);
大意就是等程序運(yùn)行完终吼,再釋放內(nèi)存空間镀赌。于是再仿真。际跪。商佛。發(fā)現(xiàn)依然進(jìn)了Hardfault。
然后我發(fā)現(xiàn)姆打,數(shù)據(jù)幀里有點(diǎn)不對(duì)勁良姆。
表示數(shù)據(jù)幀長(zhǎng)的這個(gè)字節(jié),居然是0x00幔戏!
然后我馬上意識(shí)到歇盼,我申請(qǐng)的這段內(nèi)存空間大小,是根據(jù)data_len這個(gè)成員的值來(lái)確定的评抚。
由于某種原因(通信丟包)這個(gè)地方寫入了0,于是申請(qǐng)的空間大小就是0伯复,之后再來(lái)訪問(wèn)這段內(nèi)存慨代,肯定就出錯(cuò)了!
確定了問(wèn)題啸如,接下來(lái)修改代碼侍匙。
在申請(qǐng)內(nèi)存空間之前,先判斷一下這個(gè)數(shù)叮雳,是不是在一個(gè)合理的范圍之內(nèi)想暗,如果是,就分配空間帘不,如果不是说莫,就說(shuō)明數(shù)據(jù)幀出問(wèn)題了,直接跳出寞焙。
再編譯储狭,運(yùn)行!OK了捣郊!再也沒有出現(xiàn)過(guò)Hardfault錯(cuò)誤辽狈。
五、總結(jié)
這個(gè)故事告訴我們呛牲。在寫一個(gè)程序的時(shí)候刮萌,對(duì)輸入的參數(shù)進(jìn)行合法性的檢查,是非常重要的娘扩,可以省去很多不必要的麻煩着茸。
在遇到Hardfault錯(cuò)誤時(shí)壮锻,可以用一個(gè)很直觀的方式觀察到是哪里出現(xiàn)了問(wèn)題,可以幫助我們快速的定位錯(cuò)誤元扔。解決bug躯保!