程序員,你調(diào)試過的最難的 Bug 是什么?
回想起這個bug,仍然讓我有些痛苦。作為一個程序員,在發(fā)現(xiàn)bug時,你學(xué)會了首先在自己代碼中找問題,或許在測試一萬次之后,你會把問題歸咎于編譯器。只有在這所有的都不起作用之后,你才會把問題歸咎于硬件。
這是我遭遇一個硬件bug的故事。
拋開別的不說,我曾為《Crash Bandicoot》寫存儲卡(讀寫)代碼。對于一個自大的游戲程序員,這就像是在公園里散步一樣輕松愉快,我認(rèn)為只要幾天就寫完了。我中止調(diào)試六個禮拜。在此期間我做一些其他的事情,但我一直回來處理這個bug——幾天內(nèi)每天幾個小時。這個bug實(shí)在煩人。
這個bug的癥狀是,當(dāng)你需要保存你的進(jìn)度時,代碼會訪問存儲卡,而大部分情況下沒有什么問題…但是偶爾讀寫會超時…沒有任何明顯的原因。一個短小的寫入經(jīng)常毀掉存儲卡。玩家要保存進(jìn)度,我們不僅不保存,還擦除他們存儲卡上的全部東西。天哪。
過了一段時間,我們在Sony的制作人Connie Booth慌了。我們顯然不能帶著這個bug發(fā)布游戲,而六個星期之后我對于問題出在哪一點(diǎn)線索都沒有。通過Connie我們向其他 PS1 開發(fā)者求助:有沒有人出現(xiàn)過像我們這樣的情況?沒有。絕對沒有任何人在存儲卡系統(tǒng)上出現(xiàn)任何問題。
在你絞盡腦汁之后,你能做的唯一一個調(diào)試方法就是分而治之:一點(diǎn)點(diǎn)去除程序中的代碼,直到留下的代碼很少但你仍然出問題。像木雕一樣去除沒有問題的代碼,留下的就是你的bug所在。
在這樣的背景下挑戰(zhàn)在于,視頻游戲是很難去除某一部分的。在你刪除模擬重力或者顯示字符的代碼后,如何運(yùn)行游戲?
你必須做的是用一個假裝做真正的事情,但實(shí)際上只是做很簡單的不會出現(xiàn)bug事情的東西來替換掉整個模塊。你必須寫新的支撐代碼來讓這些玩意正常工作。這是一個緩慢而痛苦的過程。
長話短說:我做完了。我移除了大片大片的代碼,相當(dāng)多,只留下了初始化代碼——就是準(zhǔn)備游戲運(yùn)行系統(tǒng),初始化底層硬件等等。當(dāng)然,我不能顯示加載/保存菜單,因?yàn)槲医爻怂械膱D像代碼。但是我能夠假裝用戶使用(不可見的)加載/保存屏幕并且請求保存,然后寫入卡中。
我最終以一個帶有這個bug的很少量的代碼結(jié)束——但問題仍然隨機(jī)出現(xiàn)!在大多數(shù)情況下沒啥問題,但是偶爾會失效?;旧纤械腃rash的實(shí)際代碼都被移除了,但還是這樣。這實(shí)在是莫名其妙:留下來的代碼基本上都沒做什么事。
在那時——估計是凌晨3點(diǎn)——一個想法蹦了出來。讀寫(I/O)涉及精確定時。無論是硬盤、存儲卡、藍(lán)牙發(fā)送器——隨便啥——做讀寫的底層代碼都是根據(jù)時鐘來的。
時鐘讓不直接連接到CPU的硬件設(shè)備和cpu運(yùn)行的代碼同步。時鐘決定了波特率——數(shù)據(jù)從一頭傳到另一頭的速率。如果計時有什么問題,硬件或者軟件或者兩者都會亂七八糟的。這真的,真的很糟糕,并且通常導(dǎo)致數(shù)據(jù)損壞。
如果我們的初始化代碼以某種方式弄亂了計時會怎么樣?我又看了一遍測試程序中和計時有關(guān)的代碼,并注意到我們將PS1上的可編程計時器設(shè)置到了 1kHz(1000跳每秒)。這是比較快了,當(dāng)PS1啟動的時候,默認(rèn)狀態(tài)大概是100Hz。因此,大多數(shù)游戲?qū)⑺麄兊挠嫊r器設(shè)置為100Hz。
這個游戲的帶頭(和除我外的唯一)開發(fā)者Andy,將計時器設(shè)置為1kHz,使得Crash的動作計算更加準(zhǔn)確。Andy喜歡矯枉過正,如果我們要模擬重力,我們應(yīng)該盡可能的提高精度!
然而如果提高計時器頻率莫名其妙的干擾了整個程序的計時,故而將這個計時器設(shè)置到存儲卡的波特率上會怎樣呢?
我將計時器代碼注釋掉。然后我就無法復(fù)原這個bug了。但是這并不表示bug被修復(fù)了,這個問題是隨機(jī)發(fā)生的。萬一我只是運(yùn)氣好呢?
幾天過去了,我還是在玩我的測試程序。Bug沒有再出現(xiàn)。我回到全部的Crash代碼中,修改了加載/保存代碼,在訪問存儲卡之前將可編程計時器重置為默認(rèn)設(shè)置(100Hz),之后設(shè)置回1kHz。從此之后沒有發(fā)現(xiàn)問題再次出現(xiàn)。
但是…為什么?
我重新回到測試程序上,試著檢測當(dāng)計時器設(shè)置為1kHz時出現(xiàn)的那些錯誤的模式。終于,我注意到這些錯誤出現(xiàn)在使用PS1手柄的人身上。因?yàn)槲易约?很少這樣做,所以我沒有注意到(為啥我要在測試加載/保存代碼的時候用手柄)。但是有一天我們的美工等我去完成測試(我確定那時候我在爆粗口),而他緊張 的擺弄著手柄??〒p壞了。“等下,怎么回事?喂,再來一次!”
一旦我發(fā)現(xiàn)了這兩件事是聯(lián)系著的,就很容易重現(xiàn)bug:開始寫入存儲卡,動一下手柄,存儲卡損壞。在我看來完全是硬件bug。
我去找Connie告訴他我的發(fā)現(xiàn)。她轉(zhuǎn)述給設(shè)計過PS1的硬件工程師。她被告知:“不可能,這不可能是硬件問題。”我跟她說問一下我能不能直接和他說。
那個工程師給我打電話了,他用著他的爛英語,我用著我更爛的日語,我們爭論一會。我最后說:“我給你一個30行的測試程序,讓你在動手柄的時候能夠 出現(xiàn)這問題。”他答應(yīng)了。他向我保證,這是浪費(fèi)時間,而他正在一個新項(xiàng)目上很忙,但因?yàn)槲覀兪荢ony很重要的開發(fā)者,他會試的。
第二天晚上(我們在洛杉磯,而他在東京,所以對于我來說是晚上而他是到了第二天),他給我打電話,不好意思的向我道歉。這是個硬件問題。
我還是沒有完全搞清楚問題到底在哪,但是我的印象中,從Sony總部的反饋聽到的是,如果將可編程計時器設(shè)置到足夠高的時鐘頻率,會影響到主板上時 鐘晶振附近的一些東西。這些東西之一就是存儲卡的波特率控制器,同時也設(shè)置手柄的波特率。我不是搞硬件的,所以對于細(xì)節(jié)我相當(dāng)模糊。
但是主旨是主板上兩個獨(dú)立部分的串?dāng)_,以及手柄接口和存儲卡接口數(shù)據(jù)發(fā)送的結(jié)合在1kHz的時鐘頻率下會導(dǎo)致丟位,從而數(shù)據(jù)丟失,以致卡損壞。
這是我全部編程生涯中,唯一一次因?yàn)榱孔恿W(xué)debug的問題。
原文鏈接:http://www.quora.com/Programming-Interviews/Whats-the-hardest-bug-youve-debugged/answer/Dave-Baggett