67/70?MCU中的HardFault_Handler問題

問題描述

使用的工具 KDE-5.11 在調試目標芯片 EFM32 時出現(xiàn)問題,起初是調試鐵電驅動,但在單步仿真時到某一步總會不再響應,或者結束仿真時 PC 跳轉到了 HardFault_Handler 異常。為了解決這個問題,于是不斷地縮減代碼,之后發(fā)現(xiàn)程序從 startup 開始執(zhí)行時,一進入 main 函數(shù)就會不響應或者跳轉到 HardFault_Handler 中,一度懷疑自己的硬件 CMSIS 移植有問題。為了解決這個問題,嘗試了多個辦法,包括并不僅僅限于更換調試工具,斷點調試,評估板代替測試,重新移植底層代碼,查看 RAM 占用,修改靜態(tài)區(qū)全局變量占用大小等等。下面總結一些遇到 HardFault_Handler 異常的解決方法。

HardFault_Handler 分析

一般 HardFault_Handler 錯誤是指 PC 指向了一個無法訪問的位置,主要可以分為兩種:

  • 內存溢出或者訪問越界。這個需要自己寫程序的時候規(guī)范代碼,遇到了需要慢慢排查。
  • 堆棧溢出。增加堆棧的大小。

仿真時,有時會出現(xiàn) HardFault_Handler 這種錯誤,這種錯誤往往會涉及到一些編譯運行時的深層次原理,但基本可以肯定的是一般都是 SP,LR,PC 這三個寄存器出了問題,下面介紹這三個寄存器:

  • 堆棧指針r13(SP):每一種異常模式都有其自己獨立的r13,它通常指向異常模式所專用的堆棧,也就是說五種異常模式、非異常模式(用戶模式和系統(tǒng)模式),都有各自獨立的堆棧,用不同的堆棧指針來索引。這樣當ARM進入異常模式的時候,程序就可以把一般通用寄存器壓入堆棧,返回時再出棧,保證了各種模式下程序的狀態(tài)的完整性。

  • 連接寄存器r14(LR):每種模式下r14都有自身版組,它有兩個特殊功能:

(1)保存子程序返回地址。使用BL或BLX時,跳轉指令自動把返回地址放入r14中;子程序通過把r14復制到PC來實現(xiàn)返回,通常用下列指令之一:
            MOV PC, LR 
            BX LR

    通常子程序這樣寫,保證了子程序中還可以調用子程序。
             stmfd sp!, {lr}
             ……
             ldmfd sp!, {pc}

(2)當異常發(fā)生時,異常模式的r14用來保存異常返回地址,將r14如??梢蕴幚砬短字袛?。
  • 程序計數(shù)器r15(PC):PC是有讀寫限制的。當沒有超過讀取限制的時候,讀取的值是指令的地址加上8個字節(jié),由于ARM指令總是以字對齊的,故bit[1:0]總是00。當用str或stm存儲PC的時候,偏移量有可能是8或12等其它值。在V3及以下版本中,寫入bit[1:0]的值將被忽略,而在V4及以上版本寫入r15的bit[1:0]必須為00,否則后果不可預測。

需要研究到底寄存器、函數(shù)是如何跳轉調用的,我們需要使用 KDE 中的兩個仿真工具: Register 和 Call Stack+Locals。Register 中主要觀察 SP,LR,PC 三個寄存器的數(shù)據(jù),PC永遠指向 CPU 正在執(zhí)行工作的位置,LR 會保存你調用子函數(shù)之前的跳轉地址,也就是說當子函數(shù)完成返回時,會回到 LR 值對應的地址繼續(xù)執(zhí)行下面的程序。我這邊實驗時,能夠看出每次執(zhí)行錯誤后, PC 會跳轉到一個很大的錯誤地址。

指針跑飛

在 HardFault_Handler 中的 while(1) 設置斷點,然后運行,給它觸發(fā) HardFault_Handler 的條件,然后到斷點處之后,查看 watch 窗口中的 Call Stack+Locals,也就是堆棧以及局部變量,程序執(zhí)行到哪一句發(fā)生的錯誤,以及當時各個壓棧的函數(shù)的各個局部變量的值一目了然。一般而言最常出現(xiàn)的就是指針跑飛,數(shù)組越界,這兩種其實可以看做一個情況,都是指針訪問了無權限訪問的空間,通過 Call Stack+Locals 窗口往往能夠定位到該函數(shù),然后可以采用單步執(zhí)行,看到具體在哪一步觸發(fā)了異常。

RAM溢出

這種情況也是我排查的一種情況,編譯完成之后,全局變量已經(jīng)占用了相應大小 RAM 中的靜態(tài)存儲區(qū)域,如果你的 MCU 本身不夠大,例如我的只有 8K RAM空間,而 COM 的緩沖數(shù)組占用了過多的全局變量,這邊就存在一定的可能 RAM 不夠分配而越界。

底層 CMSIS 問題

因為我的項目是在進入 main 函數(shù)一開始就出錯了,所以為了排除是不是之前的跳轉就有問題,所以重新移植 startup.s 文件

Jlink

不排除調試器存在缺陷,所以更換了調試器,將自己的程序移植到評估板上面運行,進一步驗證,直接使用官方自帶例程,在評估板上面運行。

inline函數(shù)無法捕捉

EFM32 芯片采用 JLINK V9 多次測試多款不同型號的芯片都無法單步執(zhí)行,單步只能在 while(1) 中執(zhí)行,一般從 startup 到 main 函數(shù)中就會發(fā)生錯誤,開始懷疑是調試工具的問題,通過增加斷點部分解決了這個問題,經(jīng)過評估板測試,查看 RAM 占用,靜態(tài)區(qū)全局變量等查看不斷排查,之后發(fā)現(xiàn)是共同規(guī)律是每次 inline 函數(shù)調用某一個普通函數(shù)時,單步執(zhí)行到 return 時,無法找到真實的返回地址。

這邊是我主要出現(xiàn)錯誤的情況,一般較為少見,根本原因在于 inline 函數(shù)是類似于宏定義,直接本地展開的,如果使用斷點是無法捕捉到的,這邊我的 inline 函數(shù)中又調用了一個普通函數(shù),因為 inline 函數(shù)是原地展開,LR 沒有載入它的地址,而每次普通函數(shù)在返回時,無法獲取到 inline 函數(shù)的返回地址,然后就跳轉了異常,這本身并不能算是一個問題,在 MCU 正常運行時不會產(chǎn)生任何影響,影響的僅是你的調試過程。另外我個人在調試 SPI 驅動時,因為也調用了固件庫自帶的 inline 函數(shù),所以導致也沒法單步執(zhí)行這段程序。

小結

雖然這邊查出我的問題出現(xiàn)在內聯(lián)函數(shù)上面,但是,以上的集中方法都可以作為常用排查 HardFault_Handler 異常的方法,且一般而言出現(xiàn)指針跑飛的可能性最高。

參考鏈接:
http://blog.csdn.net/zyboy2000/article/details/7668331
http://www.51hei.com/bbs/dpj-39846-1.html
http://blog.csdn.net/jimmy2013_1_1/article/details/9723461
http://blog.chinaunix.net/uid-26967414-id-3823606.html
http://blog.csdn.net/zhou1232006/article/details/6149548
http://blog.csdn.net/pony_maggie/article/details/5270501
http://wenku.baidu.com/view/f7bf4ad6b14e852458fb576a.html?re=view

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容