程序調(diào)試技術(shù)_第1頁(yè)
程序調(diào)試技術(shù)_第2頁(yè)
程序調(diào)試技術(shù)_第3頁(yè)
程序調(diào)試技術(shù)_第4頁(yè)
程序調(diào)試技術(shù)_第5頁(yè)
已閱讀5頁(yè),還剩18頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、C/C+程序調(diào)試技術(shù),東方網(wǎng)力技術(shù)培訓(xùn),內(nèi)容提要,中斷和異常 調(diào)試斷點(diǎn) 常見調(diào)試器功能 源代碼級(jí)別的主動(dòng)調(diào)試手段 C+異常和win32的SEH 常見程序問(wèn)題調(diào)試,性能分析和優(yōu)化 C/C+語(yǔ)言的一些陷阱 一些常見平臺(tái)差異 實(shí)例分析 閱讀程序的技巧 幾點(diǎn)建議,中斷和異常,所謂中斷是指CPU對(duì)系統(tǒng)發(fā)生的某個(gè)事件做出的一種反應(yīng),CPU暫停正在執(zhí)行的程序,保留現(xiàn)場(chǎng)后轉(zhuǎn)入去執(zhí)行相應(yīng)的中斷處理程序,執(zhí)行完中斷處理程序后再返回中斷現(xiàn)場(chǎng)繼續(xù)執(zhí)行被打斷的程序。 中斷可分為三類: 1、第一類是由CPU外部引起的,稱作中斷,如I/O中斷、時(shí)鐘中斷、控制臺(tái)中斷(重啟動(dòng)中斷, 關(guān)機(jī)中斷, )等。 2、第二類是CPU的內(nèi)部

2、事件,稱作異常,如CPU故障、程序故障(非法操作碼、地址越界、浮點(diǎn)數(shù)溢出、除0錯(cuò)誤等)。 3、第三類是由程序?yàn)榱耸褂媚承┫到y(tǒng)服務(wù)而主動(dòng)引發(fā),稱作 陷入(也叫軟中斷),如現(xiàn)在x86 CPU int 3指令,dos下著名的int 13、int 21等。程序調(diào)試斷點(diǎn)就是通過(guò)int 3指令實(shí)現(xiàn)的。 4、x86 CPU的單步中斷特性(TRAP FLAG被設(shè)置后,執(zhí)行每條指令后都會(huì)發(fā)生此中斷)。程序的指令級(jí)別的單步執(zhí)行應(yīng)該就是用單步中斷實(shí)現(xiàn)的。 中斷向量表IDT,即中斷處理程序的入口地址表。 第三類軟中斷事件(異常)處理過(guò)程,以win32平臺(tái)處理int 3指令為例: 1、保留現(xiàn)場(chǎng),進(jìn)程/線程被掛起,進(jìn)入操

3、作系統(tǒng)的處理程序(執(zhí)行系統(tǒng)int 3的中斷處理程序,下面稱為系統(tǒng))。 2、發(fā)生中斷的進(jìn)程如果處于被調(diào)試狀態(tài),則系統(tǒng)把int 3事件通知給調(diào)試進(jìn)程,嘗試由調(diào)試進(jìn)程處理int 3事件。 3、嘗試讓進(jìn)程自己處理int 3事件(參考C+的異常以及Windows Structured Exception Handling知識(shí))。 4、如果2、3情況都沒(méi)處理int 3事件,則系統(tǒng)彈出異常對(duì)話框,通知用戶進(jìn)程發(fā)生了異常(此時(shí)用戶可以使用調(diào)試器再來(lái)處理int 3事件轉(zhuǎn)入2)。 第一類中斷一般直接由系統(tǒng)處理,然后可能再分發(fā)給需要處理的用戶進(jìn)程。 第二類中斷一般處理順序?yàn)?1 - 3 - 2 - 4 .i,調(diào)試斷

