程式設計師調過的那些奇葩 Bug

一隻會飛的喵丶發表於2016-10-03

【導讀】:程式設計師工作中有兩件事,一是寫 Bug,另外就是調 Bug。 在 Quora 上有一個和 Bug 相關的熱門問答帖:《What’s the hardest bug you’ve debugged? | 你除錯過的最難 Bug 是?》,很多程式設計師參與分享自己經歷。伯樂線上曾經從中摘編過的《一次因量子力學而 Debug 的痛苦經歷》和《我遇過最難調的 Bug,最終發現是 CPU 的問題》,在今天這篇文章中,再次分享其他 4 位程式設計師的故事。

Jayesh Lalwani 的回覆,5700+ 頂

在 2000 年的時候,專案組負責有關 JPEG 檔案格式的工作,被稱為 JPEG 小組,當時決定推出一個叫做 JPEG2000 的新版本。新版本有著很多很棒的想法,其中有一點是它支援流媒體影象。一張 JP2 影象可以包含多種解析度,並且按照解析度由低到高排列。所以,在你下載圖片的時候,可以很快速的獲取低解析度的圖片。這種特性的好處是,在網路狀況很不好的情況下,瀏覽器可以迅速優先載入低解析度的影象。同時,低解析度的裝置在匹配到適合它解析度的影象後就會停止下載。

當時,JPEG2000 希望 JP2 影象標準可以被加入到瀏覽器中(劇透一下:很可惜,並沒有)。我們想要用它來構建地圖類的應用。JP2影象可以編碼衛星圖片,並且我們專門返回SVG地圖的伺服器。由於沒有瀏覽器支援JP2,我建立了一個ActiveX控制元件,用來將JP2影象以流的形式轉化為SVG地圖。這真是太酷了,(在當時)我們的影象解析度可是Google地圖的十倍。

我用的是一個叫做 Kakadu 的第三方庫。這是一個能夠解析和將JP2 影象轉化為流的開源庫。Kakadu 的表現真的非常非常棒! 它非常快!除了在使用一段時間後它會無徵兆地卡住。這引起了我的注意,並且我懷疑問題產生的原因是用於存線上程競爭。所以,我決定通過 debug 來解決這個問題。在當時,我很年輕,而且對多執行緒的理解還算不錯,但我還沒有真正解決過一個多執行緒問題,是的,對此我很興奮。

所以我通過深入它們的原始碼來開始除錯。我記得在一開始開啟debug進行除錯的時候,問題竟然消失了。該死!事實上,偵錯程式本身作為一個同步機制,它改變了執行緒中指令的執行時間。

於是我開始新增log日誌輸出來觀察一下。在我新增log之後,問題竟然又一次消失了,我的天!由於日誌資訊記錄到檔案系統,檔案系統作為一個同步機制再一次改變了指令的執行時間。

除錯不行,新增log日誌也不行。我還沒有告訴你,這是一個多麼複雜的大工程,所以,在我解決這個問題之前,我需要弄清楚如何在多執行緒環境中解決問題,該死!

於是,我開始思考,這會是由於同步機制以外的程式碼而導致程式碼的執行時間被修改麼?所以說,只要我保持內外部程式碼一致同步,那我就有可能阻止執行時間被修改。於是,我開始儘量的減少log日誌輸出。最後,我懂了,如果我輸出一個字元的log日誌就不會出錯,也就是說,log日誌最多不能超過一個字元。

所以,第一件我需要弄清楚的事是,問題的發生與否是不是由於執行了不同的程式碼所導致的。要記住,最多我只能輸出單個字元的log日誌。於是,我開始深入分析每一段程式碼,而不是僅僅理解它的作用。每當我到達一個條件判斷語句時,我會在兩條分支上分別輸出日誌資訊。其中一條分支輸出“”,另一條分支輸出“/”。當我看到一個迴圈語句時,我在迴圈體內輸出“|”。當我看到輸出的日誌時,它是這個樣子的

1. |||//|||||||||//

我會在執行程式的時候,分別記錄下程式碼執行成功與失敗兩種情況下的字串資訊。下一步,比較兩個字串,找出不同的地方。最後,我會根據字串資訊中不同的地方,對應到程式碼中去進一步分析。

因為我不能輸出過多的日誌資訊,所以我不得不仔細分析。幸運的是,Kakadu的程式碼結構非常好。我真想親一口Kakadu的開發者(雖然他們製造了這個bug)。所有的程式碼層次結構都很清晰。它們通過高階方法來呼叫底層的方法。所以,我選擇在高階方法中新增單字元log資訊。當我找到差異時,通過程式碼我可以知道產生差異的原因。在大多數情況下,差異都是由於底層呼叫的方法不同而產生的。於是,我刪除掉原有的log日誌,然後在底層的方法中新增相同的log日誌來觀察,就這樣,一層又一層,直到我找到產生bug的原因所在。

這整個過程花了大約三個星期。最後通過修改了一個字元解決了問題。由於渲染執行緒在等待生產者執行緒載入的資料,而形成一個迴圈等待,造成了卡死的問題。計數器在判斷資料時,使用的是<而不是<=。通常,計數器會從預期的情況中,得到想要的資料,並且能夠正常執行。在極少數情況下,當=條件滿足時,渲染執行緒會過早的解析資料,造成異常,並跳出執行語句。從而導致渲染執行緒中斷執行。

