1、HeapSnap 是什么
HeapSnap 是一个定位内存泄露的工具,适用于 Android 平台。
主要特性如下:
对系统负载低
不需要修改目标进程的源代码
支持 Andoroid 上的大多数 native 进程
对函数调用栈自动进行解析,大多情况下不需要找带符号表的程序/库反查地址
支持 Android 多数较新的版本(Android4.0 及以上)
需要 root 权限支持
2、HeapSnap 工具使用
先让目标进程加载 libehapsnap.so,然后再使用 kill 命令发信号给目标进程去保存 heap。下面介绍三种加载 libehapsnap.so 动态库的方法
2.1 使用 heapsnap 工具加载动态库
该方法通过进程注入的方式把代码加载到目标进程内。
把 heapsnap 和 libheapsnap.so 推送到机器(程序也可以推送到其它目录下)adb shell chmod 0777 /data/local/tmp
adb push libheapsnap/libheapsnap.so /data/local/tmp/libheapsnap.so
adb shell chmod 0644 /data/local/tmp/libheapsnap.so
adb push heapsnap /data/local/tmp/heapsnap
adb shell chmod 0755 /data/local/tmp/heapsnap
通过 adb 或者串口登陆目标机器,开启 malloc 调试,并重启目标进程setprop libc.debug.malloc 1
setprop libc.debug.malloc.options backtrace
stop;start
[执行你的应用]
可参考文章Android Libc Debug
加载动态库/data/local/tmp/heapsnap -p -l /data/local/tmp/libheapsnap.so
通过 signal 21 获取目标进程的 heap 信息,并自动保存在/data/local/tmp/heap_snap 目录下 kill -21 [pid]
多次在不同时间点获取目标进程的 heap 信息,并对这些 heap 信息进行比对,从而找出异常的内存分配
也可以选择在加载动态库时直接执行动态库中的函数保存 heap 信息,然后马上关闭动态库.以后每次获取 heap 信息都需要调用相同的命令: /data/local/tmp/heapsnap -p -l /data/local/tmp/libheapsnap.so -o -f heapsnap_save获取到的 heap 信息保存在: /data/local/tmp/heap_snap 目录下
对于已经加载库的进程,也可以这么调用获取 heap 信息.
2.2 LD_PRELOAD 加载动态库
把 libheapsnap.so 推送到机器
adb shell chmod 0777 /data/local/tmp
adb push libheapsnap/libheapsnap.so /data/local/tmp/libheapsnap.so
adb shell chmod 0644 /data/local/tmp/libheapsnap.so
通过 adb 或者串口登陆目标机器,开启 malloc 调试,并重启目标进程(以 mediaserver 为例)
setprop libc.debug.malloc 1
stop media
LD_PRELOAD=/data/local/tmp/libheapsnap.so mediaserver &
也可以配置当前 shell 的环境变量,避免每次都要在目标进程前加"LD_PRELOAD"前缀
export LD_PRELOAD=/data/local/tmp/libheapsnap.so
mediaserver &通过 signal 21 获取目标进程的 heap 信息,并自动保存文件到/data/local/tmp/heap_snap/目录下
kill -21 [pid]
多次在不同时间点获取目标进程的 heap 信息,并对这些 heap 信息进行比对,从而找出异常的内存分配
2.3 目标程序编译时候链接动态库
在目标程序的编译脚本中加入下面这行,然后在你的程序中调用 heapsnap_save(),重新编译好的程序在启动时候会自动链接 libheapsnap.so 库。
缺点就是需要目标程序的源代码及编译环境。
LOCAL_SHARED_LIBRARIES := libheapsnap
可以参考 src/leak_builtin.c 代码
3、解析 backtrace
使用 heapsnap 获得的 heap 信息,已经自动对地址做了解析,如下所示:
Heap Snapshot v1.0
Total memory: 33800
Allocation records: 3
Backtrace size: 32
z 0 sz 4096 num 8 bt 0000007f9a2b0e14 0000007f9a37f534 00000055828338bc 000000558283379c 0000007f9a37f6b0 0000005582833810
#00 pc 0000000000008e14 /system/lib64/libc_malloc_debug_leak.so (leak_malloc+408)
#01 pc 0000000000019534 /system/lib64/libc.so (malloc+24)
#02 pc 00000000000008bc /system/bin/leak_test
#03 pc 000000000000079c /system/bin/leak_test (main+28)
#04 pc 00000000000196b0 /system/lib64/libc.so (__libc_init+104)
#05 pc 0000000000000810 /system/bin/leak_test
通常,heapsnap 所解析出来的 backtrace 信息已经能够大致判断出泄露点了。
但是有时候你需要更精确的解析,如果你手上有设备所对应的 android 环境,那么可以使用 android 提供的工具进行 backtrace 地址解析
android 6 及早期版本$ development/scripts/stack heap.txt > heap_info.txt
android7 以后的版本$ development/scripts/native_heapdump_viewer.py heap.txt > heap_info.txt
解析后的信息如下:
BYTES %TOTAL %PARENT COUNT ADDR LIBRARY FUNCTION LOCATION
0 0.00% 0.00% 0 APP
107808 100.00% 100.00% 198 ZYGOTE
107800 99.99% 99.99% 197 5d1b2c0678 /system/bin/leak_test do_arm64_start external/heapsnap.git/leak_test.c:?
107800 99.99% 100.00% 197 77cd006594 /system/lib64/libc.so __libc_init /proc/self/cwd/bionic/libc/bionic/libc_init_dynamic.cpp:109
98304 91.18% 91.19% 24 5d1b2c073c /system/bin/leak_test foo /proc/self/cwd/external/heapsnap.git/leak_test.c:9 (discriminator 1)
8472 7.86% 7.86% 172 5d1b2c0764 /system/bin/leak_test main /proc/self/cwd/external/heapsnap.git/leak_test.c:22
8472 7.86% 100.00% 172 77cd02a084 /system/lib64/libc.so sleep /proc/self/cwd/bionic/libc/upstream-freebsd/lib/libc/gen/sleep.c:58
8472 7.86% 100.00% 172 77cd05756c /system/lib64/libc.so nanosleep /proc/self/cwd/bionic/libc/arch-arm64/syscalls/nanosleep.S:7
8472 7.86% 100.00% 172 77cd2206bc [vdso] ??? ???
...
注意: 对于 android7/8/9 版本,需要打下面这个补丁,才能正常解析 heapsnap 保存下来的 heap 信息
$ cd android/development
$ patch -p1 < android_7_8_9_development_script.patch
4、注意点
注意 arm/arm64 版本的区别,如果调试的目标程序是 32bit,请使用 arm 版本的 heapsnap 程序和 libheapsnap.so 库;如果调试的目标程序是 64bit,就要使用 arm64 版本的 heapsnap 程序和 libheapsnap.so 库。
对于 android 9/10 及以后的版本,若你调试的对象是/vendor目录下的程序,那么你必须把libheapsnap.so文件也放到/vendor 目录下,否则加载 libheapsnap.so 文件会失败。
对于 android 7/8/9 版本,在这几个版本中把获得的 heap 信息,以及解析脚本"native_heapdump_viewer.py"存在问题,heapsnap 对 heap 信息有做了修正;也提供了补丁"android_7_8_9_development_script.patch"修正"native_heapdump_viewer.py"脚本的问题。
LD_PRELOAD 环境变量只对当前的 shell 有效,如果进程是做为 service 由 init 启动,需要先 stop 该进程,然后在 shell 下启动进程.
一些具有 AT_SECURE 属性的进程或者环境,它们在 link 处理过程中会忽略掉 LD_PRELOAD 参数,即 LD_PRELOAD 对该类进程或环境不起作用.
使用 heapsnap 需要root 权限.