(vc base)除蟲記之十二:費解的NTDLL斷點

From

http://blog.vckbase.com/hyj/archive/2006/06/28/21006.html

 

處理在NTDll中意外的用戶斷點

很久沒有寫東西了,這次是爲了完善很久很久以前寫的一個培訓ppt(VC的使用與調試技巧),纔想起來寫點東西的。
下面的文章參考了http://www.debuginfo.com/tips/userbpntdll.html ,但不是翻譯,偶英語太爛了。


我們在調試程序的過程中,有時會突然的顯示一個對話框,上面顯示這樣一條信息:
User breakpoint called from code at 0x77fa018c
或者是
Unhandled exception at 0x77f767cd (ntdll.dll) in myapp.exe: User breakpoint.
不過我遇到過的都是第一條信息,沒有遇到過第二條信息。

怎麼回事?我們沒有設置斷點呀!爲什麼會有一個用戶斷點?
並且這個問題看起來並沒有那麼嚴重,不在調試狀態下,程序正常運行,即使在調試狀態下我們把這個對話框按了確定後,再繼續F5,好像什麼事情也沒有發生,程序仍然在正常運行!

隱患!千萬不要忽視她!這個信息告訴我們,程序中某個地方已經開始潰爛,如果你頻繁的碰到這個對話框,就說明潰爛已經擴大了。

如果你夠仔細,你會發現在你點了這個對話框的確定按鈕之後,會在Output窗口中發現多了一行信息:
HEAP[DebugInfo2.exe]: Heap block at 00030FD8 modified at 00031010 past requested size of 30

查看堆棧窗口,裏面顯示這樣的信息:
NTDLL! 7c921230()
NTDLL! 7c97db9c()
NTDLL! 7c98cd11()
NTDLL! 7c980af8()
KERNEL32! 7c85e7af()
_CrtIsValidHeapPointer(const void * 0x00031000) line 1697
_free_dbg(void * 0x00031000, int 0x00000001) line 1044 + 9 bytes
operator delete(void * 0x00031000) line 49 + 16 bytes
WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00151f15, int 0x00000001) line 13 + 15 bytes
WinMainCRTStartup() line 198 + 54 bytes
KERNEL32! 7c816d4f()

重現這樣的現場很容易的,只需幾行代碼就可以了
char *p = new char[4];
lstrcpy(p, "this is a test");
delete p;

爲什麼會有這樣的信息消息框出現呢?這是因爲如果我們在調試狀態下,操作系統用DebugWin32Heap來替代正常的heap分配內存空間。在這個堆上的任何操作,debug的堆管理器會檢查堆的數據完整性,如果它發現了一個錯誤,就會報告一個消息上來。

那麼,我們怎麼樣才能知道造成錯誤的原因呢?如果只是類似上面的演示代碼,不用任何技巧都能發現並且定位的。但是,絕大多數情況下,Output窗口和CallStack窗口告訴我們的信息都不能讓我們精確定位。

用BoundsChecker做運行期的檢查可以查到這個錯誤的原因,並且我個人認爲在程序發佈之前,在BoundsChecker下完整的跑一遍,檢查內存和資源是非常有必要的。但是每次都用BoundsChecker是比較麻煩的。

還有一個辦法,可以讓這種定位更準確一點。

那就是windows 2000 SP2之後提供的PageHeap機制。

下面的這段文字是別人寫的,我抄的,:)
當一個應用程序的PageHeap機制被激活時,該應用程序的所有的堆分配被放到內存中,這樣堆的 邊界就與虛擬內存的邊界排在一起了。與堆相鄰的虛擬內存頁面被設置爲NO_ACCESS。在該應用程序中對堆後面的空間的訪問就會立刻引起錯誤,這就可以 在一個調試工具中被捕獲。在釋放堆時,過程與之類似。PageHeap修改釋放的應用程序虛擬頁面爲NO_ACCESS,這樣,如果應用程序試圖讀寫該內 存時就會發生訪問錯誤。如果爲一個應用程序運行PageHeap特性,應用程序要比正常時運行得慢,並且需要更多的虛擬內存,因爲每一個堆的分配都需要兩 個完整的虛擬內存頁面。隨着應用程序對堆的使用的增加,可能需要增加系統的虛擬內存的大小,否則會出現虛擬內存不夠的錯誤信息。除非系統有相當大的虛擬內 存,否則建議不要同時運行兩個以上的激活了PageHeap特性的應用程序。

讓我們的程序啓用Full Page Heap機制,只需在註冊表中
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options/YourAppName.exe下增加如下設置:
GlobalFlag,字符串類型,值是x02200000
PageHeapFlags,字符串類型,值是0x3
VerifierFlags,DWORD類型,值是00000001
其中YourAppName.exe換成你的程序的名字,不要帶路徑。

如果嫌麻煩,你可以到http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx 下載Debugging Tools for Windows,安裝後,有一個gflags工具,使用下面的命令:
gflags –p /enable YourAppName.exe /full
這個工具幫你寫好了上面的註冊表項目。
這個工具是一個GUI的程序,你可以雙擊並且設置相關調試入口。

如果設置了Full PageHeap模式,我們在調試運行的時候,那麼就會在彈出那個令人費解的斷點對話框之前出現一個斷言對話框,哈哈,這個對話框可是有一個Retry按鈕的,點擊之後進入CallStack,你可以看到此時發生了什麼了。

如果你編寫的是一個dll,也可以使用這個機制的,不過註冊表項下的值就要設置成如下:
GlobalFlag,字符串類型,值是0x02000000"
PageHeapTargetDlls,字符串類型,值是調試的dll名稱,不帶路徑
VerifierFlags,DWORD類型,值是00000001
PageHeapFlags,字符串類型,0x403

或者使用命令行
gflags –p /enable YourAppName.exe /full /dlls YourDll.dll

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章