windows程序員進階系列:《軟件調試》之Win32堆的調試支持
Win32堆的調試支持
為了幫助程序員及時發現堆中的問題,堆管理器提供了以下功能來輔助調試。
1:堆尾檢查(Heap Tail Check) HTC,在堆尾添加額外的標記信息,用于檢測堆塊是否溢出。
2:釋放檢查(Heap Free Check)在釋放堆塊時進行檢查,防止釋放同一個堆塊。
3:參數檢查,對傳遞給堆的各種參數進行更多的檢查。
4:調用時驗證(Heap Validate On Call)HVC,每次調用堆函數時都對整個堆進行驗證和檢查。
5:堆塊標記(Heap Tagging)為堆塊增加附加標記,以記錄堆塊的使用情況。
6:用戶態棧回溯(User Mode Stack Trace)UST,將每次調用堆函數的函數調用信息記錄到一個數據庫中。
7:專門用于調試的頁堆(Debug Page Heap)DHP堆,頁堆比較常用,且需要專門開啟,我們會專門對其進行介紹。
創建堆時,堆會根據當前進程的全局標志來決定是否 啟用堆的調試功能。操作系統在加載一個進程時會在注冊表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Image File Execution Option表鍵下尋找以該程序命名的子鍵。如果存在該子鍵則讀取下面的GlobalFlag鍵值。
可以使用gflags.exe來編輯系統的全局標志或某個文件的全局標志。
如果在調試器運行一個程序,但注冊表中并沒有設置GlobalFlag鍵值 ,那么操作系統的加載器會默認將全局標志置為0x70,也就是啟用htc、hfc和hpc三項調試功能。
如果注冊表中設置了GlobalFlag鍵值,則使用注冊表中的設置,不再默認提供其他調試選項。
如果是附加到一個已經運行的程序上,則它的全局標志就是注冊表中的值。如果注冊表中不存在全局標志,則為0。
gflags.exe
gflags.exe被稱為全局標志編輯器是,windows調試工具集的一部分。該程序是用于對各個全局標志選項的集中式配置工具。它有gui和控制臺兩種模式。
- GUI模式開啟調試選項
運行gflag.exe,打開gui模式:
可以看到程序分為三個標簽頁:system Registry、Kernel flags 、Image File。
標簽頁System Registry用于設置針對整個系統的選項。設置之后需要重啟系統才能生效。
Image File用于設置針對單個進程的配置。設置之后重啟進程后才會生效。
Kernel Flags用于設置只對內核產生影響的選項。
在gflags中包含了針對操作系統的各個方面的配置信息,這些信息是被保存在注冊表相應的位置上。
單擊Image File標簽頁,在Image:(Tab to Refresh)編輯框內輸入要設置的進程名稱。如calc.exe。設置完成后點擊Tab進行刷新,刷新后下面的各個控件變為可用狀態。在需要設置的調試選項后點勾,確認后即可。
- 控制臺方式
在cmd窗口輸入一下命令來開啟相應的調試功能。
開啟堆尾檢查:gflags /i calc.exe +htc
開啟釋放檢查:gflags /i calc.exe +hfc
開啟調用時驗證:gflags /I calc.exe +hvc
開啟參數檢查: gflags /I calc.exe +hpc
開啟用戶態棧回溯:gflags /I calc.exe +ust
開啟頁堆:gflags /p /enable 程序名 /full
或者gflags /I 程序名 +hpa
需要關閉時只需要將+變為-即可。
在windbg中輸入!gflag開查看開啟的調試選項。
注意,一旦調試之前設置頁堆注冊表相應位置便不再為0,默認便不會開啟hfc、hpc和htc。這一點要特別注意,調試以前要通過!gflag命令查看到底開啟了何種調試選項。
因為在調試器運行一個程序且注冊表中沒有設置GlobalFlag鍵值 時,操作系統會啟用htc、hfc和hpc三項調試功能,且它們原理非常簡單,因此我們此處將主要精力放在經常使用,且需要手動開啟的頁堆上。
頁堆DPH
利用堆尾檢查可以在釋放堆塊時或在下次分配時檢查到堆結構的破壞。但是這些檢查都是滯后的,我們很難知道堆是何時發生的破壞。· 今天我們介紹的頁堆(Debug Page Heap DPH)可以解決這個問題。啟用DPH后堆管理器會在堆塊后增加用于檢測溢出的棧欄頁,一旦用戶數據溢出觸及棧欄頁將立即引發異常,從而讓我們在第一個時間知道堆破壞。
前面介紹的win32堆使用用戶數據區前面的_HEAP_ENTRY結構來描述堆塊,一旦用戶數據超出分配的空間將會覆蓋堆塊后面的數據。有可能覆蓋下一個堆塊的_HEAP_ENTRY結構或是空閑堆塊的_HEAP_FREE_ENTRY結構導致堆被破壞。為了防止堆塊的管理信息被覆蓋后使堆發生不可恢復的破壞。頁堆管理器除了在堆塊用戶區前面存儲堆塊管理信息外,還會將這些管理器信息存儲在節點池內。
啟用頁堆
開啟頁堆可以使用gflags.exe來實現。
- Gui方式開啟DPH。
- 控制臺方式
gflag.exe /I calc.exe +hpa
設置完成后,使用windbg打開進程進行調試。為了驗證是否開始DHP,可以執行!gflag /p命令:
也可以查看全局變量ntdll!RtlpDebugPageHeap的值來驗證是否開啟。當該值為1時表示開啟dhp。
與普通堆相比頁堆有很大的不同,每個堆塊至少占用兩個內存頁。在存放用戶數據的第一個內存頁后面,堆管理器會額外多分配一個內存頁。這個內存頁是用來檢測溢出的,被稱為柵欄頁。柵欄頁的屬性為PAEG_NOACCESS,因此一旦用戶數據發生溢出觸及到柵欄頁便會引發異常,使調試人員第一時間發現問題,從而可以迅速定位到導致溢出的代碼。
有人也許會有疑問,當堆塊非常小時,難道也是占用兩個內存頁么?答案是肯定的。為了及時檢測溢出,堆塊被放到第一個內存頁的末尾緊鄰柵欄頁,因此第一個內存頁前面的大半部分有可能都是沒有被使用的。由于分配粒度為8byte,堆塊和柵欄頁之間可能會有填充字段。對于很小的堆塊也需要占用兩個內存頁,這是很耗費空間的。
測試頁堆在調試中的效果
使用下面的代碼生成HeapTest.exe,并使用windbg調試。
[cpp] view plaincopy
- HANDLE hHeap = HeapCreate(HEAP_NO_SERIALIZE, 0, 1024*1024);
- char * p = (char*)HeapAlloc(hHeap, HEAP_NO_SERIALIZE, 1012);
- char *q = p;
- for(int i = 0 ;i < 1048; i++)
- {
- *q++ = 0;
- }
- bool bRetVal = HeapFree(hHeap, HEAP_NO_SERIALIZE, p);
這段代碼首先創建一個私有堆然后從私有堆分配1012個字節。但卻訪問分配地址后的1048byte的地址,很明顯發生了堆溢出。
第一次我們不開啟任何選項,觀察全局標志:
發現默認開啟了htc、hfc、hpc。
按F5繼續運行,可以看到程序拋出異常而中斷。
第一行的調試信息顯示在訪問堆塊00420648時超過了它的大小3f4 = 1012Byte。而導致了訪問違規。
查看堆棧調用:
可以發現是在對堆塊釋放時檢測到堆塊被破壞。顯然這種方法不能在第一時間中斷到出現為問題的地方。
第二次我們開啟開啟dph來觀察:
首先開啟dhp。gflags /i HeapTest.exe +hpa
在windbg中重新開始HeapTest.exe。可以看到程序發生內存訪問異常。
最后一條指令由于訪問eax所代表的地址而導致。
查看局部變量的值:
可以看到i等于1016時發生了異常。細心的同學可能會發現,我們申請的空間只有1012byte,為什么訪問到1016時才會導致異常。這是因為在堆中的分配粒度是8byte,即分配的空間大小必須為8的倍數。因此此處填充了4個字節。
啟用頁堆后我們在第一時間發現了堆溢出的問題,結合源碼分析便可很容易的找到導致堆溢出的地方。
準頁堆
使用頁堆確實是非常方便的,但是美中不足的是頁堆要為每個堆塊都分配兩個內存頁且只利用第一個內存頁得后半部分,利用率是非常低的。在調試需要使用大量內存的應用程序時有可能會導致一些問題。為此引入了準頁堆。
準頁堆彌補了頁堆的不足,同時還具有頁堆的一些功能。準頁堆也被稱為常規頁堆,頁堆也被稱為完全頁堆。
準頁堆不再為每個堆塊分配柵欄頁,只是在堆塊的前后添加一些類似于安全Cookie的附加標記。當釋放堆塊時,堆管理器會檢測這些標記的完好性。一旦檢測到這些標記被破壞,便會產生異常。這種機制與釋放時檢查hfc類似。
由于是在釋放時檢測,因此準頁堆并不具備頁堆第一時間便能檢測到堆破壞的優點。因此本文并不準備詳細介紹。
啟用準頁堆
準頁堆也需要手動開啟,開啟命令與頁堆很像:gflags /p /enable calc.exe
本文介紹了win32堆的調試支持,重點介紹了頁堆,其他調試功能調試器默認是開啟的,僅以非常小的篇幅介紹。對于一些非常復雜的堆破壞問題,使用其他方式很難發現問題。即使有錯誤報告,錯誤報告處往往距離問題發生地十萬八千里。而使用頁堆卻能很好的解決這個問題。僅僅使用一個命令打開頁堆的調試功能便可以使困擾很久的問題迎刃而解,這也是本文之所以如此推崇頁堆的原因。
下一篇文章將會介紹CRT堆。
from:http://blog.csdn.net/ithzhang/article/details/12786393
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成