Android Framework——Binder 监控方案

news2024/11/26 8:47:04

作者:低性能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 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 是比较有价值的信息。

为了帮助到大家更好的了解Android Framework框架中的知识点,这边查阅大量的素材,整理了一下的 Android Framework 核心知识点手册,里面记录了:有Handler、Binder、AMS、WMS、PMS、事件分发机制、UI绘制……等等,几乎把更Framework相关的知识点全都记录在册了,还有一些Android 相关的面试题

《Android Framework学习手册》:https://qr18.cn/AQpN4J
开机Init 进程
开机启动 Zygote 进程
开机启动 SystemServer 进程
Binder 驱动
AMS 的启动过程
PMS 的启动过程
Launcher 的启动过程
Android 四大组件
Android 系统服务 - Input 事件的分发过程
Android 底层渲染 - 屏幕刷新机制源码分析
Android 源码分析实战

Framework底层对于现在的Android开发至关重要,因为它提供了许多基础服务和API,使得应用程序可以与操作系统进行交互和通信。以下是Framework底层对于现在的Android开发的重要性:

应用程序开发:Android Framework提供了许多API和工具,可以协助开发人员创建和测试应用程序,以及与其他应用程序进行通信。例如,Activity Manager和Window Manager可以帮助开发人员管理应用程序的生命周期和窗口布局,而Resource Manager可以管理应用程序的资源。

系统性能管理:Framework底层还提供了许多服务和API,可以帮助开发人员在应用程序运行时优化系统性能。例如,Process Manager可以帮助开发人员管理内存和CPU资源,防止应用程序因为内存泄露或占用过多CPU资源而导致系统崩溃。

跨应用程序数据共享:Android Framework提供了许多服务和API,允许应用程序之间共享数据。例如,Activity Binder可以帮助应用程序之间进行跨进程通信,而Content Provider可以帮助不同应用程序之间共享数据。

多媒体文件管理:Framework底层还提供了许多服务和API,可以帮助应用程序处理和管理多媒体文件,例如图片、音频、视频等。例如,Multimedia Framework可以帮助应用程序处理各种多媒体文件的格式和编解码。

Framework底层对于现在的Android开发非常重要,因为它提供了许多基础服务和API,可以帮助开发人员创建高效、稳定和功能丰富的Android应用程序。开发人员必须深入了解Framework底层,以便更好地了解Android系统的内部工作原理,并在应用程序开发过程中合理地使用这些服务和API。

为了帮助到大家更好的了解Android Framework框架中的知识点,这边查阅大量的素材,整理了一下的 Android Framework 核心知识点手册,里面记录了:有Handler、Binder、AMS、WMS、PMS、事件分发机制、UI绘制……等等,几乎把更Framework相关的知识点全都记录在册了,还有一些Android 相关的面试题

《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 源码分析实战

Framework底层对于现在的Android开发至关重要,因为它提供了许多基础服务和API,使得应用程序可以与操作系统进行交互和通信。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/513611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统基础知识介绍之内存层次结构(一)

传统上,内存层次结构的设计者专注于优化平均内存访问时间,这由缓存访问时间、未命中率和未命中惩罚决定。 然而,最近,功率已成为主要考虑因素。 在高端微处理器中,可能有 60 MiB 或更多的片上高速缓存,并且…

并查集-- 一种路径压缩实现

并查集用于计算图连通分量。 比如回答这样的问题: 社交媒体中,用户A和用户B是否属于同一个圈子里的?一个城市到另一个城市是否是可达的? 并查集适用于并不需要计算出图上具体的路径,只需要计算是否连通。 public i…

JavaScript 链表

(成功的唯一秘诀——坚持最终一分钟。——柏拉图) 链表 众所周知,数组的查询比链表快,但插入比链表慢。 这是因为链表是一种动态的数据结构,不同于数组的是,链表分配内存空间的灵活性,它不会像…

解决车载U盘:USB设备未连接 问题

U盘是一种常用的便携式存储设备,用于存储和传输数据。在U盘上使用的文件系统类型决定了它可以支持的文件大小、安全性和其他特性。以下是几种常见的U盘文件系统类型: 1. FAT32:这是U盘上最常用的文件系统类型之一。FAT32文件系统支持的最大文件大小为4GB…

Revit楼板:建筑楼板和结构楼板区别和垫层生成

一、Revit中建筑楼板和结构楼板的区别 Revit中,在我们做项目时楼板是最常见的结构之一,几乎每次都需要使用它。分为建筑楼板和结构楼板,是不是有很多小伙伴就很好奇,为什么分为两种楼板,那么他们是什么时候使用的呢?之间又有何区…

从测试小白成功转型自动化测试,我是如何一步步掌握坚持下来的?

