内存检测模块的实现

想实现一个独立的内存检测模块,在以后的项目中可以直接链接,主要想跟踪内存的申请,释放,报告本进程的内存占用情况,求达人给个思路。

评论 (2)链接2012-02-21 
  • 0 支持
    是在什么平台的? – 马瑜 2012-02-21
  • 0 支持
    现在目标是win平台,也有做到*nix系统的打算 – 小仙 2012-02-21

@马瑜@董琛 说的一样,内存检测有很多现有的工具,可以参看:
@如何捕获崩溃进程的内存转储
@常用的程序Debug手段有哪些?
@Linux平台下如何检测、调试C/C++程序内存泄漏?

win平台下:
如果想要自己实现一个独立模块,继续推荐chrome,工程中MemoryWatcher模块(dll)实现类似你要的东西,在win平台下是dll形式载入工程的,内嵌stackwalker,hook了win平台下常用的内存申请释放方法,对内存申请释放等进行记录。根据动态内存操作方式之间单一的层次关系(Windows平台下的内存管理),dll载入初始化的时候,对win平台下原有的内存申请方法进行了方法注入,调用内部实现的方法在进程堆上分配释放存储,完成了基本的申请释放跟踪。win平台下可以基于MemoryWatcher实现你所需要的功能,如果是linux平台,可能chrome的linux版也是有这个功能模块的,自行参考一下就好了。

win下的MemoryWatcher实现源码不怎么好看,因为实现了hook的方式而不是一般实现的方法重载,接触的就稍微底层一点点,不过应该可以满足你的需求。

应要求,稍微详述一下,贴一点代码:
最主要的几个类:MemoryWatcher,MemoryHook,CallStack(参考visualleakdetectorStackWalker)

MemoryWatcher 主要完成方法的hook,以及监测内存的分配与释放。

  
class MemoryWatcher : MemoryObserver {
public:
// Dump all tracked pointers still in use.
void DumpLeaks();
// 申请存储的时候进行跟踪管理
virtual void OnTrack(HANDLE heap, int32 id, int32 size);
// 释放存储的时候释放跟踪
virtual void OnUntrack(HANDLE heap, int32 id, int32 size);

private:
// 提供日志管理操作
void OpenLogFile();
void CloseLogFile();
// 存储申请释放相关方法hook
void Hook();
// 释放存储相关方法hook
void Unhook();

// 利用hash提供快速的跟踪(查找删除等)
// The block_map provides quick lookups based on the allocation
// pointer. This is important for having fast round trips through
// malloc/free.
CallStackMap *block_map_;

}

MemoryHook 主要完成模块内部使用的存储分配工作,内部使用的空间只能是在MemoryHook维护的堆空间上,否则会造成死循环,
同时MemoryHook完成MemoryWatcher的全局注册,内存的分配释放行为都会通知给注册的MemoryWatcher。

  
class MemoryHook : MemoryObserver {
public:
// Initialize the MemoryHook. Must be called before
// registering watchers. This can be called repeatedly,
// but is not thread safe.
static bool Initialize();

// Register a class to receive memory allocation & deallocation
// callbacks. If we haven't hooked memory yet, this call will
// force memory hooking to start.
static bool RegisterWatcher(MemoryObserver* watcher);

// Register a class to stop receiving callbacks. If there are
// no more watchers, this call will unhook memory.
static bool UnregisterWatcher(MemoryObserver* watcher);

// 内部使用的存储空间不受监控,需要在私有堆空间内申请释放
// MemoryHook provides a private heap for allocating
// unwatched memory.
static void* Alloc(size_t size) {
DCHECK(global_hook_ && global_hook_->heap_);
return HeapAlloc(global_hook_->heap_, 0, size);
}
static void Free(void* ptr) {
DCHECK(global_hook_ && global_hook_->heap_);
HeapFree(global_hook_->heap_, 0, ptr);
}

}

