Android性能优化-实践篇:Frida监测Binder调用

一、前言

在目前的卡顿优化中,主线程的Binder调用优化是一个绕不过的问题,实际上,在目前的工具上,例如使用Perfetto,可以看到binder_transaction,针对某个特定的binder_transaction,也可以找到对端,但是如果想要知道对端的name、code甚至是堆栈,就是一件非常困难的事情。

二、eBPF+golang监控Binder调用

此前,在看雪上面看到一篇文章,原创btrace:binder_transaction+eBPF+Golang实现通用的Android APP动态行为追踪工具,此文通过eBPF+golang实现了对Binder调用的监测,但是只能拿到调用的name+function,如下图所示。

这是一个可以直接push到Android系统执行的工具,但是在使用此工具的时候,发现了一些问题

  • 1、不同手机上面的Code和Function映射不一致,需要多次编译
  • 2、只有部分应用可以正常映射,例如抖音、微信,别的应用一概不行。

第二个问题经过debug发现是由于拿到的data.ptr.buffer是内核态地址,但是此处不可能是内核态地址,而是用户态的,对于内部的原理不清楚,但是通过mask高位就可以正常读取了,猜测是内核做了一次转换。

修改如下:

c++ 复制代码
bpf_probe_read_user(binder_transaction_event->chunk_data, probe_read_size, (void *)((data.ptr.buffer + i * CHUNK_SIZE) & 0x0000FFFFFFFFFFFF));

编译后重新push,测试可以使用。

具体的原理可以参考原文,大致的原理如下:

  • 1、通过eBPF挂载binder_transaction
  • 2、解析Parcel数据,拿到其中的ServerName和Code
  • 3、通过映射拿到Code对应的Function
  • 4、以可读的形式输出

eBPF也是非常强大的工具,但是偏底层,这里我们不再赘述,以后有机会再做分享。

三、Frida+Binder

对于性能优化来讲,我们想要的几个信息是:

  • 1、拿到ServerName+Function
  • 2、拿到耗时
  • 3、拿到堆栈

目前前面的工具已经可以满足第一点,但是后面的不好拿到,那么Frida能做到吗?答案是肯定的。

3.1 分析

首先,观察aidl调用,可以看到第一个调用的方法是writeInterfaceToken,这个方法完成了对ServerName的写入,所以我们可以通过这个拿到ServerName,将ServerName与Parcel做映射。

基本上所有的Java层Binder调用都要经过BinderProxy,所以我们可以Hook BinderProxy的transact方法拿到Code、Parcel,通过从映射里面取数据,我们直接拿到ServerName,这里还有Code信息。

实测解析会出现一些问题,但是目前我们是在Java层进行Hook,所以我们直接hook writeInterfaceToken,在这里直接保存Parcel对ServerName的映射。

然后,Frida可以直接无视私有类,直接通过ClassLoader加载出BinderTransactionNameResolver类,直接进行方法转换,拿到FunctionName。

最后,我们打印出方法前后的方法耗时,以及堆栈信息。

至此,我们就拿到了所有信息

3.2 实现

这里我直接贴一下实现:

JavaScript 复制代码
Java.perform(() => {
    const BinderProxy = Java.use("android.os.BinderProxy");
    const Parcel = Java.use("android.os.Parcel");
    const ResolverClass = Java.use("com.android.internal.os.BinderTransactionNameResolver");
    const binderResolver = ResolverClass.$new();
    const Thread = Java.use("java.lang.Thread");
    var parcelMap = new Map();
    Parcel.writeInterfaceToken.implementation = function (name) {
        parcelMap[this] = name;
        return this.writeInterfaceToken(name);
    }
    BinderProxy.transact.overload('int', 'android.os.Parcel', 'android.os.Parcel', 'int').implementation = function (code, data, reply, flags) {
        let log = "\n=== [Binder Call Detected] ===\n";
        log += "→ Code :", code;
        const thread = Thread.currentThread();
        log += "→ Thread:" + thread.getName() + "\n";
        try {
            var name = parcelMap[data];
            log += "→ InterfaceToken:" + name + "\n";
            const stubClassName = name + "$Stub";
            let stubClass = null;
            try {
                stubClass = Java.use(stubClassName);
                const methodName = binderResolver.getMethodName(stubClass.class, code);
                log += "→ Method Name:" + methodName + "\n";
            } catch (e) {
                console.warn("⚠️ Stub class not found or getMethodName failed:", stubClassName);
            }
            const Throwable = Java.use("java.lang.Throwable");
            const stack = Throwable.$new().getStackTrace();
            var startTime = Date.now();
            var ret = this.transact(code, data, reply, flags);
            log += `Binder call cost : ${Date.now() - startTime}ms`;
            if (thread.getName() == "main") {
                log += "→ Java Stack:\n";
                for (let i = 0; i < stack.length; i++) {
                    log += "    at " + stack[i].toString() + "\n";
                }
            }
            console.log(log);
            return ret;
        } catch (e) {
            console.error("❌ Error parsing Parcel manually:", e);
        }
        return this.transact(code, data, reply, flags);
    };
});

结果如下:

四、总结

到此,我们拿到了检测Binder调用的所有信息,对于卡顿优化,这是个很有用的输入,可以检测到我们应用是否存在异常的在主线程的Binder调用。

但是Frida只是用于线下检测,如果要用线上监听的话,需要考虑别的方式实现。

参考

1、原创btrace:binder_transaction+eBPF+Golang实现通用的Android APP动态行为追踪工具

相关推荐
方白羽4 天前
OkHttp 5.3 隐形变更引发的线上偶发崩溃复盘
android·app·客户端
凡泰AI4 天前
如何为政务 APP 搭建开放平台,实现多部门、第三方供应商标准化入驻与统一管理
android·大数据·小程序·uni-app·app·政务
方白羽6 天前
性能分析:Android Studio Profiler
android·性能优化·app
河北清兮网络科技6 天前
直播APP开发怎么选?流媒体高端定制架构解析,避开模板与外包技术坑
小程序·架构·app·短剧·短剧app·广告联盟
方白羽7 天前
Android17 重写 MessageQueue,解决 Handler 隐性卡顿
android·app
MyBili9 天前
【安卓开发/搞机】快图浏览(QuickPic)技术解析:为何这款3MB应用仍是本地图库的性能天花板?
android·app·安卓·文件管理·相册·看图软件·手机相册
kango11 天前
Xcode、模拟器Runtime、真机符号完整知识手册
ios·app
蓝悦无人机11 天前
蓝悦地面站APP深度测评:一套软件,手机/电脑双端通用,打通“规划-执行-复盘”全链路
app·无人机·无人机地面站·地面站平台
梦想的颜色12 天前
【AI实战】React‑Native + AI 移动端 APP 完整实战全流程|云端 / 本地大模型、Agent 集成、安装配置、避坑指南
react.js·大模型·app·agent·reactnative·expo·ai 应用开发
黑臂麒麟14 天前
HarmonyOS鸿蒙实战应用7:随手账本——数据导出与分享
华为·app·arkts·鸿蒙