使用libumem定位memory leak和memory corruption(3)

今天开始实战。以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要报出来呢~~?!看来今天只能先写到这里,让我研究一下再回来。

 

你可能感兴趣的:(使用libumem定位memory leak和memory corruption(3))