軟體除錯 (轉)

worldblog發表於2008-01-06
軟體除錯 (轉)[@more@]

加入不久,一位久未謀面的朋友問我在做什麼。我說修理工(我在Sybase維護PowerBuilder)。說這話時,我絲毫沒有貶低修理工的意思,相反,我從小就羨慕出色的修理工。不響的收音機,他們搗鼓搗鼓就響了;不幹活的機器,他們鼓搗鼓搗就幹活了。

 :namespace prefix = o ns = "urn:schemas--com::office" />

一名出色的除錯(deging)高手是公司的寶貴財富。有一次,有人問公司的一位高階副總裁:誰是他手下最重要的人。回答是:兩名高階工程師,因為有些錯只有他們能解決。在我看來,除錯不光是一種謀生手段,還是一種益智遊戲。所以,在查錯時,千萬不要驚慌,要靜下心來,享受破案的樂趣。

 

除錯在與維護中佔很大比重。能否儘早、儘快、高質量地除錯關係到軟體的成敗與否。一個開發高手必定是除錯高手。除錯基本上分成兩部分:找出錯誤原因和改正錯誤。怎樣才能定位錯誤原因呢?

1. 熟悉你的工具

工欲善其事,必先利其器。這句成語大家雖然耳熟能詳,但我覺得我們們中國人好象更崇尚不需要任何工具就能辦大事的人,如用一根線就能給人號脈的神醫。但能達到此種境界的究竟有幾人呢?更何況,他們如果有合適的工具,不是能幹得更快更好嗎?

 

有了好工具,還要挖掘其功能。以而言,它提供了強大的除錯功能。全面掌握其功能,將大大縮短除錯週期。

 

斷點在除錯中扮演至關重要的角色。合理的設定斷點,能讓你事半功倍。最常用的斷點是位置斷點。執行到斷點時即刻停止。這時,你可以檢查變數的值,檢視的內容等等。寄希望於看到一些蛛絲馬跡。

1.1條件斷點

有時,程式要經過某斷點很多次才能到達你所希望的現場。來回按F5鍵(Go)簡直是不勝其煩。幸好Visual Studio允許我們透過斷點對話方塊為位置斷點設定條件。只有在所設的條件成立時,該斷點才起作用。斷點的條件是一個布林,例如:a==100||(b>10&&c<1.5)。其中a, b, 和c可以是區域性變數、成員變數、也可以是全域性變數。

 

需要注意的是條件表示式中不能有,包括過載的運算子。那麼,既然不能用strcmp函式,能否讓條件斷點在某一字串變數為某一特定值時起作用呢?能。假設你有一個字串變數str,並希望在str等於“abc”時斷點起作用,你可用如下條件表示式:str[0]==’a’&&str[1]==’b’&&str[2]==’c’。

 

你還可以讓某一位置斷點跳過若干次後才起作用。假設你正在排除一個GPF。你設定了一個位置斷點,並希望檢視GPF發生之前斷點處的情況。但你不知道要按F5鍵多少次才能到達你所希望的現場。即使你知道按多少次,你也不想按那麼次。幸好,Visual Studio允許一個位置斷點在跳過若干次以後才起作用。你可先讓斷點跳過1000次,然後程式。當機後,看斷點到底跳過多少次才當機。用該次數重新設定斷點的跳躍次數。再執行程式時,程式就會在當機前的一剎那停下來。

1.2資料斷點

在某些場合下,資料斷點會成為你的救星。在查錯時,如果你發現某個資料被莫名其妙地該掉了,怎樣才能查出該資料在何時何地被改掉的呢?用資料斷點。透過斷點對話方塊,給出該資料的地址及長度。以後,在任何程式碼改變該資料的值時,程式就會馬上停下來,讓你分析該變化是否合理。

1.3 Stop always

如果你的程式利用了C++的異常處理功能,在跟蹤時,你有可能發現程式突然跑到一個很遠的catch語句去了。那是因為C++的異常處理功能起作用了。在程式正常執行時,異常處理功能是你的朋友,但在跟蹤時,可就未必了,因為你不知道程式到底在什麼地方出錯了。幸好Visual Studio允許你在跟蹤時取消異常處理功能。選擇Debug—Exceptions選單,在彈出的對話方塊中,改變器(Debugger)在某一類異常發生時所採取的行動(Action)。你可將行動設定成Stop always,只要該類異常發生時,程式馬上停下來。

 

