Linux下对于程序内存泄漏检测的方法很多,最常用的的莫过于使用valgrind工具。但是valgrind相当于让程序在虚拟机中运行,会带 来较大的系统资源开销,还会对程序的运行效率产生较大影响,对于那种资源占用大的程序,如果需要长时间运行才能暴露的泄漏问题,它就显得不太好用。
linux下的c++程序中自己实现一个轻量级的泄漏检测代码其实是比较方便的,下面我就给出一个简单的范例,并作简单的说明。当然,我们还是应该提倡使用共享指针,用共享指针自动管理内存可以避免内存泄漏这样的不必要的麻烦。
基本原理:
- 利用glibc提供的__malloc_hook, __free_hook系列函数对内存分配是否做监控;(详见glibc的官方文档)
- 利用backtrace函数获取函数调用栈,并记录;
- 利用backtrace_symbols对调用栈对应的函数做解析;
进一步处理:
- 使用abi::__cxa_demangle把函数名解析为源代码风格;
- 使用addr2line解析出函数调用栈对应的代码行;
- 对于动态库(.so)中的地址解析,需要先在/proc/<pid>/maps文件中找到动态库映射的基地址,才能做解析。
注意:
编译连接参数中使用-g -rdynamic
以上每步具体实现的代码可能都没有达到***,甚至可能是笨办法,如果有更好的实现方案请直接替换,也欢迎赐教。
示例代码:
leakmom.cpp
- /* Prototypes for __malloc_hook, __free_hook */
- #include <malloc.h>
- #include <map>
- #include <utility>
- #include <execinfo.h>
- #include <errno.h>
- #include <assert.h>
- #include <cxxabi.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include "leakmon.h"
- CMutexLock gLock ;
- std::map <void*, _PtrInfo> gPtrInfo ;
- std::map <const LmCallStack*, _AllocInfo , __comp> gLeakInfo;
- const int LmCallStack:: MAX_STACK_LAYERS = 32;
- /* Prototypes for our hooks. */
- static void my_init_hook ( void);
- static void *my_malloc_hook ( size_t, const void *);
- static void my_free_hook ( void*, const void *);
- void *(*__MALLOC_HOOK_VOLATILE old_malloc_hook)( size_t __size , const void *) ;
- void (*__MALLOC_HOOK_VOLATILE old_free_hook) ( void *__ptr , const void *);
- /* Override initializing hook from the C library. */
- void (*__MALLOC_HOOK_VOLATILE __malloc_initialize_hook) ( void) = my_init_hook;
- void my_init_hook (void)
- {
- old_malloc_hook = __malloc_hook ;
- old_free_hook = __free_hook ;
- __malloc_hook = my_malloc_hook ;
- __free_hook = my_free_hook ;
- }
- static void *my_malloc_hook ( size_t size , const void *caller )
- {
- void *result ;
- gLock.lock ();
- /* Restore all old hooks */
- __malloc_hook = old_malloc_hook ;
- __free_hook = old_free_hook ;
- /* Call recursively */
- result = malloc (size);
- /* Save underlying hooks */
- old_malloc_hook = __malloc_hook ;
- old_free_hook = __free_hook ;
- /* printf might call malloc, so protect it too. */
- //printf ("malloc (%u) returns %p\n", (unsigned int) size, result);
- RecordPtr( result , size);
- /* Restore our own hooks */
- __malloc_hook = my_malloc_hook ;
- __free_hook = my_free_hook ;
- gLock.unlock ();
- return result ;
- }
- static void my_free_hook ( void *ptr , const void *caller )
- {
- gLock.lock ();
- /* Restore all old hooks */
- __malloc_hook = old_malloc_hook ;
- __free_hook = old_free_hook ;
- /* Call recursively */
- free (ptr );
- /* Save underlying hooks */
- old_malloc_hook = __malloc_hook ;
- old_free_hook = __free_hook ;
- /* printf might call free, so protect it too. */
- //printf ("freed pointer %p\n", ptr);
- RemovePtr( ptr );
- /* Restore our own hooks */
- __malloc_hook = my_malloc_hook ;
- __free_hook = my_free_hook ;
- gLock.unlock ();
- }
- void RecordPtr ( void* ptr, size_t size)
- {
- // 获取调用栈
- void *array [LmCallStack:: MAX_STACK_LAYERS];
- int cstSize = backtrace( array, LmCallStack ::MAX_STACK_LAYERS);
- // 保存指针 调用栈
- LmCallStack* callstack = new LmCallStack(array , cstSize);
- gLock.lock ();
- std::map <const LmCallStack*, _AllocInfo , __comp>:: iterator it = gLeakInfo.find (callstack);
- if (it != gLeakInfo. end())
- {
- it->second .size += size;
- it->second .alloc++;
- _PtrInfo pi (it-> first, size );
- gPtrInfo[ptr ] = pi;
- }
- else
- {
- _AllocInfo aif (size, 1, 0);
- std::pair <std:: map<const LmCallStack*, _AllocInfo, __comp>::iterator , bool> ret = gLeakInfo .insert( std::pair <const LmCallStack*, _AllocInfo >(callstack, aif));
- if (ret .second)
- {
- _PtrInfo pi (ret. first->first , size);
- gPtrInfo[ptr ] = pi;
- }
- else
- {
- // failed
- }
- }
- gLock.unlock ();
- }
- void RemovePtr ( void* ptr )
- {
- gLock.lock ();
- std::map <void*, _PtrInfo>::iterator it = gPtrInfo.find (ptr);
- if (it != gPtrInfo. end())
- {
- std::map <const LmCallStack*, _AllocInfo , __comp>:: iterator itc = gLeakInfo .find( it->second .csk);
- if (itc != gLeakInfo. end())
- {
- itc->second .size -= it->second .size;
- itc->second .free++;
- if (0 == (itc ->second. alloc - itc ->second. free))
- {
- assert(0 == itc ->second. size);
- delete itc ->first;
- gLeakInfo.erase (itc);
- }
- }
- gPtrInfo.erase (it);
- }
- gLock.unlock ();
- }
- void Report ()
- {
- char **strings = NULL;
- gLock.lock ();
- __malloc_hook = old_malloc_hook ;
- __free_hook = old_free_hook ;
- for (std ::map< const LmCallStack *, _AllocInfo, __comp>::iterator it = gLeakInfo .begin();
- it != gLeakInfo .end();
- it++)
- {
- printf("\n" );
- printf("====> size: %ld, allocs: %d, frees: %d, a-f: %d\n", it-> second.size , it-> second.alloc , it-> second.free , it->second .alloc- it->second .free );
- printf("====> stacks back trace:\n" );
- strings = backtrace_symbols ((void**) it->first ->callstack, it->first ->size);
- if (strings )
- {
- for(int i = 2; i < it ->first-> size; i ++)
- { //printf(" %s\n", strings[i]);
- char output [1024] = {0};
- memset(output , 0, 1024);
- char temp [1024] = {0};
- memset(temp , 0, 1024);
- ////
- //// get real function name
- ////
- if (1 == sscanf (strings[ i], "%*[^(]%*[^_]%[^)+]" , temp))
- {
- int status ;
- char* realname = abi::__cxa_demangle (temp, 0, 0, & status);
- if (0 == status )
- {
- char* p = strchr( strings[i ], '(');
- memcpy(output , strings[ i], p-strings [i]);
- sprintf(output +(p- strings[i ]), "(%s+%p) " , realname, (( void**)it ->first-> callstack)[i ]); //printf(" -%s\n", realname);
- free(realname );
- }
- else
- {
- char* p = strchr( strings[i ], ')');
- memcpy(output , strings[ i], p-strings [i]+2);
- }
- }
- else
- {
- char* p = strchr( strings[i ], ')');
- memcpy(output , strings[ i], p-strings [i]+2);
- }
- FILE * fp ;
- char module [1024] = {0};
- memset(module , 0, 1024);
- char* pm = strchr( strings[i ], '(');
- memcpy(module , strings[ i], pm -strings[ i]);
- if (strstr (module, ".so"))
- {
- __pid_t pid = getpid();
- sprintf(temp , "grep %s /proc/%d/maps", module, pid );
- ///
- /// get library base-map-address
- ///
- fp = popen (temp, "r");
- if (fp )
- {
- char baseaddr [64];
- unsigned long long base;
- fgets(temp , sizeof( temp)-1, fp ); //printf("memmap: %s\n", temp);
- sscanf(temp , "%[^-]", baseaddr);
- base = strtoll (baseaddr, NULL, 16); //printf("baseaddr:%s\n", baseaddr); //printf(" base:0x%llx\n", base);
- sprintf(temp , "addr2line -e %s %p", module, (void *)((unsigned long long)((void **)it-> first->callstack )[i]- base));
- }
- }
- else
- {
- sprintf(temp , "addr2line -e %s %p", module, ((void **)it-> first->callstack )[i]);
- }
- ////
- //// get source file name and line number
- ////
- fp = popen (temp, "r"); //printf("cmdline: %s\n", temp);
- if (fp )
- {
- fgets(temp , sizeof( temp)-1, fp ); //printf(" -%s\n", temp);
- strcat(output , temp);
- printf(" -> %s" , output);
- pclose(fp );
- }
- else
- {
- printf(" -> %s\n" , output);
- }
- }
- free(strings );
- strings = NULL ;
- }
- }
- __malloc_hook = my_malloc_hook ;
- __free_hook = my_free_hook ;
- gLock.unlock ();
- }
- //////////////////////////////////////////////////////////////////////////
- CMutexLock::CMutexLock ()
- {
- pthread_mutexattr_t m_attr ;
- pthread_mutexattr_init(&m_attr );
- pthread_mutexattr_settype(&m_attr , PTHREAD_MUTEX_RECURSIVE);
- if (0 != pthread_mutex_init (&m_mutex , & m_attr))
- {
- printf("c_lock::c_lock pthread_mutex_init error<%d>.\n" , errno);
- assert(0);
- }
- pthread_mutexattr_destroy(&m_attr );
- }
- CMutexLock::~CMutexLock ()
- {
- if(0 != pthread_mutex_destroy (&m_mutex))
- {
- printf("c_lock::~c_lock pthread_mutex_destroy error<%d>.\n" , errno);
- assert(0);
- }
- }
- void
- CMutexLock::lock ()
- {
- if(0 != pthread_mutex_lock (&m_mutex))
- {
- assert("c_lock::lock pthread_mutex_lock " && 0);
- }
- }
- void
- CMutexLock::unlock ()
- {
- int iRet = 0;
- if(0 != (iRet = pthread_mutex_unlock(& m_mutex)))
- {
- printf("c_lock::unlock pthread_mutex_unlock ret<%d> error<%d>.\n", iRet, errno );
- assert(0);
- }
- }
- 示例代码:
- leakmom.h
- ////////////////////////////////////////////////////////////////////////
- //
- // The Executable file MUST be linked with parameter '-rdynamic' !!!
- //
- ////////////////////////////////////////////////////////////////////////
- #pragma once
- #include <string.h>
- #include <pthread.h>
- //
- class LmCallStack
- {
- public:
- char* callstack ; // pointer to buffer recording callstack addresses
- int size ; // count of call stacks
- static const int MAX_STACK_LAYERS;
- public:
- LmCallStack(void * csk= NULL, int s=0)
- {
- if (csk )
- {
- callstack = new char[ s*sizeof (void*)];
- memcpy(callstack , csk, s*sizeof (void*));
- }
- else
- {
- callstack = (char *)csk;
- }
- size = s ;
- }
- ~ LmCallStack()
- {
- if (callstack )
- {
- delete[] callstack ;
- }
- callstack = NULL ;
- size = 0;
- }
- };
- class __comp
- {
- public:
- __comp(){};
- bool operator () (const LmCallStack* first , const LmCallStack* second)
- {
- return ((first ->size < second->size ) ||
- ( first->size == second-> size &&
- memcmp(first ->callstack, second->callstack , sizeof( void*)*first ->size) < 0)
- );
- }
- };
- struct _PtrInfo
- {
- _PtrInfo(const LmCallStack* c=NULL , long s=0)
- {
- csk = c ;
- size = s ;
- }
- const LmCallStack * csk;
- long size ;
- };
- struct _AllocInfo
- {
- _AllocInfo(long s=0, int a =0, int f=0)
- {
- size=s ;
- alloc=a ;
- free=f ;
- }
- //
- long size ;
- int alloc ;
- int free ;
- };
- class CMutexLock
- {
- public:
- CMutexLock();
- ~ CMutexLock();
- public:
- void lock ();
- void unlock ();
- private:
- pthread_mutex_t m_mutex ;
- };
- //
- void RecordPtr ( void* ptr, size_t size);
- void RemovePtr (void* ptr);
- void Report ();