<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          關(guān) 閉

          新聞中心

          EEPW首頁(yè) > 安全與國(guó)防 > 軟件故障的克星:斷言調(diào)試

          軟件故障的克星:斷言調(diào)試

          ——
          作者: 時(shí)間:2007-01-19 來(lái)源: 收藏

          斷言(Assertive debugging)是利用自帶代碼對(duì)程序進(jìn)行監(jiān)控并能確保嵌入式系統(tǒng)性能的新型方法。

          是一門(mén)有待進(jìn)一步深入研究的“藝術(shù)”……最有效的調(diào)試技術(shù)是那些在程序本身基礎(chǔ)上設(shè)計(jì)并構(gòu)建的技術(shù)。現(xiàn)在,許多最優(yōu)秀程序員都利用近一半的程序?qū)α硪话氤绦蜻M(jìn)行調(diào)試;而用于調(diào)試的這一半程序最終將完全被摒棄。出人意料的是,這最終竟也能提高生產(chǎn)效率。 —節(jié)選自Donald Knuth的《計(jì)算機(jī)編程藝術(shù)(The Art of Computer Programming)》。

          正如Don Knuth所述,調(diào)試經(jīng)常被我們嚴(yán)重忽視,而我們也因此付出了慘重的代價(jià)。近半個(gè)世紀(jì)以來(lái),我們?cè)谡{(diào)試領(lǐng)域取得的成就微乎其微,結(jié)果因程序而陷入困境的項(xiàng)目比比皆是。因調(diào)試而浪費(fèi)的時(shí)間和資源,在商業(yè)項(xiàng)目中,成本可能高達(dá)數(shù)十億美元;在軍事項(xiàng)目中,損失的不僅是金錢(qián),甚至包括生命。這種現(xiàn)狀簡(jiǎn)直讓人無(wú)法忍受:我們必須探尋新的方法和途徑。本文就提出了這樣一種新方法。

          本文提出的新型調(diào)試系統(tǒng)“斷言調(diào)試系統(tǒng)(ADS)”可以將調(diào)試由次要的“藝術(shù)形式”提升為現(xiàn)代工業(yè)流程。ADS雖然利用了既有的思想,即John von Neumann于1947年首先提出的“斷言”理論,但據(jù)我所知,ADS處理斷言的方式卻是Neumann或任何其他人從未提出的新方式:ADS更系統(tǒng)也更徹底地利用斷言,而不像其他工具只在程序員想到的時(shí)候才使用。為此,ADS將斷言由半個(gè)世紀(jì)以來(lái)一直漂浮不定且鮮有建樹(shù)的理論轉(zhuǎn)化為足以引領(lǐng)程序開(kāi)發(fā)革命的技術(shù)。與Knuth的論述不同,ADS并不摒棄那部分用于調(diào)試的程序,而是將其作為程序主體補(bǔ)充的文檔進(jìn)行保存,這樣,當(dāng)程序需要修改時(shí)完全可以加以復(fù)用。

          程序是主要的瓶頸

          現(xiàn)在幾乎不可能找到完全不需要編程運(yùn)算的科學(xué)或工程項(xiàng)目,同樣地,也很難找到不因程序原因而無(wú)法預(yù)期交付的。調(diào)試問(wèn)題對(duì)幾乎所有的項(xiàng)目都至關(guān)重要,而因軟件程序故障帶來(lái)教訓(xùn)也足夠深刻:當(dāng)客戶(hù)對(duì)產(chǎn)品不滿(mǎn)意時(shí),我們會(huì)喪失業(yè)務(wù);當(dāng)產(chǎn)品遲遲無(wú)法推向市場(chǎng)時(shí),我們的銷(xiāo)售額會(huì)下降。隨著我們?cè)陉P(guān)鍵應(yīng)用中越來(lái)越頻繁地使用計(jì)算機(jī),我們的教訓(xùn)也越來(lái)越慘重,這不僅關(guān)乎任務(wù)完成,甚至性命攸關(guān)。

          在這些關(guān)鍵應(yīng)用中,慎重地選擇調(diào)試方法并加以證明變得越來(lái)越重要,甚至成為法律需要。對(duì)于那些高度依賴(lài)調(diào)試的應(yīng)用而言,一半程序完全用于調(diào)試已日益無(wú)法忍受。ADS方法在編程的同時(shí)就能直接觸及這些問(wèn)題:這有助于開(kāi)發(fā)人員縮短調(diào)試進(jìn)程并支持軟件對(duì)象的系統(tǒng)級(jí)和存檔級(jí)調(diào)試。本文極力主張采用該方法,這或許有助于防止陷入困境。

          調(diào)試的現(xiàn)狀

          調(diào)試發(fā)展歷程中最值得關(guān)注的是,現(xiàn)代調(diào)試技術(shù)居然與半個(gè)世紀(jì)前剛剛進(jìn)入現(xiàn)代計(jì)算時(shí)代時(shí)沒(méi)有太大區(qū)別。我們?nèi)匀蛔尮收铣绦蜻\(yùn)行至預(yù)測(cè)的關(guān)鍵點(diǎn),然后停止運(yùn)行并查看關(guān)鍵變量的狀態(tài)。只要其中一個(gè)關(guān)鍵變量的值與預(yù)期的不同,我們就會(huì)努力分析為什么會(huì)造成這種結(jié)果。如果不知道什么地方出錯(cuò),我們會(huì)重復(fù)執(zhí)行這個(gè)過(guò)程,在程序更早的地方停下來(lái)。經(jīng)過(guò)若干次反復(fù),我們就能在充分接近程序故障的地方停下來(lái),結(jié)果發(fā)現(xiàn),故障的原因是:我們忘了重置某個(gè)計(jì)數(shù)器或清空某段內(nèi)存、從而使某些數(shù)據(jù)結(jié)構(gòu)產(chǎn)生溢出或犯了其他6種經(jīng)典編程錯(cuò)誤中的任意一種。

          這就是上個(gè)世紀(jì)50年代中葉的軟件調(diào)試方法,至今仍在沿用。如果時(shí)間允許且客戶(hù)足夠耐心,那么該方法仍將繼續(xù)沿用下去,直至最終找到困擾的程序故障。但這種方法的缺點(diǎn)也很明顯:通常只適用于一些特定情形,調(diào)試需要的時(shí)間也無(wú)法預(yù)知,而且并不能幫助程序員更好地理解程序調(diào)試或找到類(lèi)似的程序故障。

          什么是程序故障?

          為了說(shuō)明哪些程序故障才是真正麻煩的程序故障,即ADS專(zhuān)注解決的問(wèn)題,這里簡(jiǎn)要地對(duì)軟件故障進(jìn)行了分類(lèi)并說(shuō)明了各類(lèi)故障的嚴(yán)重性。這種分類(lèi)本身并無(wú)任何新意,只是收集并組織了一些通用常識(shí),然后以便于理解ADS的形式組織起來(lái)。這里,我們考慮的只是程序員產(chǎn)生的錯(cuò)誤,而由硬件故障、操作員錯(cuò)誤操作或其他不受程序員控制的條件引發(fā)的故障本身并不復(fù)雜,所以不在ADS處理的故障之列。程序員錯(cuò)誤包括:

          1. 算法設(shè)計(jì)錯(cuò)誤。程序員或其客戶(hù)錯(cuò)誤理解了問(wèn)題,因此解決問(wèn)題的方法(即算法)即便完美無(wú)缺,也無(wú)法發(fā)揮效用。例如,程序員假定地球是個(gè)完美的球體,然后基于此計(jì)算人造地球衛(wèi)星的軌道。他的錯(cuò)誤本身與算法無(wú)關(guān),但與他或客戶(hù)對(duì)問(wèn)題的理解有關(guān)。

          2. 程序設(shè)計(jì)錯(cuò)誤。盡管程序員對(duì)問(wèn)題的理解及解決問(wèn)題的方法都正確,但是在為解決方案設(shè)計(jì)程序時(shí)犯了錯(cuò)誤。例如,他沒(méi)有意識(shí)到計(jì)算機(jī)執(zhí)行程序的時(shí)間比根據(jù)常規(guī)預(yù)測(cè)的時(shí)間更長(zhǎng)。該問(wèn)題與計(jì)算機(jī)相關(guān):反映了程序員的理解能力,不與任何特定的計(jì)算機(jī)或編程語(yǔ)言相關(guān),而與對(duì)通用計(jì)算的理解有關(guān)。

          3. 程序?qū)崿F(xiàn)錯(cuò)誤。程序員在生成計(jì)算機(jī)執(zhí)行的指令時(shí)出現(xiàn)錯(cuò)誤。這類(lèi)錯(cuò)誤中,有以下兩種變形:

          a. 體系或語(yǔ)法錯(cuò)誤。程序違背了程序開(kāi)發(fā)工具規(guī)定的準(zhǔn)則,但這種錯(cuò)誤僅被這些工具捕獲。
          b. 獨(dú)立或邏輯錯(cuò)誤。程序本身沒(méi)有錯(cuò)誤,但無(wú)法運(yùn)行結(jié)束或產(chǎn)生錯(cuò)誤輸出。程序員要么犯了機(jī)械錯(cuò)誤(如拼寫(xiě)錯(cuò)誤),要么使用了不能被開(kāi)發(fā)系統(tǒng)捕獲的類(lèi)格式錯(cuò)誤,要么更嚴(yán)重地在詳細(xì)程序設(shè)計(jì)中犯下了邏輯錯(cuò)誤,如疏忽了緩存刷新或在數(shù)據(jù)區(qū)外進(jìn)行寫(xiě)操作(這時(shí),顯然程序開(kāi)發(fā)工具出現(xiàn)了故障。這雖然不是這位程序員的錯(cuò),卻是另一位程序員的失誤)。

          類(lèi)型1與算法無(wú)關(guān):這些故障只是因?yàn)槭韬龃笠饣蛴掴g而產(chǎn)生,因此也無(wú)須采取任何特殊補(bǔ)救措施。類(lèi)型2與計(jì)算機(jī)相關(guān),但也不是太棘手:這些故障顯而易見(jiàn),一般程序設(shè)計(jì)早期階段就能發(fā)現(xiàn),因此問(wèn)題也不是太突出。類(lèi)型3a現(xiàn)在已經(jīng)得到了很好的解決,大多數(shù)現(xiàn)代程序開(kāi)發(fā)系統(tǒng)能檢測(cè)出所有的通用語(yǔ)法錯(cuò)誤并精確地定位。有時(shí),這些軟件甚至還能糾錯(cuò),例如一些專(zhuān)用于文字處理的程序就能自動(dòng)地將“hte”糾正為“the”。

          真正危險(xiǎn)的程序故障

          類(lèi)型3b才是真正棘手的問(wèn)題:它的特點(diǎn)是容易引入、難以發(fā)現(xiàn),通常直到出現(xiàn)最壞結(jié)果才顯露出來(lái)。這類(lèi)問(wèn)題之所以難以對(duì)付,完全是因?yàn)楣收戏浅,嵥?、不引人注目,因此很難定位。類(lèi)型3b程序故障(以后簡(jiǎn)稱(chēng)為“程序故障”)確實(shí)非常危險(xiǎn),因?yàn)檫@類(lèi)故障很少立即表現(xiàn)出來(lái)。感染了這類(lèi)故障的程序通常情況下不會(huì)表現(xiàn)出任何癥狀,直到程序?yàn)?zāi)難性崩潰或產(chǎn)生明顯錯(cuò)誤的輸出才表現(xiàn)出來(lái)。這類(lèi)故障一般能使程序無(wú)故障地長(zhǎng)時(shí)間運(yùn)行,直到實(shí)際影響結(jié)果。顯然,這時(shí)不僅程序出現(xiàn)了故障,而且大多數(shù)情況下,程序還將試圖刪除或破壞定位故障所需的信息;于是,我們又不得不重新開(kāi)始漫長(zhǎng)而艱辛的回退調(diào)試流程。

          因此,我們需要的調(diào)試方法是希望通過(guò)某種途徑讓程序故障迅速自動(dòng)暴露出來(lái),這樣我們就能在第一時(shí)間意識(shí)到問(wèn)題存在并在程序破壞定位信息之前采取保護(hù)措施。理想情況下,我們希望程序故障甚至能在出現(xiàn)之前就自動(dòng)“跳出來(lái)”,也就是說(shuō),我們希望能在程序故障“干壞事”之前就一把抓住。這就是設(shè)計(jì)ADS的初衷。

          ADS的工作原理

          程序故障一出現(xiàn)即能立即捕獲的調(diào)試方法是通過(guò)在程序運(yùn)行時(shí)監(jiān)控眾多變量以找到那些違反程序員定義的斷言約束的故障。這里的“變量”并不單單指那些數(shù)學(xué)意義上的變量,還包括那些屬性以可預(yù)知方式改變的程序結(jié)構(gòu),而改變的方式可以是絕對(duì)改變,也可以是基于其他程序結(jié)構(gòu)的相對(duì)改變。在這之中,還包括那些描述循環(huán)遍歷次數(shù)、緩存寫(xiě)入前能容納的字符個(gè)數(shù)、分支開(kāi)關(guān)能處理的狀態(tài)數(shù)目等的數(shù)字變量。這些變量共同定義了程序執(zhí)行過(guò)程。ADS一個(gè)重要的前提是,如果變量不違反某項(xiàng)約束,程序故障將不會(huì)生效。如果系統(tǒng)地檢測(cè)出這些約束違反,那么每個(gè)程序故障都將在其一出現(xiàn)就發(fā)出警告,從而便于發(fā)現(xiàn)和理解。

          在整個(gè)程序執(zhí)行中對(duì)斷言進(jìn)行嚴(yán)格且系統(tǒng)的測(cè)試相當(dāng)于在程序執(zhí)行通道兩側(cè)建立起“防護(hù)墻”,這樣任何偏離通道的程序執(zhí)行都將使運(yùn)行的程序與某些斷言發(fā)生直接碰撞。因此,我們就能在每次執(zhí)行失敗中找到一些有價(jià)值的東西:找到程序故障(至少顯著縮小故障搜索范圍)或程序員的錯(cuò)誤理解。

          使用ADS

          對(duì)于每個(gè)程序結(jié)構(gòu),程序員可以在變量定義的時(shí)候,就指定變量的約束條件??赡艿募s束條件如下所示,其他的約束條件可以在使用ADS過(guò)程中不斷擴(kuò)充:

          * 最大值和最小值;
          * 變化的步長(zhǎng);
          * 對(duì)于變量的取值,可以循環(huán)取值,還是只能使用一次,或是隨機(jī)取值;
          * 該變量與其他一個(gè)或多個(gè)變量間的關(guān)系;
          * 變量可取值或不可取值的顯式列表;
          * 指針或鏈接變量所能指向的結(jié)構(gòu)類(lèi)型;

          這些斷言的表示方式是程序員使用的編程語(yǔ)言的自然延伸并可以幾種方式進(jìn)行歸類(lèi),因此,程序員可以通過(guò)一條命令使一組相關(guān)斷言生效或失效。

          在主程序的每次編譯中,主程序代碼中激活的斷言可被用來(lái)檢驗(yàn)其監(jiān)控的變量,如變量值的每次改變,是否違反了任何約束條件等(“可被用來(lái)”的含義表示,并不是每次都需要執(zhí)行每項(xiàng)測(cè)試)。當(dāng)監(jiān)控代碼檢測(cè)到任何變量已違反(或在某些情形下,即將違反)某個(gè)斷言,斷言將停止程序執(zhí)行并運(yùn)行程序員指定的異常處理程序。

          這一點(diǎn)充分顯示了ADS調(diào)試方法與目前眾多程序員使用的斷點(diǎn)終止法的主要區(qū)別。ADS停止程序執(zhí)行并不是因?yàn)槌绦驁?zhí)行到某個(gè)程序員希望在此通過(guò)檢查某些變量以獲取信息的點(diǎn)上;這個(gè)點(diǎn)或許遠(yuǎn)比程序員預(yù)想插入的斷點(diǎn)提前或滯后,只要在這點(diǎn)上檢測(cè)到異常。此外,中止點(diǎn)與異常實(shí)際發(fā)生點(diǎn)非常接近。中止點(diǎn)的檢測(cè)也不受程序員的控制,當(dāng)ADS報(bào)告該事件時(shí),與目前使用的斷點(diǎn)中止法所采用的隨機(jī)搜索機(jī)制不同,如果程序故障不馬上出現(xiàn)的話(huà),ADS用戶(hù)下一步可以返回程序并采取更大的監(jiān)控力度以使所有代碼動(dòng)態(tài)地指示異常檢測(cè),這樣就能盡早地捕獲程序故障。

          遺憾的是,由于ADS并未得到充分構(gòu)建和使用,因此缺乏有效展示ADS有效性的方法,但我們可以借助一個(gè)并不具有結(jié)論性的假想試驗(yàn)進(jìn)行說(shuō)明。根據(jù)最近解決的問(wèn)題或以往經(jīng)驗(yàn)人為地構(gòu)造一個(gè)實(shí)際的程序故障。在錯(cuò)誤指令導(dǎo)致程序正常行為出現(xiàn)首次異常之前,要記錄下程序中的變量值,同時(shí)啟動(dòng)斷言檢測(cè)。請(qǐng)記住,如果采用了ADS,那么ADS將監(jiān)控程序中的每個(gè)變量(即每個(gè)變化可預(yù)測(cè)的結(jié)構(gòu))的每次取值變化是否違反了當(dāng)初設(shè)定的范圍。通過(guò)比較程序故障首次顯現(xiàn)與ADS停下來(lái)指示程序故障之間的間隔,就能深刻體會(huì)ADS在調(diào)試中帶來(lái)的便捷。在幾乎所有情形下,我們都能找到傳統(tǒng)調(diào)試方法與ADS調(diào)試方法的巨大區(qū)別,兩者完全不可同日而語(yǔ)。

          斷言調(diào)試的成本

          雖然大多數(shù)程序員都表示ADS能幫助他們更快地找出程序故障,但許多人仍堅(jiān)持ADS的成本實(shí)在過(guò)于昂貴:實(shí)時(shí)檢測(cè)使ADS程序執(zhí)行占用的系統(tǒng)資源是普通程序執(zhí)行的數(shù)百倍。很多程序員一想到提供全部斷言檢測(cè)將使ADS嚴(yán)格地監(jiān)控整個(gè)程序就覺(jué)得難以承受。這些擔(dān)心無(wú)疑是杞人憂(yōu)天,因?yàn)樗麄冎豢吹搅吮砻娑鴽](méi)有深入分析。

          為了真正對(duì)ADS方法的成本進(jìn)行評(píng)估,首先需要明確的是,該方法比較的對(duì)象是實(shí)際應(yīng)用的現(xiàn)有調(diào)試方法?,F(xiàn)有調(diào)試方法在查詢(xún)程序故障中給出的提示信息往往很少或者甚至沒(méi)有,因此,對(duì)于整個(gè)調(diào)試的成本,這部分成本也必須加以考慮。另一方面,采用ADS方法,每次程序執(zhí)行都將得到有用的存檔信息:要么找到違反斷言約束之處,要么在運(yùn)行到結(jié)束時(shí)報(bào)告程序完全無(wú)故障。即便ADS報(bào)告了斷言違反約束,而結(jié)果表明程序代碼完全正確,那么說(shuō)明程序員設(shè)定的斷言有誤,從而可以獲得一些有效信息。實(shí)際上,其中最有價(jià)值的并不是找到單個(gè)程序故障,而是找到程序員對(duì)程序的錯(cuò)誤理解,這無(wú)疑更為重要。此外,需要注意的是,ADS的成本貫穿于整個(gè)產(chǎn)品周期,ADS節(jié)省的是項(xiàng)目預(yù)算時(shí)間、軟件工程師調(diào)試時(shí)間和產(chǎn)品的上市時(shí)間。總而言之,ADS是通過(guò)犧牲那些成本幾乎可以忽略不計(jì)的低成本資源,實(shí)現(xiàn)節(jié)省高成本的資源的目標(biāo)。

          斷言在程序員聲明變量和數(shù)據(jù)結(jié)構(gòu)就已明確表示,也就是說(shuō),當(dāng)程序員在構(gòu)造斷言時(shí)就已充分理解了該斷言。現(xiàn)有的實(shí)現(xiàn)系統(tǒng)需要程序員對(duì)變量和結(jié)構(gòu)進(jìn)行完整的靜態(tài)定義;而ADS只要求對(duì)變量或結(jié)構(gòu)在程序?qū)崟r(shí)運(yùn)行中的允許取值或禁止取值添加顯式聲明。實(shí)際上,用戶(hù)在最理想的時(shí)間完成了大量的調(diào)試工作:他并不會(huì)迫于壓力在規(guī)定的時(shí)間內(nèi)定位特定的程序故障,而且這時(shí)候他的頭腦最清晰,思維最敏銳。

          基本原理比較

          采用ADS工具與其他傳統(tǒng)工具的最大區(qū)別在于,只要預(yù)先加以定義,那么從定義開(kāi)始并在整個(gè)用戶(hù)設(shè)定的限制約束內(nèi),ADS將進(jìn)行完整的系統(tǒng)工作。在傳統(tǒng)調(diào)試方法中,系統(tǒng)只向用戶(hù)反饋這樣的信息:“我也不知道為什么程序會(huì)在這個(gè)點(diǎn)上停止下來(lái),或許你在這里設(shè)定了一個(gè)斷點(diǎn),因此這時(shí)候你可以在既有認(rèn)知能力條件下,通過(guò)一個(gè)窗口觀察任何一個(gè)可能與程序故障檢測(cè)相關(guān)的變量。如果程序當(dāng)前狀態(tài)下存在異常,你將能識(shí)別該異常,但如果跳過(guò)某個(gè)變量,我將無(wú)法識(shí)別某些異常?!?

          相反,ADS會(huì)這樣表示:“早在程序設(shè)計(jì)和開(kāi)發(fā)時(shí)期,你就告訴我,對(duì)于程序中眾多變量和結(jié)構(gòu)中的任意一個(gè),那些情況屬于異常;接著,你又告訴我將跟蹤那些異常,我就會(huì)按照你的指示尋找程序異?!,F(xiàn)在,我找到一個(gè)異常并記錄下詳細(xì)信息,如下所示……”有了ADS,軟件工程師將完全從事設(shè)計(jì)規(guī)劃,而調(diào)試系統(tǒng)則完成大量的煩瑣工作。

          上述兩種方法可以通過(guò)比較每個(gè)項(xiàng)目因軟件故障而導(dǎo)致的項(xiàng)目延遲以及能否使編程更高效可靠來(lái)區(qū)分。



          關(guān)鍵詞: 調(diào)試 故障 軟件

          評(píng)論


          相關(guān)推薦

          技術(shù)專(zhuān)區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();