最難調試修復的 bug 是怎樣的? | 知乎問答精選

 

A-A+

最難調試修復的 bug 是怎樣的?

2018年02月16日 知乎問答精選 暫無評論 閱讀 11 ℃ 次

【知乎用戶的回答(247票)】:

手機版app好像沒法設置引用,不過 whatever

這是Dave Baggett發表在Quora上一篇The hardest bug you've ever debugged,讀起來讓人十分驚歎。(如果是我的話,在代碼中找不到可能就直接放棄了。。。)

回想起這個bug,仍然讓我有些痛苦。作為一個程序員,在發現bug時,你學會了首先在自己代碼中找問題,或許在測試一萬次之後,你會把問題歸咎於編譯器。只有在這所有的都不起作用之後,你才會把問題歸咎於硬件。

這是我遭遇一個硬件bug的故事。

拋開別的不說,我曾為《Crash Bandicoot》寫存儲卡(讀寫)代碼。對於一個自大的遊戲程序員,這就像是在公園裡散步一樣輕鬆愉快,我認為只要幾天就寫完了。我中止調試六個禮拜。在此期間我做一些其他的事情,但我一直回來處理這個bug——幾天內每天幾個小時。這個bug實在煩人。

這個bug的症狀是,當你需要保存你的進度時,代碼會訪問存儲卡,而大部分情況下沒有什麼問題…但是偶爾讀寫會超時…沒有任何明顯的原因。一個短小的寫入經常毀掉存儲卡。玩家要保存進度,我們不僅不保存,還擦除他們存儲卡上的全部東西。天哪。

過了一段時間,我們在Sony的製作人Connie Booth慌了。我們顯然不能帶著這個bug發佈遊戲,而六個星期之後我對於問題出在哪一點線索都沒有。通過Connie我們向其他 PS1 開發者求助:有沒有人出現過像我們這樣的情況?沒有。絕對沒有任何人在存儲卡系統上出現任何問題。

在你絞盡腦汁之後,你能做的唯一一個調試方法就是分而治之:一點點去除程序中的代碼,直到留下的代碼很少但你仍然出問題。像木雕一樣去除沒有問題的代碼,留下的就是你的bug所在。

在這樣的背景下挑戰在於,視頻遊戲是很難去除某一部分的。在你刪除模擬重力或者顯示字符的代碼後,如何運行遊戲?

你必須做的是用一個假裝做真正的事情,但實際上只是做很簡單的不會出現bug事情的東西來替換掉整個模塊。你必須寫新的支撐代碼來讓這些玩意正常工作。這是一個緩慢而痛苦的過程。

長話短說:我做完了。我移除了大片大片的代碼,相當多,只留下了初始化代碼——就是準備遊戲運行系統,初始化底層硬件等等。當然,我不能顯示加載/保存菜單,因為我截除了所有的圖像代碼。但是我能夠假裝用戶使用(不可見的)加載/保存屏幕並且請求保存,然後寫入卡中。

我最終以一個帶有這個bug的很少量的代碼結束——但問題仍然隨機出現!在大多數情況下沒啥問題,但是偶爾會失效。基本上所有的Crash的實際代碼都被移除了,但還是這樣。這實在是莫名其妙:留下來的代碼基本上都沒做什麼事。

在那時——估計是凌晨3點——一個想法蹦了出來。讀寫(I/O)涉及精確定時。無論是硬盤、存儲卡、藍牙發送器——隨便啥——做讀寫的底層代碼都是根據時鐘來的。

時鐘讓不直接連接到CPU的硬件設備和cpu運行的代碼同步。時鐘決定了波特率——數據從一頭傳到另一頭的速率。如果計時有什麼問題,硬件或者軟件或者兩者都會亂七八糟的。這真的,真的很糟糕,並且通常導致數據損壞。

如果我們的初始化代碼以某種方式弄亂了計時會怎麼樣?我又看了一遍測試程序中和計時有關的代碼,並注意到我們將PS1上的可編程計時器設置到了1kHz(1000跳每秒)。這是比較快了,當PS1啟動的時候,默認狀態大概是100Hz。因此,大多數遊戲將他們的計時器設置為100Hz。

這個遊戲的帶頭(和除我外的唯一)開發者Andy,將計時器設置為1kHz,使得Crash的動作計算更加準確。Andy喜歡矯枉過正,如果我們要模擬重力,我們應該盡可能的提高精度!

然而如果提高計時器頻率莫名其妙的干擾了整個程序的計時,故而將這個計時器設置到存儲卡的波特率上會怎樣呢?