4、點(diǎn),調(diào)試斷點(diǎn)一般是通過(guò)int 3指令實(shí)現(xiàn)的。 調(diào)試器設(shè)置斷點(diǎn)原理(以VC調(diào)試器為例): 調(diào)試器首先找到被調(diào)試進(jìn)程需要設(shè)置斷點(diǎn)的指令地址(調(diào)試版本根據(jù)源代碼設(shè)置的斷點(diǎn)也會(huì)被轉(zhuǎn)化為實(shí)際的指令地址),然后把該地址的1byte數(shù)據(jù)記錄到一張對(duì)應(yīng)表里,接著把這1byte 改寫為0 xCC (即int 3指令碼)。這樣當(dāng)程序被調(diào)試運(yùn)行的時(shí)候,在斷點(diǎn)位置的指令其實(shí)就是int 3指令,參照上一節(jié)的int 3中斷事件處理過(guò)程,就可以明白調(diào)試器捕獲斷點(diǎn)的工作機(jī)理。取消斷點(diǎn)時(shí)則把對(duì)應(yīng)表里記錄的1byte回寫到被調(diào)試進(jìn)程。 常用跟蹤相關(guān)動(dòng)作都是通過(guò)斷點(diǎn)方式實(shí)現(xiàn)的 對(duì)于step to、 step over、step i

5、n、step out等調(diào)試器都是通過(guò)在要運(yùn)行的下一個(gè)地址處先設(shè)置一個(gè)臨時(shí)斷點(diǎn),然后調(diào)試運(yùn)行程序來(lái)實(shí)現(xiàn)的。其它斷點(diǎn)實(shí)現(xiàn)類似。 程序主動(dòng)調(diào)試斷點(diǎn),ASSERT宏,ASSERT(false)即等效為一條int 3指令。 理解和使用條件斷點(diǎn)、單次斷點(diǎn)、固定次數(shù)斷點(diǎn)等。 怎么在動(dòng)態(tài)庫(kù)(靜態(tài)load和動(dòng)態(tài)load)里設(shè)置斷點(diǎn), VC的Additional DLLs選項(xiàng)。 怎樣在模板代碼、內(nèi)聯(lián)函數(shù)、靜態(tài)庫(kù)代碼里設(shè)置斷點(diǎn)。(調(diào)試器問(wèn)題,怎樣在不能設(shè)置斷點(diǎn)的代碼位置設(shè)置斷點(diǎn))。 帶調(diào)試信息模塊和不帶調(diào)試信息模塊共存情況的調(diào)試方法,如VB、瀏覽器、Media Player使用我們需要調(diào)試的.ocx、.dll文件等

6、。,常見調(diào)試器功能 - 具體參考VC,gdb等調(diào)試器的用戶手則,查看和修改變量,監(jiān)視變量 查看和修改內(nèi)存,監(jiān)視內(nèi)存 查看和修改寄存器,監(jiān)視寄存器 全局變量寫監(jiān)視 Call Stack(調(diào)用堆棧)的查看 更改指令指針寄存器EIP,實(shí)現(xiàn)調(diào)試時(shí)強(qiáng)行跳轉(zhuǎn)(VC 的 Set Next Statement命令同此) 查看源碼對(duì)應(yīng)的匯編指令/機(jī)器碼,源代碼級(jí)別的主動(dòng)調(diào)試手段,編譯時(shí)刻防御性編程 - C+契約 (contract) 1、靜態(tài)assert(編譯時(shí)刻斷言) - STATIC_ASSERT(), must_have_base() 2、一些有用的靜態(tài)判斷:bool IS_INT_TYPE(T)、IS_

7、SIGNED_TYPE(T)、GET_INT_MAX_VALUE(T),見 npdebug.h“ 調(diào)試時(shí)刻防御性編程 宏ASSERT()、VERIFY()、TRACE()等。 MFC的AfxIsValidAddress()、AfxIsValidString()等。 1、程序應(yīng)該大量使用ASSERT()宏,保證ASSERT()覆蓋沒(méi)有正常處理的所有程序邏輯分支。 2、所有沒(méi)有完成的函數(shù)和邏輯分支應(yīng)該寫上ASSERT(false)以防止以后遺忘。 運(yùn)行/發(fā)布時(shí)刻防御性編程 即程序的各種邊界、容錯(cuò)、健壯性處理等。,C+異常和win32的SEH,什么情況下建議使用異常 a、當(dāng)使用第三方提供的庫(kù),調(diào)用該

