今天开始实战。以Test程序为例来看看如何发现memory leak。首先是以libumem方式运行程序:
//Test.cpp
int *p = (int*)malloc(50);
p = (int*)malloc(100);
free(p);
-bash-3.00$ UMEM_DEBUG=default UMEM_LOGGING=transaction LD_PRELOAD=libumem.so.1 ./Test &
-bash-3.00$ ps -ef | grep Test
jianxu1 6105 6093 0 05:08:28 pts/2 0:00 ./Test
接下来有两种方式,一是通过gcore,然后使用mdb打开core文件:
-bash-3.00$ gcore 6105
gcore: core.6105 dumped
-bash-3.00$ mdb core.6105
Loading modules: [ libumem.so.1 libc.so.1 ld.so.1 ]
>
或者直接attach到进程:
-bash-3.00$ mdb -p 6105
Loading modules: [ ld.so.1 libumem.so.1 libc.so.1 ]
>
然后要做的非常简单,使用 ::findleaks命令找出所有的memmory leaks:
> ::findleaks
CACHE LEAKED BUFCTL CALLER
0808fa90 1 0809e080 main+0×26
———————————————————————-
Total 1 buffer, 64 bytes
> 0809e080$<bufctl_audit
ADDR BUFADDR TIMESTAMP THREAD
CACHE LASTLOG CONTENTS
809e080 809cf80 d0248569f38f5 1
808fa90 806f000 0
libumem.so.1`umem_cache_alloc_debug+0x16c
libumem.so.1`umem_cache_alloc+0x15c
libumem.so.1`umem_alloc+0x3f
libumem.so.1`malloc+0×23
main+0×26
_start+0×80
其实看到了callstack再结合源代码,就可以判断是malloc(50)没有被释放了。我们在实际使用中,往往会看到很多leaks,每一个都使用上面介绍的两条命令比较麻烦,我往往这样:
> ::findleaks -d ! less
(为节省篇幅,我这里就不贴出来了)
这样就省去了我为每一处leak都敲入$<bufctl_audit,而且less能够帮助我翻页,查找。会方便很多。
这里我还想说明一点,和purify一样,libumem判断memory leaks的标准并不是:没有free的malloc。::findleaks会遍历所有的内存buffer,检查每一块buffer是不是被引用(是不是有client指向它),如果没有任何client引用/指向这块内存,这块内存就被认为是泄漏!所以看这样的程序:
int *p = (int*)malloc(50);
int *p2 = (int*)malloc(100); //和刚才发生泄漏的程序区别是这里使用p2而不是覆盖p
-bash-3.00$ g++ -o Test Test.cpp
-bash-3.00$ UMEM_DEBUG=default UMEM_LOGGING=transaction LD_PRELOAD=libumem.so.1 ./Test&
[1] 6123
-bash-3.00$ mdb -p 6123
Loading modules: [ ld.so.1 libumem.so.1 libc.so.1 ]
> ::findleaks
CACHE LEAKED BUFCTL CALLER
———————————————————————-
Total 0 buffers, 0 bytes –>没有泄漏发生!
>
最后我想说明的一点是,上面我给出的例子实在是太理想化了,在实际应用中没有这么好的事情的,实际应用中的情况往往不是这么明显,很多时候是libumem是报了memleaks,但是我对着源代码硬是找不出来。这里我给一个超微复杂一点点的例子,也是我杜撰的:
class Foo
{
public:
Foo(const int id)
{
char buf[50]={0};
snprintf(buf,sizeof(buf),"Foo Object with ID=%d" ,id);
m_IDname=buf;
}
virtual ~Foo(){};
private:
std::string m_IDname ;
};
for(int i=0; i<100; i++)
{
Foo* p=new Foo(i);
if(50==i) continue;
delete p;
}
> ::findleaks -d
CACHE LEAKED BUFCTL CALLER
0808d390 1 080a3200 libstdc++.so.6`_Znwj+0×25
08090390 1 080a7300 libstdc++.so.6`_Znwj+0×25
———————————————————————-
Total 2 buffers, 64 bytes
umem_alloc_16 leak: 1 buffer, 16 bytes
ADDR BUFADDR TIMESTAMP THREAD
CACHE LASTLOG CONTENTS
80a3200 809dfe0 d040eeb60d954 1
808d390 8075000 0
libumem.so.1`umem_cache_alloc_debug+0x16c
libumem.so.1`umem_cache_alloc+0xe1
libumem.so.1`umem_alloc+0x3f
libumem.so.1`malloc+0×23
libstdc++.so.6`_Znwj+0×25
main+0×30
_start+0×80
….(一共两处,还有一处为了解释方便,我贴在下面)
先看第一处:
> ::dem _Znwj
_Znwj == operator new –>new没有delete,但是一共循环了100次,如果没有源代码,我怎么知道是哪个Foo对象没有被释放呢?这个时候就需要具体的看,我一般会dump memory,我现在知道这块memory buffer的地址是0x80a4fc0。findleaks告诉我user data section+redzone=16字节,也就是4个4字节,一般整个结构的大小比umem_alloc_N多16字节,也就是多4个4字节,那我只要dump 8个4字节就可以了:
> 809dfe0/8X
0x809dfe0: 10 3a10bff0 8050e30 80a4fd4 feedfabb fb1
80a3200 a91afaed
其实我知道Foo对象只有一个成员变量,类型是std::string, 一般来说std::string的普通实现中,开始的一个4字节的指针指向了所存储的字符串。于是我们试图打印该字符串,在这里就是Foo的ID String:
> 8050e30/S
_ZTV3Foo+8: :r05bvr05bb2106b@1605b3Foo
发现不对呀!!怎么打出来的不是Object ID?!
> ::dem _ZTV3Foo
_ZTV3Foo == vtable for Foo –>哦,对了,我为Foo定义了virtual destructor,所以Foo对象的开始4字节是虚表指针!
所以,存储字符串的指针应该是user data中第2个4字节:
> 80a4fd4/S
0x80a4fd4: Foo Object with ID=50 –>这下对了,于是我知道100个对象中,对象ID=50的对象发生内存泄漏。
再看第二处:umem_alloc_48 leak: 1 buffer, 48 bytes
ADDR BUFADDR TIMESTAMP THREAD
CACHE LASTLOG CONTENTS
80a7300 80a4fc0 d040eeb60ddab 1
8090390 8075064 0
libumem.so.1`umem_cache_alloc_debug+0x16c
libumem.so.1`umem_cache_alloc+0xe1
libumem.so.1`umem_alloc+0x3f
libumem.so.1`malloc+0×23
libstdc++.so.6`_Znwj+0×25
libstdc++.so.6`_ZNSs4_Rep9_S_createEjjRKSaIcE+0×43
libstdc++.so.6`_ZNSs9_M_mutateEjjj+0x5a
libstdc++.so.6`_ZNSs15_M_replace_safeEjjPKcj+0×28
libstdc++.so.6`_ZNSs6assignEPKcj+0x3f
libstdc++.so.6`_ZNSsaSEPKc+0x2e
_ZN3FooC1Ei+0x5e
main+0×44
_start+0×80
说句老实话,我有点傻眼了,在我杜撰这段程序的时候,没有想到会有第二处内存泄漏的-:(~~$#@
你看,刚刚还说到即使libumem报出了memory leak,我有时候也只能对着它看,无奈地看,因为看不懂,没有泄漏啊,为什么libumem要报出来呢~~?!看来今天只能先写到这里,让我研究一下再回来。