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

相关推荐
iOS阿玮4 天前
苹果卡审情况将逐步缓解,合规的开发者请耐心等待~
uni-app·app·apple
iOS阿玮5 天前
期待iOS开发者加入,共同抵制“苹果税”反垄断招募令!
uni-app·app·apple
参宿四南河三6 天前
Kotlin的Flow用法(实例加长加倍版)
app·响应式编程
QING61812 天前
使用扩展函数为 AppCompatTextView 提供了多段文本点击区域设置功能
android·kotlin·app
iOS阿玮13 天前
苹果市场常见的处罚邮件,最低处罚基本上听劝稳过。
uni-app·app·apple
小喷友13 天前
第9章 鸿蒙微内核与系统架构
前端·app·harmonyos
无知的前端16 天前
一文精通- iOS隐私权限
ios·程序员·app
阿里云云原生17 天前
从体验到系统工程丨上手评测国内首款 AI 电商 App
app
iOS阿玮17 天前
苹果审核被拒4.8.0条款,快速过审通关指南。
uni-app·app·apple
iOS阿玮18 天前
江湖传闻谷歌比苹果严格多了,那么到底有多狠?
uni-app·app·apple