遇到过一个通信方面的软件,需要长期运行,做压力测试时,高负荷连续运行一定天数时必定崩溃,而且都是在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,问题解决。