1、引言
Cortex-M3微控制器因其功能強(qiáng)大、性價(jià)比高以及易用性好,在嵌入式體系結(jié)構(gòu)中得到了廣泛應(yīng)用。然而,在實(shí)際開(kāi)發(fā)過(guò)程中,如果程序很大或運(yùn)行很久后可能會(huì)遇到HardFault異常。為了快速有效地解決HardFault異常,本文將討論定位HardFault問(wèn)題的方法。
2、常見(jiàn)引發(fā)HardFault原因
1)訪問(wèn)非法內(nèi)存地址:編程中處理指針時(shí)可能導(dǎo)致訪問(wèn)未定義的內(nèi)存地址,特別是在數(shù)組越界、非法指針解引用等情況下。
2)疊棧溢出:程序運(yùn)行過(guò)程中如果棧溢出,會(huì)導(dǎo)致棧上數(shù)據(jù)錯(cuò)誤,從而引發(fā)異常。
3)寄存器未正確初始化:寄存器配置和使用不正確,可能導(dǎo)致硬件異常。
4)除0運(yùn)算:當(dāng)程序試圖執(zhí)行除0運(yùn)算時(shí),可能發(fā)生硬件故障異常。
5)總線錯(cuò)誤:外設(shè)總線通信發(fā)生錯(cuò)誤,可能引起硬件故障。
3、軟件觸發(fā)HardFault異常
ES32F36xx是Cortex-M3內(nèi)核,當(dāng)被除數(shù)為0時(shí),硬件將觸發(fā)一個(gè)異常,導(dǎo)致系統(tǒng)進(jìn)入Hard Fault異常狀態(tài)。這種情況下,處理器不能正常運(yùn)行,并且不能恢復(fù),直到硬件或軟件采取措施使得系統(tǒng)回到正常狀態(tài)。在ES32_SDK中,ES32F36xx 的KEY_LED_ADC例程中增加如下程序:
//B函數(shù) voidB_Function(void) { printf_e("EnterBfunctionrn"); C_Function(0); printf_e("ExitBfunctionrn"); } //A函數(shù) voidA_Function(void) { printf_e("EnterAfunctionrn"); B_Function(); printf_e("ExitAfunctionrn"); } //C函數(shù) intC_Function(intval) { return100/val; } //D函數(shù) voidD_Function(void) { printf_e("EnterDfunctionrn"); C_Function(1); printf_e("ExitDfunctionrn"); } //TestDebug函數(shù) voidTestDebug(void) { //使能除0異常 volatileint*CCR=(volatileint*)0xE000ED14; *CCR|=(1<4); ???? ????A_Function(); ????D_Function(); } //?主函數(shù) int?main(void) {??? ????uart_stdio_init(); ???? ????TestDebug(); ????while(1); }
如圖 1所示,工程文件中,User選項(xiàng)中增加“fromelf -a -c --output=all.dis .objout.axf“,生成匯編文件。
圖1 MDK工程生成匯編文件配置
4、HardFault異常函數(shù)調(diào)用關(guān)系及問(wèn)題定位
上述程序運(yùn)行過(guò)程中會(huì)發(fā)生hardfault異常,發(fā)生hardfault異常瞬間,程序立刻中止運(yùn)行,硬件自動(dòng)保存“調(diào)用者保存寄存器“的值到棧,同時(shí)跳轉(zhuǎn)到異常向量表執(zhí)行異常處理函數(shù)。如圖 2所示為發(fā)生異常瞬間,硬件自動(dòng)保存xPSR、ReturnAddress、LR、R12、R3、R2、R1及R0寄存器。
圖2 處理器進(jìn)入異常時(shí)的棧幀
硬件僅保存了部分寄存器,為了保存發(fā)生異常瞬間所有寄存器值,程序跳轉(zhuǎn)到中斷向量處需要執(zhí)行如下的匯編代碼:
;getcurrentcontext TSTlr,#0x04;if(!EXC_RETURN[2]) ITEEQ ;[2]=0==>Z=1,getfaultcontextfromhandler MRSEQr0,msp ;[2]=1==>Z=0,getfaultcontextfromthread MRSNEr0,psp STMFDr0!,{r4-r11};pushr4-r11register STMFDr0!,{lr};將EXC_RETURN值壓棧 TSTlr,#0x04;if(!EXC_RETURN[2]) ITEEQ ;R0為棧值,作為hw_hardfault_exception函數(shù)首個(gè)參數(shù) MSREQmsp,r0 ;[2]=1==>Z=0,getfaultcontextfromthread MSRNEpsp,r0 ;再次將EXC_RETURN值壓棧 PUSH{lr} ;跳轉(zhuǎn)至hw_hardfault_exception函數(shù) BLhw_hardfault_exception POP{lr} ORRlr,lr,#0x04 BXlr ENDP
上述匯編代碼的主要功能是獲取棧(SP)地址,并將R4 ~ R11壓棧,跳轉(zhuǎn)執(zhí)行hw_hardfault_exception函數(shù)。壓棧后棧中的數(shù)據(jù)情況如圖 3所示:
圖3 hardfault異常瞬間棧中完整寄存器
Return Address是發(fā)生異常指令點(diǎn),LR是發(fā)生異常指令所在函數(shù)的下一條指令地址。在hw_hardfault_exception函數(shù)中將棧中的寄存器值都通過(guò)串口打印出來(lái)。
voidhw_hardfault_exception(structexception_info*exception_info) { uint32_t*app_sp=NULL; inti=0; /*sp指向發(fā)生hardfault前棧地址*/ app_sp=(uint32_t*)(exception_info+1);/*context+16*4*/ printf_e("psr:0x%08xrn",exception_info->psr); printf_e("r00:0x%08xrn",exception_info->r0); printf_e("r01:0x%08xrn",exception_info->r1); printf_e("r02:0x%08xrn",exception_info->r2); printf_e("r03:0x%08xrn",exception_info->r3); printf_e("r04:0x%08xrn",exception_info->r4); printf_e("r05:0x%08xrn",exception_info->r5); printf_e("r06:0x%08xrn",exception_info->r6); printf_e("r07:0x%08xrn",exception_info->r7); printf_e("r08:0x%08xrn",exception_info->r8); printf_e("r09:0x%08xrn",exception_info->r9); printf_e("r10:0x%08xrn",exception_info->r10); printf_e("r11:0x%08xrn",exception_info->r11); printf_e("r12:0x%08xrn",exception_info->r12); printf_e("lr:0x%08xrn",exception_info->lr); printf_e("pc:0x%08xrn",exception_info->pc); printf_e("stacks:rn"); for(i=0;i1024;?++i) ????{ ????????printf_e("%08x?",?*app_sp); ????????app_sp++; ????????++i; ????????if?(i?%?16?==?0) ????????????printf_e("rn"); ????} ????printf_e("rn"); ????while(1); }
在hw_hardfault_handler函數(shù)中打印出了所有相關(guān)寄存器,如圖 4所示:
圖4 發(fā)生異常時(shí)棧數(shù)據(jù)
從打出來(lái)的返回地址值(PC)為0x00000644,在生成的all.dis匯編文件搜索該地址,如圖 5所示,該地址是C_Function函數(shù)中的一個(gè)除法指令,R0寄存器值除以R1寄存器值,并將結(jié)果存放R0中。R0為0x64,確認(rèn)R1寄存器值即可。
圖5 發(fā)生hardfault異常瞬間執(zhí)行的指令
圖 4中,LR的值為0x0000060f,all.dis無(wú)法搜索到該地址。由于Cortex-M3使用的是Thumb指令集,bit0置位指示該地址地址指令是Thumb指令。bit0復(fù)位,搜索0x0000060e,如圖 6所示,該地址在B_Function函數(shù)中。B_Function函數(shù)調(diào)用了C_Function函數(shù),R0為傳遞的參數(shù)0。由此可知,圖 5中,R1的除數(shù)值0,故程序會(huì)發(fā)生hardfault異常。
圖6 B_Function函數(shù)的匯編代碼
圖 6中,調(diào)用C_Function函數(shù)前,R4和LR寄存器被壓入了棧中。即圖 7中,LR的值為0x000005D1,R4的值為0xe000ed14。
圖 7 B_Function函數(shù)壓棧值
如圖 8所示,在all.dis文件搜索0x000005D0地址在A_Function函數(shù)中,在執(zhí)行A_Function函數(shù)前,對(duì)R4和LR進(jìn)行了壓棧。A_Function函數(shù)調(diào)用了B_Function函數(shù)。
圖 8 A_Function函數(shù)匯編代碼
如圖 9所示,A_Function函數(shù)壓入的R4值為0xe000ed14,LR值為0x000006a1。
圖 9 A_Function函數(shù)壓棧值
如圖 10,在all.dis文件中,搜索0x000006a0,發(fā)現(xiàn)該地址在TestDebug函數(shù)中,且該函數(shù)將R4和LR壓入棧中。
圖 10 TestDebug函數(shù)匯編代碼
如圖 11所示,A_Function函數(shù)壓入的R4值為0xe0001c18,LR值為0x00001ab9。
圖 11 TestDebug函數(shù)壓棧值
如圖 12所示,在all.dis文件中,搜索0x00001ab8,該地址在main函數(shù)中。
圖 12 main函數(shù)的匯編代碼
至此,如圖 13所示為發(fā)生hardfault異常時(shí)函數(shù)的調(diào)用關(guān)系,在C_Function函數(shù)中,被除數(shù)為0是導(dǎo)致進(jìn)入hardfault異常的原因。
圖 13 發(fā)生hardfault時(shí)的函數(shù)調(diào)用關(guān)系
來(lái)源:東軟載波微電子
-
微控制器
+關(guān)注
關(guān)注
48文章
7394瀏覽量
150629 -
寄存器
+關(guān)注
關(guān)注
31文章
5268瀏覽量
119646 -
程序
+關(guān)注
關(guān)注
115文章
3743瀏覽量
80661 -
Cortex-M3
+關(guān)注
關(guān)注
9文章
269瀏覽量
59379
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論