前言
软件版本在拷机(长稳)阶段最容易系统稳定性问题,而且任何卡死/卡顿/崩溃/重启等现场都可能是内存异常导致,从系统工程师角度会遇到各种内存问题,不管是应用挂死,系统挂死,kernel挂死,应用内存泄漏,系统内存泄漏,kernel内存泄漏等都会遇到,有时解决一个稳定性问题,需要不同的团队开发人员,且每次挂死原因都不会一样,一百次挂死有一百个原因,导致问题也不好总结,下面也是根据之前遇到过的内存问题情况,做个大概总结,经供参考;
内存异常分类
内存问题主要非两大类, 内存段错误和内存泄漏, 这两大类问题又有很多细分场景;java程序内存问题这里不说明,因为java内存问题有独立的工具和分析方法,我主要还是做系统内存分析;
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可以很快分析出有栈溢出情况。
- 其他特殊场景,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加扰流, 播放一段时候后盒子就挂掉。
问题原因:
- VMX 加扰流在使用AES-128解码的时候使用了PKCS7算法填充数据, 导致解密后的数据不是188对齐。
- 机顶盒再给TS BUF送数据的时候会做0x47开头和188字节对齐判断, 如果不是0x47开头就会在拼装188字节的时候丢的一部分数据。
- 丢掉一部分数据送给TS BUF后, 就会导致AAC播放挂掉。
验证是AAC问题测试: - 我们去掉AAC解码库,播放很久没有死机。
- 后来找到原因后,我们把送给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/
经过测试验证:
原始github上LeakTracer,在android上无法跑起来,堆栈记录会段错误,需要unwind方法。网上有人修改针对android的LeakTracer
在32位系统没有问题,在64位系统上有的泄漏定位不出来情况。
只能分析库里面的内存泄漏,如果是进程里面泄漏,堆栈地址信息不对,可以把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异常分析