User breakpoint called from code at 0x7740240f 已解決
今天在除錯QQ醫生時,突然蹦出來了這麼個錯誤,使上了渾身解數,也沒有定位到問題所在,只知道在 delete 一個堆上的物件時,程式就崩潰了,彈出了下面的對話方塊:
挺奇怪的吧,而且往往之前還彈出一個對話方塊:
提示說堆記憶體被破壞,有時候這樣的錯誤在比較小的程式裡面也許不會對整個程式造成破壞,依然能夠繼續執行,但是千萬不要放過,像這種破壞堆記憶體的隱藏BUG,說不準哪天就會造成整個軟體的crash。另外我要提醒的是,release版本也許什麼提示都沒有,直接放過了,這是因為在debug下,作業系統用DebugWin32Heap來代替正常的heap分配記憶體空間。在這個堆上的任何操作,debug的堆管理器會檢查堆的資料完整性,如果它發現了一個錯誤,就會報告一個訊息上來。
我們可以對這種情況作一個猜測,既然是delete的時候出了問題,那就是這個程式很可能去訪問了“非法”的記憶體,這裡的非法記憶體是指不是由你的程式分配的記憶體塊,但是被你的程式在某種情況下訪問到了,當然這是堆上的情況,所以在release下可能一時不會出問題,如果是棧上,程式也許早就是crase或者出現莫名其妙的錯誤了。
現在問題比較集中了,但是整個程式在堆上分配了那麼多物件,到底哪次分配出了問題?這還是很難定位到錯誤,用BoundsChecker完整跑一遍是一個好辦法,但是比較麻煩,其實微軟已經給我們想到了辦法,試想如果在每次分配的記憶體塊邊界做限制,設定為虛擬記憶體,也就是NO_ACCESS(不可訪問),那程式試圖讀寫這個地方的時候,就會出錯,程式會馬上斷下來,也就是所謂的PageHeap機制。
要讓我們的程式啟用Full Page Heap機制,需在登錄檔中
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options/appName.exe下增加如下設定,
屬性名:GlobalFlag,字串型別,值:x02200000
屬性名:PageHeapFlags,字串型別,值:0x3
屬性名:VerifierFlags,DWORD型別,值:1
另外替換appName.exe為你正在除錯的真正的程式檔名,如果你安裝了Debugging Tools for Windows,這一切會更加簡單,在安裝目錄下有個gflags工具,直接用命令列執行 gflags –p /enable appName.exe /full 即可自動幫你新增上述登錄檔值,如果你沒有該工具,你可以去
DLL也是可以用這個機制的,登錄檔的設定有點區別,如下:
GlobalFlag,字串型別,值是0x02000000
PageHeapTargetDlls,字串型別,值是除錯的dll名稱,不帶路徑
VerifierFlags,DWORD型別,值是00000001
PageHeapFlags,字串型別,0x403
或者使用命令列 gflags –p /enable appName.exe /full /dlls dllName.dll 這裡要注意的是,登錄檔的子健值應該是dll依附的exe程式名。
好,使用該機制後再次除錯,果然斷下來了,在一個物件裡面的結構體裡面出錯了,這個結構體是該類的最後一個成員,該結構體的最後四個DWORD的值顯示是無效的,把這個結構體放在前面後,這個類的最後四個DWORD大小的空間還是無效的,現在可以知道,分配記憶體的時候,根本沒有給該物件分配足夠的記憶體,而delete的時候卻按該物件的大小來釋放,自然就被d堆管理器捕獲了這個錯誤,為什麼會少分配4個dword的大小?後來發現是濫用行內函數造成的,構造和解構函式使用了內聯,其實內聯這個東東,編譯器完全可以無視,也就是說不給你內聯,但是也許又給你內聯,怎麼理解?也就是說內聯不內聯完全由編譯器決定,而程式設計師無法控制,這在win32裡面有個詞叫什麼 - 不可預測,呵呵,既然是強大的不可預測,就不要為了那麼一點點效率來使用內聯了,後來把構造解構函式搬到CPP檔案裡面之後,分配馬上就正常了。
重現上面的bug其實很容易,如下:
// 演示一個BUG
char* pHeap = new char[2];
::lstrcpy(pHeap, "tonglei");
delete pHeap;
參考文獻:
unexpected user breakpoint in ntdll.dll
[END]