譯文:我是怎樣使用BoundsChecker的
轉(zhuǎn)載自:http://blog.csdn.net/BBirdlyh/article/details/1935894
我是怎樣使用BoundsChecker的
我是怎樣使用BoundsChecker的
John Robbins How I use BoundsChecker
在開始本文之前,我想聲明的是我曾經(jīng)在NuMega工作過,并在那里參與編寫了BoundsChecker的3、4、5版本。顯然,我個(gè)人非常推崇BoundsChecker,盡管還會(huì)有一些其它能與BoundsChecker相媲美的產(chǎn)品也非常值得大家的注意,比如Rational的Purify。
作為一個(gè)終日研究調(diào)試的人,我被問到最多的問題就是,我是怎么將Compuware/NuMega BoundsChecker融入實(shí)際工作中并使用它來解決問題的。許多人都在使用或正在考慮使用BoundsChecker,但很少有人能夠最大限度的使用這個(gè)復(fù)雜的產(chǎn)品。在這篇文章里,我將向大家介紹BoundsChecker中很容易被混淆的幾個(gè)部分并向大家展示我是怎樣將BoundsChecker運(yùn)用的日常的開發(fā)中去的。這其中也包含了使用BoundsChecker在擴(kuò)展ISAPI中進(jìn)行調(diào)試,這是我遇到過的最難的部分。
如果你了解NuMega的生產(chǎn)線,你可能注意到他們還有另外一個(gè)產(chǎn)品,名字叫做SmartCheck。SmartCheck是BoundsChecker的姊妹版,用于Visual Basic的調(diào)試。這兩個(gè)產(chǎn)品在功能上是類似的。但因?yàn)镾martCheck理解所有VB的技術(shù),它可以用VB的語法形式顯示事件以及分析VB語法中的的錯(cuò)誤。在這篇文章中我只會(huì)使用BoundsChecker,如果你是VB的開發(fā)人員,你只需簡(jiǎn)單的把BoundsChecker替換為SmartCheck就可以了,因?yàn)檫@兩個(gè)產(chǎn)品從本質(zhì)上講是一模一樣的。一點(diǎn)主要的不同是,SmartCheck沒有FinalCheck技術(shù)但BoundsChecker有。
撥開云霧
BoundsChecker是一個(gè)很棒的錯(cuò)誤診測(cè)工具,它可以發(fā)現(xiàn)所有的內(nèi)存破壞問題,無論它們是分配在堆、靜態(tài)存儲(chǔ)區(qū)或是堆棧上。在我看來,BoundsChecker最牛的地方是它能夠檢查API/COM的參數(shù)。因?yàn)閃indows API已經(jīng)變得很大,為了不超出負(fù)荷,在不同部分之間會(huì)有一些微妙的聯(lián)系。你可能會(huì)覺得GetVersionEx只是直接的數(shù)據(jù)傳遞,但它也會(huì)莫名的失敗。OSVERSIONINFO結(jié)構(gòu)中有一個(gè)很煩人的size域,你必須在把結(jié)構(gòu)傳給GetVweisonEx之前填充它。BoundsChecker可以很輕易的發(fā)現(xiàn)諸如此類的問題,你完全可以將時(shí)間和精力放在程序的特性上,而不是花大量的時(shí)間在這種愚蠢的問題上。如果想最大限度的了解BoundsChecker,你可以瀏覽www.numega.com上的文章。
BoundsChecker十分容易被混淆的地方是,它有兩種工作模式:Active Check 和Final Check。我認(rèn)為部分問題在于,人們對(duì)這兩種模式在什么情況下使用存在著一些錯(cuò)誤的說法。Active Check不需要重新編譯也會(huì)為你找到大量的錯(cuò)誤,可以以兩種方式使用Active Check:可以在開發(fā)環(huán)境中使能BoundsChecker功能執(zhí)行調(diào)試,也可以在BC.EXE中直接打開編譯好的文件。FinalCheck需要重新編譯,因?yàn)樗枰獙oundsChecker的指令嵌入到工程上下文中以便隨時(shí)做出精確的錯(cuò)誤報(bào)告(比如到底在哪一行代碼出現(xiàn)了內(nèi)存泄漏)而不是象Active Check那樣需要等到程序結(jié)束。有一個(gè)問題是,有些人會(huì)錯(cuò)誤的認(rèn)為從BoundsChecker得到任何錯(cuò)誤檢查都需要重新編譯整個(gè)程序。當(dāng)你看了我是怎樣選擇使用這兩種模式時(shí),你就會(huì)知道實(shí)際情況到底是什么樣。
我想提出的最后一個(gè)問題是BoundsChecker的性能問題。假定,通常十分鐘就可以執(zhí)行完的程序用BoundsChecker卻需要三個(gè)小時(shí),這肯定是一個(gè)大問題。但我也聽到一些開發(fā)人員說他們因?yàn)锽oundsChecker需要多花幾分鐘的時(shí)間來運(yùn)行整個(gè)程序所以不用BoundsChecker。如果你寧愿花2周時(shí)間來跟蹤定位BoundsChecker很輕而易舉就找到的問題也不愿意在運(yùn)行時(shí)多花一兩分鐘的話我也沒什么話好說。BoundsChecker能夠加入對(duì)所有參數(shù),內(nèi)存,返回值以及COM接口的跟蹤。通常時(shí)間是很寶貴的,能盡快找到程序的錯(cuò)誤是最好不過的事情。如果你僅僅因?yàn)?#8220;慢”而不使用錯(cuò)誤檢查工具,就會(huì)在代碼中留下大量的問題。
我喜歡用的的設(shè)置方法
BoundsChecker大量的選項(xiàng)可能就會(huì)讓你忙活一陣子。別著急,我給你來個(gè)簡(jiǎn)單的。設(shè)置錯(cuò)誤檢測(cè)級(jí)別為最大模式,因?yàn)槲矣X得正常模式會(huì)壓制一些你需要看到的錯(cuò)誤。如果你從沒使用過最大模式,你會(huì)感覺比以前更多的錯(cuò)誤報(bào)告彈出來。不要著急,如果你不想關(guān)心存在于第三方代碼中的錯(cuò)誤的話點(diǎn)擊suppress就好了。設(shè)置最大模式的方法是:打開VC的IDE或者BoundsChecker獨(dú)立的外殼程序,BC.EXE,但不要加載任何工程或可執(zhí)行文件。在BoundsChecker設(shè)置窗口的錯(cuò)誤診測(cè)標(biāo)簽下最大模式。這樣以后你所打開的任何工程或文件就都會(huì)使用最大模式了。
如果我要跟蹤一些非常混亂的內(nèi)存問題,我就會(huì)選擇自定義模式。所有其它設(shè)置都是跟最大模式相同的,但我會(huì)在“Memory error checking”選項(xiàng)中選中“Check all heap blocks on each memory function call”。這樣一來,內(nèi)存總是會(huì)被檢查到,執(zhí)行雖然慢一些,但錯(cuò)誤報(bào)告會(huì)定位到更接近出錯(cuò)代碼的地方。
默認(rèn)情況下,我不會(huì)選擇“Collect and report program event data”一項(xiàng)。當(dāng)我想看到程序流程時(shí)我也只會(huì)打開事件報(bào)告。如果我必須看到所有事件,我就會(huì)將“Additional Event Reporting”里面的所有選項(xiàng)都選中。但有一個(gè)我通常是關(guān)掉的,就是“Event Reporting”選項(xiàng)卡里面的“Prompt to save program results”。因?yàn)榇蠖鄷r(shí)候,僅從BoundsChecker里面看一下實(shí)時(shí)的日志就可以了,沒必要將它們保存起來。
使用BoundsChecker
就算NuMega的市場(chǎng)部要槍殺我,我還是要承認(rèn)我并不是每時(shí)每刻都會(huì)使用BoundsChecker。在開發(fā)新代碼或是更新現(xiàn)有代碼時(shí)我會(huì)遵循標(biāo)準(zhǔn)的模式:寫一小段代碼,可能是幾個(gè)函數(shù)或者是一個(gè)復(fù)雜函數(shù)的初始化部分,然后立即調(diào)試,測(cè)試這段代碼。這樣我就可以大體上評(píng)估一下程序的邏輯和流程。因?yàn)锽oundsChecker不能發(fā)現(xiàn)這種類型(邏輯、流程――譯著)的問題,所以我會(huì)在程序的一開始就去避免它們的出現(xiàn)。當(dāng)我完成某個(gè)功能或是寫完一段重要的代碼(比如100-200行,包括注釋)時(shí),我就會(huì)打開BoundsChecker或者直接運(yùn)行BC.EXE,使用ActiveCheck來測(cè)試我的這段代碼。我發(fā)現(xiàn),在這樣的漸增的開發(fā)模式下,ActiveCheck不會(huì)發(fā)現(xiàn)太多的錯(cuò)誤。實(shí)際上,如果你的工作正確無誤,你不會(huì)用BoundsChecker找到任何錯(cuò)誤。
在開發(fā)代碼時(shí),大約每天或者頂多每隔一天,我就會(huì)用BoundsChecker的Final Check來測(cè)試所有新代碼。對(duì)于這樣的測(cè)試版本,我會(huì)盡可能覆蓋到每句代碼。因?yàn)榭梢酝瑫r(shí)使用BoundsChecker和TrueCoverage,我通常會(huì)把兩個(gè)都打開,這樣我就會(huì)知道哪部分代碼還需要更多的測(cè)試(沒有覆蓋到――譯著)。
我會(huì)在把代碼提交到主代碼之前反復(fù)使用BoundsChecker來錘煉它們,這是很關(guān)鍵的。如果開發(fā)小組的每個(gè)人都這么做的話,主代碼的質(zhì)量就會(huì)大幅度提高。是的,這樣做花費(fèi)在代碼測(cè)試上的功夫會(huì)超乎想象,但我認(rèn)為代碼質(zhì)量是每個(gè)開發(fā)者的責(zé)任,我們必須做好充分的準(zhǔn)備去保證它們的質(zhì)量。
我還想告訴大家的是Final Check的使用強(qiáng)度問題。如果我參與的是一個(gè)很大的程序,我通常只會(huì)在我添加了代碼的模塊中使用Final Check。沒必要將超過15個(gè)模塊的30MB代碼都提交給BoundsChecker。每周,我會(huì)將整個(gè)工程全部提交一次,來測(cè)試它的所有路徑,這樣,你就不會(huì)看到任何未知的錯(cuò)誤。
我前面建議大家用最大診測(cè)模式,你可能會(huì)看到更多的錯(cuò)誤彈出。為了不讓這些錯(cuò)誤報(bào)告打斷程序的運(yùn)行,我經(jīng)常會(huì)關(guān)掉“Report Errors Immediately”。運(yùn)行完之后,再從頭到尾查看錯(cuò)誤信息。錯(cuò)誤經(jīng)常會(huì)分布在很多不同模塊中,而有一些是沒有源代碼的。比如,當(dāng)我使用Intellipoint軟件時(shí),它會(huì)調(diào)用到MSH_ZWF.DLL來處理鼠標(biāo)滾輪的相關(guān)功能。當(dāng)在BoundsChecker打開執(zhí)行這個(gè)程序時(shí),會(huì)彈出一些屬于DLL的錯(cuò)誤。顯然,所有出自MSH_ZWF.DLL的錯(cuò)誤都是我想壓制的。我可以通過下面的方法來做到這一點(diǎn):在這個(gè)錯(cuò)誤上右擊,彈出菜單選擇“Suppress”在彈出的對(duì)話框中選擇“Suppress this error only when it occurs in the EXE or DLL”。任何類型的錯(cuò)誤,只要它是出現(xiàn)在沒有代碼的模塊中的,就可以首先檢查這個(gè)錯(cuò)誤是否是傳遞錯(cuò)誤的參數(shù)造成的,如果排除了這一點(diǎn),通常就可以安全的將這個(gè)錯(cuò)誤壓制掉。我最關(guān)心的錯(cuò)誤其實(shí)是在我所擁有代碼中出現(xiàn)的錯(cuò)誤,并且這些錯(cuò)誤是我寫入的代碼導(dǎo)致的。
對(duì)于那些并非出自我代碼中的錯(cuò)誤,我會(huì)認(rèn)真的評(píng)估一下。如果BoundsChecker報(bào)告的是API調(diào)用失敗,我會(huì)看一下我是否恰當(dāng)?shù)臋z驗(yàn)了返回值,如果是這樣,我就會(huì)將錯(cuò)誤壓制掉。當(dāng)遇到內(nèi)存泄漏的錯(cuò)誤時(shí),我會(huì)更加小心的進(jìn)行檢查。有時(shí)候,當(dāng)應(yīng)用程序調(diào)用ExitProcess后,BoundsChecker的注入DLL,BCCORE.DLL會(huì)先于程序的其它部分退出。在這種情況下,BoundsChecker為了安全起見,會(huì)將所有內(nèi)存分配都看作內(nèi)存泄漏。所以當(dāng)靜態(tài)類分配內(nèi)存或資源時(shí),你會(huì)經(jīng)常看到內(nèi)存或資源泄漏,這是因?yàn)殪o態(tài)類是進(jìn)程中最后被釋放的東西。這種情況下,如果你能確保會(huì)恰當(dāng)?shù)尼尫艃?nèi)存和資源,將這些錯(cuò)誤壓制也是安全的。
我是個(gè)妄想狂,我經(jīng)常會(huì)把工程的錯(cuò)誤壓制文件(.SUP)更名,來檢查一下我是否將真正的錯(cuò)誤也失手壓制了。工程的.SUP文件和可執(zhí)行文件放在同一個(gè)目錄下。如果你是一個(gè)項(xiàng)目小組的一員,你很可能會(huì)想將自己的.SUP文件整合到整個(gè)項(xiàng)目的.SUP文件中去。這個(gè)很容易,因?yàn)?SUP文件只是一個(gè)文本文件。你只要保證不要更改.SUP的開頭部分(如下面所示)然后將自己.SUP文件中以“ignore”開頭的每一行拷貝到項(xiàng)目.SUP文件中就可以了。但要注意,每一行都是以“;”結(jié)尾的。
//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.DLLignore param 1 GDI32.DLL:GetDeviceCaps in module RICHED20.DLLignore param 1 USER32.DLL:GetSystemMetrics in module CSCUI.DLLignore param 1 KERNEL32.DLL:VerifyVersionInfoW in module CSCUI.DLLignore failure KERNEL32.DLL:VerifyVersionInfoW in module CSCUI.DLL為了減輕我在錯(cuò)誤壓制上面做的繁瑣工作,我會(huì)將沒有代碼的DLL加入到主.SUP文件中去。主.SUP文件在BoundsChecker安裝目錄的/Data目錄下,SKIP_32C.SUP是Windows 9x下使用的,SKIP_NT.SUP是NT/2000下使用的。這兩個(gè)文件里已經(jīng)包含了很多DLL,但你可能擁有一些第三方庫,而你不想看到這些庫中的錯(cuò)誤報(bào)告。比如,如果我想將MSH_ZWF.DLL加進(jìn)主.SUP中,我可以打開SKIP_NT.SUP,在文件的底部加入一行:
ignore everything in module MSH_ZWF.DLL
在擴(kuò)展的ISAPI中使用BoundsChecker
現(xiàn)在每個(gè)人都準(zhǔn)備建立下一個(gè)amazon.com,所以似乎每個(gè)人都會(huì)在自己的web站點(diǎn)中使用ISPAI擴(kuò)展。但調(diào)試擴(kuò)展ISAPI是十分困難的,而且想要使用內(nèi)存診測(cè)工具更困難。幸運(yùn)的是,我發(fā)現(xiàn)了一些小技巧,使使用內(nèi)存診測(cè)工具變得非常簡(jiǎn)單。正如你所知道的,可以以提示符模式運(yùn)行IIS(MSDN有一篇文章“TN063: Debuging Internet Extension DLLs”)。這使我們的調(diào)試變得相對(duì)比較簡(jiǎn)單,但想使內(nèi)存診測(cè)工具能正確報(bào)告內(nèi)存泄漏還需要一些其它的設(shè)置。
我的技巧就是,在擴(kuò)展DLL中加入個(gè)特殊的命令,比如“killIIS”,這個(gè)命令可以在瀏覽器中調(diào)用。命令響應(yīng)函數(shù)所作的僅僅是調(diào)用ExitProcess。這樣就可以在VC++中使用BoundsChecker得到錯(cuò)誤信息了。盡管以命令提示符模式執(zhí)行IIS還存在一些問題,但如果能徹底的進(jìn)行錯(cuò)誤檢查,這樣做也是值得的。
小結(jié)
這篇文章中,我只是粗淺的介紹了一下BoundsChecker的強(qiáng)大功能。還有一個(gè)地方我沒有介紹到,就是你可以在BoundsChecker加入自己的函數(shù)參數(shù)檢查策略,當(dāng)你的程序由于調(diào)用了某些API而不能在其它版本的Windows上使用時(shí)BoundsChecker就會(huì)報(bào)告這個(gè)錯(cuò)誤。我希望大家從這篇文章中得到了一些使用BoundsChecker的好的思路,并幫助你們加快程序調(diào)試的速度。以后的專欄中,我會(huì)簡(jiǎn)單介紹一下逆向工程技術(shù),所以請(qǐng)大家想一想BoundsChecker在逆向工程的用處。
posted on 2014-04-24 23:11 楊粼波 閱讀(1112) 評(píng)論(0) 編輯 收藏 引用