在dll入口方法,如下,监测者对象MemoryWatcher的创建,MemoryWatcher调用Hook方法完成内存相关操作的hook,同时完成CallStack初始化操作,将MemoryWatcher注册给MemoryHook以接收内存申请释放消息。入口方法中还创建了一个后台线程监测关键事件,注入heap句柄或指针无效等等,发生此类事情的时候可以生成转储记录日志等。

  
BOOL WINAPI DllMain(HINSTANCE dll_instance, DWORD reason,
LPVOID reserved) {
if (!IsChromeExe())
return FALSE;
switch (reason) {
case DLL_PROCESS_ATTACH:
CreateMemoryWatcher();
CreateBackgroundThread();
break;
case DLL_PROCESS_DETACH:
DeleteMemoryWatcher();
StopBackgroundThread();
break;
}
return TRUE;
}

上面说过这里是通过hook内存申请释放方法辅助监测的,hook发生在MemoryHook::Hook()中,涵盖所有可能的申请释放行为:

  
bool MemoryHook::Hook() {
DCHECK(!hooked_);
if (!hooked_) {
DCHECK(global_hook_);

// Luckily, Patch() doesn't call malloc or windows alloc routines
// itself -- though it does call new (we can use PatchWithStub to
// get around that, and will need to if we need to patch new).

HMODULE hkernel32 = ::GetModuleHandle(L"kernel32");
CHECK(hkernel32 != NULL);

HMODULE hntdll = ::GetModuleHandle(L"ntdll");
CHECK(hntdll != NULL);
// Now that we've found all the functions, patch them
INSTALL_PATCH(HeapCreate);
INSTALL_PATCH(HeapDestroy);
INSTALL_PATCH(HeapAlloc);
INSTALL_PATCH(HeapReAlloc);
INSTALL_PATCH(HeapFree);
INSTALL_PATCH(VirtualAllocEx);
INSTALL_PATCH(VirtualFreeEx);
INSTALL_PATCH(MapViewOfFileEx);
INSTALL_PATCH(MapViewOfFile);
INSTALL_PATCH(UnmapViewOfFile);
INSTALL_NTDLLPATCH(NtUnmapViewOfSection);
INSTALL_PATCH(GlobalAlloc);
INSTALL_PATCH(GlobalReAlloc);
INSTALL_PATCH(GlobalFree);
INSTALL_PATCH(LocalAlloc);
INSTALL_PATCH(LocalReAlloc);
INSTALL_PATCH(LocalFree);
// We are finally completely hooked.
hooked_ = true;
}
return true;
}

举例INSTALL_PATCH的实现,记录来时的路,不然回不了家,看到了do{} while(0)@为什么有时候在C/C++宏里,有毫无意义的do/while和if/else块?

  
// Macro to install Patch functions.
#define INSTALL_PATCH(name) do { \
patch_##name.set_original(GET_PROC_ADDRESS(hkernel32, ##name)); \
patch_##name.Install(&Perftools_##name); \
} while (0)

install过程稍微麻烦一些,感兴趣的话可以自己看一下,在preamble_patcher.cc
举例HeapAlloc与HeapFree的HeapFree"替代者",内部调用了MemoryHook的hook,后者调用了MemoryWatcher的hook,MemoryWatcher的hook中对存储进行跟踪记录。

  
static LPVOID WINAPI Perftools_HeapAlloc(HANDLE hHeap, DWORD dwFlags,
DWORD_PTR dwBytes) {

LPVOID rv = patch_HeapAlloc()(hHeap, dwFlags, dwBytes);
MemoryHook::hook()->OnTrack(hHeap, reinterpret_cast<int32>(rv), dwBytes);
return rv;
}

static BOOL WINAPI Perftools_HeapFree(HANDLE hHeap, DWORD dwFlags,
LPVOID lpMem) {
size_t size = 0;
if (lpMem != 0) {
size = HeapSize(hHeap, 0, lpMem); // Will crash if lpMem is 0.
// Note: size could be 0; HeapAlloc does allocate 0 length buffers.
}
MemoryHook::hook()->OnUntrack(hHeap, reinterpret_cast<int32>(lpMem), size);
return patch_HeapFree()(hHeap, dwFlags, lpMem);
}

