微软 php Windows shell java Firefox Android 程序员 google 云计算 编程 centos Python 开源 Ubuntu apache mysql linux nginx wordpress

譯文:我是怎樣使用BoundsChecker的

在開始本文之前,我想聲明的是我曾經在NuMega工作過,並在那裏參與編寫了BoundsChecker的3、4、5版本。顯然,我個人非常推崇BoundsChecker,盡管還會有一些其它能與BoundsChecker相媲美的產品也非常值得大家的註意,比如Rational的Purify。

作為一個終日研究調試的人,我被問到最多的問題就是,我是怎麽將Compuware/NuMega BoundsChecker融入實際工作中並使用它來解決問題的。許多人都在使用或正在考慮使用BoundsChecker,但很少有人能夠最大限度的使用這個復雜的產品。在這篇文章裏,我將向大家介紹BoundsChecker中很容易被混淆的幾個部分並向大家展示我是怎樣將BoundsChecker運用的日常的開發中去的。這其中也包含了使用BoundsChecker在擴展ISAPI中進行調試,這是我遇到過的最難的部分。

如果你了解NuMega的生產線,你可能註意到他們還有另外一個產品,名字叫做SmartCheck。SmartCheck是BoundsChecker的姊妹版,用於Visual Basic的調試。這兩個產品在功能上是類似的。但因為SmartCheck理解所有VB的技術,它可以用VB的語法形式顯示事件以及分析VB語法中的的錯誤。在這篇文章中我只會使用BoundsChecker,如果你是VB的開發人員,你只需簡單的把BoundsChecker替換為SmartCheck就可以了,因為這兩個產品從本質上講是一模一樣的。一點主要的不同是,SmartCheck沒有FinalCheck技術但BoundsChecker有。

 

撥開雲霧

BoundsChecker是一個很棒的錯誤診測工具,它可以發現所有的內存破壞問題,無論它們是分配在堆、靜態存儲區或是堆棧上。在我看來,BoundsChecker最牛的地方是它能夠檢查API/COM的參數。因為Windows API已經變得很大,為了不超出負荷,在不同部分之間會有一些微妙的聯系。你可能會覺得GetVersionEx只是直接的數據傳遞,但它也會莫名的失敗。OSVERSIONINFO結構中有一個很煩人的size域,你必須在把結構傳給GetVweisonEx之前填充它。BoundsChecker可以很輕易的發現諸如此類的問題,你完全可以將時間和精力放在程序的特性上,而不是花大量的時間在這種愚蠢的問題上。如果想最大限度的了解BoundsChecker,你可以瀏覽www.numega.com上的文章。

BoundsChecker十分容易被混淆的地方是,它有兩種工作模式:Active Check 和Final Check。我認為部分問題在於,人們對這兩種模式在什麽情況下使用存在著一些錯誤的說法。Active Check不需要重新編譯也會為你找到大量的錯誤,可以以兩種方式使用Active Check:可以在開發環境中使能BoundsChecker功能執行調試,也可以在bc.EXE中直接打開編譯好的文件。FinalCheck需要重新編譯,因為它需要將BoundsChecker的指令嵌入到工程上下文中以便隨時做出精確的錯誤報告(比如到底在哪一行代碼出現了內存泄漏)而不是象Active Check那樣需要等到程序結束。有一個問題是,有些人會錯誤的認為從BoundsChecker得到任何錯誤檢查都需要重新編譯整個程序。當你看了我是怎樣選擇使用這兩種模式時,你就會知道實際情況到底是什麽樣。

我想提出的最後一個問題是BoundsChecker的性能問題。假定,通常十分鐘就可以執行完的程序用BoundsChecker卻需要三個小時,這肯定是一個大問題。但我也聽到一些開發人員說他們因為BoundsChecker需要多花幾分鐘的時間來運行整個程序所以不用BoundsChecker。如果你寧願花2周時間來跟蹤定位BoundsChecker很輕而易舉就找到的問題也不願意在運行時多花一兩分鐘的話我也沒什麽話好說。BoundsChecker能夠加入對所有參數,內存,返回值以及COM接口的跟蹤。通常時間是很寶貴的,能盡快找到程序的錯誤是最好不過的事情。如果你僅僅因為“慢”而不使用錯誤檢查工具,就會在代碼中留下大量的問題。

我喜歡用的的設置方法

