ResucParty机制

779 阅读11分钟

一、引言

智能手机已然是当今世界上最常用以及最重要的电子设备之一,它从一开始就是集成影音播放、电子书、照相机、通讯、指南针等多种实用工具为一体的先进电子产品,让人们的生活更加便利。因此用户平时都严重依赖它们的智能手机。

然而有时智能手机会陷入循环重启启动,或者核心服务或程序循环崩溃的僵局,这样用户就不得不花费时间去寻求售后的帮助。在这个过程中,不仅用户会因为智能手机无法正常使用而感到体验非常差,而且对智能手机的制造商及运营商而言,也同样需要付出高昂的代价。

RescueParty机制正是在这个背景下诞生的,当它注意到系统或系统核心组件陷入循环崩溃状态时,就会根据崩溃的程度执行不同的救援行动,以期望让设备恢复到正常使用的状态。

二、简介

RescueParty机制监测着系统核心组件,当系统核心组件循环Crash的时候,会根据严重性逐渐上调RescueParty的级别,并且执行对应的救援机制。

image.png 每个阶段都会执行对应的救援机制,如果没有生效,那么将无法结束系统核心组件循环Crash的状况,严重性会逐渐增加触发RescueParty等级上调,并且执行下一个等级的救援机制,直到最后所有措施都无济于事,进入Recovery尝试让用户通过清除数据的方式进行设备恢复。

三、行为

下面这个表格,列举了RescueParty各阶段的救援方式。

异常类型系统常驻AppSystemServer crash or watchdog
异常阶段第一阶段非SystemPackage的设置项重置为默认值重置失败的package所访问namespace的DeviceConfig
第二阶段非SystemPackage的设置如果是系统默认属性(isDefaultFromSystem)重置为默认值否则直接删除这个属性重置失败的package所访问namespace的DeviceConfig
第三阶段如果是系统默认属性(isDefaultFromSystem)重置为默认值否则直接删除这个属性重置所有namespace的DeviceConfig配置
第四阶段设置sys.attempting_reboot属性为true (为了允许在早期进行底层重启)整机重启
第五及以上阶段设置sys.attempting_factory_reset属性为true (为了允许在早期进行底层重启)整机重启并进入Recovery

关于RescueParty级别提升的条件:

  • System Server在 5 分钟内重启 5 次及以上。
  • PackageWatchdog监控的系统常驻App(每个App单独计算)在 1 分钟内崩溃 5 次及以上

关于RescueParty阶段下降的条件:

  • 系统常驻App提升的每个阶段,在一小时后都会自动下调
  • System Server提升的阶段,在一小时无crash或watchdog的情况下重置为0

四、原理

异常监测原理

针对SystemServer的监测原理

image.png

  1. 当SystemServer启动时,在Bootstrap Services启动阶段会将RescuePartyObserver注册到PackageWatchdog中,RescuePartyObserver是RescueParty的一个监视器。
  2. 紧接着SystemServer会调用PackageWatchdog的noteBoot方法,它将会记录一次SystemServer启动的时间戳和次数。
  3. 如果5分钟内记录SystemServer启动了5次,那么将会上调RescueParty等级并且调用RescuePartyObserver的executeBootLoopMitigation方法执行RescueParty等级对应的救援措施,然后将SystemServer启动次数归零

由于SystemServer发生Watchdog或Crash等异常信息时都会重启,因此利用重启时记录启动次数和时间戳,就能够计算SystemServer循环崩溃的严重性,转换成对应的RescueParty级别。

image.png

Settings数据库的存储类似表结构

Namespace/PackageKeyValue
androiddark_mode_time_type2
com.android.settingsenable_three_gesture1
com.miui.homelast_call_forward_action-1
com.miui.homemode_ringer2

O 那么RescueParty是如何来记录SystemServer启动次数、时间戳和RescueParty级别的呢?

答案是通过属性来进行记录,属性即使在SystemServer死亡之后,也可以保留下来。

  • sys.rescue_boot_count:记录SystemServer 5分钟内启动了几次
  • sys.rescue_boot_start:记录SystemServer启动开始时的时间
  • sys.boot_mitigation_start:记录RescueParty机制激活的时间
  • sys.boot_mitigation_count:记录RescueParty的级别

O RescueParty救援计划在第四级别会重启,重启后RescueParty如何知道它SystemServer处于救援的第几个级别?

答案是,每次由于SystemServer的问题更新RescueParty级别的时候,都会将RescueParty级别写入到mitigation_count.txt文件中。当SystemServer第一次启动的时候,RescueParty机制会尝试从mitigation_count.txt中还原RescueParty的级别。

