2018年1月25日 星期四

Timer 除錯法

強者我朋友習得這招之後一直沒有機會實驗,直到最近終於有機會派上用場,苦於 debug 的朋友或許可以進來瞧瞧。

什麼是 Timer 除錯法?

  • 使用 hardware timer,中斷週期為一秒,如果沒有可用的 hardware timer,就找個工作量最輕的 timer 寄人籬下,用軟體計數。
  • 每次中斷發生時,用 UART 或其他方式,把進入中斷前的 PC(Program Counter)印出來,以 ARM 來說就是 LR(Link Register)。

這樣當韌體程式發生死機的情形時:
  1. 只要 Timer 中斷還活著,就會不斷列印相同的 PC 值,這個 PC 值就是最後程式死掉的地方。
  2. 再藉由這個 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 企業同事還拿這個去發表專題報告,當時台下的我實在很想舉手說你這書上早就有了(只是本人一向與人為善不愛出風頭),也許就是這樣能見度不佳,只能被叫去做些瑣事黯然退場吧?

希望本文對您有幫助!

沒有留言:

張貼留言