淘先锋技术网

首页 1 2 3 4 5 6 7

最近發現Visual C++ 6.0有一個有趣的,能讓programmer發瘋的bug。Debug版本編譯出來的程序可能最終會異常中止,也許是報告程序停在斷點xxxxxxx。更確切地說是用debug 版本的運行時庫編譯的程序。

由於項目需要寫了一個求最短路徑的引擎。 運行穩定(內存,CPU,Handle count 保持未定),但是一段時間後,進程突然退出(crash?)。確切地說是計算了20000+ 對(源點、目標點)後。沒有捕捉到任何異常。就好像有人殺掉進程似的。通過分析在Adplus在進程退出前創建的進程dump,發現是CTRL+C導致進 程退出,堆棧信息顯示計算線程最後調用函數是_heap_alloc_dbg。 這個函數是被由new 或者malloc調用的。

難道是out of memory?可是performance monitor 現實內存穩定,並沒有洩漏。仔細分析dump,發現程序最後一個語句是dbgheap.c的338 行

if (lRequest == _crtBreakAlloc)   //337
           _CrtDbgBreak();                //338

根據msdn,_CrtDbgBreak 用來設定斷點。為什麼呢?閱讀_heap_alloc_dbg函數發現:

  lRequest = _lRequestCurr;

       
       if (lRequest == _crtBreakAlloc)
           _CrtDbgBreak();

       //省略如干行
       ++_lRequestCurr;

也就是說一旦_heap_alloc_dbg被調用,_lRequestCurr就增加1,搜索_lRequestCurr和_crtBreakAlloc,發現定於如下:

static long _lRequestCurr = 1;
_CRTIMP long _crtBreakAlloc = -1L;

並且只有函數_CrtSetBreakAlloc改變_crtBreakAlloc 。在我的程序中沒有地方調用_CrtSetBreakAlloc,所以_crtBreakAlloc 應該保持-1,所以只要_heap_alloc_dbg, 也就是說new 或malloc被不停的調用,即使請求的內存被正確釋放,條件lRequest == _crtBreakAlloc最終會滿足。Yes, that's it!!!

寫了一個簡單的小程序如下,證實了我的想法。

int nCount = 1;
while(1)
{  
  char* p = new char[4];    //cause _lRequestCurr to increase
  delete []p;
  nCount++;
  if(nCount == 0xffffffff)  //_lRequestCurr should reach this value already
break;              //to ensure that if my thought is wrong, loop could break    
}

所以我認為這是debug run-time library的一個bug。如果程序(compiled with vc6 debug version run-time library)中會不停的請求內存,那麼該程序最終會異常中止,只是時間問題。

所幸的是微軟在以後的版本中對這個做了修改,vs2005中已經沒喲這個bug了。具體不知道是從那個版本開始的。

資料來源:http://blog.csdn.net/Miracle08/archive/2006/12/24/1457060.aspx

 

遇到過一個通信方面的軟件,需要長期運行,做壓力測試時,高負荷連續運行一定天數時必定崩潰,而且都是在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;      

extern "C" _CRTIMP long _crtBreakAlloc = -1L;  

_lRequestCurr表示當前的申請次數,_crtBreakAlloc表示當內存申請次數達到某個值時break,即調用_CrtDbgBreak。詳情可參考debugheap.c中的_heap_alloc_dbg_impl函數:

lRequest = _lRequestCurr;

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,問題解決。

資料來源:http://blog.csdn.net/someonea/archive/2008/03/29/2229183.aspx