android系统稳定性-内存问题分析总结

前言

软件版本在拷机(长稳)阶段最容易系统稳定性问题,而且任何卡死/卡顿/崩溃/重启等现场都可能是内存异常导致,从系统工程师角度会遇到各种内存问题,不管是应用挂死,系统挂死,kernel挂死,应用内存泄漏,系统内存泄漏,kernel内存泄漏等都会遇到,有时解决一个稳定性问题,需要不同的团队开发人员,且每次挂死原因都不会一样,一百次挂死有一百个原因,导致问题也不好总结,下面也是根据之前遇到过的内存问题情况,做个大概总结,经供参考;

内存异常分类

内存问题主要非两大类, 内存段错误和内存泄漏, 这两大类问题又有很多细分场景;java程序内存问题这里不说明,因为java内存问题有独立的工具和分析方法,我主要还是做系统内存分析;


Android&Linux内存问题总结
linux内存管理

Linux内核并不是将物理内存直接分配给进程,而是采用虚拟内存结构。每个进程拥有一个独立的虚拟内存空间和内存映射表(page table),虚拟内存到物理内存的地址映射关系记录于page table中,当CPU执行指令时,若该指令是从内存中读取或写入数据时,就需要通过MMU(Memory Manage Unit)将内存virtual address替换为physical address。

内存管理太复杂了,一般只有kernel大神才可以写,这里简单记录下,内存分析有必要了解基础的内存管理基础知识。
Linux内存管理
Linux虚拟地址空间布局
Memory Management系列文章

段错误定位

模块分类 可能原因 定位手段
app应用 Java运行异常,空指针,无效引用,数组越界 抓取tombstone或者dropbox日志分析,java程序段错误墓碑日志一般都可以分析出来
jni 1. java对象已经被回收,jni还在调用java方法失效(生命周期不同步)
2.jni接口方法和java层不对齐,比如jni接口改动,java还是调用之前接口
3. jni传入错误的参数
抓取tombstone,分析虚拟机调用栈
native进程或库 使用未初始化指针,空指针,数组溢出,内存踩踏,栈空间使用完,数据字节不对齐 1. 抓取tombstone或者dropbox日志分析堆栈信息,
2. 地址分析法(objdump,addr2line),后面单独说明,
3.打开debug编译选型,给可能问题的进程和库添加调试符号。
kernel 空指针,解码异常,wifi驱动异常 kernel内存错误检测工具,kernel panic一般会找芯片原厂一起分析
硬件 wifi信号干扰ddr时钟频率 之前遇到过记录下,wifi信号影响了ddr稳定性,导致各种段错误
段错误定位流程

1,根据logcat, tombstone或者dropbox,kernel日志等都可以看到段错误发生的堆栈信息,如果是固定位置发生异常,堆栈信息一般就可以定位出错误代码。

2,堆栈错误符合不足,版本发布后系统库都是经过裁剪的,如果出现段错误符信息很少,无法分析的情况,可以尝试使用objdump反汇编定位大概位置,再进一步分析。如果还是符号不足,需要编译debug版本复习问题,再分析。

3,栈溢出,进程栈溢出,线程栈溢出都可能会出现的,栈溢出通过段错误地址位置(栈空间),以及SEGV_ACCERR错误,再通过log可以很快分析出有栈溢出情况。

  1. 其他特殊场景,SIGBUS,SIGFPE,SIGILL,SIGABRT这些错误都是特定情况才会发生,可以根据堆栈信息,或者objdump反编译定位错误代码位置。

5, 内存踩踏,内存踩踏会出现随机段错误的情况,这个情况定位比较麻烦,我这边定位方法是全代码内存检查(人工+工具)和排除法,网上也有内存踩踏的定位方法,估计都是kernel大神才能使用吧。

6,复杂场景段错误,在快速操作(压力测试,Monkey测试,Fuzzing测试),多线程场景下可能会突然出现各种段错误, 这个时候可以考虑根据场景规避,或者分析多线程场景调整代码逻辑。

