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"