Binder Driver缺陷导致定屏的实战分析

本文深入探讨了一个Android系统在连续运行Monkey测试后出现的罕见定屏问题,原因是异步Binder调用在特定条件下被错误消耗导致无限阻塞。通过分析Trace、CPU调度、Ramdump和binder通信协议,揭示了问题的关键在于BR_DEAD_REPLY被错误处理,提出解决方案是确保错误事务及时处理。
摘要由CSDN通过智能技术生成

引用

本文讲解异步binder call是如何阻塞整个系统的,通过ramdump信息以及binder通信协议来演绎并还原定屏现场。

一、背景知识点

解决此问题所涉及到的基础知识点有:Trace、CPU调度、Ramdump推导、Crash工具、GDB工具、Ftrace, 尤其深入理解binder IPC机制。


1.1 工具简介
  • Trace:分析死锁问题的最基本的技能,通过kill -3可生成相应的traces.txt文件,里面记录着当前时刻系统各线程 所处在的调用栈。

  • CPU调度:可通过查看schedstat节点,得知该线程是否长时间处于RQ队列的等待

  • Ramdump:把系统memory中某一个时间点的数据信息保存起来的内存崩溃文件,属于ELF文件格式。 当系统发生致命错误无法恢复的时候,主动触发抓取ramdump能异常现场保留下来,这是属于高级调试秘籍。

  • Crash工具:用于推导与分析ramdump内存信息。

  • GDB工具:由GNU开源组织发布的、UNIX/LINUX操作系统下的基于命令行的强大调试工具,比如用于分析coredump

  • Ftrace:用于分析Linux内核的运行时行为的强有力工具,比如能某方法的耗时数据、代码的执行流情况。


1.2 Binder简介
Binder IPC是最为整个Android系统跨进程通信的基石,整个系统绝大多数的跨进程都是采用Binder,如果对Binder不太了解看本文会非常吃力,在Gityuan.com博客中有大量讲解关于Binder原理的文章,见http://gityuan.com/2015/10/31/binder-prepare/,这里不再赘述。

简单列两张关于Binder通信架构的图,640?wx_fmt=jpegBinder通信采用C/S架构,主要包含Client、Server、ServiceManager以及binder驱动部分,其中ServiceManager用于管理系统中的各种服务。Client向Server通信过程图中画的是虚线,是由于它们彼此之间不是直接交互的,而是采用ioctl的方式跟Binder驱动进行交互的,从而实现IPC通信方式。

接下来再以startService为例,展示一次Binder通信过程的方法执行流:640?wx_fmt=jpeg

从图中,可见当一次binder call发起后便停在waitForResponse()方法,等待执行完具体工作后才能结束。 


那么什么时机binder call端会退出waitForResponse()方法?见下图:

640?wx_fmt=png

退出waitForResponse场景说明:

  • 1)当Client收到BR_DEAD_REPLY或BR_FAILED_REPLY(往往是对端进程被杀或者transaction执行失败),则无论是同步还是异步的binder call都会结束waitForResponse()方法。

  • 2)正常通信的情况下,当收到BR_TRANSACTION_COMPLETE则结束异步binder call; 当收到BR_REPLY则结束同步binder call。


二、初步分析

有了以上背景知识的铺垫,接下来就进入正式实战分析过程。


2.1 问题描述

Android 8.0系统用几十台手机连续跑几十个小时Monkey的情况下有概率出现定屏问题。

定屏是指屏幕长时间卡住不动,也可以成为冻屏或者hang机,绝大多数情况下都是由于多个线程之间存在直接或者间接死锁而引发,而本案例实属非常罕见例子, 异步方法处于无限等待状态被blocked,从而导致的定屏。

2.2 初步分析

通过查看trace,不难发现导致定屏的原因如下:

system_server的所有binder线程以及其中重要现场都在等待AMS锁, 而AMS锁被线程Binder:12635_C所持有; Binder:12635_C线程正在执行bindApplication()方法,调用栈如下:

640?wx_fmt=png

终极难题:attachApplicationLocked()是属于异步binder call,之所以叫异步binder call,就是由于可异步执行而并不会阻塞线程。 但此处却能阻塞整个系统,这一点基本是毁三观的地方。

怀疑1:有同学可能会觉得是不是Binder驱动里的休眠唤醒问题,对端进程出现异常导致无法唤醒该binder线程从而阻塞系统? 

回答1:这个观点咋一看,好像合情合理,还挺能唬人的。接下来,我先来科普一下,以正视听。

如果熟悉Binder原理的同学,应该知道上面说的是不可能发生的事情。oneway binder call,也就是所谓的异步调用, Binder机制设计绝不可能傻到让异步的binder call来需要等待对端进程的唤醒。

真正的oneway binder call, 一旦是事务发送出去。 a)如果成功,则会向自己线程thread->todo队列里面放上BINDER_WORK_TRANSACTION_COMPLETE; b)如果失败,则会向自己线程thread->todo队列里面放上BINDER_WORK_RETURN_ERROR。

紧接着,就会在binder_thread_read()过程把刚才的BINDER_WORK_XXX读取出去,然后调出此次binder call。 之所以要往自己队列放入BINDER_WORK_XXX,为了告知本次事务是否成功的投递到对端进程。但整个过程,无需对端进程的参与。

也就是说bindApplication()方法作为异步binder调用方法,只会等待自己向自己todo队列写入的BR_TRANSACTION_COMPLETE或BR_DEAD

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值