Visual Studio還提供各種視窗,讓你檢查程式呼叫堆疊、變數、積存器和記憶體等。

1.4其它工具

除了Visual Studio,還要一些很有用的工具,如 NT操作提供的Performance Monitor、Visual Studio的附帶工具Spy++、以及提供的HandleEx、FileMon和RegMon等等。熟練掌握這些工具將對你大有裨益。

2. 熟悉你的程式

有了像樣的工具並不能使你馬上成為除錯高手。一個不懂得機器工作原理的修理工,即使有再好的工具,也很難快速並高質量地修理機器。為什麼在一些大專案中,有些錯只有某幾個人才能排除呢?主要原因是隻有他們才更全面地瞭解他們的程式。

 

要想成為真正的除錯高手,你要主動出擊,理解程式的總體結構,熟悉程式碼。怎樣才能理解程式總體結構呢?怎樣才算理解了程式總體結構呢?如果你有完備的文件,那算你幸運。如果沒有,那只有憑自己了。我個人的是畫UML圖。有道是“一張圖頂一千句話”。看一眼圖(Component Diagram),馬上就能想到整個程式有哪幾塊組成;看一眼類圖(Class Diagram),馬上就能回想起這幾個類之間的關係;看一眼時序圖(Sequence Diagram),馬上就能想起這幾個是如何互動的。畫圖的工具可以是Rational Rose,也可以是Sybase PowerDesigner,Microsoft Visio,甚至可以是。然後用一個把這些圖串起來,check in到原始檔控制系統( Control System),以便隨時補充修改。

 

沒有緊急任務時,我會藉助程式碼分析軟體,如SourceNavigator,Source Insight,或 Outline等,概略分析(而不是逐行閱讀),補充所缺的圖。有時,我也會有意識地跟蹤程式,逐個研究函式呼叫堆疊(call stack)中的每個函式,找出類之間的關係並記錄在冊。值得注意的是一定要把你的發現記錄在冊,否則幾個月後,你可能要重新來過。好記性不如爛筆頭子。

 

另外,查錯的過程也是一個很好的理解原始碼的機會。對那些有重要意義的call stack,我都會記錄下來,以便有空時研究。日積月累,你會發現你不但對總體結構有了深刻理解,而且也掌握了相當多的細節。到這時,查錯就不再是大海撈針,也不是走迷宮,因為你已經有了一張地形圖。

3. 管理的功用

熟練掌握了先進的工具,又對程式有了相當程度的理解,你已經具備了成為除錯高手的條件。而良好的軟體管理能使你事半功倍。軟體管理基本上包括:

 

l  程式碼控制系統

l  質量跟蹤系統

l  軟體釋出及存檔系統

3.1程式碼控制系統

程式碼控制系統有諸多好處:防止程式碼意外丟失、能保證程式碼同步、允許回溯到開發過程中的任一點、允許多個工程師同時修改同一個檔案等等。由於每一個修改都有記錄,能促使工程師提高程式質量,以免被別人。另外,程式碼控制系統在除錯中也有重要意義。下面將有詳述。

3.2質量跟蹤系統

Sybase有一套相當嚴格的質量跟蹤系統。質量控制工程師、技術支援工程師、甚至是開發工程師都可在質量跟蹤系統中輸入新的錯誤報告(Sybase稱之為change request)。每個錯誤報告都需有詳細的說明、嚴重程度、錯誤復現(reproduce)步驟以及相應的test case等。Test case要儘量小。

 

每個產品有一個小組,由技術支援經理負責,每週挑選出最嚴重的錯誤,並通知相應的開發小組組長。組長再把錯誤分給相應的工程師。該工程師找出錯誤原因,提出解決方案。方案複查透過後,將經修改的程式碼check in到程式碼控制系統。同時把錯誤報告轉交給質量控制小組。質量控制小組在確認錯誤被解決後,關閉該錯誤報告。

3.3軟體釋出及存檔系統

程式的每一個重要的build都要存檔,可隨時執行。

4. 一些實用技巧

