浅谈 Android Binder 监控方案

这篇具有很好参考价值的文章主要介绍了浅谈 Android Binder 监控方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在 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 API。

这一方案实现简单,兼容性好,主要的毛病在于:

只支持 Android 10 及以上版本,不过现状 Android 10 以下的设备占比已经不高了。
ProxyTransactListener 的接口设计上不带 data 参数( Binder 调用的传入数据 ),我们也就无法做传输数据大小的统计。此外,对于多进程应用来说,实践上可能会以统一的 AIDL 对象作为通信通道封装一层屏蔽了匿名 Binder 对象传递和 AIDL 模版代码的 IPC 框架,实际 IPC 调用的目标逻辑则以统一的调用约定封装在 data 参数中。这种情况下,只有拿到 data 参数( 将 IPC 调用统一放到线程池中执行的情况,堆栈没有意义 )我们才能实际确认 IPC 调用的真正目标逻辑。

JNI Hook BinderProxy.transactNative

实际上 Java 的 Binder 调用总是会走到 BinderProxy 的 JNI 方法 transactNative,我们可以基于 JNI Hook 来 hook transactNative,实现全版本的进程内 Java 的 Binder 调用的全局监控,也可以拿到 Binder 调用的完整参数和返回结果。

这里稍微提一下 JNI Hook,JNI Hook 基于 JNI 边界 hook Java JNI 方法对应的 Native 函数实现,具体实现上 hack 点少,稳定性较好,算得上是线上比较常用的一类通用 Native Hook 方案。大体上讲,JNI Hook 实现上分为两步,找到原 Native 函数和替换该 Native 函数。

  • Native 函数的替换比较简单,我们通过调用 JNI 的 RegisterNatives 接口就可以实现 Native 函数的覆盖。( 注意到如果原始 JNI 方法也是通过 RegisterNatives 注册的,我们需要保证 JNI Hook 的 RegisterNatives 执行在后 )
  • 找到原 Native 函数则稍微复杂些,而且我们总是需要依赖原 Native 函数已经先行注册才能找到该 Native 函数。
    • 一个可行的方案是手动实现一个 JNI 方法,用来计算实际 ArtMethod 对象( 即 Java 方法在 art 中的实际表示 )中存放 Native 函数的属性的偏移。得到这个偏移之后即可以基于被 Hook JNI 方法的 ArtMethod 对象拿到原 Native 函数。怎么拿到 ArtMethod 对象?实际上,在 Android 11 以前,jmethodID 就是 ArtMethod 指针,在 Android 11 之后,jmethodID 默认变成了间接引用,但我们仍然可以通过 Java Method 对象的 artMethod 属性拿到 ArtMethod 指针。详细介绍可参考一种通用超简单的 Android Java Native 方法 Hook,无需依赖 Hook 框架。
    • 另一个可行的方案是可以基于 art 的内部函数 GetNativeMethods 来直接查询得到原 Native 函数。GetNativeMethods 几个函数是 art 用于支持 NativeBridge 的,稳定性上也有所保证。NativeBridge 详细介绍可参考用于 Android ART 虚拟机 JNI 调用的 NativeBridge 介绍。

具体到 JNI Hook BinderProxy.transactNative,实际在跑到应用的第一行业务代码( Application 的 attachBaseContext )之前,就已经有 Java 的 Binder 调用发生,因此我们根本不需要手动触发 Binder 调用来保证 BinderProxy.transactNative 的 Native 函数注册。另外,注意到 BinderProxy 的 transactNative 也是 hidden api,这里也需要先行绕过 hidden api 的限制。

Hook BinderProxy.transactNative 的方案可以很好地满足监控进程内全局 Java Binder 调用的需要,但却监控不到 Native 的 Binder 调用。注意到这里的 Java/Native Binder 调用的差异在于 IPC 通信逻辑的实现位置,而非实际业务逻辑的实现位置。典型的如 MediaCodec 一类的音视频接口,实际的 Binder 调用封装都实现在 Native 层,我们使用 Java 调用这些接口,通过 BinderProxy.transactNative 也无法监控到实际的 Binder 调用。要实现包含 Native 的全局 Binder 调用监控,我们需要考虑 Hook 更下一层的 Native 的 transact 函数。

PLT Hook BpBinder::transact

和 Java 层的 Binder 接口设计类似,Native 层 Client 端发起的 Binder 调用,总是会走到 libbinder.so 中 BpBinder 的 transact 函数。注意到 BpBinder 的 transact 函数是一个导出的虚函数,而且使用上总是基于基类 IBinder 指针做动态绑定调用( 也就是说,其他 so 总是基于 BpBinder 的虚函数表来调用 BpBinder::transact,而不是直接依赖 BpBinder::transact 这一符号,而 BpBinder 的虚函数表在 libbinder.so 内部 ),因此我们直接 PLT Hook libbinder.so 对于 BpBinder::transact 的调用即可。

