赞
踩
Android Recovery问题排查是一个关键的过程,它主要集中在找出触发Recovery模式的原因。Recovery模式是一个特殊的引导模式,通常是因为系统出现严重错误或者需要进行系统更新等操作时被触发。了解触发Recovery模式的原因有助于我们有效地进行问题排查和解决。
以下是一些可能触发Recovery模式的常见原因:
了解以上触发Recovery模式的原因,可以帮助我们更深入地理解Android系统的工作方式,从而更有效地进行问题的排查和解决。
部分S10设备会在运行一段时间后进入到Recovery状态,时间周期长,大概在25~30天中会复现,且无法明确复现场景,发生后通过日志大部分已经是系统异常前的日志,分析不到根因。
是由于内存不足导致关键service无法正常启动,导致在执行方法getService时抛出异常,zygote强制重新forkSystemServer进程后仍在短时间内重复触发导致设备进入Recovery。
搞明白这50份tombstone日志
我们在设备异常进入到recovery后,通过pull获取tombstones文件夹(/data/tombstones
),可以看到进入Recovery模式的设备中会有且只有50份文件,why?这是因为墓碑文件数量有上限的,达到上限时会删除最旧的墓碑文件,可以通过配置属性tombstoned.max_tombstone_count
来修改默认的墓碑文件数量,也可以通过adb查看。
*rk3566_r:/ # getprop | grep max_tombstone_count
[tombstoned.max_tombstone_count]: [50]
rk3566_r:/ #*
但之前了解的进入Recovery不是只需要达到*LEVEL_FACTORY_RESET*
(4)便会触发吗?为什么这里会多达50份文件?
虽然文件有五十份,但分析后发现共性,通过filter,可以看到:
注意:并不一定是三个一组,可以看这个:
(Linux kernel会发送相应的signal
给异常进程,debuggerd捕获这些signal
,做出相应处理的同时(一般来说是退出异常进程),在/data/tombstones
目录下生成一个tombstone
。)
也就是说,虽然生成了tombstone
,但其实只有在system_server
在挂了之后,才会通过RescueParty
进行increaseLevelCount
(这一点后面通过代码和日志结合看也可以证实),所以,我们重点关注计数连续阶段(非连续触发会重置level)的system_serve
最后和首次发生异常的时间,但首次已经无法关注,因为被覆盖了,所以我们就关注最后一次即可。
锁定案发时间点和日志现场
OK,接着我们可以通过排序的方法得到tombstone的最晚时间,再结合该时间点,通过日志查看对应的Rescue-Tag相关日志,相互验证是否为触发Recovery的原因。
# sort -t ' ' -k4,4: 使用空格作为分隔符(-t ' '),并按第四列(时间部分)排序。
❯ cat all | grep '2024-05-23' | sort -t ' ' -k4,4
5: Timestamp: 2024-05-23 07:16:18+0800
5: Timestamp: 2024-05-23 07:16:18+0800
5: Timestamp: 2024-05-23 07:16:30+0800
5: Timestamp: 2024-05-23 07:16:30+0800
...
5: Timestamp: 2024-05-23 07:19:37+0800
//↑这个就是最晚的时间点,基本锁定案发时间点,那么我们再通过android log去到日志现场
在目录/data/vendor/logs
下可以看到捕获到的日志文件目录有如下这些
bash-3.2$ tree -d
.
├── logs
├── 17-08-04-17-00-10-0001
├── 17-08-04-17-00-24-0002
├── 24-04-01-12-04-14-0000
├── 24-04-28-10-41-27-0003 ←←←这里
└── 24-05-23-10-09-19-0004
这里的文件夹名称是文件创建时的时间,通过比对, 2024-05-23 07:19:37时间点就在文件夹**24-04-28-10-41-27-0003
** 中。查找到日志:
为什么我们在tombstone中看到的都是getService错误?
由于我们的tombstones目录下批量出现两个服务和system_serveer错误,那么我们再往前看看,是否有/apex/com.android.os.statsd/bin/statsd
的相关日志(不一定有,可以看看),我们可以看到确实在2024-05-23 07:19:36
时,/apex/com.android.os.statsd/bin/statsd
已经发生异常并且写入到tomestone目录下了,这里指向的是pid:32128
,这个服务会在系统启动后被系统拉起,在frameworks/base/apex/statsd/statsd.rc
下定义
这里我们不去关注这个服务是干嘛的先,关注这个进程id和tid,pid: 32128
, tid: 32477
,往上看。
这里的HwBinder.getService
看着很眼熟,就是对应到我们的tombstone中的错误
那这个为什么会getService报错呢,我们可以看到报错的错误类型是NoSuchElementException
,结合源码推测是不是服务获取失败导致的.
再继续跟踪源码,可以在文件android_os_HwParcel.cpp
的signalExceptionForError()
中看到:
再结合android_os_HwBinder.cpp
的JHwBinder_native_getService
方法中确实是由于service不存在发出signalNAME_NOT_FOUND
抛出了NoSunchElementException
错误
服务为何启动失败?
在getService
异常发生前,我们能够通过过滤正则条件low.*mem
看到以下这些日志内容:
report的file是system_server_lowmem@
,我们可以取任意一个看下这些文件的内容(都类似):
由于内容较多,我这边只截取了本次分析用到部分
上面的Low on memory — 800MB total中的800MB
,取决于当前系统中VSS>0的应用进程所占用的pss之和,是通过ProcessCpuTracker
进行获取,通过读取 /proc
文件系统中的进程信息来更新现有进程的统计数据或添加新进程的数据和移除不存在的进程。
补充知识:TOTAL 是695016 KB, 所以这里显示的是800MB….What?怎么算都不会是800MB啊,原因就是在AMS中的reportMemUsage
方法中,我们本次分析该文本也是通过该方法进行深度分析:
下面是我们重点关注的地方:
Lost Ram,定义如下:
memInfoBuilder.append(" Lost RAM: ");
memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
- (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
- kernelUsed - memInfo.getZramTotalSizeKb()));
memInfoBuilder.append("\n");
Free RAM,定义如下:
memInfoBuilder.append(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb()
+ memInfo.getFreeSizeKb()));
具体的内存获取,大家可以去看看Debug.java#getMemInfo()
方法。
从源码也可以看出,当system_server
crashed后,则关闭zygote,以便由init重新启动,系统服务器将从那里重新启动。
那system_server
是什么时候crashed的呢?以pid为22945的system_server
crash来看,往上继续跟踪,可以看到
再继续往上看,我们来看看这个statsd
服务到底被拉起后发生了什么,再继续往上看可以看pid: 32128
的存活日志,发现就只有几行了,除了main.cpp
中打印的ALOGI("Statsd starts to listen to socket.");
被打印外,在下面就是
这些错误日志来自 IPCThreadState
,表示在尝试增加某个句柄的强引用计数时遇到了问题。attemptIncStrongHandle(N): Not supported
表示对于句柄 N
的操作不被支持。这通常与进程间通信(IPC)系统的内部问题有关,通常是由于资源不足导致无法分配或权限问题。
statscompanion service died
表示 statscompanion
服务已崩溃。
崩溃链路通过日志可以看到:statds获取失败→system_server getSignal → gpu_service getSignal → Zygote Exit and restart.
到这我们getService
异常的原因就找到了。
重启进入Recovery的证据
先抛结论:system_server在{triggerWindow
}内异常退出20次,导致sys.rescue_level
达到等级*LEVEL_FACTORY_RESET*
从而进入Recovery。
接下来,我们会从源码的角度再结合日志来验证我们的推断。
我们可以通过上方的日志可以看出等级是如何一步一步到达等级FACTORY_RESET
的.并且也可以确认在触发后便执行了reboot
,且显示出reason
为recovery
!
Recovery的真相
由于涉及的流程较多,我会list出涉及到的角色,并且介绍其作用(注意,这里的作用重点介绍其在流程中的,不发散),通过画图的方式方便大家理解。
RescueParty
:此类将通过几次救援操作缓慢升级,然后最终重新启动并提示用户是否要擦除数据作为最后的手段。
RescueParty#Threshold(android11后,这个逻辑也移到了PackageWatchdog
):
记录触发次数,设定触发窗口时间,设定触发阈值(这里需要注意,是升级的触发阈值,不是进入Recovery的阈值,目前android11系统下,rescue_level
是固定的,分为5个级别,如下):
BootThreshold
:触发于SystemServer#startBootstrapServices
,关注system_server
的异常, triggerCount
为5,triggerWindow
为300s。AppThreshold
:触发于AppErrors
对象中,关注persistent 级别的应用异常, triggerCount
为5,triggerWindow
为30s。ActivityManagerService
:持有AppErrors
,在异常时除了通知AppErrors
之外,还会通过方法addErrorToDropBox()
将异常信息输出到/data/system/dropbox
文件夹中
AppErrors
:对application异常时进行处理,包括dump消息(我们看到的/data/anr/trace.txt
文件就是它给输出的),anr弹窗拉起,调度RescueParty
等工作。在该流程中,起到了判断是否为persistent应用,直接调用或通过PackageWatchdog调用RescueParty
进行记录。
PackageWatchdog
:在android11版本后出现,顾名思义,就是对Package进行观测,可以在/data/system/pacakge-watchdog.xml
中找到对应的观测者.
rk3566_r:/data/system # cat package-watchdog.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<package-watchdog version="1">
<observer name="rescue-party-observer" />
<observer name="rollback-observer" />
</package-watchdog>
RecoverySystem
:可通过Context.*RECOVERY_SERVICE
获取,调用PMS执行重启*
这里我们只需要知道程序异常时,AMS会调用handleApplicationCrash
,之前的链路暂不追溯(停不下来,就不在本次分析中继续深耕)。
RescueParty工作流程图如下:
在分析过程中发现了LostRam的增长,目前并无法掌握该值的释放和增长规律,如何排查
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。