查錯很重要的一環是設定斷點。斷點位置選得好能讓你更快地查出錯誤的原因。這裡有一個小技巧:如果錯誤的表現是程式彈出了一個對話方塊顯示某一出錯資訊,你可以啟動Visual Studio,attach到你的程式,然後選擇Debug—Break選單暫停程式執行。從函式呼叫堆疊上,也許會有所發現。如果程式陷入死迴圈,也可以使用這一技巧。如果錯誤的表現是當機,那就更容易找到切入點了。

 

在除錯多執行緒(multi-threading)程式時,把程式執行情況寫入檔案中(logging)是一個常用的方法。在你認為有可能出現問題的函式中,把重要資料寫入檔案中。問題出現後,仔細研究該檔案,試圖發現一些蛛絲馬跡。逐漸縮小範圍,知道找出問題之所在。

 

記憶體洩漏(memory leak)是C++程式的常見問題。檢查記憶體洩漏問題最好要有工具。通常我會先用Performance Monitor證明程式確實發生了洩漏。然後用BoundsChecker或其它工具幫忙確定記憶體洩漏的根源。

 

有時程式會發生退化(regression),即某一功能突然不工作了。如果不能快速查出原因,我們通常會採用折半查詢(binary search)的方法找出究竟是哪個change list惹的禍。方法是先找出從哪一個build開始出現問題,然後再找出是哪個change list。假設build 1000工作正常,build 1010出現了問題。我們就從程式存檔中下載build 1005,看是否有問題。如果有問題,就下載build 1003,否則下載build 1007。依此類推,直到找出那個build為止。然後從程式碼控制系統中查出這個build與上一個build之間有那些change list,再次採用折半查詢的方法。方法是把程式同步(synchronize)到某個change list,編譯並執行程式,看是否有問題。如此這般,直到找出引起問題的change list。把check in該change list的同事找來一起研究問題的原因並加以解決。

 

最另人頭痛的錯誤是那些隨機發生、很難再現的錯誤。這些錯誤通常是由於變數未賦初值,或記憶體被破壞等原因造成的。如果程式發生隨機的當機現象,你可以利用Windows提供的SetUnhandledExceptionFilter 函式設定你自己的未處理異常過濾器(unhandled exception filter)。當程式發生未被處理的異常時,Windows會自動呼叫過濾器函式。過濾器函式中,你可以把程式執行現場的情況,如函式呼叫堆疊、暫存器的值等,寫入一個檔案以便分析。如果你的釋出版本(release build)中帶有最基本的除錯資訊,你就能在函式呼叫堆疊中看到真實的函式名稱,而不是密碼一般的函式地址。如果release build中沒有任何除錯資訊,你可從編譯原始檔時生成的map檔案中查出究竟是在執行哪一行程式碼時出現了問題。所以如果你不想讓release build帶除錯資訊,你一定要把map檔案存檔,以備不日之需。

 

如果你招數用盡,仍無法查出錯誤的根源。你不妨請同事幫忙,或呼叫外部力量。俗話說:沒有過不了的火焰山。只要你不洩氣、群策群力,一定能解決問題。畢竟,是講邏輯的。

 

錯誤解決後,在大鬆一口氣之前,你最好總結一下查錯的過程及心得。如果你的程式有單元測試程式(unit test),你應該增加一些單元測試程式,防止類似情況再次發生。如果你的程式有自動整合測試,你也應加入相應的測試。

 

另外,與其被動等錯誤來找你,不如主動出擊。方法之一是進行程式碼複查(code review)。選出某個檔案,由小組成員分頭複查該檔案,然後集體討論,找出所有錯誤及有待提高的地方。程式碼複查不僅能防患於未然,還是提高水平的好招。

 

態度在除錯中也扮演著重要角色,尤其是在承受著很大的壓力時。由於這個錯誤軟體不能釋出,定單被擱置。但你一定要保持冷靜。“這肯定是的錯”,“這肯定是作業系統的錯”,“這肯定是第三方軟體的錯”等等。說這些話都是不冷靜的。你要“拿證據來”。總之,不要怨天尤人。是自己的錯,勇於承認。是別人的錯,切莫譏諷。

 

掌握了方法,又有良好的態度,何愁不能成為除錯高手?

 

5. 參考資料:

《Debugging Applications》

《MSDN》Microsoft

《UML Distilled》Martin Fowler

《Refactoring》Martin Fowler

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-996720/,如需轉載,請註明出處,否則將追究法律責任。

相關文章