《Effective Debugging:软件和系统调试的66个有效方法》一第5条:在能够正常运作的系统与发生故障的系统之间寻找差别...
本節(jié)書摘來自華章出版社《Effective Debugging:軟件和系統(tǒng)調(diào)試的66個有效方法》一書中的第1章,第1.5節(jié),作[希]迪歐米迪斯·斯賓奈里斯(Diomidis Spinellis),更多章節(jié)內(nèi)容可以訪問云棲社區(qū)“華章計(jì)算機(jī)”公眾號查看
第5條:在能夠正常運(yùn)作的系統(tǒng)與發(fā)生故障的系統(tǒng)之間尋找差別
我們通常都能夠同時訪問這樣兩個系統(tǒng),其中一個是發(fā)生故障的系統(tǒng),另一個是與之相似但卻可以正常運(yùn)行的系統(tǒng)。當(dāng)我們實(shí)現(xiàn)了某項(xiàng)新功能、更新了某些工具或基礎(chǔ)組件,或是把系統(tǒng)部署在某個新的平臺上面時,就可能會遇到新系統(tǒng)無法正常運(yùn)行的問題,此時如果舊系統(tǒng)依然正常,那么我們通常可以通過尋找(下面就會講到如何尋找)或盡量縮小(參見第45條)新舊兩個系統(tǒng)之間的差別來鎖定問題的原因。
之所以能根據(jù)新舊系統(tǒng)間的差距來進(jìn)行調(diào)試,其原因在于:盡管各人所經(jīng)歷的問題有所不同,但計(jì)算機(jī)的底層運(yùn)作方式卻是十分確定的,也就是說,同樣的輸入會產(chǎn)生同樣的輸出。因此,只要能夠深入故障系統(tǒng)中,并對其進(jìn)行足夠的探查,我們就遲早能夠找到相關(guān)的bug,從而揭示出該系統(tǒng)為什么會在行為上與正常系統(tǒng)有所不同。
其實(shí)有很多時候,系統(tǒng)的故障原因都會非常明確地出現(xiàn)在你面前,只要你肯打開程序的日志文件(參見第56條),就有可能發(fā)現(xiàn)里面有一條消息告訴你,clients.conf這個配置文件有錯誤:
在另外一些情況下,錯誤的原因可能會隱藏得比較深,此時你必須提升系統(tǒng)日志的詳細(xì)程度(verbosity),才能把它暴露出來。
如果系統(tǒng)沒有提供足夠詳細(xì)的日志機(jī)制,那我們就需要用追蹤工具來梳理其運(yùn)行時的行為。除了DTrace和SystemTap等通用的工具,還有一些專門的工具可以用來追蹤對操作系統(tǒng)的調(diào)用(strace、truss、Procmon)、對動態(tài)鏈接庫的調(diào)用(ltrace、Procmon)、網(wǎng)絡(luò)包(tcpdump、Wireshark)以及SQL數(shù)據(jù)庫調(diào)用(參見第58條)。有很多Unix應(yīng)用程序(如R Project)是借助復(fù)雜的shell腳本來啟動的,因此可能會以極其隱晦的方式出錯。針對這樣的錯誤,在大多數(shù)情況下,我們都可以通過給相應(yīng)shell傳入-x選項(xiàng)的辦法來進(jìn)行追蹤,這樣得到的數(shù)據(jù)通常很龐大,所幸現(xiàn)在的系統(tǒng)都有很大的容量能夠存放這兩份日志(以其中一份表示那個可以正常運(yùn)作的系統(tǒng),另一份表示出現(xiàn)了故障的系統(tǒng)),而且都有很強(qiáng)的CPU能夠?qū)ζ溥M(jìn)行處理與比較。
就系統(tǒng)的操作環(huán)境而言,我們應(yīng)該盡量確保這兩個系統(tǒng)擁有相似的環(huán)境,因?yàn)檫@樣能夠更加方便地對比日志文件或追蹤信息,有時甚至可以直接找到造成bug的原因。我們可以先從一些較為明顯的部分入手,例如,程序的輸入以及命令行參數(shù)等。與早前所說的原則一樣,我們也要親自進(jìn)行驗(yàn)證,而不能想當(dāng)然地接受假設(shè)。例如,應(yīng)該在兩個系統(tǒng)的輸入文件之間進(jìn)行對比,如果它們都比較龐大并且離得比較遠(yuǎn),那可以考慮對比它們的MD5校驗(yàn)和。
然后,我們應(yīng)該把重點(diǎn)放在代碼上。首先對源代碼進(jìn)行對比,我們可能要挖得深一些才能找到bug所在的地方。可以通過ldd命令(適用于Unix系統(tǒng))或是帶有/dependents選項(xiàng)的dumpbin命令(適用于Visual Studio)來查看與每個可執(zhí)行文件有關(guān)的動態(tài)程序庫,并通過nm命令(適用于Unix系統(tǒng))、帶有/exports/imports選項(xiàng)的dumpbin命令(適用于Visual Studio)或javap命令(適用于以Java語言開發(fā)出來的程序)來查看程序所定義和使用的符號。如果你確信問題肯定出現(xiàn)在代碼中,但又看不出明顯的差別,那么可能就要往更深的層次去探查了,也就是需要對比由編譯器所生成的匯編代碼(參見第37條)。
然而在進(jìn)行更深層次的探查之前,應(yīng)該先考慮一下有沒有其他因素會影響程序的執(zhí)行情況,環(huán)境變量就是這樣一個容易忽視的因素,即便是沒有特權(quán)的用戶,也依然可以通過設(shè)置環(huán)境變量來破壞程序的正常執(zhí)行。另一個因素是操作系統(tǒng)。與運(yùn)行著正常程序的那個操作系統(tǒng)相比,故障程序所在的這個操作系統(tǒng),可能新了10年或是舊了10年。此外,也要考慮編譯器、開發(fā)框架、第三方鏈接庫、瀏覽器、應(yīng)用程序服務(wù)器、數(shù)據(jù)庫系統(tǒng)以及其他一些中間件。至于怎樣在這么多的因素中確定問題的根源,則是我們接下來要講的話題。
大多數(shù)情況下,我們都是在一堆干草里面找一根針(大海撈針),因此應(yīng)該盡量使這堆干草變得小一些,于是,就要花時間來構(gòu)造一個既能體現(xiàn)bug,又最為簡單的測試用例(參見第10條)。(另外一種辦法是把要找的針變大一些,也就是命令這個有bug的程序輸出更多的信息,然而這種做法很少能起到比較好的效果。)簡明的測試用例可以縮短日志文件與追蹤信息的長度并減少處理時間,從而令調(diào)試工作變得更加輕松。要想有條理地簡化測試用例,我們可以在確保能夠重現(xiàn)bug的前提下,逐漸刪除用例中的元素或系統(tǒng)中的配置選項(xiàng),直到刪至最簡。
如果正常系統(tǒng)和故障系統(tǒng)的區(qū)別位于源代碼中,那么有一種很實(shí)用的辦法,就是對這兩個版本之間的歷次修改進(jìn)行二分搜索(binary search),以確定問題所在。例如,如果正常系統(tǒng)的版本號是100,而故障系統(tǒng)的版本號是132,那我們首先測試116版的程序是否正常,如果116版正常,那就判斷它與132版之間的中點(diǎn),也就是124版是否正常,如果116版有錯,則判斷它與100版之間的中點(diǎn),也就是108版是否正常,并依此類推。每次修改完程序之后,我們都應(yīng)該把代碼單獨(dú)提交到版本控制系統(tǒng)里面,這樣做的好處之一,就是使得我們能夠進(jìn)行二分搜索。某些版本控制系統(tǒng)提供了可以自動執(zhí)行搜索的命令,例如,Git就提供了git bisect命令(參見第26條)。
還有一個很有效的辦法,是用Unix工具對比兩份日志文件(參見第56條),以找出其中與bug有關(guān)的區(qū)別。我們在這種情況下所使用的工具,是diff命令,它可以顯示出兩份文件的不同之處。然而日志文件經(jīng)常會在無關(guān)緊要的地方表現(xiàn)出差別,這會把那些與bug真正有關(guān)的差別給掩蓋掉,于是,我們可以考慮用各種辦法來過濾干擾因素。例如,如果每一行開頭的幾個字段,都是時間戳與進(jìn)程ID等信息,那我們就可以用cut或awk命令來把這些大同小異的信息裁掉。下面這條命令可以對Unix系統(tǒng)的messages日志文件進(jìn)行裁切,它會從每一行的第4個字段開始顯示其內(nèi)容:
只把你感興趣的那些事件選出來就可以了,例如,如果你只對打開的文件感興趣,那么可以用grep'open('這樣的命令來進(jìn)行篩選。你也可以用grep-v gettimeofday等命令來把對自己有干擾的文本行過濾掉(例如,在Java程序里面,會有成千上萬次與獲取系統(tǒng)時間有關(guān)的調(diào)用)。此外,還可以在sed命令中指定適當(dāng)?shù)恼齽t表達(dá)式,以便把文本行中自己不感興趣的那一部分裁掉。
最后再講一個高級的實(shí)用技巧:如果兩份文件各自的排序方式無法使diff命令給出有效的對比結(jié)果,那我們可以把感興趣的字段提取出來,對其進(jìn)行排序,然后用comm工具在排好順序的兩個集合中找尋不同的元素。例如,如果我們想對比t1和t2這兩份追蹤信息,以找出有哪些文件只出現(xiàn)于t1中,那么可以在Unix的Bash shell中輸入下列命令,它會在包含字符串open(的那些文本行里面提取表示文件名的第二個字段,并在提取出來的這兩個集合之間尋找差別:
兩對小括號里面的那兩個元素,會分別生成兩份有序列表,列表中的每一項(xiàng)都是一個傳給open的文件名,而comm命令(這個命令用來在兩份列表之間尋找共同的元素)則以這兩份列表為輸入值,并把只出現(xiàn)在第一份列表中的內(nèi)容列出來。
要點(diǎn)
在能夠正常運(yùn)作的系統(tǒng)與出現(xiàn)故障的系統(tǒng)之間對比,找出行為上的區(qū)別,以求發(fā)現(xiàn)故障的原因。
影響系統(tǒng)行為的所有因素都要考慮到,包括代碼、輸入、調(diào)用時的參數(shù)、環(huán)境變量、服務(wù)以及動態(tài)鏈接庫。
總結(jié)
以上是生活随笔為你收集整理的《Effective Debugging:软件和系统调试的66个有效方法》一第5条:在能够正常运作的系统与发生故障的系统之间寻找差别...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 双曲线和直线联立公式_圆锥曲线联解公式
- 下一篇: python3 实现对比conf 文件差