弗雷德里克·布魯克斯(Frederick P. Brooks)博士在他那篇著名的《沒有銀彈——軟件工程中的根本和次要問題》一文中,將軟件項(xiàng)目比作可怕的人狼(werewolves),并大膽地預(yù)言十年內(nèi)不會(huì)找到特別有效的銀彈,
調(diào)試之劍
。該論文發(fā)表的時(shí)間是1986年,如今整整20年過去了,盡管不時(shí)有人驚呼找到了神奇的銀彈,但是冷靜的人們很快發(fā)現(xiàn)那只是美好的愿望。如果說軟件工業(yè)中與人狼的戰(zhàn)斗還在持續(xù),那么在這些戰(zhàn)役中一定會(huì)有程序員的身影,筆者也是其中的一個(gè)。我的編程生涯是從使用匯編語言編寫DOS下的TSR程序開始的。今天DOS操作系統(tǒng)已經(jīng)成為歷史,在那個(gè)年代最值得炫耀的TSR技術(shù)也早已經(jīng)過時(shí)了。十幾年中,OWL、VFW、VDX、ISAPI、Active Movie等技術(shù)也被時(shí)間淘汰……然而,在這漫長的時(shí)間當(dāng)中,我最看重的是軟件調(diào)試技術(shù)。它是十幾年中我學(xué)到的最有用、一直受用、而且日久彌新的一項(xiàng)技術(shù)。
從軟件工程的角度來講,軟件調(diào)試是軟件工程的一個(gè)重要部分,軟件調(diào)試過程出現(xiàn)在軟件工程的各個(gè)階段。從最初的可行性分析、原型驗(yàn)證、到開發(fā)和測試階段、再到發(fā)布后的維護(hù)與支持,都有軟件調(diào)試過程的參與。通常認(rèn)為,一個(gè)完整的軟件調(diào)試過程由以下幾個(gè)步驟組成:
● 重現(xiàn)故障,通常是在用于調(diào)試的系統(tǒng)上重復(fù)導(dǎo)致故障的步驟,使要解決的問題出現(xiàn)在被調(diào)試的系統(tǒng)中。
● 定位根源,即綜合利用各種調(diào)試工具,使用各種調(diào)試手段尋找導(dǎo)致軟件故障的根源(root cause)。通常測試人員報(bào)告和描述的是軟件界面或工作行為中所表現(xiàn)出的異常,或者是與軟件需求和功能規(guī)約不符的地方,泛指軟件缺欠(defect)或者故障(failure)。而這些表面的缺欠總是由于一或多個(gè)內(nèi)在因素所導(dǎo)致的。這些內(nèi)因要么是代碼的行為錯(cuò)誤,要么是不行為錯(cuò)誤(該作而未作)。
● 探索和實(shí)現(xiàn)解決方案,即根據(jù)尋找到的故障根源、和資源情況、緊迫程度等要求設(shè)計(jì)和實(shí)現(xiàn)解決方案。
● 驗(yàn)證方案,在目標(biāo)環(huán)境中測試方案的有效性,又稱為回歸(regress)測試。如果問題已經(jīng)解決,那么就可以關(guān)閉問題。如果沒有解決則回到第3步調(diào)整和修改解決方案。
這些步驟中,定位根源常常是最困難也是最關(guān)鍵的步驟,它是軟件調(diào)試過程的核心和靈魂。如果沒有找到故障根源,那么解決方案便很是隔靴搔癢,或者頭痛醫(yī)腳,白白浪費(fèi)了時(shí)間。
對軟件調(diào)試的另一種更通俗的解釋是指使用調(diào)試工具求解各種軟件問題的過程,例如跟蹤軟件的執(zhí)行過程,探索軟件本身或者與其配套的其它軟件或者硬件系統(tǒng)的工作原理等,這些過程的目的有可能是為了去除軟件缺欠,也可能不是。
在了解了軟件調(diào)試技術(shù)的基本概念以后,下面我們來看一下支撐軟件調(diào)試技術(shù)的幾種基本機(jī)制。
● 斷點(diǎn):即當(dāng)被調(diào)試程序執(zhí)行到某一空間或時(shí)間點(diǎn)時(shí)將其中斷到調(diào)試器中。根據(jù)中斷條件分為如下幾種:
○ 代碼斷點(diǎn):當(dāng)程序執(zhí)行到指定內(nèi)存地址的代碼時(shí)中斷到調(diào)試器。
○ 數(shù)據(jù)斷點(diǎn):當(dāng)程序訪問指定內(nèi)存地址的數(shù)據(jù)時(shí)中斷到調(diào)試器。
○ I/O斷點(diǎn):當(dāng)程序訪問指定I/O地址的端口時(shí)中斷到調(diào)試器。
根據(jù)斷點(diǎn)的設(shè)置方法,斷點(diǎn)又分為軟件斷點(diǎn)和硬件斷點(diǎn)。軟件斷點(diǎn)通常是通過向指定的代碼位置插入專用的斷點(diǎn)指令來實(shí)現(xiàn)的,比如IA32 CPU的INT 3指令(機(jī)器碼為0xCC)就是斷點(diǎn)指令。硬件斷點(diǎn)通常是通過設(shè)置CPU的調(diào)試寄存器來設(shè)置的。IA32 CPU定義了8個(gè)調(diào)試寄存器,DR0~DR7,可以最多同時(shí)設(shè)置4個(gè)硬件斷點(diǎn)(對于一個(gè)調(diào)試會(huì)話)。通過調(diào)試寄存器可以設(shè)置以上三種斷點(diǎn)中的任一種,但是通過斷點(diǎn)指令只可以設(shè)置代碼斷點(diǎn)。
● 單步跟蹤:即讓應(yīng)用程序按照某單位一步步執(zhí)行。根據(jù)單位,又分幾種:
○ 每次執(zhí)行一條匯編指令,稱為匯編語言一級(jí)的單步跟蹤。設(shè)置IA32 CPU標(biāo)志寄存器的TF(Trap Flag,即陷阱標(biāo)志位)位,便可以讓CPU每執(zhí)行完一條指令便產(chǎn)生一個(gè)調(diào)試異常(INT 1),中斷到調(diào)試器。
○ 每次執(zhí)行源代碼(比匯編語言更高級(jí)的程序語言,如C/C++)的一條語句,又稱為源代碼級(jí)的單步跟蹤。通常高級(jí)語言的單步跟蹤是通過反復(fù)設(shè)置CPU的陷阱標(biāo)志位來實(shí)現(xiàn)的,如果當(dāng)前源代碼行還沒有執(zhí)行完,那么調(diào)試器重新設(shè)置陷阱標(biāo)志并讓程序繼續(xù)執(zhí)行,直到該語句結(jié)束(EIP指向另一語句)才中斷給用戶。
○ 每次執(zhí)行一個(gè)程序分支,又稱為分支到分支單步跟蹤。設(shè)置IA32 CPU的DbgCtl MSR寄存器的BTF(Branch Trap Flag)標(biāo)志后,便可以啟用分支到分支單步跟蹤。
○ 每次執(zhí)行一個(gè)任務(wù)(線程),即當(dāng)一個(gè)任務(wù)(線程)被調(diào)度執(zhí)行時(shí)中斷到調(diào)試器。IA32架構(gòu)所定義的任務(wù)狀態(tài)段(TSS)中的T標(biāo)志為實(shí)現(xiàn)這一功能提供了硬件一級(jí)的支持,但是很多調(diào)試器還有提供這項(xiàng)功能,
管理資料
《調(diào)試之劍》(http://www.stanzs.com)。● ;厮荩╯tack backtrace):即通過記錄在棧中的函數(shù)返回地址顯示(追溯)函數(shù)調(diào)用過程。在將返回地址翻譯成函數(shù)名時(shí)需要有調(diào)試符號(hào)(debug symbol)的支持。大多數(shù)編譯器都支持在編譯時(shí)生成調(diào)試符號(hào)。微軟的調(diào)試符號(hào)服務(wù)器(http://msdl.microsoft.com/download/symbols)提供了大多數(shù)Windows系統(tǒng)文件的調(diào)試符號(hào),是調(diào)試和學(xué)習(xí)Windows操作系統(tǒng)的寶貴資源。
● 調(diào)試信息輸出(debug output/print):即將程序運(yùn)行的位置、變量狀態(tài)等信息輸出到調(diào)試器、窗口、文件或者其它可以觀察到的地方。這種方法的優(yōu)點(diǎn)是簡單方便、不依賴于調(diào)試器,但也有明顯的缺點(diǎn),如效率低,安全性差,通常不可以動(dòng)態(tài)開啟,且難以管理等。在Windows操作系統(tǒng)中,驅(qū)動(dòng)程序可以使用DbgPrint/DbgPrintEx來輸出調(diào)試信息,應(yīng)用程序可以調(diào)用OutputDebugString API。
● 日志(log):將程序運(yùn)行的狀態(tài)信息寫入到特定的文件或者數(shù)據(jù)庫中。Windows操作系統(tǒng)提供了記錄、觀察和管理(刪除和備份)日志的功能。Windows Vista新引入了名為Common Log File System(CLFS.SYS)的內(nèi)核模塊,用于進(jìn)一步加強(qiáng)日志功能。
● 事件追蹤(event trace):通常用來監(jiān)視頻繁的復(fù)雜的軟件過程,滿足普通日志機(jī)制難以勝任的需求。比如監(jiān)視大信息量的文件操作、網(wǎng)絡(luò)通信等。ETW(Event Trace for Windows)是Windows操作系統(tǒng)內(nèi)建的事件追蹤機(jī)制,Windows內(nèi)核本身和很多Windows下的軟件工具(如Bootvis,TCP/IP View)都使用了該機(jī)制。
在以上機(jī)制中,斷點(diǎn)和單步跟蹤通常必須在有調(diào)試器參與的情況下才能使用。調(diào)試器(software debugger)是綜合提供各種調(diào)試功能的軟件工具。除了處理斷點(diǎn)、單步跟蹤、模塊映射等調(diào)試事件外,調(diào)試器通常還提供如下功能:
● 觀察和編輯被調(diào)試程序的內(nèi)存和數(shù)據(jù),如全局變量、局部變量、以及程序的棧和堆等重要數(shù)據(jù)結(jié)構(gòu)。
● 觀察和反匯編被調(diào)試程序的代碼。
● 顯示線程棧中的函數(shù)調(diào)用信息。
● 管理調(diào)試符號(hào)。
● 控制進(jìn)程和線程,例如將被調(diào)試程序中斷到調(diào)試器中,和恢復(fù)其執(zhí)行等。
根據(jù)調(diào)試器所調(diào)試目標(biāo)程序的工作模式,可以把調(diào)試器分為用戶態(tài)調(diào)試器和內(nèi)核態(tài)調(diào)試器,前者用于調(diào)試用戶態(tài)下的各種程序(應(yīng)用程序、系統(tǒng)服務(wù)、或者用戶態(tài)的DLL模塊),后者用于調(diào)試工作在內(nèi)核模式的程序,如驅(qū)動(dòng)程序和操作系統(tǒng)的內(nèi)核部分。WinDbg是微軟開發(fā)的一個(gè)免費(fèi)調(diào)試器,它既可以用作用戶態(tài)調(diào)試器,也可以用作內(nèi)核態(tài)調(diào)試器,是調(diào)試Windows操作系統(tǒng)下的各種軟件的一個(gè)強(qiáng)有力工具。我?guī)缀趺刻於际褂肳inDbg,它是我的計(jì)算機(jī)中使用頻率最高的軟件之一。
最后,簡要地描述一下軟件調(diào)試技術(shù)的幾個(gè)特征。
系統(tǒng)性——很多看似簡單的調(diào)試機(jī)制都是依靠系統(tǒng)內(nèi)的多個(gè)部件協(xié)同工作而完成的。以軟件斷點(diǎn)為例,CPU提供了指令支持和硬件級(jí)的異常機(jī)制,操作系統(tǒng)將異常以調(diào)試事件的形式分發(fā)給調(diào)試器,調(diào)試器響應(yīng)調(diào)試事件并與用戶交互。如果在做源代碼級(jí)的調(diào)試,那么調(diào)試器又需要編譯器所產(chǎn)生的調(diào)試符號(hào)來幫忙。
全局性——對于一個(gè)軟件項(xiàng)目,應(yīng)該在項(xiàng)目的設(shè)計(jì)和架構(gòu)階段就制定出全局的調(diào)試支持機(jī)制,并貫徹實(shí)施。比如,所有模塊都應(yīng)該使用統(tǒng)一的方法來輸出調(diào)試信息、記錄日志、報(bào)告錯(cuò)誤,并公開統(tǒng)一的接口用做單元測試和故障診斷。這樣不僅可以避免重復(fù)工作,而且增加了軟件的可調(diào)適性(debuggability),有利于保證產(chǎn)品的質(zhì)量和進(jìn)度。
困難性——《C語言編程》一書的作者Brian Kernighan曾經(jīng)說過,“調(diào)試天生就比編寫代碼難上一倍,如果你寫出了最聰明的代碼,那么你的智商就不足以調(diào)試這個(gè)代碼。”因?yàn),要調(diào)試一個(gè)程序,就必須深刻理解它的工作原理,不僅要知道how和表層的東西,還要知道why和深層次的內(nèi)幕。另外,調(diào)試需要鍥而不舍的探索精神和堅(jiān)韌的耐力,這也讓很多人望而卻步。
綜上所述,軟件調(diào)試技術(shù)是與軟件開發(fā)密不可分的一門技術(shù),其初衷是為了定位和去除軟件故障,但因?yàn)檎{(diào)試技術(shù)所具有的對軟件的強(qiáng)大控制力和觀察力,其應(yīng)用早已延伸到了很多其它領(lǐng)域,比如逆向工程、計(jì)算機(jī)安全等等。
學(xué)習(xí)和靈活運(yùn)用軟件調(diào)試技術(shù),不僅可以提高程序員的工作效率,而且有利于提升對代碼的感知力和控制力,加深對軟件和系統(tǒng)的理解。此外,調(diào)試技術(shù)是解決各種軟件難題的一種有效武器。它直擊要害、銳不可擋,相對其它間接方法具有明顯的優(yōu)勢。
軟件有大美,調(diào)試見真功。在尋找銀彈的努力還在繼續(xù)的時(shí)候,衷心地希望所有程序員朋友都學(xué)會(huì)使用調(diào)試這把利劍吧,使用它為你披荊斬棘,幫你探索前進(jìn)。只要你的這把劍依然鋒利,那你的軟件青春就永遠(yuǎn)不老。
來自:http://blog.csdn.net/programmer_editor/archive/2007/03/21/1536200.aspx