Android Framework——Binder 监控方案

作者:低性能JsonCodec

在 Android 应用开发中,Binder 可以说是使用最为普遍的 IPC 机制了。我们考虑监控 Binder 这一 IPC 机制,一般是出于以下两个目的:

  • 卡顿优化:IPC 流程完整链路较长,且依赖于其他进程,耗时不可控,而 Binder 调用本身通常又是以 RPC 形式对外提供能力的,使得我们在使用时更容易忽略其 IPC 的本质。总的来说,主线程阻塞在同步 Binder 调用上是导致应用卡顿的一大典型原因。
  • 崩溃优化:Binder 调用过程中可能出现各类异常情况,典型的是 Binder 缓冲区耗尽的情况( 经典 TransactionTooLargeException )。Binder 的缓冲区大小大约为 1M( 注意到这里的缓冲区是最早 mmap 出来进程内全局共享的,而非单次 Binder 调用的限制 ),而异步调用( oneway )的情况更是被限制只能使用缓冲区的一半大小( 约 512K )。

考虑只监控某些个系统服务的情况,得益于 ServiceManager 和 AIDL 的设计,我们可以简单基于动态代理替换当前进程对应的 Proxy 对象来实现监控;而要实现进程内全局 Binder 监控的话,我们需要考虑怎么拦截到 Binder 调用通用的 transact 方法。

基于 Binder.ProxyTransactListener

注意到 Android 10 上系统引入了 Binder.ProxyTransactListener,在 Binder 调用前后( BinderProxy 的 transactNative 方法内部 )会触发回调。从提交记录来看,ProxyTransactListener 引入的目的之一就在于支持 SystemUI 监控主线程的 Binder 调用。

美中不足的地方在于,ProxyTransactListener 和相应的设置接口都是 hide 的,且 BinderProxy 的类属性 sTransactListener 在 hidden api 名单中。不过到目前为止,我们始终是有稳定的方案可以绕过 hidden api 的限制的,因此我们可以基于动态代理创建一个 ProxyTransactListener 实例设置给 BinderProxy,来实现对进程内 Java 的 Binder 调用的全局监控。

这里稍微提一下 hidden api 的绕过方案,在 Android 11 系统禁掉元反射之后,一个简单的方案是先创建一个 Native 线程再 Attach 拿到 JNIEnv,就可以在该线程内正常使用

VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"}); 

来实现全局 hidden api 加白。原理是系统对于基于 JNI 访问 Java API 的情况,在回溯 Java 堆栈找不到 caller 的情况,会信任该次调用不做 hidden api 的拦截,详细逻辑见 GetJniAccessContext。因此我们可以通过创建 Native 线程再 AttachCurrentThread 访问 JNI 接口的方式来构造没有 Java caller 的情况( 这也是 Native 线程 AttachCurrentThread 无法访问应用类的原因,没有 caller 就找不到可用的 ClassLoader )。比较有意思的地方是实际上官方早就意识到了这类 hidden api 后门的存在,但由于改动风险太大一类的原因一直没有合入限制逻辑,类似的讨论可以参考 Don’t trust unknown caller when accessing hidden A

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值