其实想一下原理挺简单的,实现起来却有一些难度,特别是chrome的实现。总体流程简单说来就是,先创建一个观察者MemoryWatcher和类似被观察的MemoryHook,在完成了主要的方法hook之后,只要发生了内存申请释放,则MemoryHook通知MemoryWatcher,MemoryWatcher进行一些跟踪处理,进行日志、dump等。

王辉
编辑于 2012-02-22
该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (1)链接 • 2012-02-21
  • 0 支持
    能否详述一下啊 – 小仙 2012-02-21

内存泄漏检查的原理不复杂,但实现起来要花不少功夫。他的实现原理就是接管已有的系统堆分配策略,并把已经分配好的堆放到自己管理的链表中,当释放堆时把堆块从你的链表中移除。当进程退出时,也就是从amin返回时,你管理的链表中的堆块就是内存泄漏了

当然了,这里有很多技巧和细节需要注意,比如你需要额外记录内存块所对应的的文件名和行号,一般都是加在堆的尾部或头部,以便可以以明文的方式查找泄漏位置。

具体实现linux上没我有研究过,windows平台你以参考一下_CrtSetAllocHook接口,当发生堆的申请会触发回调,其它的实现就得自己来实现了。

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (1)链接 • 2012-02-21
  • 0 支持
    内存占用,可以通过系统的API来实现 – 马瑜 2012-02-21

其实已有现成的工具,Valgrind有个Memcheck工具,这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等,你可以看看这篇文章,应用 Valgrind 发现 Linux 程序的内存问题

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (1)链接 • 2012-02-21
  • 0 支持
    您说的这些工具我都清楚,但现在我的需求是实现类似一个独立库,可以很快的集成到工程中。 – 小仙 2012-02-21

执行期内存检测分为三种:
一种是嵌入调试代码,比如重载或接管new,或者allocate等.
一种是ApiHook,Hook堆和虚拟内存申请的函数,进行处理
大部分工具都是前两种.
但是还有很牛B的第三种,目前没有哪个工具有实现这种的,作为一种思路,可以参考.
就是指针引用搜索,原理是:先枚举所有堆,和私有内存,然后一个一个处理.比如当处理内存块A时,就在A以外的堆,私有内存,可执行文件data段以及所以线程ESP以上的栈中搜索该内存的指针,逐步搜索,直至有一个指针存在与线程ESP以上或者可执行文件data段为止.如果不存在这样一个指针链,则基本为泄漏(也有存在C++对象继承导致的引用的指针并非真实的申请内存起始地址的情况).
可以使用第一种方式,因为第二种方式不适合,原因是你是直接加到工程中的,通常使用第二中方式的是需要一个另外的程序主动启动目标进程,然后在目标进程代码没有开始执行时进行Hook,否则对全局类的构造函数是Hook不了滴.

Geo5
Geo5
463
编辑于 2012-06-08
该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2012-06-07

重载new操作符吧,可以消除大部分情况下的内存泄露问题

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2012-10-09

可以在编码时为每种结构体内存申请添加计数,类似

  
enum mem_type {
MTYPE_LINE,
MTYPE_CIRCLE,
MTYPE_MAX,
};

然后开辟一个数组

  
unsigned int mem_cnt[MTYPE_MAX];

定义私有的malloc/free函数

  
void *mtype_alloc(enum mem_type type, size_t size)
{
void *ptr;
ptr = malloc(size);
if (ptr) {
mem_cnt[type]++;
}
return ptr;
}
  
void *mtype_free(enum mem_type type, void *ptr)
{
if (ptr) {
mem_cnt[type]--;
free(ptr);
}
}

这样只要打印数组值的大小就可以知道每种结构体申请的数量。

该答案已被锁定,无法对其进行评论,编辑及投票。
()
评论 (0)链接 • 2012-11-10

不是您所需,查看更多相关问题与答案

德问是一个专业的编程问答社区,请 登录注册 后再提交答案