我將計時器代碼註釋掉。然後我就無法復原這個bug了。但是這並不表示bug被修復了,這個問題是隨機發生的。萬一我只是運氣好呢?

幾天過去了,我還是在玩我的測試程序。Bug沒有再出現。我回到全部的Crash代碼中,修改了加載/保存代碼,在訪問存儲卡之前將可編程計時器重置為默認設置(100Hz),之後設置回1kHz。從此之後沒有發現問題再次出現。

但是…為什麼?

我重新回到測試程序上,試著檢測當計時器設置為1kHz時出現的那些錯誤的模式。終於,我注意到這些錯誤出現在使用PS1手柄的人身上。因為我自己很少這樣做,所以我沒有注意到(為啥我要在測試加載/保存代碼的時候用手柄)。但是有一天我們的美工等我去完成測試(我確定那時候我在爆粗口),而他緊張的擺弄著手柄。卡損壞了。「等下,怎麼回事?喂,再來一次!」

一旦我發現了這兩件事是聯繫著的,就很容易重現bug:開始寫入存儲卡,動一下手柄,存儲卡損壞。在我看來完全是硬件bug。

我去找Connie告訴他我的發現。她轉述給設計過PS1的硬件工程師。她被告知:「不可能,這不可能是硬件問題。」我跟她說問一下我能不能直接和他說。

那個工程師給我打電話了,他用著他的爛英語,我用著我更爛的日語,我們爭論一會。我最後說:「我給你一個30行的測試程序,讓你在動手柄的時候能夠出現這問題。」他答應了。他向我保證,這是浪費時間,而他正在一個新項目上很忙,但因為我們是Sony很重要的開發者,他會試的。

第二天晚上(我們在洛杉磯,而他在東京,所以對於我來說是晚上而他是到了第二天),他給我打電話,不好意思的向我道歉。這是個硬件問題。

我還是沒有完全搞清楚問題到底在哪,但是我的印象中,從Sony總部的反饋聽到的是,如果將可編程計時器設置到足夠高的時鐘頻率,會影響到主板上時鐘晶振附近的一些東西。這些東西之一就是存儲卡的波特率控制器,同時也設置手柄的波特率。我不是搞硬件的,所以對於細節我相當模糊。

但是主旨是主板上兩個獨立部分的串擾,以及手柄接口和存儲卡接口數據發送的結合在1kHz的時鐘頻率下會導致丟位,從而數據丟失,以致卡損壞。

這是我全部編程生涯中,唯一一次因為量子力學debug的問題。

Retrieve from

quora.com/Programming-I

Baggett, D.

【小熊的回答(46票)】:

Bug有一個特點,就是你沒調出來之前千難萬難,你覺得自己一輩子都不會忘,可是一旦調出來了,90%的時候都會覺得,怎麼這麼簡單……然後不出一星期就全忘了……至少我現在是一個也想不起來了……

【vczh的回答(28票)】:

腳本技術 - λ-calculus(驚愕到手了歐耶,GetBlogPostIds.aspx)

這個目錄裡面大部分文章是我在做本科的畢業設計(KernelFP)的時候順手記下來的,當時正在山寨一個沒有type class的haskell。當然type class並不是多餘的,我刪去了之後做了一半發現這語言不完整很多東西沒法表達,因此加入了一個很傻逼的feature來彌補type class的缺失(Kernel FP的Expected語法實驗),因為畢業設計不想大改,反正老師也看不明白。寫這個東西的時候出現的bug是我調試得最痛苦的。

這個東西的主要目的不是為了讓程序運行起來,而是想做一個類型推導程序。這種算法跟普通的算法不一樣,因為這是圖論的算法,而且還有很多遞歸,因此運行起來就像你的函數只有那麼幾行但是callstack卻有幾千個。於是中間一個出了問題,你怎麼知道掛掉的那個地方,程序是運行到你輸入的KernelFP代碼的哪一部分的時候掛掉的?你怎麼知道是哪裡算錯了,才導致掛在了這個地方的?這是不可能知道的。

所以當初採取的一個辦法就是打log。log怎麼打呢,類型推導程序運行到哪裡我就把log打到哪裡。而且這個算法不是掃瞄一次程序就可以把結果做出來的,當你遇到互相遞歸的函數的時候,你得一直做到他們收斂為止,你才知道類型是什麼,或者程序寫錯了。於是這個類型推導程序會做大量的計算,產生大量的log。一個幾十行的程序就可以log出好幾M的推導過程。這裡是log的例子:改進Kernel FP編譯器,生成類型推導的調試信息