8、庫(kù)接口的代碼需要放在異常塊里面 (對(duì)于第三方庫(kù)內(nèi)部有獨(dú)立線程或獨(dú)立進(jìn)程時(shí),目前我還沒(méi)想到好的辦法增強(qiáng)程序健壯性) 。 b、構(gòu)造函數(shù)可能失敗的情況必須使用異常。 c、在使用異??梢源蟠蠛?jiǎn)化程序邏輯的地方也可以使用異常。 d、內(nèi)存分配可能失敗的地方。 異常不可能全面代替錯(cuò)誤處理。 不可使用異常來(lái)做一般的邏輯控制。 宏NP_BEGIN_CATCH_ALL()和NP_END_CATCH_ALL(),常見程序問(wèn)題調(diào)試,內(nèi)存泄漏 內(nèi)存溢出/越界 多線程死鎖 發(fā)布版本的調(diào)試 分析只在發(fā)布版本才會(huì)出現(xiàn)的問(wèn)題 多平臺(tái)調(diào)試,內(nèi)存泄漏,盡量減少對(duì)new和delete,malloc和free的使用,盡量使用C+的自動(dòng)

9、對(duì)象,如std:string, std:vector, class CAutoPtr, class CAutoObj等。 檢查低級(jí)錯(cuò)誤,通查程序里面的new、delete、malloc、free等內(nèi)存操作,delete和delete是否混用 如果對(duì)象有引用計(jì)數(shù),查看計(jì)數(shù)是否有問(wèn)題??梢允褂谜{(diào)試器分析是誰(shuí)在申請(qǐng)內(nèi)存而沒(méi)釋放,VC里面可以直接在C/C+運(yùn)行庫(kù)源代碼里面設(shè)斷點(diǎn),其它平臺(tái)通過(guò)重載全局的new、delete或者使用hook技術(shù)鉤住malloc、free后,再在重載/鉤子函數(shù)里設(shè)斷點(diǎn)。 用VC自帶的內(nèi)存檢測(cè)機(jī)制(調(diào)試運(yùn)行程序,正常退出后檢查內(nèi)存信息)。使用Visual Leak Detec

10、tor。 其它方法,如打印、程序折半法等。,內(nèi)存溢出/越界,得到寫越界/出錯(cuò)的內(nèi)存地址(分為全局heap內(nèi)存和函數(shù)局部stack內(nèi)存),并監(jiān)控該內(nèi)存的內(nèi)容,接著單步執(zhí)行程序,找到引起該內(nèi)存變化的語(yǔ)句,此語(yǔ)句就是導(dǎo)致內(nèi)存越界的直接原因,然后再深入分析,找出真正bug。,多線程死鎖,理解程序發(fā)生死鎖的機(jī)理。 建議程序里面的線程同步對(duì)象全部使用 npsync.h、ILocker.h、NPRWLock.h等代碼庫(kù)里面的函數(shù),struct tagOSMutex:lockedThreadID,即專為解決死鎖而設(shè)計(jì)。 程序發(fā)生死鎖后利用調(diào)試器的線程切換和堆棧查看能力配合lockedThreadID信息,一般

11、來(lái)說(shuō)可以很快找到死鎖原因。 如果死鎖實(shí)在不能避免,建議改造程序邏輯,使用TryLock、SendTimeout等方式。,發(fā)布版本的調(diào)試,使用map文件。 使用手工插入軟斷點(diǎn) int 3,直接查看匯編指令。 查看程序CPU、內(nèi)存、各種句柄使用情況(windows的任務(wù)管理器,linux top命令)。 原始方式:打印,printf()、OutputDebugString(),分析只在發(fā)布版本才會(huì)出現(xiàn)的問(wèn)題A,首先需要理解發(fā)布版本和調(diào)試版本的不同。 發(fā)布版本沒(méi)有任何調(diào)試相關(guān)代碼,檢查是否有錯(cuò)用VERIFY為ASSERT的地方。 發(fā)布版本一般的內(nèi)部不會(huì)有初始化動(dòng)作,而在調(diào)試版本,編譯器為了便于調(diào)試,

12、一般會(huì)對(duì)內(nèi)存做初始化。 如VC在調(diào)試版本會(huì)用0 xCC初始化所有自動(dòng)變量,用0 xCD填充new出來(lái)的內(nèi)存,用0 xDD填充delete的內(nèi)存,用0 xFD填充受保護(hù)的內(nèi)存(動(dòng)態(tài)分配內(nèi)存的前后地址),以上值都是比較大的奇數(shù),這樣便于查錯(cuò)。,分析只在發(fā)布版本才會(huì)出現(xiàn)的問(wèn)題B,發(fā)布版本會(huì)優(yōu)化掉一些不必要的操作和變量。 如優(yōu)化掉一些局部變量就會(huì)引發(fā)一些只會(huì)在發(fā)布版本發(fā)生的錯(cuò)誤,如:【理解x86體系的CPU的堆棧地址是遞減的,著名的c語(yǔ)言buffer溢出攻擊即是基于此理】 int a; char ch4; /* */ 變量a沒(méi)有使用或者只是在調(diào)試版本使用,當(dāng)對(duì)ch發(fā)生向后越界操作時(shí)(小于4bytes的

13、越界),在調(diào)試版本因?yàn)橛凶兞縜,不會(huì)產(chǎn)生錯(cuò)誤,但發(fā)布版本int a可能被優(yōu)化掉,則會(huì)引發(fā)堆棧錯(cuò)誤。 檢查有使用#ifdef _DEUBG的地方是否會(huì)導(dǎo)致調(diào)試版本和發(fā)布版本有邏輯差異。 也有可能因?yàn)槭褂孟到y(tǒng)庫(kù)的不同,如MFC庫(kù),引發(fā)一些差異性錯(cuò)誤。,多平臺(tái)調(diào)試,若程序不是特別平臺(tái)相關(guān),應(yīng)盡量讓程序可以在多個(gè)平臺(tái)下編譯運(yùn)行,比如在linux平臺(tái)不易查的問(wèn)題,可以到win32平臺(tái)下來(lái)查。盡量使用標(biāo)準(zhǔn)C庫(kù)、stl以及codelib里面的跨平臺(tái)庫(kù)。,性能分析和優(yōu)化A,利用x86的RDTSC指令進(jìn)行精確的時(shí)間統(tǒng)計(jì)(在多核CPU系統(tǒng)下使用該指令需要小心) 。 對(duì)程序進(jìn)行時(shí)間復(fù)雜度統(tǒng)計(jì)。 利用編譯器提供的統(tǒng)計(jì)

