堆異常調試

因爲VS對於堆調試的支持不夠,與堆相關的問題一般較爲棘手,近日,我在使用VS調試平臺SDK時,發現一個問題。程序正常運行沒有問題,當程序退出時,會報告一個堆錯誤異常,如下圖:

類似的錯誤比較常見了,相信很多人都見過,提示也顯而易見,是操作程序的堆時發生了異常。

因爲VS識別程序堆比較困難,遇到這種問題就比較捉襟見肘了,此時,強大的windbg就派上了用場。

Windbg有兩種調試模式,侵入式和非侵入式。在侵入式調試模式下,可以操作程序的線程,控制程序流程,修改內存數據,這也是我們平時使用VS的調試模式,在這種模式下,進程的debugport會被獨佔,因此不能使用兩個調試器侵入同一個進程。而在非侵入模式下,基本不能做任何控制程序流程,修改內存的操作,只能是做一些檢查進程狀態的操作,但這種模式有個好處,它並不佔用進程的debugport,也就是說,可以附加到一個正在被調試的進程上。

打開windbg,按F6附加到進程,此時程序已經被VS調試,因此當附加的時候,要選擇非侵入式調試,即勾上最下面那個noninvasive選項。這樣我們既能利用VS的易操作,界面友好,又能使用windbg查看到一些vs看不到的信息。如下圖所示:

 

附加成功後,windbg顯示了當前進程正在執行的命令,敲入kb,查看當前線程的調用棧:

從上圖可以看出,該線程在調用free()這個C運行時庫函數,用來釋放一塊內存。Kb命令顯示了函數調用的前三個參數,由於free只有一個參數,即圖中的0975bfa0,所以我們可以斷定,在使用free釋放0975bfa0處的內存時出錯了。

具體什麼錯誤呢,接着往下看,free調用了RtlFreeHeap這個API,通過查詢MSDN,得到這個函數的聲明如下:

BOOLEAN RtlFreeHeap(

 _In_      PVOID HeapHandle,

 _In_opt_  ULONG Flags,

 _In_      PVOID HeapBase

);

第一個參數是堆的句柄,第二個是一個標識,第三個參數是要釋放的內存地址。從函數的調用棧中,可以看出,第一個參數爲030a0000,第二個參數爲00000000,第三個參數爲0975bfa0,即我們要釋放的內存地址。

這樣可以得出了結論,當釋放030a0000這個堆上的地址爲0975bfa0內存塊時發生了異常。

使用windbg查看030a0000這個堆,可以敲入命令!heap 030a0000,查看該堆分配的segment。

通過觀察這個堆發現,0975bfa0這個地址,並不在這個堆上,釋放了一塊不屬於這個堆的內存,難怪會發生問題。那這塊地址到底屬於哪個堆呢?敲入!heap 0 顯示出所有堆的堆塊。

輸出比較長,截取了一部分。

可以看到,0975bfa0恰好位於第19個堆的第二個堆塊範圍內。我們可以進一步驗證,顯示出第19個堆塊的詳細分配情況,輸入命令!heap  -a 03e10000

輸出很長,圖中被標記的堆塊即爲要釋放內存所在地址,它們之間相差了8個字節,這8個字節是管理結構。

到此爲止,一切都清楚了,程序在19號堆申請了一塊內存,卻在5號堆裏面進行釋放,因此出現了問題。

那麼爲什麼會出現這個情況?windows C運行時庫會維護一個CRT堆,以支持new,delete等調用。Delete調用了FREE()這個函數,默認操作的是CRT堆,即本案例中的5號堆,而調用new的時候,默認操作的CRT堆是19號堆,也就是說這個進程中居然有兩個CRT堆。

這種情況一般是因爲兩個模塊引用了不同的CRT堆導致,該demo有兩個模塊,一個是平臺sdk,一個是exe執行文件,檢查下工程配置選項中,可以看到DLL引用的編譯選項是/MT

 

/MT和/MTd表示採用多線程CRT庫的靜態lib版本。該選項會在編譯時將運行時庫以靜態lib的形式完全嵌入。該選項生成的可執行文件運行時不需要運行時庫dll的參加,會獲得輕微的性能提升,但最終生成的二進制代碼因鏈入龐大的運行時庫實現而變得非常臃腫。當某項目以靜態鏈接庫的形式嵌入到多個項目,則可能造成運行時庫的內存管理有多份,最終將導致致命的“Invalid Address specified to RtlValidateHeap”問題。另外託管C++和CLI中不再支持/MT和/MTd選項。

/MD和/MDd表示採用多線程CRT庫的動態dll版本,會使應用程序使用運行時庫特定版本的多線程DLL。鏈接時將按照傳統VC鏈接dll的方式將運行時庫MSVCRxx.DLL的導入庫MSVCRT.lib鏈接,在運行時要求安裝了相應版本的VC運行時庫可再發行組件包(當然把這些運行時庫dll放在應用程序目錄下也是可以的)。因/MD和/MDd方式不會將運行時庫鏈接到可執行文件內部,可有效減少可執行文件尺寸。當多項目以MD方式運作時,其內部會採用同一個堆,內存管理將被簡化,跨模塊內存管理問題也能得到緩解。

因此,通過將各個模塊的C運行庫鏈接選項改爲/MD,可以保證進程內只有同一個CRT堆,這樣就避免了以上問題的產生。

 
————————————————
版權聲明:本文爲CSDN博主「馬大叔小舅舅」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/shuizhilan/article/details/46799657

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