因此調試的時候就是這樣的,運行一下,看看結果,發現不對,我就打開這幾M的文件一行一行的看,看看哪裡出了問題,然後猜一下代碼哪裡寫錯了,然後繼續干,一直幹到我所有的test case都過了為止。

【YongHe的回答(27票)】:

高中的時候試圖用OpenGL在ATI的傻逼GPU上搞HDR渲染,結果總是全屏幕NaN。調試了一個月到現在都不知道為什麼,在nVidia上則沒有問題。現在想來可能是driver有bug(可能沒開centroid sampling導致光照中某個像素算出了nan,blur之後就污染了全部像素).

做GPU還是要珍愛生命遠離ATI。

【李刀刀的回答(16票)】:

修改本地文件卻預覽線上版本,調了一天。

【蕭文彬的回答(2票)】:

我實現了A,要跟現成的B整合在一起,然後在Benchmark上跑。結果A自己沒有任何問題,B也沒問題,但放在一起就是invalid pointer error, 用Valgrind跑出現如下結果:

invalid read of size 8, the address xxxxx is 8 bytes before the alloc'd block of 32...

作為一個C/C++完全沒入門的人,被這個bug嚇得生活不能自理,把關於A的論文又讀了一遍,把Benchmark的論文讀了一遍,把B的論文也讀了一遍,結果找了一個星期,最後發現問題是在B的[這個文件](github.com/daveboutcher)的第31行。

【知乎用戶的回答(9票)】:

放眼望去怎麼基本都是軟件的bug。。。。。。。。廣大的硬件工程師在哪裡?

來吧,我來分享一下硬件方面的bug吧。在以前的回答中,我曾經零零碎碎寫過一些,這裡先搬一點兒過來。如這個答案(怎樣讓別人明白學習或從事計算機專業的人不一定會修電腦?)裡的我的回答。

