看到一篇文章
簡體中文翻譯: 程序员,你调试过的最难的 Bug 是?
原文: What’s the hardest bug you’ve debugged?
看原文比翻譯好一點, 從原文大概只可能猜出, 改過 timer 之後, 可能影響到 interrupt 的速度.
但是從最後的描述猜也可能是動到 clock tree 去影響 timer 進而影響到其他的行為.
有碰到這種 BUG 的第一個一定先猜 timing , 純軟體的反而不會這樣想, 所以他覺得最難的 BUG
我們看起來就還好.
這個描述讓我也想寫一下, 我碰過最難的 BUG.
那是新的 SoC 剛回來的時候, 各個 block 都要驗, 我是負責帶領系統驗証部門的,
所以一開始就有一大堆 BUG, 其中有一個 BUG 就是資料存取有問題, 而且要很久才會出現一次的.
最難的 BUG 要件之一就是很久才會出現一次, 這表示會搞很久.
因為當時我是負責 SATA 部份的驗証, 所以這個工作自然就是我去追了.
看倌呀, SOC 就是統統要自己來, 從 hardware CPU/SATA/CLOCK 到 kernel 層都可能有問題.
第一件事情就是, 想辦法找出可以複製的方法. 我們知道資料存取有問題
所以我就寫了一個 sh script 從 A Disk Copy 到 B Disk 再做比對. 然後一直 COPY 下去, 直到找到為止.
這個 sh script 大概可以在 200 次大檔案 COPY, 找到一次錯誤的結果.
大概要花二個小時以上, 一開始的時候, 這個 script 執行下去, 其實並沒有辦法抓出問題.
流程大概是這樣
# dd if=/dev/zero of=test bs=1M count=1024
# md5sum test
這樣就會有一次寫入, 一次讀出, 這樣就可以比對 md5.
有沒有看出什麼問題?
其實這個錯誤可能很少人會意會到, 就是測試 pattern 不能為零, 假設今天 block 寫錯了, 而資料是零, 再怎麼寫入讀出, 資料還是正確的. 所以後來我都改用 AV file 做 test pattern, 工程師的惡趣味就在這邊了.
“我今天有 update 新的 test pattern 哦…. ”
大家就知道發生什麼事情了.
修改完 script 之後, md5 checksum 的結果的確是不一樣. 這時就要猜測是什麼地方在作怪.
首先一定先關心 SATA Hardware 是不是有問題, 所以就拿 PCIe SATA 還有 USB 來驗証比對.
同時間, 看 Linux kernel 追 SATA 這一層在搞什麼飛機.
其實相對於 USB Protocol , AHCI SATA Interface 是很簡單的 interface, 很快的就排除掉這個犯人了. 而且 PCIe SATA 和 USB 的結果顯示也是會複製錯誤.
這下子人就可能是 CPU 和 Kernel 殺的. 還有記憶體.
SoC 守則, 只要資料有錯, 八成是記憶體殺的. 各位可能會問, 那為什麼要先測 SATA? 因為記憶體己經先掃過一輪沒問題了, 所以當然先丟 SATA. 不過這邊的先掃過一輪沒有問題是不是真的沒有問題….
這個原因是這樣, 所謂的記憶體沒有問題是用 NON-OS Code 下去掃的, 可能會有切 MMU Mode 和 NonMMU Mode. 總之是這樣測的.
然後我就 mount ramdisk 開 memtest 狂測一天. 果然出問題了. 哼哼, 是你吧.
不過接下來問題才大條. RAM 出問題很麻煩的. 要怎麼定位到到底是什麼出問題呢?
專業的讀者應該就會猜, 那就降速跑.
正解, RAM 出問題就要先降速跑. 所以 DRAM 就跑在 200Mhz (DDR2-800 是 400Mhz, 最低應該是 DDR-400 跑在 200Mhz). 反正都降速, 所以 CPU 也從 600Mhz 降到 400Mhz 跑.
當然老天一定不會這麼容易讓我們破關的, 所以資料錯誤的 BUG 還是可以重現.
同時間 NON-OS 的測試結果仍然是顯示沒有問題.
然後就是猜了. 因為如果 NON-OS 的 memory test 是正確的, 而 Linux 是錯誤的, 那可能問題是出在其他的部份. 這麼複雜的系統一定就是要猜.
猜也是一門學問, 接下來就是先關 L1/L2 Cache .
各位, 為什麼 CACHE 要最後再測, 因為 CACHE 關掉整個系統會變更慢. 所以預期二小時會發生的, 可能測試就要拉到二天, 我還有碰過 Cache 關掉就複製不出來的 BUG.
Disable Cache 有二項, 因為 L2 Enable 一定要 L1 也 Enable.
Disable L1/L2
Disable L2
除了 Disable Cache 以外, 和 CPU 相關的還有 SMP (因為 SoC 有二顆 CPU), 還有 Cache Driver 新舊版的問題.
這幾個測試項測下來, 果然 Disable Cache 就不會出問題了, 而且是 Disable L2 Cache.
走到這邊大概己經測試了三個星期. 快搞死了, SoC 慢就算了, 掉資料是不允許的啊啊啊.
對一個 engineer 來說這根本是不可接受的事.
而且同時間還有一堆 BUG 待解待查. SoC 剛回來天天都是屁股在燒.
為了保險起見. 必需要找到資料錯誤的 Pattern .
所以寫了一個可以 binary compare 並且顯示出那邊不一樣的小程式.
翻了一下當年的痕跡, 原來除了 binary compare, 還有程式內加上 invalidate cache 的相關程式.
我還記得為了這件事翻了整層的 Linux kernel cache 和 memory management 機制.
最後確認到是 cache 的問題是, 使用 binary compare 找出 32bytes, 相當於一條 L2 cache line size.
而且可以重覆這個測試.
到這邊幾乎可以確定是 L2 cache Line 的問題, 而且是硬體的問題, 當然 L2 還有一些測試項目.
像是調整 Cache Latency 之類的. 這個應該都做了, 不過做了以後應該是更久才會再出現一次.
所以我就將我知道的東西整理一下, 寫了一封信問 ARM . (還好當時公司有付錢買 ARM 的服務).
ARM 的回答也很乾脆,
關掉那個 L2 Cache I Prefetch 的功能就可以了. 在 PL310 內, 這個功能只是 1 bit 的改變.
(I Prefetch 是 Instruction Prefetch command , 快要執行到指令時, 先去記憶體先抓回來放在 Cache 內)
然後就一切正常了…..
然後就一切正常了…..
然後就一切正常了…..
1 個 bit, 快要一個月的青春啊~~ 所以這個 BUG 記到現在還在記.
這應該算難嗎? 不知道的 BUG 都很難, 知道之後就不難了.
==
後記1: NON-OS 為什麼沒有測出來, 其實 Cache 那邊的參數也是從 NON-OS 來的, 為什麼 NON-OS 沒有問題呢?
猜想是因為 NON-OS 的 Code Size 很小, 所以 Code 都在 L1 Cache 內, 並不會動用到 I prefetch 的功能. 所以自然也驗不出來了.
後記2: 我覺得很多細節我都忘了, 所以這應該只是大致的情節. 雖然寫起來很輕鬆, 但是當時很慘烈呀~~~~
anivek
想當年還在系統廠的時候, 也解過很多類似的issue, 而且完全沒有IC vendor support, 連register都要reverse engineering… 慘慘慘… 不過話說現在都turnkey系統廠就沒人在全部自己來了…
八足
看完只能拜 m(_ _)m