目录 学习自动化测试的初衷 克服困难,掌握自动化测试技能 自动化测试在日常工作中的应用 第一个自动化测试脚本的完成 自动化测试技能带来的机会和挑战 【自动化测试工程师学习路线】 学习自动化测试的初衷 作为一名测试新人,刚进入测试行业的时候…

工业视觉检测的8个技术优势

工业4.0时代,自动化生产线成为了这个时代的主旋律,而工业视觉检测技术也成为其中亮眼的表现,其机器视觉技术为设备提供了智慧的双眼,让自动化的脚步得以加速! 在实际的生产应用中,视觉技术方案往往先被着手…

zed2i相机中imu内参的标定及外参标定

zed2i中imu内参的标定 参考: https://blog.csdn.net/weixin_42681311/article/details/126109617 https://blog.csdn.net/weixin_43135184/article/details/123444090 值得注意,imu内参的标定其实不是那么重要,大致上给一个值应该影响不大…

金字塔特征融合

金字塔的三种主要结构 FPN: Feature Pyramid Networks for Object Detection (CVPR 2017) PANet: Path Aggregation Network for Instance Segmentation (CVPR 2018) BiFPN: EfficientDet: Scalable and Efficient Object Detection (CVPR 2020) Deep High-Resolution Repre…

神奇哈哈镜-第14届蓝桥杯省赛Scratch初级组真题第3题

[导读]:超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成,后续会不定期解读蓝桥杯真题,这是Scratch蓝桥杯真题解析第132讲。 神奇哈哈镜,本题是2023年5月7日举行的第14届蓝桥杯省赛Scratch图形化编程初级组真题第3题&#…

颜值经济崛起,伽蓝开启采购数字化之旅

今天,数字化转型已成为颠覆性力量,很多行业被裹挟其中,或主动或被动,美妆行业也不例外。 作为国内最大的化妆品企业之一的伽蓝,在过去的几年当中,一直是以 7% 到 10% 的速度快速增长,在此过程中…

计算机组成原理---第二章 习题详解版

(一)课内习题 1. (二)课后练习 1.写出下列各整数的原码、反码和补码表示(用8位二进制表示)。其中MSB是最高位(符号位),LSB是最低位。 (1)-35 &#…

DVWA之文件包含漏洞

文件包含漏洞原理 1、什么是文件包含 程序开发人员一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件,而无需再次编写,这中文件调用的过程一般被称为文件包含。 2、文件包含漏洞 程序开发人员一般希望代码更灵活&a…

自学网络安全【黑客】,一般人我劝你还是算了吧

前言:我是劝一般人算了,看你是一般人还是。。。 一、网络安全学习的误区 1.不要试图以编程为基础去学习网络安全2.不要刚开始就深度学习网络安全3.收集适当的学习资料4.适当的报班学习二、学习网络安全的些许准备 1.硬件选择2.软件选择3.外语能力三、网…

数据结构:双向链表(带头循环)

朋友们、伙计们,我们又见面了,本期来给大家解读一下数据结构方面有关双向链表的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏:C语言&#xff1a…

时至今日,Linux会开源,也是一种态度

什么是开源?开源通常指开发者公开系统/应用程序源代码。通过对代码进行共享和重用,可以快速开发出高质量、低维护成本的应用程序。这意味着你不再需要花很多时间来学习新技术或编写复杂的代码。 一、Linux永远的神 就拿linux来举例子。 Linux系统的发起…

分享Python采集190个jQuery代码,总有一款适合您

分享Python采集190个jQuery代码,总有一款适合您 Python采集的190个jQuery代码下载链接:https://pan.baidu.com/s/1KxEOw7IfgZJq7yhYBM1nwg?pwdz3r1 提取码:z3r1 可拖拽的谷歌样式纯javascript模态窗口插件 简单实用的轻量级jQuery评分插…

ubuntu系统配置大恒相机驱动并读取ros话题

文章目录 0. 说明1. 安装大恒相机sdk1.1 下载1.2 安装sdk(用于配置ip和调试相机参数)(1) 电脑网卡配置(网卡固定ip)(2)查看相机图像以及配置相机参数 2. 安装ros驱动包(注:大恒相机官方没ros驱动)2.0 正确流程2.1 错误示范2.1 报错1--缺包2.2 报错2--包编译顺序问题…

CnOpenData缺陷产品召回数据

一、数据简介 缺陷产品召回,是指缺陷产品的生产商、销售商、进口商在得知其生产、销售或进口的产品存在可能引发消费者健康、安全问题的缺陷时,依法向职能部门报告,及时通知消费者,设法从市场上、消费者手中收回缺陷产品&#xff…

Python神经网络学习(六)--机器学习--强化学习

前言: 属实是失踪人口回归了。继续神经网络系列。 强化学习: 强化学习也是一个很重要的方向了,很多人用强化学习玩游戏,可能有人觉得强化学习很难(包括我),但是我今天用网上流传很广的、很经…