Memory Corruption
今天介绍memory corruption的处理,往往在程序中会发生SEGMENMT FAULT, ARRAY BOUND WRITER,DOUBLE-FREE等等,处理以来很麻烦,这里先给一个教科书级别的例子,实际中的情况要复杂很多,特别是在多线程的时候更麻烦。
int *p = (int*)malloc(16);
for(int i=0; i<= 4;i++) p[i]=0;
> ::umem_verify
Cache Name Addr Cache Integrity
umem_magazine_1 8087010 clean
umem_magazine_3 8087390 clean
…
umem_alloc_24 808c710 1 corrupt buffer
…
umem_verify会检查所有在umem管辖之下的内存的完整性(integrity),我还不知道umem_verify的算法,希望有知道的人能够帮助我理解,因为我在实际使用中有发现memory已经corrupt了,但是verify还是报clean的。上面检查出来umem_alloc_24所管辖的内存中,有一个buffer是corrupted buffer,改cache的地址是0x808c710:
> 808c710::umem_verify
Summary for cache ‘umem_alloc_24′
buffer 809cfd0 (allocated) has a corrupt redzone size encoding //–>这里给出了更详尽的信息
上面的0x809cfd0就是buffer的地址,我们来看一下:
> 809cfd0/10X
0x809cfd0: 18 3a10bfe8 0 0 0
0 0 1789 80a2e30 a91ae6dd
> 80a2e30^a91ae6dd=X
a110c8ed
> 1789=D
6025 // (6025-1)/251 – 8 =16 分配了16字节
注意到我们没有看到feedface,那是redzone的标志,还好redzone的第二个字节并没有被破坏。在user data中一共有5个4字节被初始化为0了,但是应该只分配了4个4字节,所有最后一个4字节是指针越界了。这一点808c710::umem_verify已经告诉我们了。但是注意,这个时候程序并没有崩溃,往往就是这样的,这个时候程序埋下了一个地雷,要之后后面有人去踩了这个地雷,问题才会爆发,所以一般情况下程序奔溃的时候我们看到的call stack并不是问题发生的第一现场,这一点要牢记!
还有在程序出问题的时候,libumem会把最后的出错信息保存下来,我们可以通过::umem_status来查看:
> ::umem_status
Status: ready and active
Concurrency: 8
Logs: transaction=128k
Message buffer: –>注意到这个时候虽然mmeory已经corruption了,但是umem_status什么错误也没有留下。
我现在把程序稍微改一下:
int *p = (int*)malloc(16);
free(p);
free(p);
for(int i=0; i<=4;i++) p[i]=0;
-bash-3.00$ UMEM_DEBUG=default UMEM_LOGGING=transaction LD_PRELOAD=libumem.so.1 ./Test &
[1] 5571
-bash-3.00$
[1]+ Abort (core dumped) UMEM_DEBUG=default UMEM_LOGGING=transaction LD_PRELOAD=libumem.so.1 ./Test
这次程序崩溃了,于是我们分析一下core 文件:
-bash-3.00$ mdb core
Loading modules: [ libumem.so.1 libc.so.1 ld.so.1 ]
> $C
080475f0 libc.so.1`_lwp_kill+7(1, 6)
08047608 libc.so.1`raise+0x1f(6)
08047618 libumem.so.1`umem_do_abort+0×25(0, fef85000, 8047654, fef67b8a, fef72f3c, fef89a00)
08047628 libumem.so.1`umem_err_recoverable+0×46(fef72f3c, fef89a00, 809cfd8, fef72f64)
08047654 libumem.so.1`process_free+0×82(809cfd8, 1, 0, 80476ac, 8050995, 809cfd8)
08047668 libumem.so.1`free+0×14(809cfd8, 8060a3c, 8047694, 8047760)
080476ac main+0×45(1, 80476d8, 80476e0)
080476cc _start+0×80(1, 80477c8, 0, 80477cf, 80477e2, 80477fa)
我们看到是free的时候出了错,抛出了异常,然后由abort导致程序崩溃。下面我按部就班的用上面介绍的方法试一下:
> ::umem_status
Status: ready and active
Concurrency: 8
Logs: transaction=128k
Message buffer:
free(809cfd8): double-free or invalid buffer //这次umem_status帮了忙,告诉我很有可能是double-free造成的
stack trace:
libumem.so.1′?? (0xfef66099)
libumem.so.1′?? (0xfef67b8a)
libumem.so.1′free+0×14
Test’main+0×45
Test’_start+0×80
> ::umem_verify ! grep corrupt
> //注意这里,umem_verify认为所有的memory都是clean的!!这一点跟我的想象有点出入
> 809cfd8::whatis
809cfd8 is 809cfd0+8, bufctl 80a2e30 freed from umem_alloc_24
> 809cfd0/10X
0x809cfd0: deadbeef deadbeef deadbeef deadbeef deadbeef
deadbeef feedface feedface 80a2e30 fce4dade
> 80a2e30^fce4dade=X
f4eef4ee
这下理解为什么umem_verify认为都是 clean的了,确实是clean的嘛,这块内存好好的。
> ::umalog
T-0.000000000 addr=809cfd0 umem_alloc_24
libumem.so.1`umem_cache_free_debug+0×135
libumem.so.1`umem_cache_free+0x3f
libumem.so.1`umem_free+0xd5
libumem.so.1`process_free+0xfd
libumem.so.1`free+0×14
main+0×37
_start+0×80
T-0.000008164 addr=809cfd0 umem_alloc_24
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
注意到umalog只显示了两次memory 分配释放的记录,也就是说最后那次free操作并没有被记录下来。所以我的理解是,失败的内存操作不会被logging,但是我们可以通过查看umem_status得到最后出错的原因。
最后我想说的是,最好要好好学习一下mdb。网上还有一些比较好的资源:
http://blogs.sun.com/jwadams/entry/debugging_with_libumem_and_mdb 这是libumem的作者的blog。
http://reader.feedshow.com/show_items-feed=9d02698904f608e3444e87df42a7ac8a
昨天早上给组里的同事做了个讲座,介绍这些技巧的。Manager倒是挺鼓励我的,一是希望我翻译成英文,这样美国那边的同事也可以看看,二是希望能够平时多积累解决实际问题的经验,和美国那边的同事分享,这样对自己,对整个CDC SBE都有益处。
最近还在查另外一个memory tracking的问题,我基本上已经确定不是memory leak的问题,接下来就是要找出来是谁不停得问系统要资源,我觉得dtrace帮了我大忙,再接再厉,希望能够尽快找出来问题的根源。