一. 問題的產生
Hard fault (硬錯誤,也有譯為硬件錯誤的)是在STM32上編寫程序中所產生的錯誤,造成Hard Fault錯誤的可能原因較多,排除硬件問題,如何在代碼量較大的情況下,快速定位造成的hardfault的問題代碼,就成為比較關鍵的問題。
本文將基於STM32處理器(stm32f091),keil-MDK開發環境,總結hardfault的調試定位方法。在其他Cortex-M0 (m3,m4)內核處理器,和其他開發環境下,也可作為參考。
二. Cortex-M 處理器內核異常中斷簡介
1)錯誤種類
對於Cortex-M內核,架構采用錯誤異常的機制來檢測問題,當核心檢測到一個錯誤時,異常中斷會被觸發,並且核心會跳轉到相應的異常終端處理函數執行,錯誤異常的終端分為以下四種:
HardFault
MemManage
BusFault
UsageFault
其中hardfault為最常見的錯誤類型,並且,在沒有開啟其他異常處理的情況下,默認進入hardfault異常中斷處理函數:
void HardFault_Handler(void)
{
/* USER CODE BEGIN HardFault_IRQn 0 */
/* USER CODE END HardFault_IRQn 0 */
while (1)
{
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
/* USER CODE END W1_HardFault_IRQn 0 */
}
}
2) 可能的原因
從軟件角度,產生hardfault的可能原因有:
(1) 數組越界
(2)野指針
(3)未初始化硬件卻開始操作,或無中斷服務函數等
(4)任務堆棧溢出
《ARM Cortex-M0權威指南》中提到,關於 Cortex M0+內核主要有以下幾點引起 HardFault 的原因:
非法存儲器訪問
非對齊數據訪問
從總線返回錯誤
異常處理中的棧被破壞
程序在某些 C 函數中崩潰
意外地試圖切換至 ARM 狀態
在錯誤的優先級上執行系統服務調用指令(SVC)
下面將以一個stm32f091上運行的數組越界代碼為例,具體闡述定位步驟:
產生越界的代碼段:
void StackFlow(void)
{
int a[3],i;
for(i=0; i<10000; i++)
{
a[i]=1;
}
}
三. 人工查找hardfault 方法和步驟
方法1.查看寄存器
1)查看fault種類
通過菜單欄Peripherals >Core Peripherals >Fault Reports打開fault reports
2)調試定位步驟
查看fault種類有時可能對解決問題並沒有直接幫助,關鍵是如何定位在進入異常中斷前執行的代碼段,步驟如下:
a. 確定當前使用堆棧是MSP還是PSP
異常發生后會把進入異常前的 R0-R3,R12, LR, PC,PSR 寄存器值棧入 Main Stack 或Process Stack(取決於異常發生時使用的哪個棧)。 進入異常后鏈接寄存器 LR 中存放異常返回值 EXC_RETURN, 如果其 bit 2=0 那么用的就是 Main Stack,如果 bit 2=1,那么用的就是 Process Stack。
由下圖可以看出,當前使用的堆棧為PSP
b.找到異常發生代碼地址
在memory中,定位到堆棧地址:0x200020E0,依據:R0~R3、R12、LR、PC、XPRS 順序,找到LR的值,即第6個寄存器值
LR = 0x0800632B
PC = 0x08006300
c.Disassembly中,查找定位代碼
在反匯編窗口中點擊右鍵,選中show disassembly at address 。
輸入LR地址:為發生異常后調用的下一條指令的地址,可看到發生異常的為StackFlow()函數
輸入PC地址:可以定位到發生異常的調用語句
方法2:調試步驟
在仿真狀態下,調出Call Stack Window,可直接跳轉到調用代碼
四. 程序查找hardfault 方法和步驟
實際環境中,由於測試時產品常常無法連接調試器,故需要代碼來定位目標語句地址,並通過一定手段保存:
在stm32中,需先修改啟動文件startup_stm32f091xc.s:
HardFault_Handler\
PROC
MOVS r0, #4
MOV r1, LR
TST r0, r1
BEQ stacking_used_MSP
MRS R0, PSP
B get_LR_and_branch
stacking_used_MSP
MRS R0, MSP
get_LR_and_branch
MOV R1, LR
IMPORT hard_fault_handler_c
BL hard_fault_handler_c
ENDP
該段代碼會判斷當前堆棧使用的是MSP或PSP,然后將堆棧參數傳遞給hard_fault_handler_c函數,該函數定義如下:
void hard_fault_handler_c(unsigned int * hardfault_args, unsigned lr_value)
{
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]);
while(1)
{
printf ("[Hard fault handler]\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 ("Stacked LR = %x\n", stacked_lr);
printf ("Stacked PC = %x\n", stacked_pc);
printf ("Stacked PSR = %x\n", stacked_psr);
printf ("Current LR = %x\n", lr_value);
for(int i = 10000;i>0;i--)
for(int j = 1000;j>0;j--);
}
//while(1); // endless loop
}
代碼跑死后,會將R0~R3、R12、LR、PC信息通過串口打印,之后根據寄存器信息排查問題代碼即可~
參考資料:
(1)https://blog.csdn.net/geek_liyang/article/details/83510518
(2)https://blog.csdn.net/Maple_Leaf_15/article/details/51443310
(3)https://www.freertos.org/Debugging-Hard-Faults-On-Cortex-M-Microcontrollers.html
(4)https://blog.csdn.net/Wang_yf_/article/details/75003611
(5)http://blog.sina.com.cn/s/blog_908da74601011g31.html
(6)https://blog.csdn.net/guozhongwei1/article/details/88418760
(7)https://community.nxp.com/thread/306244
(8)http://www.keil.com/appnotes/files/apnt209.pdf
(9)《ARM Cortex-M0權威指南》Joseph Yiu 著,宋岩 譯