遇到過一個通信方面的軟件,需要長期運行,做壓力測試時,高負荷連續運行一定天數時必定崩潰,而且都是在msvcrtd.dll中崩潰。負責維護的人百思不得其解,就去問微軟的人,結果微軟的人說這是VC6帶的msvcrtd.dll的一個問題,VC2005已經沒有這個問題了,請升級到新的版本。這個軟件規模比較大,依賴於很多庫,後臺都是用VC6編譯的調試版本,爲了方便定位問題,沒有Release版本。升級到VC2005後會不會出現別的問題,沒有人敢冒這個風險,於是沒有使用VC2005。
閒着沒事的時候分析了一下,才發現問題其實很簡單。msvcrtd.dll對每次內存申請都進行計數,當計數值達到設定的某個值時,就會調用_CrtDbgBreak()。MSDN對_CrtDbgBreak的說明是:Sets a break point on a particular line of code,其實_CrtDbgBreak在X86下只有一條指令就是int 3(0xCC)。
在dbgheap.c中定義了下面兩個變量:
static long _lRequestCurr = 1; /* Current request number */
extern "C" _CRTIMP long _crtBreakAlloc = -1L; /* Break on allocation by request number */
_lRequestCurr表示當前的申請次數,_crtBreakAlloc表示當內存申請次數達到某個值時break,即調用_CrtDbgBreak。詳情可參考debugheap.c中的_heap_alloc_dbg_impl函數:
lRequest = _lRequestCurr;
/* break into debugger at specific memory allocation */
if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)
_CrtDbgBreak();
VC6附帶的dbgheap.c中沒有添加_crtBreakAlloc != -1L的判斷,而是:
if (lRequest == _crtBreakAlloc)
_CrtDbgBreak();
_lRequestCurr初始化爲1,每次申請內存都加1,當_lRequestCurr爲-1時在VC6的dbgheap.c中就會觸發int 3導致程序退出,而在新的版本中添加了_crtBreakAlloc != -1L的判斷,所以默認的情況下是不會觸發int 3 退出的。
可以通過調用_CrtSetBreakAlloc設置_crtBreakAlloc的值,當我們設置了新的_crtBreakAlloc,而且_crtBreakAlloc等於_lRequestCurr時就會觸發int 3。
弄清楚了問題的所在,我們就可以着手解決問題了。VC6的dbgheap.c中有兩個地方判斷了lRequest 是否與_crtBreakAlloc相等,相等後執行指令int 3。我們不用複雜的處理,把int 3替換爲nop(0x90)指令即可。首先得到“if (_crtBreakAlloc != -1L && lRequest == _crtBreakAlloc)” 對應的二進制指令,用UE打開msvcrtd.dll,使用16進制編輯模式,查找得到的二進制指令,發現確實只有二處,把緊接着它們的0xCC替換爲0x90,問題解決。