简述
内存泄露一直是一个C++开发程序员需要重点关注的问题。虽然在C++11引入智能指针后,内存泄露有了很大的改善,但未使用智能指针,用错的地方或循环引用等情况下,还是会有很多内存泄露的产生。
在windows系统下用的最多的第三方内存泄漏工具主要有:
-
vld
此工具是通过将库引入工程来做检测,具有侵入性。默认只能检测Debug版,Release版下使用,请点击这里。
类似的原理可以通过自己重写malloc,free等函数和重载new,delete运算符,然后在程序中,不引用系统头文件,而引用我们重写的头文件让相应的函数和运算符优先进到我们的函数来做内存泄漏检测或其他的坏事。 - UMDH
使用方法,请点击这里
Detours
Detours是一个用于重新路由Win32 api的软件包。由于重新路由的是应用层API,所以它是一个应用层Hook机制。
如果某个库重新路由的是驱动层API,则就是驱动层Hook机制,可以监控文件读写,注册表操作等。
编译Detours
该库的编译方法,请点击这里。
Detours的作用
通过该库,我们可以截获任何一个API,让程序进入相应API前,先进入我们的函数,我们可以改写对应的参数,终止调用等,来达到我们想要的目的。这其中就包括我们要做的内存泄露检测的功能。
内存泄露检测关键项
函数Hook的时机,可以在动态库加载时就启动Hook,在程序退出时结束Hook,也可以由用户指定。
- 启动Hook
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
//VS2010
g_pOldMalloc100 = (PFN_MALLOC)DetourFindFunction("msvcr100.dll", "malloc");
DetourAttach(&(PVOID&)g_pOldMalloc100, MyMalloc100);
//VS2013
g_pOldMalloc120 = (PFN_MALLOC)DetourFindFunction("msvcr120.dll", "malloc");
DetourAttach(&(PVOID&)g_pOldMalloc120, MyMalloc120);
DetourTransactionCommit();
- 结束Hook
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)g_pOldMalloc100, MyMalloc100);
DetourDetach(&(PVOID&)g_pOldMalloc120, MyMalloc120);
DetourTransactionCommit();
- 重写的malloc函数
void* __cdecl MyMalloc100(
_In_ _CRT_GUARDOVERFLOW size_t _Size
)
{
void *pVoid = g_pOldMalloc100(_Size);
char s[MAX_PATH] = { 0 };
sprintf(s, "MyMalloc100, Malloc: %0x, Size: %d Bytes\r\n", pVoid, _Size);
//OutputDebugStringA(s);
//要打印调用堆栈,就不能用自动分配内存的stl对象,或自己new(malloc)空间,否则会陷入死循环
//可以考虑在栈空间中申请内存,然后写入文件中
MemoryLeaker *pLeaker = &g_array[g_nIndex];
g_nIndex++;
pLeaker->pAddress = pVoid;
pLeaker->nSize = _Size;
//先将堆栈指针保存下来,等到退出Hook后,再获取堆栈信息,如果再此时获取,则会陷入死循环
pLeaker->frames = CaptureStackBackTrace(0, MAX_STACK_FRAMES, pLeaker->pStack, NULL);
return pVoid;
}
- 打印堆栈
void *pStack[MAX_STACK_FRAMES];
WORD frames = CaptureStackBackTrace(0, MAX_STACK_FRAMES, pStack, NULL);
std::ostringstream oss;
oss << "stack traceback: " << std::endl;
for (WORD i = 0; i < frames; ++i) {
DWORD64 address = (DWORD64)(pStack[i]);
DWORD64 displacementSym = 0;
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = MAX_SYM_NAME;
DWORD displacementLine = 0;
IMAGEHLP_LINE64 line;
//SymSetOptions(SYMOPT_LOAD_LINES);
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
if (SymFromAddr(m_hProcess, address, &displacementSym, pSymbol)
&& SymGetLineFromAddr64(m_hProcess, address, &displacementLine, &line)) {
oss << "\t" << pSymbol->Name << " at " << line.FileName << ":" << line.LineNumber << "(0x" << std::hex << pSymbol->Address << std::dec << ")" << std::endl;
}
else {
oss << "\terror: " << GetLastError() << std::endl;
}
}
return oss.str();
注意事项
- Hook后,会检测到所有通过exe申请的内存,包括第三方库或微软的API。
- 在Hook的malloc等函数中,尽量不要动态内存分配,否则可能导致循环嵌套,最后搞崩程序。
- 打印详细堆栈需要在退出Hook后。
网友评论