App Crash时监测原理

只有系统常驻App才能触发RescueParty第四、第五级别,并被PackageWatchdog监测。而且有一个细节,每个被监测App的RescueParty级别、异常计数都是分离开来的,它们之间相互独立。

但是有一种情况,如果发生OTA升级,有一个系统常驻App变为系统App,但是它在升级前提升过RescueParty的等级,那么它在升级之后将依旧处于RescueParty的监控状态。当RescueParty服务启动的时候,该App的信息会从package-watchdog.xml中还原

image.png

  1. 当App发生了异常,且未被自身捕获时,该异常将会被Runtime捕获。
  2. 当Runtime捕获到App异常之后,它会与AMS进行Binder通讯,将App和异常信息通过handleApplicationCrash方法进行初步处理,然后传递给AppErrors
  3. AppErrors会使用crashApplication对这些信息进行处理,我们只看最后的处理,即调用PackageWatchdog的onPackageFailure方法。

image.png

  1. PackageWatchdog会对当前程序的异常类型进行监测,如果是Native发生Crash或者有其他健康方面的异常,都会立刻执行RescueParty第一级别的救援。
  2. PackageWatchdog针对系统常驻App特别创建一个MonitoredPackage容器,用于存放Crash的时间戳,以及App所对应的RescueParty的级别。
  3. 之后会立刻对MonitoredPackage的异常计数+1,发现在5分钟内记录了5个异常时间戳,那么将会尝试提高RescueParty级别(同时清空异常时间戳)。如果异常是由于ANR或者App Crash引起的,那么RescueParty会立刻提升RescuePraty级别,记录到App对应的MonitoredPackage中,并执行对应的救援操作。否则不做任何处理。

O RescueParty救援计划在第四级别会重启,重启后RescueParty如何知道被监测的 App 处于救援的第几个级别?

每当监测新的App、更新observer、和进行健康检查的时候,PackageWatchdog都会将所有MonitoredPackage的信息序列化并存储到package-watchdog.xml文件中。每次启动RescueParty的时候,都会从package-watchdog.xml文件还原监测数据。

关于对Device Config 的监控

有趣的是,国内版本现在似乎不会触发这个步骤,也许与Settings的修改有关

在系统运行的过程中,如果一个App修改了Device Config,那么它就会被添加到RescueParty的监听之中。具体的流程如下:

当Namespace中的数据被更新时,会将此前访问过这个Namespace所有App添加到RescueParty的监测之中,持续两天。

当一个App访问了一个Namespace的数据,会将这个App和访问的这个Namespace都进行监测,并且添加双向的映射关系。

image.png 对Device Config的监控,将会在RescueParty触发救援机制的时候发挥作用,它会根据最近App对Device Config的访问或更新记录,对指定范围内的配置项进行重置,尝试去除此前对Device Config的修改来解决循环崩溃的问题。

五、分析与案例

一般 App Crash分析流程:

  1. 当遇到一个重启(并且reason是rescueparty)、或者进入Recovery的问题的时候,可以查找mqsasd\RescuePartyLog文件夹下是否生成了日志信息,将其解压后就可以进行分析了。
  2. 之后我们首先查看uiderrors.txt的信息最后面的信息(如果是重启,看关键词WARM_REBOOT;如果是Recovery,查看关键词FACTORY_RESET)

Finished rescue level FACTORY_RESET for package com.android.nfc

里面的记录类似上面这样,黄色指的是执行了RescueParty某个级别的救援行动,红色的是循环崩溃触发RescueParty救援的App包名。

  1. 此时可以将整个信息用于RescuePartyLog中进行搜索,然后向上查找"AndroidRuntime: FATAL EXCEPTION:"关键信息,就可以找到App因为什么异常而频繁Crash,类似下面这样
07-25 09:08:26.132   nfc 13354 13354 D AndroidRuntime: Shutting down VM
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime: FATAL EXCEPTION: main
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime: Process: com.android.nfc, PID: 13354
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Configuration android.content.res.Resources.getConfiguration()' on a null object reference
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at android.app.ConfigurationController.updateLocaleListFromAppContext(ConfigurationController.java:275)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6808)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2177)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at android.os.Handler.dispatchMessage(Handler.java:106)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at android.os.Looper.loopOnce(Looper.java:210)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at android.os.Looper.loop(Looper.java:299)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at android.app.ActivityThread.main(ActivityThread.java:8090)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at java.lang.reflect.Method.invoke(Native Method)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556)
07-25 09:08:26.132   nfc 13354 13354 E AndroidRuntime:         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:972)