本答案會繼續更新,有空就來寫點兒:

  1. (搬過來的答案,懶得重新整理了) 不嚴謹的USB驅動撰寫者:
  • 一個英國的驅動高手(56歲的老頭),有一次客戶報bug說在一家醫院裡他們的一個USB設備就是沒法在新一代電腦上用,而且是時好時壞,沒有規律可言。客戶派了硬件軟件BIOS幾個工程師過去解都沒解出來。我請到這位高手出馬,高手拿到USB設備和電腦後,花了一天的時間做初步驗證,然後花了一個晚上寫了個小程序模擬那個USB設備的驅動的掛起/卸載,然後花了2小時運行那個程序,發現USB設備的驅動在掛起/卸載很多次以後Windows就會有一定的幾率拒絕掛載這個設備,然後他在和微軟以及那個設備的廠商聯繫,花了一天時間確定USB設備的驅動裡的一個bug,第三天,USB設備的廠商出了一個測試驅動,問題完美解決。

  • 粗心的硬件工程師
    • 某台式機機型,出廠後大約一年後,開始出現大批量的返廠。返廠的現象驚人一致:主板掛了無法開機,掛掉的是主板上的芯片組。 由於該機型芯片組損壞率遠高於其他機型,我們一開始懷疑是客戶使用不當(因為最早返回的都是某一特定大客戶的在某國工廠退回來的機器),但隨著時間的推移,全世界各地都有返廠的現象。於是目標轉向懷疑該主板設計有問題。但是,妖異的來了,從工廠抽調數百台該機型進行壓力測試(沒日沒夜跑高壓程序,進烤箱,各種設備插拔),就是沒有一台機器掛掉。前前後後折騰了一個月,大家都喪氣了。最後,好不容易,從客戶手中拿到一些壞掉的機器,我們把芯片組拆下,重新植球,檢測後發現是芯片裡有一部分電路已經損壞,多塊芯片組損壞的地方完全一致。最後,我們去檢查該主板的設計,發現芯片組的某個輸入電源,應該使用1.5V,卻使用了3.3V電壓。由於芯片組本身質量不錯,儘管設計需要1.5V但是在3.3V下仍舊可以忍辱負重工作很久才燒掉,所以這也就解釋了為什麼我們無論怎麼壓力測試都測不出來,要等機器在客戶手裡使用一年左右才會燒掉。 最後,全球召回更換主板,問題解決。
  • 奇異的環境
    • 某台式機墨西哥工廠生產線,操作系統有時會無法download(這邊普及一下知識,生產線上的操作系統不是安裝的,都是通過網絡直接把image下載到硬盤)。該問題在其他國家的產線上完全無法複製。經過多個工廠嚴格的對比檢驗(工程師飛來飛去好幾個星期),發現唯一的區別是墨西哥工廠生產線上的網絡環境使用的是hub(有點窮),而其他工廠使用的是switch(別的富裕一點)。進一步debug發現,台式機自帶的網卡的驅動,在用hub的環境下會丟包導致image download失敗,而在switch環境下就不會。通過網卡廠商驅動程序工程師debug之後,更新了驅動,問題解決。

  • 睡覺去了,待續。。。。。。。。。。。。
  • 【知乎用戶的回答(7票)】:

    說個歡樂的吧,室友學C++,第一次作業是輸出Hello world,要提交到清橙平台上。四行代碼,這姑娘調試了一晚上都沒通過...

    第二天起床我們問她de出來沒,她幾乎是哭著說:

    「我把Hello world第一個字母的大小寫搞錯了……」

    這是我印象最深的一個bug了。

    至於自己不惜熬夜也要de出來的那些,誰記得啊~

    【麥加C的回答(5票)】:

    表示當前排名第一的所說的bug對整天搞軟硬結合的小眾系統的人來說就是家常便飯。特定的硬件,特定的操作系統,特定的組件,特定的應用,出了問題沒有廠商支持,沒有社區討論,只能苦逼的調試。

    曾經遇到的一個逆天的bug是應用程序的數據,應該寫入到作為存儲使用的塊設備中,但是居然結果就是有極低的幾率把隨機數據寫入存儲系統固件的空間。

    由於問題複雜,難以調試,追溯(這時候啥都不可靠了,系統?驅動?硬件?應用?),最後只能把這個問題繞過去了事。

    還有一個是,生產出來的外設可以對整個系統從SLEEP模式轉換到普通模式的過程造成不可預知的影響。有的外設就OK,有的就不行。滋滋的電流聲簡直炫酷。

    @趙佳棟 說上述都是沒能解決的不算。那就隨便寫個解決了的。

    特定的嵌入式設備和特定的某個品牌的筆記本電腦進行數據傳輸時可能會出現莫名其妙的中斷。反覆嘗試後更改了一個容錯設置,這項設置可以讓windows在出現某種硬件錯時對傳輸進行容錯。然後,這個問題解決了。這個問題僅發生在使用自行編寫的對時序有一定要求的通信程序與特定品牌的電腦進行通信的情況下,而且這種容錯設置自我感覺是屬於比較玄妙的範疇的……

    調試這個問題簡直是噩夢。反覆的壓力測試,排查代碼,比對不同設備出現的不同現象,嘗試更改各種不同的針對硬件的設定。

    我想表達的是在特定平台,常跟硬件打交道的程序員都會遇到很多奇怪的問題。很虐心。能否解決問題往往不在於你個人有多優秀或者多努力。

    【王洪波的回答(4票)】:

    別人代碼產生的bug

    【TottiChen的回答(3票)】:

    因為不小心引起的bug永遠是最難找的,因為你總以為那地方是ok的,直到那天掃地大媽的出現...

    【劉曉翔的回答(3票)】:

    客戶說這是BUG。

    【知乎用戶的回答(3票)】:

    UTF-8 with BOM 的 html

    【Sven的回答(2票)】:

    java程序員,很久以前,剛接觸javascript的時候,寫了一個for循環

    for(int i=0; i<xxx.length; i++){xxxx}

    但是死活不起作用,那時候還不會用FF和Chrome這類的可以F12的神器,我自己調了好久好久,把身邊的同學喊來,他們也都表示吃驚,然後紛紛搖頭,結果我把教我的老師喊來了,老師也是java出身,也對這我這段代碼表示不理解,怎麼他媽的就沒有結果呢。

    也不知道過了多久,我發現了問題,我偷偷的把int 改成了var,然後為了在同學面前保全老師的顏面,我說:你看,可以了,突然可以了,是瀏覽器問題嗎?這太奇怪了。老師仍然不解,讓我把這斷代碼發給他,他自己回去研究了。

    【Jeffchen的回答(3票)】:

    正好整理了最近解決的bug,十幾個,每一個都至少花一個人天,還是因為我們有經驗. 如果沒有經驗,不可想像.....

    但是,那又有什麼意義?

    「一個bug要這麼久?一定是你們能力不行」

    【達達的回答(4票)】:

    下一個BUG

    【邰文淵的回答(1票)】:

    印象最深的bug:

    ====分割線=====

    最難的一個?像 @小熊所說的那樣,真忘了。

    個人感覺性能bug以及不必現的bug這類bug整體調試難度較大。

    標籤:-編程 -bug -程序員 -測試


    相關資源:

    
    
    

    給我留言