關(guān) 閉

新聞中心

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

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

——
作者: 時間:2007-01-19 來源: 收藏

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

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

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

本文提出的新型調(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)也更徹底地利用斷言,而不像其他工具只在程序員想到的時候才使用。為此,ADS將斷言由半個世紀(jì)以來一直漂浮不定且鮮有建樹的理論轉(zhuǎn)化為足以引領(lǐng)程序開發(fā)革命的技術(shù)。與Knuth的論述不同,ADS并不摒棄那部分用于調(diào)試的程序,而是將其作為程序主體補(bǔ)充的文檔進(jìn)行保存,這樣,當(dāng)程序需要修改時完全可以加以復(fù)用。

程序是主要的瓶頸

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

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

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

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

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

什么是程序故障?

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

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

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

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

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

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

真正危險的程序故障

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

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

ADS的工作原理

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

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

使用ADS

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

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

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

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

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

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

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

雖然大多數(shù)程序員都表示ADS能幫助他們更快地找出程序故障,但許多人仍堅持ADS的成本實在過于昂貴:實時檢測使ADS程序執(zhí)行占用的系統(tǒng)資源是普通程序執(zhí)行的數(shù)百倍。很多程序員一想到提供全部斷言檢測將使ADS嚴(yán)格地監(jiān)控整個程序就覺得難以承受。這些擔(dān)心無疑是杞人憂天,因為他們只看到了表面而沒有深入分析。

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

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

基本原理比較

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

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

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



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

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