Android Binder 监控

932 阅读4分钟

Android Binder 监控

BinderProxy 泄漏监控

正常情况下BinderProxy不使用时会被回收,进而回收BpBinder,减少BBinder的引用计数,当BBinder所在进程退出后,BinderProxy也应该被回收(通过link to death)。 因此为了监控BinderProxy的状态,Android增加了一套监控逻辑。

首先在BinderProxy类中声明了一个静态的成员变量,用来保存BinderProxy Map,其中key是BinderProxy对应的BpBinder的指针地址,value是BinderProxy对象。

private static final ProxyMap sProxyMap = new ProxyMap();

创建BinderProxy对象是会调用成员函数getInstance:

private static BinderProxy getInstance(long nativeData, long iBinder) {
    BinderProxy result;
    synchronized (sProxyMap) {
        try {
            result = sProxyMap.get(iBinder);
            if (result != null) {
                return result;
            }
            result = new BinderProxy(nativeData);
        } catch (Throwable e) {
            // We're throwing an exception (probably OOME); don't drop nativeData.
            NativeAllocationRegistry.applyFreeFunction(NoImagePreloadHolder.sNativeFinalizer,
                    nativeData);
            throw e;
        }
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(result, nativeData);
        // The registry now owns nativeData, even if registration threw an exception.
        sProxyMap.set(iBinder, result);
    }
    return result;
}

首先从sProxyMap中查找是否有对应的BinderProxy对象,有的话直接返回,没有则创建一个,并设置到sProxyMap中。

BinderProxy的泄露监控机制就是在sProxyMap的set函数中实现的:

/**
 * Add the key-value pair to the map.
 * Requires that the indicated key is not already in the map.
 */
