最近同事的代碼中碰到一個bug會導致奔潰的bug,從dump上看是由於某個對象的堆內存指針被釋放了,但代碼仍調用了該對象指針的虛函數,從而引起內存訪問違法崩潰,由於該類被大量使用,無法直接定位到具體哪個類被提前釋放了,從而打算開啓堆頁檢查,跟蹤該對象堆內存指針被釋放的代碼位置,從而揪出元兇。
由於此bug在win7的機器上不易重現,在xp sp3的機器上較容易重現,故準備在xp sp3的機器上開啓堆頁檢查(DHP),跟蹤該對象指針被釋放的代碼位置和時機,由於未從用過gflag進行堆頁檢查和調試,故先寫了段小代碼練練手:
用gflag開啓堆頁檢查:
設好符號文件後祭出 Windbg 走起,崩潰觸發後斷下,輸入 !heap -p -a ecx 指令一舉揪出元兇,但現實卻是如此的骨感:
004010d9 8b11 mov edx,dword ptr [ecx] ds:0023:0161cff0=????????
0:000> !heap -p -a ecx
ReadMemory error for address eeddccee
Use `!address eeddccee' to check validity of the address.
注:此處 ecx = pCTest"mov edx,dword ptr [ecx]"表示取虛表指針
Windbg提示的讀取內存錯誤,且改地址的內容無法顯示,查看下該內存地址屬性:
0:000> !address ecx
015d0000 : 0161b000 - 000b5000
Type 00020000 MEM_PRIVATE
Protect 00000001 PAGE_NOACCESS
State 00001000 MEM_COMMIT
Usage RegionUsagePageHeap
Handle 015d1000
address命令正確的指示了該地址爲私有堆內存,但該內存頁不可訪問。難道是堆頁開啓不正確?檢查註冊表:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options,確定已經正確的設置了,嘗試其他多種設置,甚至換工具進行設置,但結依然如此。難道是機器問題?於是在win7 32位機器上重複上述過程,發現是可以正確的打印出堆內存指針被釋放的棧回溯的:
但更換其他xp機器依然不能正確顯示。百度搜索無果,仔細看glfag使用說明和各項設置文檔也無果,最後去翻了翻 Windows 調試的權威書《軟件調試》關於頁堆的章節,並按照該書666頁查找棧回溯數據庫的方法查了下所有UST數據庫的回溯記錄,找到了"CTEST* pCTest = new CTEST(); "的棧回溯機率,即申請堆內存的記錄,但始終未找到釋放堆內存的記錄。於是再次懷疑xp下的頁堆並沒有真正啓動或啓動是有問題的,於是檢查下頁堆啓動情況:
驚現“ReadMemory error for address eeddccee”,且只展示一個Page Heap句柄了,剩下的未展示完全,但頁堆明明白白的現實已經開啓,也有了準頁堆,但數據卻顯示不出來,說明數據可能被破壞,但測試代碼如此簡單,而且也被windbg第一時間斷下,不可能去破壞數據,難道是Windbg讀取有問題?於是再次對此疑問進行google,果然有個外國朋友也碰到了類似的問題,其在帖子中提出換成6.6.0007.5版即可解決,試了下果然在xp下順利輸出了用戶態棧回溯。那麼爲什麼高版本的Windbg會出現此問題呢?
我想起在查找UST數據結構的時候,發現和《軟件調試》上寫的不一致,當時疑惑了下沒有在意,再次翻出來對比發現:
Win7下StackTraceDataBase結構
Win7下的 _STACK_TRACE_DATABASE 結構和xp下並不完全相同,關鍵的 Buckets(棧回溯記錄)的結構偏移改了,而且原xp下是個數組,但win7下卻變成了鏈表,故猜測高版本的Windbg在xp下依然使用了win7下的某些數據結構,從而導致Windbg解析出了問題,不知道算不算微軟的bug。
由於低版本的Windbg已經很難找到了,故這裏也放出我找到的6.6.0007.5版:
Widnbg6.6.0007.5.exe