具体看下 BpBinder::transact 的函数声明:

    // NOLINTNEXTLINE(google-default-arguments)
    virtual status_t    transact(   uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0) final;

其中,status_t 实际上只是 int32_t 的别名,但 Parcel 则不是 NDK 暴露的接口,我们没有途径拿到绝对稳定的 Parcel 对象的布局,好在 transact 函数对于 Parcel 的使用是基于引用和指针的( 引用在汇编层面的实现和指针类似 ),我们不需要依赖 Parcel 对象的布局也可以实现一个 transact 的替代函数。

在成功拦截到 BpBinder::transact 的调用之后,我们还需要考虑怎么基于 transact 的调用参数和返回值来获取到我们需要的信息。

对于 Binder 对象( 即 transact 的隐含调用参数 this 指针 )本身,我们通常会关注它的 descriptor( 再结合 code 参数可以定位到实际 IPC 调用的目标逻辑 ),这里我们直接调用导出接口 BpBinder::getInterfaceDescriptor 即可。

    virtual const String16&    getInterfaceDescriptor() const;

比较麻烦的是 String16 也不是 NDK 暴露的接口,而且它用来转成 char16_t* 字符创的函数实现是内联的,

    inline  const char16_t*     string() const;

我们只能重新 hardcode 声明一个类似的 String16 类来做强转。好在从系统源码看,String16 的对象布局比较简单且稳定,只有一个 const char16_t* 类型的私有属性 mString,而且不存在虚函数。类似这样:

class String16 {
public:
    [[nodiscard]] inline const char16_t *string() const;
private:
    const char16_t* mString;
};

inline const char16_t* String16::string() const {
    return mString;
}

拿到 String16 对应的 char16_t* 字符串之后,我们直接在回调 Java 时用 JNI 接口将其转为 jstring 即可。

另一个常用的信息是 data 的数据大小。我们可以直接调用导出接口 Parcel::dataSize 来获取。注意到 transact 函数 的 data 参数是 Parcel 的引用,我们直接声明一个空类来承接 data 参数,再对拿到的 data 取址,让编译器可以正常完成引用到指针的转换即可。类似这样:

class Parcel {};

// size_t dataSize() const;
typedef size_t(*ParcelDataSize)(const Parcel *);

// virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) = 0;
status_t HijackedTransact(void *thiz, uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags);

ParcelDataSize g_parcel_data_size = nullptr;
auto data_size = g_parcel_data_size(&data);

此外,注意到对于 Java 的 Binder 调用而言,会在 BinderProxy.transactNative 的内部再调用到 BpBinder::transact,我们可以结合 JNI Hook 和 PLT Hook 两个方案,对 Java 的 Binder 调用,基于 JNI Hook 拿到完整的 Java 参数,方便我们在 Java 回调中直接基于 Java 参数做进一步处理。

One More Thing

拦截到 Binder 调用只是监控的第一步,更为重要的是在这个基础上如何做数据处理来发现和定位问题。

前面提到的两类经典问题:IPC 耗时卡顿和传输数据过大崩溃,可以通过前后打点统计 transact 耗时以及调用前获取传输数据大小的方式来挖掘。 定位问题上,堆栈和当次 Binder 调用的 descriptor 和 code 是比较有价值的信息。

如果你还没有掌握Framework,现在想要在最短的时间里吃透它,可以参考一下《Android Framework核心知识点》,里面内容包含了:Init、Zygote、SystemServer、Binder、Handler、AMS、PMS、Launcher……等知识点记录。

《Framework 核心知识点汇总手册》:https://qr18.cn/AQpN4J

Handler 机制实现原理部分:
1.宏观理论分析与Message源码分析
2.MessageQueue的源码分析
3.Looper的源码分析
4.handler的源码分析
5.总结

浅谈 Android Binder 监控方案,移动开发,Android,Framework,android,移动开发,架构,framework,安卓

Binder 原理:
1.学习Binder前必须要了解的知识点
2.ServiceManager中的Binder机制
3.系统服务的注册过程
4.ServiceManager的启动过程
5.系统服务的获取过程
6.Java Binder的初始化
7.Java Binder中系统服务的注册过程

浅谈 Android Binder 监控方案,移动开发,Android,Framework,android,移动开发,架构,framework,安卓

Zygote :

  1. Android系统的启动过程及Zygote的启动过程
  2. 应用进程的启动过程

浅谈 Android Binder 监控方案,移动开发,Android,Framework,android,移动开发,架构,framework,安卓

AMS源码分析 :

  1. Activity生命周期管理
  2. onActivityResult执行过程
  3. AMS中Activity栈管理详解

浅谈 Android Binder 监控方案,移动开发,Android,Framework,android,移动开发,架构,framework,安卓

深入PMS源码:

1.PMS的启动过程和执行流程
2.APK的安装和卸载源码分析
3.PMS中intent-filter的匹配架构

浅谈 Android Binder 监控方案,移动开发,Android,Framework,android,移动开发,架构,framework,安卓

