今天繼續聊聊開發中常見的 HardFault,這個問題應該從學習 STM32 開發以來就一直伴隨著我們,很多人遇到這種問題也是不知道該如何定位。
如果只是獨立開發,遇到這種問題,一般都是看代碼、修改代碼等等這些常規手段,因為自己寫的代碼最熟悉,改動一般也不會太大,容易縮小范圍,也更容易定位。
但現在的產品越來越復雜,目前的開發模式都是合作開發,每個人負責各自的模塊,這樣的項目代碼量大、復雜度高,也就更難定位問題。
而有的時候,剛入職一家公司,什么代碼都不熟悉,又出現了 HardFault,更是讓人崩潰,分分鐘有跑路的沖動(你和代碼,有一個能跑就行)。
此時,有一個能解決這種疑難雜癥的大牛是能大大節省時間的,而我在公司也解決不少類似的問題,所以經驗也算豐富,充當的也是這一類角色。
而魚鷹定位 Hardfault 的方法一般是靠 KEIL在線調試+C語言+權威指南中的知識搞定。
目前魚鷹的解 BUG 差不多是這樣的:
1、必現,代碼熟悉的情況下,幾個小時內搞定。
2、偶現,根據出現情況決定解決問題的時間,一般出現個四五次,基本就能定位。
3、難現。這種一般要掛一個記錄儀實時記錄運行情況。
經歷了這么多,已經很少有能讓魚鷹需要花費幾天時間才能解決的 Hardfault 問題了(猶記得剛來深圳時,因為別人寫的一個 BUG 導致的 Hardfault,不得已加了幾天通宵,要不是偶然機會還不一定能搞定)。
這里打個小廣告,如果難解決,可以有償請魚鷹解決 Hardfault 問題哦。
不過最近工作上因為用了 C++,這個基礎不是很熟悉,解決 Hardfault 的速度又下降了。而工程編譯優化等級 -O2 也加大了不少調試難度,因此掌握下面的方法是很重要的:
總結 MDK 幾種編譯優化設置的方法
關于 Hardfault,魚鷹以前也是分享了不少筆記的,不知道有多少人認真看過。
HardFault 之 INVSTAE 錯誤定位(一)
見鬼,過年回來后板子就 hardfault 了?
今天,魚鷹繼續分享關于在 FreeRTOS 定位 Hardfault 方法。
這里需要一個大佬寫的組件 :CmBacktrace(事實上,如果能在線調試,魚鷹是不需要借助這個組件的,但是難復現的情況下用這個組件還是比較香的)。
gitee 倉庫:https://gitee.com/Armink/CmBacktrace
這個組件估計很多道友都聽說過,也用過,但魚鷹想說的是,有些道友在用的組件可能比較老,沒有下面這種追蹤功能,建議大家更新一下。
上面可以看到出錯時,函數的調用棧(有時可能是錯誤的,需要實際分析,僅做參考)
_call_main -> main -> fult_test_by_div0
相當實用。
同時,本篇筆記不僅適用于在 FreeRTOS 定位 Hardfault,實際上uCOS、rt-thread 等其它 RTOS 照樣可以修改后使用(裸機更不用說了)。
倉庫例子支持的平臺:裸機、rt-thread、ucoss-ii、freertos。
這里重點在如何移植這個組件到freertos 中(實際上,倉庫的說明文檔也非常詳細,可以參考)。由于freertos 也是不斷更新中,所以這個組件的例子不能完全適用于新版本,而魚鷹剛好移植好了,在此記錄一下,方便大家移植。
1、將倉庫中的 cm_backtrace(源碼文件)整個文件夾拷貝到自己的工程文件夾下。
2、在自己的工程中添加這些文件(我們可以打開 demos -> os -> freertos 工程查看)
只有兩個文件,相當簡單。
一個是核心源碼,另外一個則是匯編代碼,代碼執行入口。
注意,根據 IDE 不同,選擇的匯編文件也不同:
其實就是將 startup_stm32f10x_hd.s 中的hardfault 默認處理函數重定位到 cmb_fault.S 中了。
注意這里有一個weak,這樣鏈接的時候就不會鏈接這個,而是cmb_fault.S這個:
為了更方便的定位問題,我們后面還需要修改一下這個代碼才行。
注意,如果你的啟動文件內的 hardfault 代碼被修改了,而你不懂匯編,建議恢復成上面那種,不然可能運行不正常。
3、主函數中初始化代碼。
這里的字符串需要和這個一樣(根據自己的工程名修改):
所以建議用英文建工程。這個在輸出錯誤信息的時候用的上,否則每次查看調用棧都需要修改一下,比較麻煩。
如果開啟了內部看門狗,建議關閉一下:
//HAL 庫
__HAL_DBGMCU_FREEZE_IWDG1();
//標準庫
DBGMCU_Config(DBGMCU_IWDG_STOP, ENABLE);
在斷言失敗的位置添加該函數 cm_backtrace_assert:
這樣斷言失敗了也能看到調用棧了。
4、FreeROTS內核文件修改(內核版本 V10.2.1)
為了分析出錯的代碼,必須知道每個任務的棧信息,而 FreeRTOS 可能沒有這些信息,因此,我們需要添加進去。
task.c
FreeRTOS.h
注意,老版本freertos 是只要修改一處的,但新版本需要修改兩處,否則會斷言失敗,運行不下去。
建議把注釋也一起添加進去。
UBaseType_t uxSizeOfStack; /*< Support For CmBacktrace >*/
相關函數修改 task.c prvInitialiseNewTask():
task.c 文件最后添加如下代碼用于獲取棧地址、大小、名字:
為方便復制,在此貼代碼
/*-----------------------------------------------------------*/
/*< Support For CmBacktrace >*/
uint32_t * vTaskStackAddr()
{
return pxCurrentTCB->pxStack;
}
uint32_t vTaskStackSize()
{
return (pxNewTCB->pxEndOfStack - pxNewTCB->pxStack + 1);
return pxCurrentTCB->uxSizeOfStack;
}
char * vTaskName()
{
return pxCurrentTCB->pcTaskName;
}
/*-----------------------------------------------------------*/
5、根據所屬 RTOS 平臺和芯片內核修改組件配置信息
cmb_cfg.h
1)需要定義打印輸出函數,一般用 printf 打印,也可以用你自定義的一些打印函數,功能和 printf 類似即可。
printf(__VA_ARGS__);printf("
") define cmb_println(...)
2)使能 RTOS 支持
3)具體 RTOS 選擇FreeRTOS
4)芯片內核根據實際選擇,目前支持 M0、M3、M4、M7。
5)打印虛擬棧,可以將出錯時的原始棧信息打印出來,可能對分析有些幫助
6)語言支持:英語。實際也支持中文,但建議使用英語(不配置,默認就是英語)
7)如果是 C++ 編譯的,有可能出錯,可以在開頭定義這個:
7、根據需要修改組件,方便使用(這些看看能不能有機會合并到大佬的分支里面)
1)因為功能涉及范圍小,因此可以將相關頭文件包含形式改成這種,這樣就不需要改頭文件路徑了,移植更方便:
-->>
-->>
-->>
main 中也不需要包含頭文件,而是在需要位置直接聲明這個函數即可,因為外部只需要調用這個函數
-->
void cm_backtrace_init(const char *firmware_name, const char *hardware_ver, const char *software_ver);
這樣一來,就不需要添加頭文件的路徑了。
或者使用相對路徑的方式添加頭文件:
另外,我們可以讓程序進入 Hardfault 前,讓代碼自動停止,這樣我們能更好的利用在線調試代碼,《傳說中的軟件斷點到底是什么?》。
HardFault_Handler PROC
LDR r0, =0xE000EDF0; DEMCR
LDR r0,[r0,#0x00]
AND r0,r0,#0x00000001
CBZ r0,not_in_debug
BKPT 0
not_in_debug
MOV r0, lr ; get lr
MOV r1, sp ; get stack pointer (current is MSP)
BL cm_backtrace_fault
因為剛進入 Hardfault 時的信息最全,又不想每次打斷點,上面的代碼很好的實現了功能,同時也不會影響程序的正常運行(會自動判斷是否處于調試模式)。
8、實驗。
上面都搞定了,就可以驗證一下效果了。這里我們我們可以模擬仿真看看情況。(修改工程配置,這些內容魚鷹以前分享過,不多說)
運行倉庫例子后,應該能打印下面的信息,告訴我們出現了 div 0 錯誤
而你移植好的工程也應該打印類似的信息(加入測試代碼 :fault_test_by_div0();)如果打印不出來,有兩種可能:
1、打印函數沒初始化好就進入了Hardfault
2、打印函數有問題。
之后我們復制最后一行,然后運行倉庫 tools 里面的 add2line 工具看看調用棧信息:
在 git bash 中可能會執行失敗,可以加入程序路徑,當然也可以將該工具路徑加入到Windows 環境變量中。有可能提示找不到 axf 文件,把這個文件拷貝到工具下即可。
正確的做法是,將該工具放到 C 盤目錄下,同時添加環境變量,之后就可以在 axf 目錄下打開 gitbash 或者 cmd 窗口執行命令即可。
這里只作為演示,就不多介紹這些了。
最后再簡單介紹一下這個組件的實現原理:
如果出現hardfault, 首先進入匯編文件的 HardFault_Handler處理,這里會得到當前的棧指針和 LR,并且根據 LR 確定出錯的棧是哪個《STM32 兩個棧,你用哪一個?》(這里是 PSP 棧)
根據錯誤寄存器信息,確定哪種錯誤(這里為除 0 錯誤)。
然后對棧信息 和 LR 、PC 進行 FLASH 上的匯編代碼分析,找出可能的跳轉指令,這里找到的兩個跳轉地址為 0x08001f96 0x08000368,進而得到調用棧。
因此,要能準確的得到調用棧,有兩點重要前提條件(建議優化等級 -O0):
1、棧未被破壞
2、芯片運行代碼和 axf 文件保持一致。
即使如此,你也不能保證找出的調用棧就是正確的,比如你使用 fault_test_by_unalign() 測試,得到的結果如下:
中間多了一個 fputc。
因此,這些打印信息只能作為參考使用。
但在線調試不同,它更專業,不容易出現錯誤調用關系。
魚鷹想分享的內容到此就結束了,下期再見!
審核編輯 :李倩
-
C語言
+關注
關注
180文章
7601瀏覽量
136251 -
FreeRTOS
+關注
關注
12文章
483瀏覽量
62018
原文標題:FreeRTOS 中如何定位 HardFault?
文章出處:【微信號:麥克泰技術,微信公眾號:麥克泰技術】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論