Android native memory leak detect (Android native内存泄露检测)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a740169405/article/details/81032228

简介

Android应用中,经常会有业务需要使用到Native实现。比如加密,音视频播放等。也就是常见的二进制文件xxx.so

这部分代码,申请的内存不走Java Heap管理。那么一旦发生内存泄露,无法使用导出MAT来进行查看。

本篇文章将讲解如何使用Google霸霸提供的方法Malloc Debug,定位Native内存泄露问题。

关于wrap shell脚本

Android 8.1及以后的系统里,Google允许应用程序开发者通过wrap.sh脚本文件,来设置Native代码运行时环境。这个wrap脚本有多个功能,本篇文章只讲解其中一个,使用malloc debug追踪Native内存申请,进而定位到Native内存泄露。

切记,是在Android 8.1以后的系统才具备该特性。 当然Android
8.0
系统也可以,但是需要Root,且比较麻烦,设备必须切换到宽容模式

编写自己的wrap shell脚本

#!/system/bin/sh
LIBC_DEBUG_MALLOC_OPTIONS=backtrace $@

这里的backtrace代表打开malloc debug功能,保存为wrap.sh,并赋予可执行权限。

配置自己的应用

  1. 准备好了wrap.sh之后,需要将其放在自己的应用程序中,需要新建一个用于存放脚本的目录结构,与so目录一一对应。放好之后的应用目录结构如下:
|- src
    |- main
        |- jniLibs
            |- x86
              |- libhello-jni.so
           |- arm64-v8a
              |- libhello-jni.so
           |- 其他指令集对应的目录
        |- shell
           |- lib
               |- x86
                  |- wrap.sh
               |- arm64-v8a
                  |- wrap.sh
               |- 其他指令集对应的目录

上面省略了部分指令集的目录,根据自己的应用适配的指令集添加或者删除对应的目录。每一个so指令集都需要拷贝一份wrap.sh到对应的shell目录下。

紧接着,build.gradle文件下应该要有如下配置:

android {

    // 其他配置

    sourceSets {
        main {
            jniLibs {
                srcDir {
                    "src/main/jniLibs"
                }
            }
            resources {
                srcDir {
                    "src/main/shell"
                }
            }
        }
    }
}

jniLibs是指定so路径,resources是指定shell脚本的路径。

编译Debug包并安装运行

运行Debug APK,使用,并复现Native内存泄露的场景,Native内存占用多少,可以使用Android Studio进行查看。

如果希望在Release包下也开启调试,可以在AndroidManifest.xml文件下的<application>标签下添加android:debuggable="true"但是,需要注意,应用正式上线的时候,需要去掉,否则后果自负!!!!

经过上面这些步骤打出来的apk,可以看到lib目录下会有shell脚本:
打好的包

导出内存申请调用栈信息

在程序发生Native内存泄露之后,通过下面的命令可以导出内存申请的调用栈信息

adb shell am dumpheap -n <PID_TO_DUMP> /data/local/tmp/heap.txt

其中<PID_TO_DUMP>是你的应用进程对应的PID

紧接着,把heap.txt从手机里导出来

adb pull /data/local/tmp/heap.txt .

导出后的heap.txt需要进行转换才能查看,google提供了转换的python脚本文件native_heapdump_viewer.py

我这里给的脚本链接,是没问题的版本,google最新提交的一个版本无法跑通。

修改脚本

如果你是Mac/windows,需要修改下载好的脚本,用文本编辑器打开,修改两处。

第一处:

result = subprocess.check_output(["objdump", "-w", "-j", ".text", "-h", sofile])

将这里的objdump改成NDK路径下的对应路径:

/Users/liangqiu/android/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-objdump

第二处:

p = subprocess.Popen(["addr2line", "-C", "-j", ".text", "-e", sofile, "-f"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)

将这里的addr2line改成NDK路径下的对应路径:

/Users/liangqiu/android/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line

改好python脚本后,接着执行:

python development/scripts/native_heapdump_viewer.py --symbols /some/path/to/symbols/ heap.txt > heap_info.txt

这里--symbols选项指定的目录是含有要分析的应用使用的so库的带符号版本。且该目录下的各so库的路径应该和Android系统上该应用使用的so库路径一致,比如要分析的应用使用了/data/app/myapp/foo.so,那么按照上面这个命令指定的symbols目录,带符号的foo.so的路径应该是/some/path/to/symbols/data/app/myapp/foo.so

接着,打开生成的heap_info.txt文件,就能看到所有so,每个方法申请的内存信息,举个栗子

5831776  29.09% 100.00%    10532     71b07bc0b0 /system/lib64/libandroid_runtime.so Typeface_createFromArray frameworks/base/core/jni/android/graphics/Typeface.cpp:68

引用官方的解释

   5831776 is the total number of bytes allocated at this stack frame, which
   is 29.09% of the total number of bytes allocated and 100.00% of the parent
   frame's bytes allocated. 10532 is the total number of allocations at this
   stack frame. 71b07bc0b0 is the address of the stack frame.

到这,搜索一下自己的应用包名,看看内存占整体的百分比,找到泄露内存多的,so,对应能看到申请内存的行数。

demo项目

见github仓库

总结

  1. Google在8.1及以后,才允许程序开发人员通过wrap.sh来配置自己的程序进程二进制运行环境。起步有点晚,相对ios。
  2. 其实对于Rom开发人员,有更便捷的方式,具体参考Rom开发的同学看过来

Native内存泄露连接总结

  1. Google官方对Malloc Debug工具说明文档:mallock debug
  2. wrap.sh脚本文件说明:wrap.sh
  3. native_heapdump_viewer.py脚本最新版地址(不一定能用,可能需要自己进行修改)
  4. 我测试可用的native_heapdump_viewer.py脚本地址

更多的Native调试工具?

Google官方:

https://source.android.com/devices/tech/debug/native-memory
没错,这个是Google为Android平台Native问题排查提供的方法引导,如果这里没有你想要的,那其他路径怕是也找不到了,如果有,请兄弟们分享出来一块学习。

问题

描述

在Demo中,一切运行正常。然鹅,放入自己的项目里之后,发现很快就报错了:

07-16 17:18:58.453 10422-10422/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/taimen/taimen:8.1.0/OPM4.171019.021.R1/4833808:user/release-keys'
Revision: 'rev_10'
ABI: 'arm'
pid: 10391, tid: 10391, name: immomo.momo.dev  >>> com.immomo.momo.dev <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xffffea8c
    r0 00000001  r1 00000008  r2 ffffea8c  r3 00000004
    r4 00000008  r5 ffadf168  r6 00000000  r7 ffadec54
    r8 ffadec2c  r9 00000ff0  sl 00000000  fp ffadf8fc
    ip 00000008  sp ffadeaf8  lr d5ed58ec  pc d5ed5030  cpsr 000f0010
07-16 17:18:58.462 10422-10422/? A/DEBUG: backtrace:
    #00 pc 00037030  /data/app/com.immomo.momo.dev-cMfhOMn7a0CDUJybWWvv9A==/lib/arm/libmoleveldb.so (_Unwind_VRS_Pop+84)
    #01 pc 000378e8  /data/app/com.immomo.momo.dev-cMfhOMn7a0CDUJybWWvv9A==/lib/arm/libmoleveldb.so (__gnu_unwind_execute+876)
    #02 pc 00037938  /data/app/com.immomo.momo.dev-cMfhOMn7a0CDUJybWWvv9A==/lib/arm/libmoleveldb.so (__gnu_unwind_frame+52)
    #03 pc 00029559  /data/app/com.immomo.momo.dev-cMfhOMn7a0CDUJybWWvv9A==/lib/arm/libmoleveldb.so
    #04 pc 0002070b  /system/lib/libc_malloc_debug.so (_Unwind_Backtrace+146)
    #05 pc 00007ec9  /system/lib/libc_malloc_debug.so (backtrace_get(unsigned int*, unsigned int)+28)
    #06 pc 000059b7  /system/lib/libc_malloc_debug.so (InitHeader(Header*, void*, unsigned int)+194)
07-16 17:18:58.463 10422-10422/? A/DEBUG:     #07 pc 0000556b  /system/lib/libc_malloc_debug.so (internal_malloc(unsigned int)+82)
    #08 pc 000054d1  /system/lib/libc_malloc_debug.so (debug_malloc+48)
    #09 pc 00024a63  /data/app/com.immomo.momo.dev-cMfhOMn7a0CDUJybWWvv9A==/lib/arm/libmoleveldb.so (operator new(unsigned int)+22)
    #10 pc 00024a9b  /data/app/com.immomo.momo.dev-cMfhOMn7a0CDUJybWWvv9A==/lib/arm/libmoleveldb.so (operator new[](unsigned int)+2)
    #11 pc 00021d9b  /data/app/com.immomo.momo.dev-cMfhOMn7a0CDUJybWWvv9A==/lib/arm/libmoleveldb.so (leveldb::Status::Status(leveldb::Status::Code, leveldb::Slice const&, leveldb::Slice const&)+34)
    #12 pc 00023f9f  /data/app/com.immomo.momo.dev-cMfhOMn7a0CDUJybWWvv9A==/lib/arm/libmoleveldb.so

大致信息是说,当业务模块通过malloc申请内存时,会走到代理的/system/lib/libc_malloc_debug.so里的debug_malloc方法,接着该方法通过libunwind模块来获取调用栈,此时报错了。具体的报错分析,参考大神博客

解决方案

一种方案是等待Google更新新系统之后解决,然鹅,解决不知道是什么时候的事,所以参照大神的思路,自己编译Android固件并刷入手机。不过得修改libunwind llvm模块的UnwindLevel1-gcc-ext.c文件(参考大神git),这样,生成的/system/lib/libc_malloc_debug.so便可以正常获取调用栈信息。

这里我把 libc_malloc_debug.so 保留下来(Android 8.1)供大家使用,如果手上有Android 8.1的root手机,可以用我这个so文件替换/system/lib下的对应文件,即可正常跑起来Malloc Debug

这里为何选择编译Android 8.1而不是Android 5/6/7是因为Android 8.0及以上才能够针对某一个进程开启Malloc Debug功能,低版本只能针对所有进程开启,会导致手机非常卡。

尊重原创,转债请注明出处:https://blog.csdn.net/a740169405/article/details/81032228

猜你喜欢

转载自blog.csdn.net/a740169405/article/details/81032228