14、功能,如gcc的GPROF(參考Makefile)。 使用調(diào)試器配合軟斷點(diǎn)(int 3)查看發(fā)布版本的匯編代碼,了解代碼在發(fā)布版本里對(duì)應(yīng)的實(shí)際執(zhí)行指令。 優(yōu)化,找到關(guān)鍵問(wèn)題所在,記得二八原則,即程序80%的時(shí)間在執(zhí)行20%的代碼。寫一個(gè)模塊(函數(shù)、類)的時(shí)候,時(shí)刻想到是可讀性重要,還是性能重要。 分清楚什么時(shí)候該用ASSERT,什么時(shí)候該用錯(cuò)誤處理邏輯,常見的做法是在最底層函數(shù)使用ASSERT聲明所有的非法情況,在上層函數(shù)使用錯(cuò)誤邏輯處理,這樣既保證了正確性,也獲得了發(fā)布版本的效率。例:在TYPE* CAutoObj:operator - () 對(duì)對(duì)象是否合法的ASSERT檢測(cè),為了效率,此處

15、顯然不應(yīng)該用錯(cuò)誤處理(非法時(shí)返回NULL)。,性能分析和優(yōu)化B,沒(méi)必要在某些問(wèn)題上耗費(fèi)我們的時(shí)間,現(xiàn)代編譯器對(duì)于有些優(yōu)化比你做的更好,如:a/2 - a1,沒(méi)必要把整數(shù)乘法變成位移,破壞了程序可讀性,這件事情編譯器會(huì)幫你做。時(shí)刻謹(jǐn)記編譯時(shí)刻常量(C+摸板元編程)是不會(huì)耗費(fèi)任何執(zhí)行時(shí)刻開銷的,比如定義 #define XXX (12) 就比 #define XXX 0 x8可讀性好。 盡量減少大數(shù)據(jù)拷貝動(dòng)作,在讀磁盤和內(nèi)存緩存之間做權(quán)衡。減少網(wǎng)絡(luò)訪問(wèn)次數(shù)和傳輸?shù)臄?shù)據(jù)量。 怎么節(jié)約內(nèi)存和避免內(nèi)存碎片【在服務(wù)器程序和內(nèi)存受限系統(tǒng)中這是個(gè)重要問(wèn)題】 1、內(nèi)存池、重載new和delete,class S

