内存泄漏工具VLD1.0_要点分析
0X01 关闭FPO优化
// Frame pointer omission (FPO) optimization should be turned off for this // entire file. The release VLD libs don't include FPO debug information, so FPO // optimization will interfere with stack walking. #pragma optimize ("y", off) |
Release版本的VLD库不包含FPO调试信息,进而影响栈回溯,所以文件开头关闭FPO优化;
0X02 内存泄漏检测器全局对象
// The one and only VisualLeakDetector object instance. This is placed in the // "compiler" initialization area, so that it gets constructed during C runtime // initialization and before any user global objects are constructed. Also, // disable the warning about us using the "compiler" initialization area. #pragma warning (disable:4074) #pragma init_seg (compiler) VisualLeakDetector visualleakdetector; |
对于同一个文件来说,全局变量的构造顺序是按照全局变量的声明顺序来构造的。但对于不同文件来说,不同文件间的全局变量构造顺序是不确定的。在C++标准中,只要求“处于同一编译但与的全局对象按其声明顺序初始化并倒序析构”,但并没有对处于不同编译单元的全局对象的初始化顺序做出要求。
通过init_seg预处理器指令中,:
#pragma init_seg(compiler) #pragma init_seg(lib) #pragma init_seg(user) #pragma init_seg("user_defined_segment_name") |
编译器、库、用户和用户定义段,按顺序依次初始化(但据实测,用户和用户定义段的优先级相同);
注:一个源文件中只能出现一次init_seg指令;且编译器段是微软保留给C/C++运行时库用的,一般情况下我们不应该使用它。
VLD为了保证尽可能的全面诊断内存泄漏,势必需要在其他全局变量构造前完成初始化。VLD就利用init_seg了处理器指令,让VLD的VisualLeakDetector在绝大多数情况下早于其他全局变量进行构造和初始化,即上面的实现。
#pragma warning (disable:4074):用于屏蔽C4074编译器警告,否则编译时会提示:warning C4074: initializers put in compiler reserved initialization area
0X03 设置内存开辟钩子
VisualLeakDetector::VisualLeakDetector () { 。。。 。。。 else if (linkdebughelplibrary()) { // 显示加载dbghelp.dll中必要的API // Register our allocation hook function with the debug heap. m_poldhook = _CrtSetAllocHook(allochook); report("Visual Leak Detector Version "VLD_VERSION" installed ("VLD_LIBTYPE").\n"); reportconfig(); 。。。 。。。 } } |
|
0x04 钩子函数
// 该函数不用考虑线程安全问题,CRT有调试堆有锁定 int VisualLeakDetector::allochook (int type, void *pdata, size_t size, int use, long request, const unsigned char *file, int line) { static bool inallochook = false; int status = true;
// 1. 防止递归;2.不处理CRT内部使用的内存块 if (inallochook || (use == _CRT_BLOCK)) { if (visualleakdetector.m_poldhook) { status = visualleakdetector.m_poldhook(type, pdata, size, use, request, file, line); } return status; } inallochook = true;
// Call the appropriate handler for the type of operation. switch (type) { case _HOOK_ALLOC: visualleakdetector.hookmalloc(request); break;
case _HOOK_FREE: visualleakdetector.hookfree(pdata); break;
case _HOOK_REALLOC: visualleakdetector.hookrealloc(pdata, request); break;
default: visualleakdetector.report("WARNING: Visual Leak Detector: in allochook(): Unhandled allocation type (%d).\n", type); break; }
if (visualleakdetector.m_poldhook) { status = visualleakdetector.m_poldhook(type, pdata, size, use, request, file, line); } inallochook = false; return status; } |
|
0X05 栈回溯
#if defined(_M_IX86) || defined(_M_X64) #pragma auto_inline(off) DWORD_PTR VisualLeakDetector::getprogramcounterx86x64 () { DWORD_PTR programcounter;
// Get the return address out of the current stack frame __asm mov AXREG, [BPREG + SIZEOFPTR]
// Put the return address into the variable we'll return __asm mov [programcounter], AXREG
return programcounter; } #pragma auto_inline(on) #endif // defined(_M_IX86) || defined(_M_X64)
void VisualLeakDetector::getstacktrace (CallStack *callstack) { DWORD architecture; CONTEXT context; unsigned int count = 0; STACKFRAME64 frame; DWORD_PTR framepointer; DWORD_PTR programcounter;
// Get the required values for initialization of the STACKFRAME64 structure // to be passed to StackWalk64(). Required fields are AddrPC and AddrFrame. #if defined(_M_IX86) || defined(_M_X64) architecture = X86X64ARCHITECTURE; programcounter = getprogramcounterx86x64(); __asm mov [framepointer], BPREG // Get the frame pointer (aka base pointer) #else // If you want to retarget Visual Leak Detector to another processor // architecture then you'll need to provide architecture-specific code to // retrieve the current frame pointer and program counter in order to initialize // the STACKFRAME64 structure below. #error "Visual Leak Detector is not supported on this architecture." #endif // defined(_M_IX86) || defined(_M_X64)
// Initialize the STACKFRAME64 structure. memset(&frame, 0x0, sizeof(frame)); frame.AddrPC.Offset = programcounter; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = framepointer; frame.AddrFrame.Mode = AddrModeFlat;
// Walk the stack. while (count < _VLD_maxtraceframes) { count++; if (!pStackWalk64(architecture, m_process, m_thread, &frame, &context, NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) { // Couldn't trace back through any more frames. break; } if (frame.AddrFrame.Offset == 0) { // End of stack. break; }
// Push this frame's program counter onto the provided CallStack. callstack->push_back((DWORD_PTR)frame.AddrPC.Offset); } } |
StackWalk64进行栈回溯需要精确的知道栈从哪里开始。这需要提供当前的栈帧地址和当前的EIP(RIP)寄存器值,而通过GetThreadContext获取的当前线程上下文对于一个正在运行的线程是不可靠的。所以这里通过了内联汇编代码实现,即getprogramcounterx86x64()函数的作用,其返回函数的返回地址,当函数返回时即当前的EIP(RIP)寄存器值。