一次Android平台native heap内存泄露的追查过程

转载自我的CSDN Blog

前段时间有个客户报了个问题:他们的一个视频播放app在我们的Android 6.0 SDK上出现内存泄露,我用showmap查了下,该app在长时间播放视频过程中出现native heap内存的持续泄露。

原以为只要把libc debug开关打开了,就能很容易查出问题点,却没想到碰到了一些意想不到的情况。

libc debug开关使能之后,在我们的Android6.0 SDK上执行所有命令及程序都会异常崩溃,系统跑不起来了

是的,通过”setprop libc.debug.malloc 1”使能libc debug后,所有命令都跑不了了,客户的app也跑不了了,出现的Fatal信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
215 F DEBUG   : ABI: 'arm'
215 F DEBUG : pid: 893, tid: 893, name: iptables >>> /system/bin/iptables <<<
215 F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7
614 W NativeCrashListener: Couldn't find ProcessRecord for pid 893
215 F DEBUG : r0 ffffffff r1 0000000d r2 be97e1a4 r3 00000000
215 E DEBUG : AM write failed: Broken pipe
215 F DEBUG : r4 be97e1a4 r5 0000000d r6 be97e298 r7 00000000
215 F DEBUG : r8 be97e298 r9 00000084 sl b6ed5bbc fp 00000000
215 F DEBUG : ip b6ee606c sp be97e178 lr b6ecf791 pc b6ecfca2 cpsr 200e0030
215 F DEBUG :
215 F DEBUG : backtrace:
215 F DEBUG : #00 pc 00077ca2 /system/lib/libc++.so
215 F DEBUG : #01 pc 0007778d /system/lib/libc++.so
215 F DEBUG : #02 pc 0007742d /system/lib/libc++.so
215 F DEBUG : #03 pc 00048a3f /system/lib/libc++.so (__gxx_personality_v0+290)
215 F DEBUG : #04 pc 0001e384 /system/bin/iptables (__gnu_Unwind_Backtrace+160)
215 F DEBUG : #05 pc 0001ecb4 /system/bin/iptables (___Unwind_Backtrace+20)

异常发生在libc++库中,头大,查了半天没找到原因。

试用了下valgrind,发现速度奇慢,且无法用在客户app上,用valgrind加载其它系统自带的app也是跑不起来,果断放弃。

还是想办法把Android自带的libc debug用起来。正向查不出来,那就曲线救国吧。

我修改Android5.1 SDK上面的libc++源码,添加必要函数后编译完成,替换掉样机上原来的libc++.so(样机上跑的是Android6.0系统),居然能工作正常,而且打开libc debug开关后,执行命令也不会崩溃了。

使能libc debug后,打开app,结果又出状况了…

libc debug使能后,客户app无法工作了

在没有打开libc debug时候,客户的app是能正常工作的,但通过以下命令使能libc debug之后:

1
2
3
setprop libc.debug.malloc 1
stop
start

然后打开客户app,一播放视频就会异常退出,异常LOG如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ABI: 'arm'
pid: 4011, tid: 4104, name: AsyncTask #1 >>> com.hikvision.stabilitytesttool <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x59a70ecf
r0 00000001 r1 00004080 r2 59a70ecf r3 00000008
r4 00004080 r5 9edf3b60 r6 00000080 r7 00000000
r8 9edf361c r9 9edf36e0 sl 80000000 fp 00000005
ip 9f488158 sp 9edf34f0 lr 9f651971 pc 9f6514d8 cpsr 240b1c30

backtrace:
#00 pc 0027e4d8 /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so
#01 pc 0027e96d /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so
#02 pc 0027eb61 /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so
#03 pc 002722d5 /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so
#04 pc 00008a03 /system/lib/libc_malloc_debug_leak.so (_Unwind_Backtrace+130)
#05 pc 000060ef /system/lib/libc_malloc_debug_leak.so
#06 pc 00006b69 /system/lib/libc_malloc_debug_leak.so (leak_malloc+84)
#07 pc 00272851 /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so (_Znwj+12)
#08 pc 000b51bf /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so (_ZN9CRenderer16InitVideoDisplayEi+102)
#09 pc 000b4d61 /data/app/com.hikvision.stabilitytesttool-1/lib/arm/libPlayCtrl.so (_ZN9CRenderer14SetVideoWindowEPvii+122)

因为打开了libc debug,因此在malloc时候会保存内存分配的调用路径,从上面异常backtrace可以看到在获得该调用路径时候崩溃了。崩溃点发生在客户的库文件中。

由于没有客户app源码,无法正向去查这个异常崩溃问题,所以,依然走曲线救国的道路…

我修改libc,在反查调用路径时候,把客户app中会导致异常的线程都过滤掉,之后,客户app终于能够工作了 ^_^

使用heapsnap工具获得heap的backtrace信息

