Android Native进程内存泄露检测

Android Native进程内存泄露检测

简介

对于Android的native进程, Android源码中的Bionic库提供了一个很棒的API,get_malloc_leak_info用来检测Native代码内存泄露。

相关原理

相关的Property

使用这个API需要设置libc.debug.malloc这个property。这个property用来控制malloc信息的debug等级。在使用这个API之前,把libc.debug.malloc设置为1。关于这个property的说明可以在bionic/libc/bionic/malloc_debug_common.cpp中找到。下面代码给出其中的片段。

//when libc.debug.malloc enviroment variable value than
//zero:
//1 - For memory leak detection

API:get_malloc_leak_info

在设置完property后, 当在程序中调用malloc分配内存的时候,系统会调用到leak_malloc()这个API,这个API定义在bionic/libc/bionic/malloc_debug_leak.c。leak_malloc()与普通malloc()的区别在于leak_malloc()会在真正分配的内存空间前面会分配一段额外的空间来存储分配的内存信息。简而言之,如果使用malloc(4) (leak_debug版,系统会分配4个字节的内存,然后在这个4字节内存的前面分配了一个头。所以整个malloc(4)产生的内存就像这样:

[AllocationEntry | space[4bytes]].

这个AllocationEntry记录了malloc的call stack,内存大小还有一些其他的信息。它是存储在一个hashtable里的。可以在malloc_debug_common.h这个文件中找到这个Hashtable的定义。以下选取了部分代码

struct HashEntry{
    ....
    size_t size;
    size_t allocations;
    ....
}


struct HashTable{
    pthread_mutex_t lock;
    size_t count;
    HashEntry* slots[HASHTABLE_SIZE];
}

这篇文章,不讨论如何去记录这些malloc信息,只概述一下如何去使用这项技术。这里有一篇文章很清楚的讲解了记录这些malloc信息的原理。

Android中native进程内存泄露的调试技巧

知道 leak_malloc 如何工作之后,那么这个API

void get_malloc_leak_info(uint8_t** info, size_t* overallSize, size_t* infoSize, size_t* totalMemory,  size_t* backtraceSize),

就很容易使用了。它被定义在bionic/libc/bionic/malloc_debug_common.cpp这个文件中。

//"*info" is set to a buffer we allocate
//"*overallSize" is set to the size of "info" buffer
//"*infoSize" is set to the size of single entyr
//"*totalMemory" is set to the sum of all allocations we're tracking; does
//not include heap overhead
//"*backtraceSize" is set to the maximum number of entries in the back trace

在实际使用中,我们需要关注的是info指针,这个指针记录了malloc信息,从中解析后我们可以得到这些malloc大小和调用堆栈等等。

下面代码演示了如何去解析info指针

....
uint8_t *info = NULL;
size_t overallSize = 0;
size_t infoSize = 0;
size_t totalMemory = 0;
size_t backtraceSize = 0;
get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize);
if (info) {
         AllocEntry * entries = new AllocEntry[count];
         for (size_t i = 0; i < count; i++) {
                e->size = *reinterpret_cast<size_t *>(ptr);
                ptr += sizeof(size_t);
                e->dups = *reinterpret_cast<size_t *>(ptr);
                ptr += sizeof(size_t);
                e->backtrace = reinterpret_cast<intptr_t *>(ptr);
                ptr += sizeof(intptr_t) * backtraceSize;
}    
....

如何在实际工程中使用

以在CameraService中debug为例

扫描二维码关注公众号,回复: 1629088 查看本文章

代码/编译部分

首先,MemoryTrackUtil.cpp (附件1)需要放在工程目录下。

dumpMemoryAddresses(int fd)

这个API封装了get_malloc_leak_info,它可以直接用来检测内存泄露,并将一次dump解析的信息放在了一个文件里。
举个例子,开发者想要去检测camera在preview时候的内存泄露。那么把这个API放在CameraService 的connect(),那么每次当进入preview的时候都会去call connect()这个函数,此时他都会几下当前一次的内存情况。通过调用多次,dump出不同时间段该进程的内存使用情况,就可以判断出是否存在内存泄露。

The MemoryTrackUtil.cpp 这个文件也可以在源码目录下这个位置找到: /android/frameworks/av/media/libmedia。

以下代码片段演示了如何使用dumpMemoryAddresses(int fd)

status_t CameraService::connect(...){
    ....
    Int fd = open(...);
    dumpMemoryAddresses(fd);
    ....
}

这样malloc的信息就会被保存在fd里,修改了这个文件之后,重新编译libcameraservice.so然后替换掉system.img中的.so

运行时使用方法

1.如果system.img中不存在libc_malloc_debug_leak.so,那么把它push到/system/lib中并修改权限。

2.setprop libc.debug.malloc 1

3.kill需要检测的进程,使这个property设置后生效。

这三部之后,camera在每次进入preview的时候都会去记录call stack和malloc的大小。
dump出来的文件如以下的格式:

 size   262888, dup    1, 0x769a2032, 0x76ed9cce, 0x76ec991a, 0x76f52034, 0x76f62af8,
 size   262144, dup    1, 0x769a2032, 0x76ed9cce, 0x76a824d4, 0x76a5e99a, 0x76bf39c2,
 size   172036, dup    3, 0x769a2032, 0x76ed9cce, 0x764378c6, 0x7643791e, 0x76414912,
 size   172036, dup    3, 0x769a2032, 0x76ed9cce, 0x764378c6, 0x7643791e, 0x76414912,

size代表malloc的内存大小。

dup值代表同一个地方分配的内存次数。对比dump出的不同时间段的文件,如果发现某一条malloc信息的dup越来越大,那么可以怀疑这个地方存在内存泄露。

最后一串字符串表示的是调用的堆栈。这个堆栈需要去减掉栈区间的起始位置,然后才能使用addr2line去查找这个调用堆栈。

举个例子:

size   262888, dup    1, 0x765a2032,
------------------------------------------
765b1000-76688000 r-xp 00000000 b3:05 875        /system/lib/libc.so

那么实际去使用addr2line的stack值应该是 0x765a2032 - 765b1000

在MediaServer中调用这个API就很容易了

Android天然的已经在mediaserver中支持了这个功能。在MediaPlayerService.cpp中已经加入了dumpMemory 这个API。 并且以dumpsys的接口提供给用户使用,所以在对mediaserver相关进程进行dubug的时候只需要如下几步即可:

setprop libc.debug.malloc 1
busybox killall -HUP mediaserver
dumpsys media.player -m

当然,如果需要dump不同时间段的信息,那么只需要写一个shell脚本就可以了。

工具

手工计算堆栈信息是非常无聊的,所以我写了一个java程序(附件中)来去解析这个堆栈信息。

  1. 导入map文件。 将cat /proc/pid/maps所产生的字符串保存在一个文件里,这个文件就是map文件。
  2. 复制一条dump出来的malloc信息到文本框里
  3. 点击开始解析。

这里写图片描述

附件

猜你喜欢

转载自blog.csdn.net/u011280717/article/details/51820268