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动态行为追踪工具

相关推荐
方白羽9 天前
Android 开发中,准确判断应用处于“前台(Foreground)”还是“后台(Background)
android·app·客户端
iOS阿玮9 天前
死了么 - 官方正版惨遭下架,背后原因竟是ta!
uni-app·app·apple
CareyWYR11 天前
我开发了一款工具箱类型APP:CreativeUtil
ios·app·mac
得物技术11 天前
得物App智能巡检技术的探索与实践
app·客户端
大熊猫侯佩11 天前
App 暴毙现场直击:如何用 MetricKit 写一份完美的“验尸报告”
app·xcode·apple
iOS阿玮14 天前
“死了么”App荣登付费榜第一名!
uni-app·app·apple
先飞的笨鸟15 天前
2026 年 Expo + React Native 项目接入微信分享完整指南
前端·ios·app
iOS阿玮15 天前
AppStore卡审依旧存在,预计下周将逐渐恢复常态!
uni-app·app·apple
FinClip16 天前
微信AI小程序“亿元计划”来了!你的APP如何一键接入,抢先变现?
前端·微信小程序·app