2020年12月08日16:56:51更新

准备搞一个类似malloc debug的东西,监控系统的内存申请与释放,最后导出申请了但是未释放部分的代码,有兴趣的一起开发:传送门

简介

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,并赋予可执行权限。

关于wrap.sh这里不详细介绍,详情看考封装 Shell 脚本(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 ./symbols/ heap.txt > heap_info.txt

注意这里的./symbols/参数,该参数指定的是带符号表so所在目录。
开发过NDK动态库的都应该清楚,需要指定带符号表的so才能解析出函数名字,这里的--symbols参数就是需要指定的带符号so所在的目录。

特别提醒,这里的带符号表so路径是有要求的

比如我们开发了一个libtest.so动态库,需要对其泄露方法调用栈进行解析。
假设我们的应用程序安装到手机libtest.so所在路径为/data/app/com.mydemo.demo/lib/arm/libtest.so,如下

|- data|- app|- com.mydemo.demo|- lib|- arm|- libtest.so

那么我们就需要在./symbols/目录下建立以下层级的目录结构/data/app/com.mydemo.demo/lib/arm/并且把带符号表的libtest.so放在arm目录下。建立好之后,带符号so路径应该为./symbols/data/app/com.mydemo.demo/lib/arm/libtest.so,如下:

|- symbols|- data|- app|- com.mydemo.demo|- lib|- arm|- libtest.so

传递给脚本的--symbols参数为./symbols/即可。脚本将根据heap.txt中动态库libtest.so的路径在./symbols/目录下,以相对路径找到带符号表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, whichis 29.09% of the total number of bytes allocated and 100.00% of the parentframe's bytes allocated. 10532 is the total number of allocations at thisstack 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 0xffffea8cr0 00000001  r1 00000008  r2 ffffea8c  r3 00000004r4 00000008  r5 ffadf168  r6 00000000  r7 ffadec54r8 ffadec2c  r9 00000ff0  sl 00000000  fp ffadf8fcip 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便可以正常获取调用栈信息。

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

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


2019年11月07日11:27:51更新

为了方便大家使用,将自己写的替换导出脚本以及GUI程序开源到github方便大家使用。

传送门

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

Android native memory leak detect (Android native内存泄露检测)相关推荐

  1. Android系统(31)--- 如何分析native memory leak

    如何分析native memory leak 分析native程序发生内存泄漏问题,就需要对每一笔内存申请做记录,释放内存时清除记录,然后在认为存在内存泄漏时将记录提取出来分析,看看在哪段代码存在申请 ...

  2. Android 系统(30)---如何用DDMS分析native memory leak

    native程序如果发生内存问题,一般都比较难查,幸好DDMS有集成native memory leak(仅仅针对app,无法分析mediaserver等非app的进程)功能,通过DDMS可以观察na ...

  3. android native堆内存泄露,Android Native内存泄露检测

    Android Studio没有提供直接的Native层的内存泄露检测工具,但我们仍可以通过开源工具进行动态检测和静态检测 动态检测 在APP运行时进行检测,就像LeakCanary Update: ...

  4. Android NDK 内存泄露检测

    前言 最近写C++代码,老是担心代码存在内存泄露,胆战心惊的,Andorid中Java层代码内存泄露可以借助leakcanary进行检测:找了一番,找到了PC上C++上的内存泄露检测库LeakTrac ...

  5. SQL Server 内存泄露(memory leak)——游标导致的内存问题

    原文:SQL Server 内存泄露(memory leak)--游标导致的内存问题 转自:http://blogs.msdn.com/b/apgcdsd/archive/2011/07/01/sql ...

  6. memwatch内存泄露检测工具

    工具介绍 官网 http://www.linkdata.se/sourcecode/memwatch/ 其功能如下官网介绍,挑选重点整理: 1. 号称功能: 内存泄露检测 (检测未释放内存, 即 动态 ...

  7. DevPartner Studio Professional Edition 11 内存泄露检测使用

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! DevP ...

  8. C语言内存泄露检测--Memwatch

    项目:迅雷下载库内存泄露检测 作者:曾金龙 供职:深圳迅雷网络技术股份有限公司 领域:迅雷下载库 时间:2014-07-26 迅雷的移动下载库是用C语言编写的,为了能够横跨欧亚非拉(ios,andro ...

  9. Ubuntu下内存泄露检测工具Valgrind的使用

    在VS中可以用VLD检测是否有内存泄露,可以参考http://blog.csdn.net/fengbingchun/article/details/44195959,下面介绍下Ubuntu中内存泄露检 ...

  10. 精准 iOS 内存泄露检测工具

    MLeaksFinder:精准 iOS 内存泄露检测工具 发表于 2016-02-22   |   zepo   |   23 Comments 背景 平常我们都会用 Instrument 的 Lea ...

最新文章

  1. SQL--添加角色权限
  2. ofbiz之entity 实体解析 扩展 视图 复合列写法
  3. python运行错误-Python在运行中发生错误怎么正确处理方法,案例详解!
  4. 【Oracle】PL/SQL Developer使用技巧(持续更新中)
  5. SkinMagic使用后按钮加自定义图标或菜单GetMneu返回NULL的解决方法
  6. memcached mysql缓存_memcached做数据库缓存
  7. 数据结构与算法(C#版)第二章 C#语言与面向对象技术(中)V1.0
  8. 【Python CheckiO 题解】Secret Message
  9. android l 效果,[原]Android L中水波纹点击效果的实现
  10. Go Web 编程--超详细的模板库应用指南
  11. google浏览器不能登录
  12. Windows C盘格式化或者同平台迁移oracle数据库
  13. mysql sum 对应_ASK MySQL查询SUM不同的表
  14. Python自动化之操作PPT--更新模板数据,减少重复操作
  15. JavaScript替换字符串中括号里的内容
  16. pyCharm最新2018.2激活 附激活文件/教育版免费使用
  17. About Config
  18. 解决ERROR: text file '***' contains disallowed UTF-8 whitespace character(s)
  19. JAVA实验七 图形用户界面的设计与实现
  20. Deferred Decal(延迟贴花)

热门文章

  1. xshell与虚拟机VMware中centos6.7系统突然连不上
  2. λ^n |λI−AB|= λ^m |λI−BA|
  3. python docx 表格样式修改 Package not found at ‘*.docx‘; “no style with name ‘Table Grid‘“
  4. adc分辨率和精度的区别_STM32学习笔记—ADC采集数据常见问题
  5. 使用Gotestwaf测试WAF检测能力
  6. 巨牛,访问github速度加快数倍
  7. 133个Java面试问题列表
  8. Android 内存监测工具 DDMS -- Heap
  9. 数独游戏的设计与实现
  10. android API Guides学习--Introduction(1)