一般SystemServer重启分析流程

  1. 当遇到一个重启(并且reason是rescueparty)、或者进入Recovery的问题的时候,可以查找mqsasd\RescuePartyLog文件夹下是否生成了日志信息,将其解压后就可以进行分析了。
  2. 之后我们首先查看uiderrors.txt的信息最后面的信息(如果是重启,看关键词WARM_REBOOT;如果是Recovery,查看关键词FACTORY_RESET)

Finished rescue level FACTORY_RESET

里面的记录类似上面这样,黄色指的是执行了RescueParty某个级别的救援行动。如果后面没有其他内容,说明是SystemServer循环重启引发了救援机制

  1. 此时可以将整个信息用于RescuePartyLog中进行搜索,然后向上查找以下关键词:
  • "AndroidRuntime: *** FATAL EXCEPTION" : 一般是System进程Crash引发SystemSever重启
  • "Failure starting" : 一般是启动过程中,有System服务启动失败引起的SystemServer重启
  • "Watchdog: *** WATCHDOG KILLING SYSTEM" : 由于WatchDog引发SystemServer重启
04-28 14:02:26.112  1000  2050  2050 E System  : ******************************************
04-28 14:02:26.112  1000  2050  2050 E System  : ************ Failure starting system services
04-28 14:02:26.112  1000  2050  2050 E System  : java.lang.RuntimeException: Failed to start service com.android.server.usage.UsageStatsService: onStart threw an exception
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.SystemServiceManager.startService(SystemServiceManager.java:131)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.SystemServiceManager.startService(SystemServiceManager.java:116)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.SystemServer.startCoreServices(SystemServer.java:904)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.SystemServer.run(SystemServer.java:556)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.SystemServer.main(SystemServer.java:381)
04-28 14:02:26.112  1000  2050  2050 E System  :    at java.lang.reflect.Method.invoke(Native Method)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:972)
04-28 14:02:26.112  1000  2050  2050 E System  : Caused by: android.util.proto.ProtoParseException: Unexpected wire type: 4 at offset 0x1
04-28 14:02:26.112  1000  2050  2050 E System  : mFieldNumber : 0x7
04-28 14:02:26.112  1000  2050  2050 E System  : mWireType : 0x4
04-28 14:02:26.112  1000  2050  2050 E System  : mState : 0x1
04-28 14:02:26.112  1000  2050  2050 E System  : mDiscardedBytes : 0x0
04-28 14:02:26.112  1000  2050  2050 E System  : mOffset : 0x1
04-28 14:02:26.112  1000  2050  2050 E System  : mExpectedObjectTokenStack : null
04-28 14:02:26.112  1000  2050  2050 E System  : mDepth : 0xffffffff
04-28 14:02:26.112  1000  2050  2050 E System  : mBuffer : [B@a045a7e
04-28 14:02:26.112  1000  2050  2050 E System  : mBufferSize : 0x2000
04-28 14:02:26.112  1000  2050  2050 E System  : mEnd : 0x2000
04-28 14:02:26.112  1000  2050  2050 E System  :    at android.util.proto.ProtoInputStream.skip(ProtoInputStream.java:817)
04-28 14:02:26.112  1000  2050  2050 E System  :    at android.util.proto.ProtoInputStream.nextField(ProtoInputStream.java:225)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.usage.UsageStatsProto.read(UsageStatsProto.java:515)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.usage.UsageStatsDatabase.readLocked(UsageStatsDatabase.java:887)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.usage.UsageStatsDatabase.readLocked(UsageStatsDatabase.java:859)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.usage.UsageStatsDatabase.readLocked(UsageStatsDatabase.java:850)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.usage.UsageStatsDatabase.getLatestUsageStats(UsageStatsDatabase.java:573)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.usage.UserUsageStatsService.init(UserUsageStatsService.java:111)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.usage.UsageStatsService.getUserDataAndInitializeIfNeededLocked(UsageStatsService.java:399)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.usage.UsageStatsService.onStart(UsageStatsService.java:249)
04-28 14:02:26.112  1000  2050  2050 E System  :    at com.android.server.SystemServiceManager.startService(SystemServiceManager.java:129)
04-28 14:02:26.112  1000  2050  2050 E System  :    ... 7 more

参考资料

引言部分:

page.om.qq.com/page/ORMJMt…

RescueParty机制作用:

source.android.com/docs/core/d…

RescueParty分析