布景: 当线上数据装备反常时,客户端代码未做好兜底走到反常分支就可能触发crash,如果在发动阶段就可能导致大面积的crash

目的: 降低线上crash产生到修正期间的影响,熔断导致crash的事务逻辑,规避应用程序发动闪退导致的应用无法运用问题。

计划实现

java crash体系处理流程

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

从art角度剖析java crash

下面别离打印了主线程和子线程触发crash时调用Thread.dispatchUncaughtException的backtrace (红色的为主线程),经过代码去看一下怎么触发仓库。

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash
反常抛出流程

  1. 打包阶段 java->class->dex dex中对应的指令是throw
  2. dex2oat 阶段。 throw->pDeliverException
  3. 运行时履行阶段pDeliverException-> art_quick_deliver_exception-> artDeliverExceptionFromCode-> Thread::SetException

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

反常处理流程

Thread::QuickDeliverException

->QuickExceptionHandler::FindCatch(从仓库中找try catch块)

-> CatchBlockStackVisitor::VisitFrame(遍历仓库,从底层method开端找)

->HandleTryItems(ArtMethod* method)

->ArtMethod::FindCatchBlock (在办法中找到匹配到catch块的dex_pc)

->exception_handler.DoLongJump(清理exception 后跳转到catch块代码履行)

如果找不到catch块处理,就会去经过DetachCurrentThread()和Destroy()去完成,也便是一开端的backtrace ,终究经过调用Thread的dispatchUncaughtException办法履行.

crash降级处理技术原理

经过上面的图,咱们能发现最容易打破crash处理流程的点便是在Thread.dispatchUncaughtException,这儿咱们能够设置自己的一个UncaughtExceptionHandler 来接收流程,各种Crash sdk也是经过这个地方去做crash 捕获。 难道咱们只需直接回来就好吗?这边先给出流程图再讨论里边遇到的问题。

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

  1. 针对没有looper的子线程,咱们确实只需直接return就行,影响的只是子线程的逻辑。
  2. 而主线程处理完uncaughtException,就算咱们不去kill进程,应用程序也会卡死终究ANR。因为主线程履行任务是handler机制,当反常抛出来时looper的任务也不再履行了,此时也无法消费system 进程发过来的消息。 针对上述问题咱们能够测验在uncaughtException下调用Looper.loop重新启用。 可是如果在loop中又抛出了反常。那之前阻塞的逻辑就继续往下走了导致卡死。这时就需求再去try catch Looper.loop这个循环 然后在外面加上while(true)。主线程影响的是一个handler task履行的逻辑。所以类似存在looper逻辑的线程,咱们就要想办法把looper重新发动
  3. 上述计划在兜底一些主线程主流程crash时会导致卡死或白屏,可是发动crash又不可防止会出现在这些常见仓库中。举个比方咱们在app的attach或许oncreate办法中调用了下面办法,这种用上面的计划就不可行了。

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

如果这时候能加个try catch就好了 比方这样

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

有没有办法动态去增加呢?

逆向剖析中经常会用到的一个结构叫xposed,也衍生出了LSPosed(github.com/LSPosed/LSP…, 部分原理便是动态生成dex代码,然后在原办法artmethod entry_point_from_quick_compiled_code_处增加hook代码跳转到新生成的代码。

那么咱们也能够运用该原理去做代码的动态try catch功用。

接连发动crash的主动测验维护

一个装备反常导致线上app发动接连crash无法发动这种场景也是有的。 为了防止这种场景,安全气垫也做了接连crash的主动降级维护功用

  1. 产生发动crash时记录标记,满意接连发动crash时,咱们测验从上面各种case 次序敞开维护。

    1. 满意子线程无looper条件 直接return
    2. 主线程非主流程crash, 测验经过重启loop的计划
    3. 在发动主线程主流程主中产生的crash,需求去剖析仓库测验动态try catch
  2. 触发阶段。发动crash处理时 满意兜底条件 且crash仓库产生在主线程主流程中。

  3. 保存错误办法签名。 经过crash仓库 找到最底部的报错仓库,然后找到crash办法签名,经过仓库可能会找到多个重名办法,这边悉数保存比及下次发动收效。

  4. 办法hook阶段。 对上次保存下的办法进行hook ,被hook的办法默许加上了try catch逻辑。 在反常中咱们根据returnType 回来一些默许值 简单测验自修正。 比方 Object void 回来 null, boolean 回来false, int ,short,long ,byte 回来0, 如果还产生crash则会对上一个办法进行try catch

  5. 稳定性方面,arthook 需求兼容不同的体系版本,存在一些兼容性问题,但自身只做为发动crash兜底的话没什么影响,因为自身就现已要crash了,作为一个抢救措施。

根据仓库下发crash维护

线上配有全局兜底开关,这也是在极端状况下的抢救措施,正常状况下咱们还是需求正常的去收集crash仓库来暴露线上问题。所以通常状况下咱们去兜底维护已知问题是用下发仓库办法的办法去进行匹配。 比方匹配版本,crash类型,crash线程,厂商,体系版本,办法签名等维度。下方不同战略的维护办法。

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

举个比方。

issuetracker.google.com/issues/2108… 最近发现android 12上的一个bug 下发了仓库维护,维护后运用剖析东西检查用户后续行为都是正常运用的

Crash排查系列第四篇|安全气垫如何有效拦截一个java crash

事务价值

  1. 拥有线上问题紧急维护才能,下发兜底crash降级装备,完成线上crash问题维护
  2. 线上接连发动故障中,成功阻拦crash反常, 主动触发兜底
  3. 经过下发仓库修正framework crash问题,有用防止App线上溃散状况