三個星期,一個字元,我應該得到一件T恤上面寫著:

感謝你為解決這個bug做出的重大貢獻。

Fredrik Zettergren 的回覆,5500+ 頂

雖然解決這個問題並沒有花太長時間,但它絕對是我所除錯過的最怪異的一個bug。

當時在學校學習電氣工程,我們有一節關於嵌入式系統的課。我和其他兩個同學真的很喜歡這門課,並且我們決定做一個自動化的遙控直升機。我們通過加速計為直升機連線了MCU(微控制器),以此來控制伺服系統。在3個月的課程中,我們將全部的精力都投入到這個專案裡,特別這也是我們第一次接觸嵌入式系統。這個過程很有趣,我們也很努力,所以一切都朝著好的方向發展。

一天傍晚,我們差不多完成了直升機各個部分的工作,並且準備組裝到一起開始做幾次試飛。在試飛後,我們發現了一個問題,就是當系統啟動的時候,伺服系統有時不會立即運轉。我們檢查了所有的程式碼,一層一層分析,移除了多餘的元件,但還是無法解決這個問題。

經過一個漫長的夜晚的除錯之後,能想到的可能出問題的地方都已經檢查遍了,我們真的不知道該怎麼辦了。其中一個成員實在是太累了,他靠在椅背上,把鞋子放在桌子上,閉上眼睛準備休息一會。忽然,這個bug就不見了。由於太累了,並且對這個bug沒有任何主意,我們開玩笑說,他的鞋子也許真是解決這個bug的神奇良方。奇怪的是,當把他的鞋子放在桌子上的時候,bug就消失了,鞋子放下去bug就出現了。過了一會兒,我們開始思考導致這個現象的原因。哭笑不得的是,我們大概做了10-20次把腳放到桌子上和放下來的動作,每當他把腳放下來的時候,bug就會出現。我想這可能是我人生中最令人困惑的時刻了吧,至少涉及到技術方面是。

我們突然想到了這之間的共同點!原來我們忘記了,直升機各個元件之間的連線是極其不穩定的。當我的組員把他的腳放到桌子上的時候,他的腳使地面與桌子上系統之間的元件建立了連線。雖然這方面的連線可能是極其微弱的,但這足以使元件間的通訊變得更穩定。當我們意識到問題產生的原因所在,並且增加了缺少的電線,直升機終於可以完美執行了。(問題的關鍵就是在這個地方)

我們很幸運,偶然的發現了問題的關鍵所在,並很快解決了問題,所以,比起痛苦得到更多的是快樂的回憶。

Amir Memon 的回覆,2500+ 頂

幾年前,Mozilla 和 Microsoft 報告給我們 Flash Player 存在崩潰的問題。我們沒有人能重現這個崩潰,我們只能從崩潰日誌中知道哪裡發生了崩潰,但是這沒有任何意義。實際上,雖然崩潰日誌中所指向不同行的程式碼,但這都是由於同一個bug引起的(我們後來才知道)。

最後,我們團隊中有一個很棒的質量工程師,他找到了一個發生過崩潰的裝置,並且制定出一個可靠的崩潰重現步驟。發現崩潰產生的原因是由於使用了低速硬碟。

當一個視訊正在被釋放的時候,在Flash Player的釋放序列中會發生崩潰(比如說當你瀏覽另一個網頁的時候)。視訊流檔案不會被馬上清除,並且暴露了執行緒同步的問題。

這個bug解決如此之難的原因是,很難找到一個系統可以去重現這個bug,並且在崩潰發生的地方存在著另人討厭的多執行緒問題。

最終,我解決了這個bug,它在之前是很常見的。在瀏覽器隨著外掛發生異常的時候,它可能阻止了數以萬次的崩潰發生。

感覺我們就像英雄一樣 :-) 。

Stan Hanks,1800+ 頂

80年代中期,我在一家醫療裝置公司做顧問工作,公司的主要研究方向是新一代正電子發射斷層掃描器。悄悄地告訴你,這個專案可是要和市場上那個有著不為人知的新技術的大傢伙正面對抗的。

我主要研究嵌入式系統下的UNIX實時處理。我曾經做過無數次關於其他型別的控制系統的開發專案,這是我所做過的第三個有關醫療裝置的專案。

我是這樣考慮的,先畫草圖,再做產品原型,然後在三天內快速完成程式碼部分的編寫。點選“安裝”,看著它編譯,生成,構造一個下載包,然後下載到裝置上,重新啟動。

就這樣實施,很完美!

但是我不能這樣做…

事實上,第一次執行的時候你永遠不會寫程式碼,它只是一個存根啟動除錯,每個人都知道。

於是,我把它分成了幾個部分,其中在bug除錯和邏輯分析上,花費了我近一個月的時間。

但這可是我們必須要克服的事情

我和專案經理都知道這很難,甚至我們組內的一個同事提供了第二種方案來嘗試。

是的,在6周後,我們克服了種種磨難。最終取得了成功,正如預期的那樣,首次執行的時候,它能夠正常工作,非常完美。

並且從那以後,這個專案一直由我負責。

相關文章