7, Kernel panic,内核段错误空指针,kernel panic自己定位的少,一般会找芯片或者驱动原厂协助分析,也可以google错误,有些系统原生bug,网上会有修改方法。

objdump示例
//测试代码
#include 
#include 
#include 
int main(int argc, char*argv[])
{
    while(fgetc(stdin) == 'q')
    {   
        break;
    }   
    char *p = "1233454";
    free(p);
}

//运行出现段错误
--------- beginning of crash

01-01 17:41:36.643  6346  6346 F libc    : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x1b1c003f8 in tid 6346 (malloc_test)
01-01 17:41:36.673  6363  6363 I crash_dump64: obtaining output fd from tombstoned
01-01 17:41:36.679  1886  1886 I /system/bin/tombstoned: received crash request for pid 6346
01-01 17:41:36.680  6363  6363 I crash_dump64: performing dump of process 6346 (target tid = 6346)
01-01 17:41:36.680  6363  6363 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
01-01 17:41:36.680  6363  6363 F DEBUG   : Build fingerprint: 'HiDPT/Hi3751V811/Hi3751V811:8.0.0//:eng/release-keys'
01-01 17:41:36.680  6363  6363 F DEBUG   : Revision: '0'
01-01 17:41:36.680  6363  6363 F DEBUG   : ABI: 'arm64'
01-01 17:41:36.680  6363  6363 F DEBUG   : pid: 6346, tid: 6346, name: malloc_test  >>> malloc_test <<<
01-01 17:41:36.680  6363  6363 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x1b1c003f8
01-01 17:41:36.680  6363  6363 F DEBUG   :     x0   000000791c627208  x1   00000001b1c7d710  x2   000000791c60d000  x3   0000000000000000
01-01 17:41:36.680  6363  6363 F DEBUG   :     x4   000000791c6024d8  x5   0000000000000001  x6   2f6d65747379732f  x7   6c6c616d2f6e6962
01-01 17:41:36.680  6363  6363 F DEBUG   :     x8   00000001b1c00380  x9   0000000000000070  x10  000000000000007d  x11  0000000000000000
01-01 17:41:36.680  6363  6363 F DEBUG   :     x12  0000007ffd1e3e18  x13  0000007ffd1e3e50  x14  000000000000000d  x15  aaaaaaaaaaaaaaab
01-01 17:41:36.680  6363  6363 F DEBUG   :     x16  000000791ccb1cc0  x17  000000791cc50718  x18  00000000a0ff71b5  x19  00000001b1c7d710
01-01 17:41:36.680  6363  6363 F DEBUG   :     x20  000000791c627208  x21  000000791c60d000  x22  0000000000000000  x23  000000791ccbc878
01-01 17:41:36.680  6363  6363 F DEBUG   :     x24  0000000000000000  x25  0000000000000000  x26  0000000000000000  x27  0000000000000000
01-01 17:41:36.680  6363  6363 F DEBUG   :     x28  0000000000000000  x29  0000007ffd1e3f40  x30  000000791cc7ad44
01-01 17:41:36.680  6363  6363 F DEBUG   :     sp   0000007ffd1e3f10  pc   000000791cc7a7ec  pstate 0000000080000000
01-01 17:41:36.683  6363  6363 F DEBUG   :
01-01 17:41:36.683  6363  6363 F DEBUG   : backtrace:
01-01 17:41:36.683  6363  6363 F DEBUG   :     #00 pc 00000000000907ec  /system/lib64/libc.so (ifree+88)
01-01 17:41:36.683  6363  6363 F DEBUG   :     #01 pc 0000000000090d40  /system/lib64/libc.so (je_free+120)
01-01 17:41:36.683  6363  6363 F DEBUG   :     #02 pc 0000000000000700  /system/bin/malloc_test (main+32)
01-01 17:41:36.683  6363  6363 F DEBUG   :     #03 pc 000000000001b66c  /system/lib64/libc.so (__libc_init+88)
01-01 17:41:36.683  6363  6363 F DEBUG   :     #04 pc 0000000000000640  /system/bin/malloc_test (do_arm64_start+80)

