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