BoundsChecker大量的選項可能就會讓你忙活一陣子。別著急,我給你來個簡單的。設置錯誤檢測級別為最大模式,因為我覺得正常模式會壓制一些你需要看到的錯誤。如果你從沒使用過最大模式,你會感覺比以前更多的錯誤報告彈出來。不要著急,如果你不想關心存在於第三方代碼中的錯誤的話點擊suppress就好了。設置最大模式的方法是:打開VC的IDE或者BoundsChecker獨立的外殼程序,BC.EXE,但不要加載任何工程或可執行文件。在BoundsChecker設置窗口的錯誤診測標簽下最大模式。這樣以後你所打開的任何工程或文件就都會使用最大模式了。

如果我要跟蹤一些非常混亂的內存問題,我就會選擇自定義模式。所有其它設置都是跟最大模式相同的,但我會在“Memory error checking”選項中選中“Check all heap blocks on each memory function call”。這樣一來,內存總是會被檢查到,執行雖然慢一些,但錯誤報告會定位到更接近出錯代碼的地方。

默認情況下,我不會選擇“Collect and report program event data”一項。當我想看到程序流程時我也只會打開事件報告。如果我必須看到所有事件,我就會將“Additional Event Reporting”裏面的所有選項都選中。但有一個我通常是關掉的,就是“Event Reporting”選項卡裏面的“Prompt to save program results”。因為大多時候,僅從BoundsChecker裏面看一下實時的日誌就可以了,沒必要將它們保存起來。

使用BoundsChecker

就算NuMega的市場部要槍殺我,我還是要承認我並不是每時每刻都會使用BoundsChecker。在開發新代碼或是更新現有代碼時我會遵循標準的模式:寫一小段代碼,可能是幾個函數或者是一個復雜函數的初始化部分,然後立即調試,測試這段代碼。這樣我就可以大體上評估一下程序的邏輯和流程。因為BoundsChecker不能發現這種類型(邏輯、流程――譯著)的問題,所以我會在程序的一開始就去避免它們的出現。當我完成某個功能或是寫完一段重要的代碼(比如100-200行,包括註釋)時,我就會打開BoundsChecker或者直接運行BC.EXE,使用ActiveCheck來測試我的這段代碼。我發現,在這樣的漸增的開發模式下,ActiveCheck不會發現太多的錯誤。實際上,如果你的工作正確無誤,你不會用BoundsChecker找到任何錯誤。

在開發代碼時,大約每天或者頂多每隔一天,我就會用BoundsChecker的Final Check來測試所有新代碼。對於這樣的測試版本,我會盡可能覆蓋到每句代碼。因為可以同時使用BoundsChecker和TrueCoverage,我通常會把兩個都打開,這樣我就會知道哪部分代碼還需要更多的測試(沒有覆蓋到――譯著)。

我會在把代碼提交到主代碼之前反復使用BoundsChecker來錘煉它們,這是很關鍵的。如果開發小組的每個人都這麽做的話,主代碼的質量就會大幅度提高。是的,這樣做花費在代碼測試上的功夫會超乎想象,但我認為代碼質量是每個開發者的責任,我們必須做好充分的準備去保證它們的質量。

我還想告訴大家的是Final Check的使用強度問題。如果我參與的是一個很大的程序,我通常只會在我添加了代碼的模塊中使用Final Check。沒必要將超過15個模塊的30MB代碼都提交給BoundsChecker。每周,我會將整個工程全部提交一次,來測試它的所有路徑,這樣,你就不會看到任何未知的錯誤。

我前面建議大家用最大診測模式,你可能會看到更多的錯誤彈出。為了不讓這些錯誤報告打斷程序的運行,我經常會關掉“Report Errors Immediately”。運行完之後,再從頭到尾查看錯誤信息。錯誤經常會分布在很多不同模塊中,而有一些是沒有源代碼的。比如,當我使用Intellipoint軟件時,它會調用到MSH_ZWF.DLL來處理鼠標滾輪的相關功能。當在BoundsChecker打開執行這個程序時,會彈出一些屬於DLL的錯誤。顯然,所有出自MSH_ZWF.DLL的錯誤都是我想壓制的。我可以通過下面的方法來做到這一點:在這個錯誤上右擊,彈出菜單選擇“Suppress”在彈出的對話框中選擇“Suppress this error only when it occurs in the EXE or DLL”。任何類型的錯誤,只要它是出現在沒有代碼的模塊中的,就可以首先檢查這個錯誤是否是傳遞錯誤的參數造成的,如果排除了這一點,通常就可以安全的將這個錯誤壓制掉。我最關心的錯誤其實是在我所擁有代碼中出現的錯誤,並且這些錯誤是我寫入的代碼導致的。