通过前面的更改,终于能用libc debug了,接下来就是用heapsnap工具抓取客户app的heap详细信息了。
heapsnap工具可以参考这篇:《HeapSnap工具原理及其应用》
以查明内存泄露的地方,很快查到了两个异常的内存泄露点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
size       12, dup 214822
....
size 12, dup 53687, 0xb4ce3e20, 0xb6d26fa8, 0xb4bcdb8a, 0xb4bbf944, 0xb4bc78e0, 0xb4ba2a96, 0xb4acb512, 0xb49732f4, 0xb6e2770c, 0xb6e27780, 0xb6bb4aee, 0xb6bad77a, 0xb6bb2e8c, 0xb6bc3cf2, 0xb5c71588, 0xb5c70200, 0xb5c73516, 0xb5bf6e1c, 0xb5bf6c84, 0xb5bf8c4e, 0xb5bf768e, 0xb6ec8854, 0xb6cb27e6, 0xb6c8a332
#00 pc 00006e20 /system/lib/libc_malloc_debug_leak.so (leak_malloc+83)
#01 pc 00049fa8 /system/lib/libc++.so (operator new(unsigned int)+15)
#02 pc 0034cb8a /system/lib/libart.so (art::ThreadList::Register(art::Thread*)+257)
#03 pc 0033e944 /system/lib/libart.so (art::Thread::Init(art::ThreadList*, art::JavaVMExt*, art::JNIEnvExt*)+487)
#04 pc 003468e0 /system/lib/libart.so (art::Thread::Attach(char const*, bool, _jobject*, bool)+135)
#05 pc 00321a96 /system/lib/libart.so (art::Runtime::AttachCurrentThread(char const*, bool, _jobject*, bool)+13)
#06 pc 0024a512 /system/lib/libart.so
#07 pc 000f22f4 /system/lib/libart.so
#08 pc 0008870c /system/lib/libandroid_runtime.so (android::JNISurfaceTextureContext::getJNIEnv(bool*)+47)
#09 pc 00088780 /system/lib/libandroid_runtime.so (android::JNISurfaceTextureContext::onFrameAvailable(android::BufferItem const&)+11)
#10 pc 00032aee /system/lib/libgui.so (android::ConsumerBase::onFrameAvailable(android::BufferItem const&)+93)
#11 pc 0002b77a /system/lib/libgui.so (android::BufferQueue::ProxyConsumerListener::onFrameAvailable(android::BufferItem const&)+45)
#12 pc 00030e8c /system/lib/libgui.so (android::BufferQueueProducer::queueBuffer(int, android::IGraphicBufferProducer::QueueBufferInput const&, android::IGraphicBufferProducer::QueueBufferOutput*)+1803)
#13 pc 00041cf2 /system/lib/libgui.so (android::Surface::queueBuffer(ANativeWindowBuffer*, int)+641)
#14 pc 0006b588 /system/lib/libstagefright.so (android::ACodec::BaseState::onOutputBufferDrained(android::sp<android::AMessage> const&)+483)
#15 pc 0006a200 /system/lib/libstagefright.so (android::ACodec::BaseState::onMessageReceived(android::sp<android::AMessage> const&)+619)
#16 pc 0006d516 /system/lib/libstagefright.so (android::ACodec::ExecutingState::onMessageReceived(android::sp<android::AMessage> const&)+481)
#17 pc 0000be1c /system/lib/libstagefright_foundation.so (android::AHierarchicalStateMachine::handleMessage(android::sp<android::AMessage> const&)+63)
#18 pc 0000bc84 /system/lib/libstagefright_foundation.so (android::AHandler::deliverMessage(android::sp<android::AMessage> const&)+15)
#19 pc 0000dc4e /system/lib/libstagefright_foundation.so (android::AMessage::deliver()+53)
#20 pc 0000c68e /system/lib/libstagefright_foundation.so (android::ALooper::loop()+221)
#21 pc 00010854 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
#22 pc 000417e6 /system/lib/libc.so
#23 pc 00019332 /system/lib/libc.so

上述信息中,size后面数值是申请内存的大小,dup后面数值是申请的次数。
这两处泄露点都是12字节的内存泄露,其中一个申请了214822次,另一个申请了53687次。前面一处的heap信息没有输出backtrace,是因为我们修改libc把异常线程过滤掉的缘故;而后面的heap信息中就带有backtrace了。

通过解读这个backtrace,可以看到libart库中的art::ThreadList::Register函数中申请了内存,但没有释放掉。

最后

最后,修改libcxx/include/list中的remove函数,重新编译art推送到样机上,再用heapsnap抓取heap信息,前面发现的那两处heap泄露点都已经不存在。至此,内存泄露问题终于解决了。

如果没有libc debug,还有哪些方式可以查这个问题呢?