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/<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
();