void set(long key, @NonNull BinderProxy value) {

    // 省略部分代码
    ```
if (size >= mWarnBucketSize) {
    final int totalSize = size();
    Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
            + " total = " + totalSize);
    mWarnBucketSize += WARN_INCREMENT;
    if (Build.IS_DEBUGGABLE && totalSize >= CRASH_AT_SIZE) {
        // Use the number of uncleared entries to determine whether we should
        // really report a histogram and crash. We don't want to fundamentally
        // change behavior for a debuggable process, so we GC only if we are
        // about to crash.
        final int totalUnclearedSize = unclearedSize();
        if (totalUnclearedSize >= CRASH_AT_SIZE) {
            dumpProxyInterfaceCounts();
            dumpPerUidProxyCounts();
            Runtime.getRuntime().gc();
            throw new AssertionError("Binder ProxyMap has too many entries: "
                    + totalSize + " (total), " + totalUnclearedSize + " (uncleared), "
                    + unclearedSize() + " (uncleared after GC). BinderProxy leak?");
        } else if (totalSize > 3 * totalUnclearedSize / 2) {
            Log.v(Binder.TAG, "BinderProxy map has many cleared entries: "
                    + (totalSize - totalUnclearedSize) + " of " + totalSize
                    + " are cleared");
        }
    }
}
}

  • sProxyMap通过hash实现,当每个hash下的数组大小大于mWarnBucketSize(20)以后,BinderProxy每增加WARN_INCREMENT(10)个就会打印一次警告
  • 之后如果当前版本是debug版,同时当sProxyMap的大小超过CRASH_AT_SIZE(20000) 以后会获取没有回收的BinderProxy对象数量,如果也超过CRASH_AT_SIZE的话则会打印不同BinderProxy的数量(dumpProxyInterfaceCounts)和通过UID统计的BinderProxy数量(dumpPerUidProxyCounts),同时抛出一个异常退出当前进程
  • 如果未回收的BinderProxy对象数量小于CRASH_AT_SIZE,同时总的BinderProxy数量大于未回收数量的1.5倍,则打印Warning

dumpProxyInterfaceCounts

dumpProxyInterfaceCounts的实现比较简单,就是遍历sProxyMap并根据Binder的interface name出现的次数进行排序,打印排名前十的Binder信息。

dumpPerUidProxyCounts

dumpPerUidProxyCounts函数依赖与Binder服务的设置,默认是没有打开的,检索android 11 源码,目前只有system server/Systemui进程打开了该配置。他会根据按照BinderProxy对应的Binder实体对象所属的UID来统计Binder信息,并打印,结合上面的Binder排名我们就可以比较清楚的知道是哪个服务的BinderProxy对象发生了泄露。

dumpPerUidProxyCounts的主要实现在native层,在BpBinder的类中定义了一个map类型的成员变量sTrackingMap来track,其中key是uid,value是Bpbinder的数量:

std::unordered_map<int32_t,uint32_t> BpBinder::sTrackingMap;

在BpBinder创建的时候进行统计,该功能受几个变量约束:

  • sCountByUidEnabled:是否打开该功能,可以通过函数 enableCountByUid设置;
  • sBinderProxyThrottleCreate:是否对BpBinder进行节流;
  • sBinderProxyCountHighWatermark:BpBinder的上限,当高于上限的时候就会打印Error log,并会调用LimitCallback进行处理,处理后如果当sBinderProxyThrottleCreate设置了,则不会继续创建BpBinder。通过setBinderProxyCountWatermarks函数设置;
  • sBinderProxyCountLowWatermark:BpBinder下限,当Bpbiner已经超过上限,只有当其数量再低于该值后再次超过上限才会打印BpBinder error信息。setBinderProxyCountWatermarks函数设置;
  • sLimitCallback:Binder服务设置到Bpbinder的回调,当BpBinder的数量超过上线以后可以调用该回调确定如何处理,通过setLimitCallback函数设置;

BpBinder* BpBinder::create(int32_t handle) {
    int32_t trackedUid = -1;
    if (sCountByUidEnabled) {
        trackedUid = IPCThreadState::self()->getCallingUid();
        AutoMutex _l(sTrackingLock);
        uint32_t trackedValue = sTrackingMap[trackedUid];
        if (CC_UNLIKELY(trackedValue & LIMIT_REACHED_MASK)) {
            if (sBinderProxyThrottleCreate) {
                return nullptr;
            }
        } else {
            if ((trackedValue & COUNTING_VALUE_MASK) >= sBinderProxyCountHighWatermark) {
                ALOGE("Too many binder proxy objects sent to uid %d from uid %d (%d proxies held)",
                      getuid(), trackedUid, trackedValue);
                sTrackingMap[trackedUid] |= LIMIT_REACHED_MASK;
                if (sLimitCallback) sLimitCallback(trackedUid);
                if (sBinderProxyThrottleCreate) {
                    ALOGI("Throttling binder proxy creates from uid %d in uid %d until binder proxy"
                          " count drops below %d",
                          trackedUid, getuid(), sBinderProxyCountLowWatermark);
                    return nullptr;
                }
            }
        }
        sTrackingMap[trackedUid]++;
    }
    return new BpBinder(handle, trackedUid);
}

SystemServer中的应用:

BinderInternal.nSetBinderProxyCountWatermarks(BINDER_PROXY_HIGH_WATERMARK,
        BINDER_PROXY_LOW_WATERMARK);
BinderInternal.nSetBinderProxyCountEnabled(true);
BinderInternal.setBinderProxyCountCallback(
        (uid) -> {
            Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
                    + Process.myUid());
            BinderProxy.dumpProxyDebugInfo();
            if (uid == Process.SYSTEM_UID) {
                Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
            } else {
                killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
                        "Too many Binders sent to SYSTEM");
            }
        }, mHandler);

Binder 线程池监控

Android提供了接口blockUntilThreadAvailable可以判断服务端Binder线程是否用尽,如果用尽则会一直block,system server通过该接口来监控自身binder线程的使用情况。

IPCThreadState类的getAndExecuteCommand函数中会在执行binder调用的时候判断Binder线程是否用尽,如果是的话会打印如下日志:

"binder thread pool (%zu threads) starved for %" PRId64 " ms"