對於那些並非出自我代碼中的錯誤,我會認真的評估一下。如果BoundsChecker報告的是API調用失敗,我會看一下我是否恰當的檢驗了返回值,如果是這樣,我就會將錯誤壓制掉。當遇到內存泄漏的錯誤時,我會更加小心的進行檢查。有時候,當應用程序調用ExitProcess後,BoundsChecker的註入DLL,BCCORE.DLL會先於程序的其它部分退出。在這種情況下,BoundsChecker為了安全起見,會將所有內存分配都看作內存泄漏。所以當靜態類分配內存或資源時,你會經常看到內存或資源泄漏,這是因為靜態類是進程中最後被釋放的東西。這種情況下,如果你能確保會恰當的釋放內存和資源,將這些錯誤壓制也是安全的。

我是個妄想狂,我經常會把工程的錯誤壓制文件(.SUP)更名,來檢查一下我是否將真正的錯誤也失手壓制了。工程的.SUP文件和可執行文件放在同一個目錄下。如果你是一個項目小組的一員,你很可能會想將自己的.SUP文件整合到整個項目的.SUP文件中去。這個很容易,因為.SUP文件只是一個文本文件。你只要保證不要更改.SUP的開頭部分(如下面所示)然後將自己.SUP文件中以“ignore”開頭的每一行拷貝到項目.SUP文件中就可以了。但要註意,每一行都是以“;”結尾的。

//SUPPRESSIONPROJ:wordpad
//VERSION:5.00
//enable:yes
!include Mfc.sup ; NOTE!!include's lines are part of the header.

 
ignore failure USER32.DLL:ShowCaret in module RICHED20.DLL
ignore param 1 GDI32.DLL:GetDeviceCaps in module RICHED20.DLL
ignore param 1 USER32.DLL:GetSystemMetrics in module CSCUI.DLL
ignore param 1 KERNEL32.DLL:VerifyVersionInfoW in module CSCUI.DLL
ignore failure KERNEL32.DLL:VerifyVersionInfoW in module CSCUI.DLL

為了減輕我在錯誤壓制上面做的繁瑣工作,我會將沒有代碼的DLL加入到主.SUP文件中去。主.SUP文件在BoundsChecker安裝目錄的/Data目錄下,SKIP_32C.SUP是Windows 9x下使用的,SKIP_NT.SUP是NT/2000下使用的。這兩個文件裏已經包含了很多DLL,但你可能擁有一些第三方庫,而你不想看到這些庫中的錯誤報告。比如,如果我想將MSH_ZWF.DLL加進主.SUP中,我可以打開SKIP_NT.SUP,在文件的底部加入一行:

ignore everything in module MSH_ZWF.DLL
在擴展的ISAPI中使用BoundsChecker

現在每個人都準備建立下一個amazon.com,所以似乎每個人都會在自己的web站點中使用ISPAI擴展。但調試擴展ISAPI是十分困難的,而且想要使用內存診測工具更困難。幸運的是,我發現了一些小技巧,使使用內存診測工具變得非常簡單。正如你所知道的,可以以提示符模式運行IIS(MSDN有一篇文章“TN063: Debuging Internet Extension DLLs”)。這使我們的調試變得相對比較簡單,但想使內存診測工具能正確報告內存泄漏還需要一些其它的設置。

我的技巧就是,在擴展DLL中加入個特殊的命令,比如“killIIS”,這個命令可以在瀏覽器中調用。命令響應函數所作的僅僅是調用ExitProcess。這樣就可以在VC++中使用BoundsChecker得到錯誤信息了。盡管以命令提示符模式執行IIS還存在一些問題,但如果能徹底的進行錯誤檢查,這樣做也是值得的。

小結

這篇文章中,我只是粗淺的介紹了一下BoundsChecker的強大功能。還有一個地方我沒有介紹到,就是你可以在BoundsChecker加入自己的函數參數檢查策略,當你的程序由於調用了某些API而不能在其它版本的Windows上使用時BoundsChecker就會報告這個錯誤。我希望大家從這篇文章中得到了一些使用BoundsChecker的好的思路,並幫助你們加快程序調試的速度。以後的專欄中,我會簡單介紹一下逆向工程技術,所以請大家想一想BoundsChecker在逆向工程的用處。

延伸阅读

    评论