16、ameSizeMemMgr。 2、在堆棧夠用的情況下盡量使用堆棧內(nèi)存,即盡量使用局部對(duì)象這也有利于編譯器優(yōu)化。如當(dāng)一個(gè)數(shù)值的長(zhǎng)度是常數(shù)編譯時(shí)刻確定的數(shù),則一般使用局部數(shù)組。推薦大家盡量使用class CSmartBuf和class CSmartArray來(lái)定義局部數(shù)組對(duì)象。 3、盡量減少new和delete的使用,建議使用自動(dòng)對(duì)象包容摸板class CAutoObj。,C/C+語(yǔ)言的一些陷阱A,整數(shù) 1、回環(huán)問(wèn)題,怎樣用tick統(tǒng)計(jì)時(shí)間長(zhǎng)度; 2、擴(kuò)展問(wèn)題,如16位擴(kuò)展到32; 3、位移問(wèn)題,如: int32 33不是我們想象的0,而是與int32 1等價(jià),因?yàn)閏/c+編譯器為了效率直接使用了

17、硬件移位,而很多硬件指令的位移就是這么做的。另:在32位平臺(tái)下VC和gcc實(shí)現(xiàn)的INT64 對(duì)于 int64 65結(jié)果為0 而不是等價(jià)于 int64 1。 宏,別忘了在宏里面加括號(hào),如:#define XXX(a) a10 則必須寫為#define XXX(a) (a)10)。宏不是函數(shù)。宏不是類型定義。 C+的自動(dòng)類型轉(zhuǎn)換,如果不想被隱含使用,就在構(gòu)造函數(shù)前面加上explicit, struct A A(int i); ; struct A A(int i, int b=0); ; 是需要加explicit 的兩種形式。小心使用 operator TYPE()。 對(duì)VC6 for語(yǔ)句bug的

18、修正 #define for if(0) (void)0); else for 見 msc_opt.h“。 intel CPU浮點(diǎn)數(shù)和mmx問(wèn)題,emms指令。 比sizeof(ar)/sizeof(ar0)更好的數(shù)組長(zhǎng)度計(jì)算子 ARRAY_LEN, 見 “nprbase.h”。,C/C+語(yǔ)言的一些陷阱B,關(guān)于0的特殊用法,在C+里,0可以是int類型,也可以是任何指針類型,如NULL一般定義如下#define NULL 0,就會(huì)導(dǎo)致如: int index = NULL;這樣就可能有潛在邏輯錯(cuò)誤的代碼被編譯過(guò)(正確應(yīng)該是int index = -1)。 bool和BOOL問(wèn)題,sizeof(bool)和sizeof(BOOL)可能不等,BOOL和int可能是同一個(gè)類型。 std:string作為printf()的可變參數(shù)(C語(yǔ)言可變參函數(shù)語(yǔ)法陷阱)會(huì)導(dǎo)致程序崩潰。 語(yǔ)言庫(kù)提

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論