什麼是 Timer 除錯法?
- 使用 hardware timer,中斷週期為一秒,如果沒有可用的 hardware timer,就找個工作量最輕的 timer 寄人籬下,用軟體計數。
- 每次中斷發生時,用 UART 或其他方式,把進入中斷前的 PC(Program Counter)印出來,以 ARM 來說就是 LR(Link Register)。
這樣當韌體程式發生死機的情形時:
- 只要 Timer 中斷還活著,就會不斷列印相同的 PC 值,這個 PC 值就是最後程式死掉的地方。
- 再藉由這個 PC 值,回頭查詢 compiler 編譯完產生的 map file(例如 Keil C 會產生 .map),找到相關 symbol,就可以知道程式死在哪個範圍。
步驟 2. 可能有人認為 map file 很大裡面位址太多,要找很久,那我是不是還要先寫個 script 用 binary search 演算法搜尋檔案?不用那麼辛苦,有個簡單粗暴的方法: grep,輸入部份位址,通常只要一兩次就能找到。
以嵌入式網路那些事範例 3-1 為例,編譯成功 Keil C 會產生檔案: STM32-LwIP.map,假設我們要找的位址是 0x08008d80,那要怎麼知道這個位址是落在哪個 C 函式裡面?那我們可以猜測 0x08008d80 應該是落在位址0x08008000 ... 0x08008FFF 的相關函式裡,我們用 0x08008\w+ 這個 regular expression 搜尋,這裡使用 dnGrep 這個小工具(或任何支援 regular expression 的工具都可以):
搜尋結果:
搜尋結果:
由上圖可知,用眼睛大概掃描一下就知道這個位址位於 tcp_recv() 裡面。如何?很方便吧!
Timer 除錯法的限制
- Timer 中斷不能死掉,如果當在 Timer 中斷裡這招就沒轍了。
- 不能 disable 中斷,例如 uC/OS-II 有個常用的巨集 OS_ENTER_CRITICAL(),通常實作的方式是 disable 中斷,也就是如果死在 OS_ENTER_CRITICAL()...OS_EXIT_CRITICAL() 的區間裡就 gg 了。
- 對記憶體覆蓋類的 bug 比較沒有幫助,因為可能連 Timer ISR 都被蓋掉、或者是 Timer Interrupt stack 也被摧毀。
- 對不合法的指標類 bug 效果有限甚至沒有,不過在傳統 ARM 裡面這會觸發下面三種 exception 之一:
- Data Abort exception:
- 記憶體沒有對齊,如 int 指標應該指向位址為 4 的倍數的記憶體位址
- 不小心寫到 ROM space
- 該 SoC 的保留區
- Prefetch abort:
- 提取指令失敗
- Undefined exception:
- 一條不屬於 ARM or thumb 指令集的指令到達 pipeline 的執行階段
- 在過去開發 HMI 的經驗裡,碰到 ARM 發生 exception 的處理方式就是把發生 exception 前的 PC 值印在 LCD 上。不過有些產品沒有 LCD,這時候大多數人都是用 UART,也有人把腦筋動到移位暫存器 + 7 段顯示器上。
由此可知,沒有一個 debug 的萬能解法,每種方法都有他的限制。
至於上面的記憶體覆蓋問題,如果是 stack,uC/OS-II 到是提供了一個簡單的解法,就是在 stack 頂端放入某個特殊 pattern,如果 pattern 被蓋掉了,那就代表發生了記憶體覆蓋或 stack 爆掉的問題,n 年前幸福 M 企業同事還拿這個去發表專題報告,當時台下的我實在很想舉手說你這書上早就有了(只是本人一向與人為善不愛出風頭),也許就是這樣能見度不佳,只能被叫去做些瑣事黯然退場吧?
希望本文對您有幫助!
沒有留言:
張貼留言