Linux下对于程序内存泄漏检测的方法很多,最常用的的莫过于使用valgrind工具。但是valgrind相当于让程序在虚拟机中运行,会带来较大的系统资源开销,还会对程序的运行效率产生较大影响,对于那种资源占用大的程序,如果需要长时间运行才能暴露的泄漏问题,它就显得不太好用。
linux下的c++程序中自己实现一个轻量级的泄漏检测代码其实是比较方便的,下面我就给出一个简单的范例,并作简单的说明。当然,我们还是应该提倡使用共享指针,用共享指针自动管理内存可以避免内存泄漏这样的不必要的麻烦。
基本原理:
1.利用glibc提供的__malloc_hook, __free_hook系列函数对内存分配释放做监控;(详见glibc的官方文档)
2.利用backtrace函数获取函数调用栈,并记录;
3.利用backtrace_symbols对调用栈对应的函数做解析;
进一步处理:
4.使用abi::__cxa_demangle把函数名解析为源代码风格;
5.使用addr2line解析出函数调用栈对应的代码行;
6.对于动态库(.so)中的地址解析,需要先在/proc//maps文件中找到动态库映射的基地址,才能做解析。
注意:
编译连接参数中使用-g -rdynamic
以上每步具体实现的代码可能都没有达到最优,甚至可能是笨办法,如果有更好的实现方案请直接替换,也欢迎赐教。
示例代码:
leakmom.cpp
/* Prototypes for __malloc_hook, __free_hook */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
//
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 ();
最后给出测试代码作为使用范例:
testso.cpp和testso.h需要编译为动态库(.so),由于测试动态库中的内存分配
testso.cpp源码:
#include "testso.h"
void testsoleak()
{
char* p = new char[15];
}
testso.h源码:
void testsoleak();
test.cpp源码:
#include "leakmon.h"
#include "testso.h"
char* func1()
{
char* p = new char[10];
return p;
}
int main (int argc, char *argv[])
{
gStartLeakMon = 1;
char* p = new char[3];
for (int i=0; i<10; i++)
{
p = func1();
delete[] p;
}
for (int i=0; i<5; i++)
{
testsoleak();
}
Report();
return(0);
}