WMS:
1.WMS的诞生
2.WMS的重要成员和Window的添加过程
3.Window的删除过程

浅谈 Android Binder 监控方案,移动开发,Android,Framework,android,移动开发,架构,framework,安卓

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

浅谈 Android Binder 监控方案,移动开发,Android,Framework,android,移动开发,架构,framework,安卓文章来源地址https://www.toymoban.com/news/detail-681470.html

到了这里,关于浅谈 Android Binder 监控方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • BpBinder与PPBinder调用过程——Android开发Binder IPC通信技术

    在Android系统中,进程间通信(IPC)是一个非常重要的话题。Android系统通过Binder IPC机制实现进程间通信,而Binder IPC通信技术则是Android系统中最为重要的进程间通信技术之一。本文将介绍Binder IPC通信技术的原理,并详细解析BpBinder与PPBinder调用过程的使用以及注意事项。 Bind

    2024年02月13日
    浏览(38)
  • 浅谈城市轨道交通视频监控与AI视频智能分析解决方案

    一、背景分析 地铁作为重要的公共场所交通枢纽,流动性非常高、人员大量聚集,轨道交通需要利用视频监控系统来实现全程、全方位的安全防范,这也是保证地铁行车组织和安全的重要手段。调度员和车站值班员通过系统监管列车运行、客流情况、变电所设备室设备运行情

    2024年02月10日
    浏览(59)
  • Android Framework 常见解决方案(20)UDP广播无效问题

    该问题同时存在于android App和Framework系统中。最终效果是在Android系统中直接使用UDP广播无效,有意思的是有的android系统可以,有的Android 系统不行。然而该部分代码自己在Linux上测试时是有效的,代码不变,只是简单的编译移植过来就变得莫名其妙的不行了,头还真是大的不

    2024年02月12日
    浏览(57)
  • Android Framework层开发

    查看源码工具: SourceInsight Instrumentation :可以理解为ActivityThread的一个工具类,在ActivityThread中初始化,一个进程只存在一个Instrumentation对象,在每个Activity初始化时,会通过Activity的Attach方法,将该引用传递给Activity。Activity所有生命周期的方法都有该类来执行。 Init进程(pi

    2024年02月04日
    浏览(45)
  • Android Framework 常见解决方案(24)屏蔽FallbackHome,去除 Android正在启动,直接进入Launcher

    开机以后,设备会有一个“android正在启动”这样的弹框,这个界面是一个叫FallbackHome的Activity来展示的。FallbackHome机制是Android系统启动过程中的一种降级处理机制。当系统启动时,如果默认的Launcher应用无法正常加载或出现错误,系统会自动启用FallbackHome来替代默认Launcher。

    2024年01月24日
    浏览(49)
  • 千里马android framework车机车载手机智能驾驶双屏互动实现方案

    今年车展上,网友蚱蜢同学带回来了一些车载rom相关的一些素材,刚好也发布了wms/ams专题课程,有粉丝朋友提供了一个车机的双屏互动的产品交互视频如下: 上面的就是车机两个屏幕的互动联动情况,转化成设计图如下: 这里对在个双屏需求进行要点细分: 1、通过多手指

    2024年02月16日
    浏览(42)
  • 我是如何从Android开发转framework开发的

    转framework开发快一年了,一直都想写一篇文章,分享一下自己的工作心得,也让做应用开发的小伙伴对framework开发有一定的了解,但因为种种原因耽搁了,今天就趁着工作闲暇之余,聊聊我从应用开发转framework开发的心路历程,自己也是刚开始学着写文章,文笔不太好,请见

    2024年02月02日
    浏览(51)
  • 【Android Framework (十二) 】- 智能硬件设备开发

    针对我过往工作经历,曾在一家智能科技任职Android开发工程师,简单介绍下关于任职期间接触和开发过的一些项目经历,智能多与物联网(LOT)进行联系,从对Android智能硬件一无所知到现在算是略有小成,期间踩了很多坑,也接触到了许多非Android方面的知识,现用文章的方

    2024年02月12日
    浏览(45)
  • SurfaceControl之Transaction事物深入剖析-android framework实战开发

    前面已经讲解清楚了SurfaceControl整个创建过程,一般SurfaceControl都是一个静态图层的代表,但往往只有静态一个图层是没有意义的,即只是创建了一个图层其实啥也看不到,更多需要是SurfaceControl对应的Transaction,这个事务才是真正可以让SurfaceControl可以显示的关键所在,Transac

    2024年01月24日
    浏览(43)
  • 【Android】Binder(一)Binder的介绍和AIDL使用Binder的实例

    Android 中的 Binder 是一个进程间通信机制,它允许不同进程之间相互调用方法和传递数据。Binder 主要用于实现系统服务和应用程序之间的通信,以及实现 IPC(Inter-Process Communication,进程间通信)。 Binder 的核心是 Binder 驱动程序,它负责管理不同进程之间的通信。每个进程都可

    2024年02月07日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包