//使用反汇编在程序查找段错误位置。
bzl@ubuntu:~/samba/develop$ aarch64-hisiv610-linux-objdump -dS  out/target/product/Hi3751V811/system/bin/malloc_test  | egrep 700
700:    97ffffb8     bl    5e0 
bzl@ubuntu:~/samba/develop$ aarch64-hisiv610-linux-objdump -dS  out/target/product/Hi3751V811/system/lib64/libc.so  | egrep 907ec
   907ec:    f9403d08     ldr    x8, [x8,#120]
bzl@ubuntu:~/samba/develop$ aarch64-hisiv610-linux-objdump -dS  out/target/product/Hi3751V811/system/lib64/libc.so  | egrep 90d40
   225d0:    7290d40a     movk    w10, #0x86a0
   235a4:    7290d408     movk    w8, #0x86a0
   90d40:    97fffe95     bl    90794 
   90d7c:    17fffff1     b    90d40 
bzl@ubuntu:~/samba/develop$

内存踩踏举例

I/DEBUG   (  885): Build fingerprint: 'full_godbox/godbox:4.0.3/Sbox8040/80163107:eng/ test-keys'
I/DEBUG   (  885): pid: 12736, tid: 13016  >>> net.xxx.app.xx.xx <<<
I/DEBUG   (  885): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 68fb2000
I/DEBUG   (  885):  r0 68fb2000  r1 010aa470  r2 000013c0  r3 00000000
I/DEBUG   (  885):  r4 00000000  r5 00000000  r6 00000000  r7 00000000
I/DEBUG   (  885):  r8 00000000  r9 00000000  10 00000000  fp 00000000
I/DEBUG   (  885):  ip 010aa4a0  sp 65f5b1d0  lr 40696094  pc 4004ebac  cpsr 20000010
I/DEBUG   (  885):  d0  449ed0004432aa00  d1  000004f600000000
I/DEBUG   (  885):  d2  44340000000002d0  d3  3ffc71c720000000
I/DEBUG   (  885):  d4  4434000000000000  d5  44a0000000000000
I/DEBUG   (  885):  d6  3f00000000000000  d7  3ecccccd3f000000
I/DEBUG   (  885):  d8  0000000000000000  d9  0000000000000500
I/DEBUG   (  885):  d10 0000000000000000  d11 0000000000000000
I/DEBUG   (  885):  d12 0000000000000000  d13 0000000000000000
I/DEBUG   (  885):  d14 0000000000000000  d15 0000000000000000
I/DEBUG   (  885):  scr 20000012
I/DEBUG   (  885): 
D/dalvikvm(12736): GC_CONCURRENT freed 114K, 7% free 10218K/10951K, paused 3ms+16ms
I/DEBUG   (  885):          #00  pc 0000dbac  /system/lib/libc.so (memcpy)
I/DEBUG   (  885):          #01  pc 00067090  /system/lib/libskia.so (_ZN23SkARGB32_Shader_Blitter5blitHEiii)
I/DEBUG   (  885):          #02  pc 00061458  /system/lib/libskia.so (_ZN9SkBlitter8blitRectEiiii)
I/DEBUG   (  885):          #03  pc 00094128  /system/lib/libskia.so
I/DEBUG   (  885):          #04  pc 000941e0  /system/lib/libskia.so
I/DEBUG   (  885):          #05  pc 0009428c  /system/lib/libskia.so
I/DEBUG   (  885):          #06  pc 00094a54  /system/lib/libskia.so (_ZN6SkScan12AntiFillRectERK6SkRectPK8SkRegionP9SkBlitter)

//最终通过规避解决,这个问题是用户快速操作遥控器操作,我们应用在init过程中,被强制退出,来回快速操作导致。

线程栈空间使用完

如果线程中变量总大小超出栈大小,如果线程中操作了超出栈的以外变量,就会报signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr错误信息,
另外在Android中,线程的栈被是受保护的,默认设定为mprotect(stack, thread->attr.guard_size, PROT_NONE),不可被读写的,所以会显示如图,如果变量做修改的const变量,则会显示是可读的。
所以,如果libiptvmw.so出现如有莫名其妙的问题,如果有类似的信息,则会由此原因造成的。为了安全起见,建议把 libiptvmw.so中的所有线程的栈大小,都应该调大些。


音频解码导致踩内存

问题描述:
机顶盒播放VMX加扰流, 播放一段时候后盒子就挂掉。
问题原因:

  1. VMX 加扰流在使用AES-128解码的时候使用了PKCS7算法填充数据, 导致解密后的数据不是188对齐。
  2. 机顶盒再给TS BUF送数据的时候会做0x47开头和188字节对齐判断, 如果不是0x47开头就会在拼装188字节的时候丢的一部分数据。
  3. 丢掉一部分数据送给TS BUF后, 就会导致AAC播放挂掉。
    验证是AAC问题测试:
  4. 我们去掉AAC解码库,播放很久没有死机。
  5. 后来找到原因后,我们把送给TS BUF之前的数据保存下来(就是有丢包的数据),然后用sample_tsplay播放, 发现每次都能死机。

总结:
如果因为丢包导致AAC库内存溢出, 那如果网络异常的时候也有可能会导致挂掉问题, 所有还需要你们进一步定位。

内存泄漏分析--传统方法

之前写linux c程序,都是自己打桩linux 程序打桩,core文件, 或者在C里面添加打桩分析内存异常,这个方法后面在android native分析也挺适应,现在有很多android native定位工具我也没有用过,工具的核心原理和之前用的都差不多;传统方法就是先抓整体内存信息,定位到哪个模块,再抓具体模块内存信息分析;
有时候内存分析,还是靠老方法和经验,比如数据库内线泄漏,tmpfs泄漏,资源泄漏等工具就无法分析出来;

抓取内存信息。

#!/system/bin/sh

#脚本说明:抓取系统cpu和内存信息脚本,长稳使用 ,在跑长稳的过程中,
#         有时候logcat日志已经无法满足定位,需要抓取系统的cpu和内存状态信息,才能进一步定位问题。
#注意事项:
#        1) 由于脚本启动会启动其他后来命令,如果有操作失误,需要重新抓log,记得先重启机顶盒。
#        2)长稳日志,重启不会丢失,但是如果重新抓日志,会删除之前的log, 防止data分区填满。
#作者: bzl

#开始抓日志时间
start_time=$(date +%Y-%m-%d-%H-%M-%S)
echo "++++ data_time: ${start_time} \n"

#锁定输入,无法响应遥控器
echo 1 > /proc/inputblock/status

#清除系统缓存
echo 3 > /proc/sys/vm/drop_caches

#删除之前的anr, tombstones, log目录
rm -rf /data/log
rm -rf /data/anr/*
rm -rf /data/tombstones/*

#重新创建log目录
mkdir -p /data/log

#抓android的logcat日志
logcat -vtime > /data/log/logcat.txt &

#抓取系统内核日志
cat /proc/kmsg > /data/log/log_kmsg.txt &

while true
do
{
    #记录当前时间
cur_time=$(date +%Y-%m-%d-%H-%M-%S)
    
    #抓取top信息, top信息一般包含cpu的使用率
top -m 10 -n 1 >> /data/log/log_top.txt
echo "----------${cur_time}-------------\n" >> /data/log/log_top.txt
    sleep 1
    
    #抓取用户内存信息
    busybox free >> /data/log/log_free.txt
    echo "----------${cur_time}-------------\n" >> /data/log/log_free.txt
    sleep 1
    
    #抓取详细内存信息
    cat /proc/meminfo >> /data/log/log_meminfo.txt
echo "----------${cur_time}-------------\n" >> /data/log/log_meminfo.txt
    sleep 1
    
    #抓取SDK内存信息。
    cat /proc/media-mem >> /data/log/log_media-mem.txt
echo "----------${cur_time}-------------\n" >> /data/log/log_media-mem.txt
    sleep 1
    
    #抓取当前进程信息
    ps >> /data/log/log_ps.txt
    echo "----------${cur_time}-------------\n" >> /data/log/log_ps.txt
    
    #抓取芯片温度
    cat /proc/msp/pm_cpu >> /data/log/log_pm_cpu.txt
    echo "----------${cur_time}-------------\n" >> /data/log/log_pm_cpu.txt
    sleep 1
    
    #抓取莫个apk应用内存使用信息,根据实际情况替换包名,下面是卡电的iptv apk应用
    #dumpsys meminfo xxxx >> /data/log/log_app_meminfo.txt
    #dumpsys meminfo net.xxx.app.youtube >> /data/log/log_app_meminfo.txt
    dumpsys meminfo com.huawei.ahdptc >> /data/log/log_app_meminfo.txt
    echo "----------${cur_time}-------------\n" >> /data/log/log_app_meminfo.txt
    sleep 1
    
    #查看所有进程咱用的内存情况,查看单个进程使用情况可以用procmem pid
    procrank -u >> /data/log/log_procrank.txt
    echo "----------${cur_time}-------------\n" >> /data/log/log_procrank.txt
    
    #拷贝anr, tombstones日志
    cp -rf /data/anr /data/log/
    cp -rf /data/tombstones /data/log/
    
    #刷新系统缓存,日志文件及时保存到flash上
    sync
    
    #睡眠时间,
    sleep 60 #60s
}
done
Anroid native内存分析,官网工具

https://developer.android.com/studio/profile/memory-profiler?hl=zh-cn
https://source.android.com/devices/tech/debug/native-memory

Android Native内存分析工具LeakTracer:

https://bbs.huaweicloud.com/blogs/194369
https://blog.csdn.net/SCHOLAR_II/article/details/111930672
https://www.jianshu.com/p/fbc865ea5816
https://www.codeleading.com/article/31113021034/

经过测试验证:

  1. 原始github上LeakTracer,在android上无法跑起来,堆栈记录会段错误,需要unwind方法。网上有人修改针对android的LeakTracer

  2. 在32位系统没有问题,在64位系统上有的泄漏定位不出来情况。

  3. 只能分析库里面的内存泄漏,如果是进程里面泄漏,堆栈地址信息不对,可以把native服务修改下,把主要函数放到库里面,main只负责拉起来。原始LeakTracer只支持命令,不支持库,经过修改后的LeakTracer能把库符号打印正确,进程符号就不对了。

Android Native内存分析工具,libc.debug

https://blog.csdn.net/u010481276/article/details/78959368
https://www.daimajiaoliu.com/daima/479ca70349003fc
https://www.cxybb.com/article/xlnaan/84325880
https://www.jianshu.com/p/983728f34f74
https://albuer.github.io/2019/11/30/Android-libc-debug/

Android Native内存分析工具 Raphael

https://mp.weixin.qq.com/s/RF3m9_v5bYTYbwY-d1RloQ
https://jishuin.proginn.com/p/763bfbd6979d

参考文档

应用稳定性优化系列(一),ANR问题全面解析
应用稳定性优化系列(二),Crash/Tombstone问题分析及定位
应用稳定性优化系列(三),资源泄露问题分析及定位
Android Log的分布、收集、整理
Android中native进程内存泄露的调试技巧(一)-- libc debug
使用objdump进行Android crash 日志 分析
android objdump 用法,【转】 Android调试的必杀技——反汇编
Linux环境下段错误的产生原因及调试方法小结
Android异常分析

你可能感兴趣的:(android系统稳定性-内存问题分析总结)