Merge changes I4e06b3f9,Iffcbbb82,If9dd6913
* changes: Move proxy debug info dumping back to Java. Dump top binder proxy interface names in dumpsys. Make BinderProxy @hide public class.
This commit is contained in:
committed by
Android (Google) Code Review
commit
09fa656886
@@ -21,9 +21,7 @@ import android.annotation.Nullable;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.os.BinderCallsStats;
|
||||
import com.android.internal.os.BinderInternal;
|
||||
import com.android.internal.util.FastPrintWriter;
|
||||
@@ -36,12 +34,7 @@ import libcore.util.NativeAllocationRegistry;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for a remotable object, the core part of a lightweight
|
||||
@@ -783,442 +776,3 @@ public class Binder implements IBinder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Java proxy for a native IBinder object.
|
||||
* Allocated and constructed by the native javaObjectforIBinder function. Never allocated
|
||||
* directly from Java code.
|
||||
*/
|
||||
final class BinderProxy implements IBinder {
|
||||
// See android_util_Binder.cpp for the native half of this.
|
||||
|
||||
// Assume the process-wide default value when created
|
||||
volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
|
||||
|
||||
/*
|
||||
* Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
|
||||
* We roll our own only because we need to lazily remove WeakReferences during accesses
|
||||
* to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
|
||||
* because we want weak values, not keys.
|
||||
* Our hash table is never resized, but the number of entries is unlimited;
|
||||
* performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
|
||||
* Not thread-safe. Client ensures there's a single access at a time.
|
||||
*/
|
||||
private static final class ProxyMap {
|
||||
private static final int LOG_MAIN_INDEX_SIZE = 8;
|
||||
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
|
||||
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
|
||||
// Debuggable builds will throw an AssertionError if the number of map entries exceeds:
|
||||
private static final int CRASH_AT_SIZE = 20_000;
|
||||
|
||||
/**
|
||||
* We next warn when we exceed this bucket size.
|
||||
*/
|
||||
private int mWarnBucketSize = 20;
|
||||
|
||||
/**
|
||||
* Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
|
||||
*/
|
||||
private static final int WARN_INCREMENT = 10;
|
||||
|
||||
/**
|
||||
* Hash function tailored to native pointers.
|
||||
* Returns a value < MAIN_INDEX_SIZE.
|
||||
*/
|
||||
private static int hash(long arg) {
|
||||
return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of pairs in the map.
|
||||
*/
|
||||
private int size() {
|
||||
int size = 0;
|
||||
for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
|
||||
if (a != null) {
|
||||
size += a.size();
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of pairs in the map containing values that have
|
||||
* not been cleared. More expensive than the above size function.
|
||||
*/
|
||||
private int unclearedSize() {
|
||||
int size = 0;
|
||||
for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
|
||||
if (a != null) {
|
||||
for (WeakReference<BinderProxy> ref : a) {
|
||||
if (ref.get() != null) {
|
||||
++size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove ith entry from the hash bucket indicated by hash.
|
||||
*/
|
||||
private void remove(int hash, int index) {
|
||||
Long[] keyArray = mMainIndexKeys[hash];
|
||||
ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
|
||||
int size = valueArray.size(); // KeyArray may have extra elements.
|
||||
// Move last entry into empty slot, and truncate at end.
|
||||
if (index != size - 1) {
|
||||
keyArray[index] = keyArray[size - 1];
|
||||
valueArray.set(index, valueArray.get(size - 1));
|
||||
}
|
||||
valueArray.remove(size - 1);
|
||||
// Just leave key array entry; it's unused. We only trust the valueArray size.
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the supplied key. If we have a non-cleared entry for it, return it.
|
||||
*/
|
||||
BinderProxy get(long key) {
|
||||
int myHash = hash(key);
|
||||
Long[] keyArray = mMainIndexKeys[myHash];
|
||||
if (keyArray == null) {
|
||||
return null;
|
||||
}
|
||||
ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
|
||||
int bucketSize = valueArray.size();
|
||||
for (int i = 0; i < bucketSize; ++i) {
|
||||
long foundKey = keyArray[i];
|
||||
if (key == foundKey) {
|
||||
WeakReference<BinderProxy> wr = valueArray.get(i);
|
||||
BinderProxy bp = wr.get();
|
||||
if (bp != null) {
|
||||
return bp;
|
||||
} else {
|
||||
remove(myHash, i);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int mRandom; // A counter used to generate a "random" index. World's 2nd worst RNG.
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
int myHash = hash(key);
|
||||
ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
|
||||
if (valueArray == null) {
|
||||
valueArray = mMainIndexValues[myHash] = new ArrayList<>();
|
||||
mMainIndexKeys[myHash] = new Long[1];
|
||||
}
|
||||
int size = valueArray.size();
|
||||
WeakReference<BinderProxy> newWr = new WeakReference<>(value);
|
||||
// First look for a cleared reference.
|
||||
// This ensures that ArrayList size is bounded by the maximum occupancy of
|
||||
// that bucket.
|
||||
for (int i = 0; i < size; ++i) {
|
||||
if (valueArray.get(i).get() == null) {
|
||||
valueArray.set(i, newWr);
|
||||
Long[] keyArray = mMainIndexKeys[myHash];
|
||||
keyArray[i] = key;
|
||||
if (i < size - 1) {
|
||||
// "Randomly" check one of the remaining entries in [i+1, size), so that
|
||||
// needlessly long buckets are eventually pruned.
|
||||
int rnd = Math.floorMod(++mRandom, size - (i + 1));
|
||||
if (valueArray.get(i + 1 + rnd).get() == null) {
|
||||
remove(myHash, i + 1 + rnd);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
valueArray.add(size, newWr);
|
||||
Long[] keyArray = mMainIndexKeys[myHash];
|
||||
if (keyArray.length == size) {
|
||||
// size >= 1, since we initially allocated one element
|
||||
Long[] newArray = new Long[size + size / 2 + 2];
|
||||
System.arraycopy(keyArray, 0, newArray, 0, size);
|
||||
newArray[size] = key;
|
||||
mMainIndexKeys[myHash] = newArray;
|
||||
} else {
|
||||
keyArray[size] = key;
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump a histogram to the logcat. Used to diagnose abnormally large proxy maps.
|
||||
*/
|
||||
private void dumpProxyInterfaceCounts() {
|
||||
Map<String, Integer> counts = new HashMap<>();
|
||||
for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
|
||||
if (a != null) {
|
||||
for (WeakReference<BinderProxy> weakRef : a) {
|
||||
BinderProxy bp = weakRef.get();
|
||||
String key;
|
||||
if (bp == null) {
|
||||
key = "<cleared weak-ref>";
|
||||
} else {
|
||||
try {
|
||||
key = bp.getInterfaceDescriptor();
|
||||
} catch (Throwable t) {
|
||||
key = "<exception during getDescriptor>";
|
||||
}
|
||||
}
|
||||
Integer i = counts.get(key);
|
||||
if (i == null) {
|
||||
counts.put(key, 1);
|
||||
} else {
|
||||
counts.put(key, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
|
||||
new Map.Entry[counts.size()]);
|
||||
Arrays.sort(sorted, (Map.Entry<String, Integer> a, Map.Entry<String, Integer> b)
|
||||
-> b.getValue().compareTo(a.getValue()));
|
||||
Log.v(Binder.TAG, "BinderProxy descriptor histogram (top ten):");
|
||||
int printLength = Math.min(10, sorted.length);
|
||||
for (int i = 0; i < printLength; i++) {
|
||||
Log.v(Binder.TAG, " #" + (i + 1) + ": " + sorted[i].getKey() + " x"
|
||||
+ sorted[i].getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump per uid binder proxy counts to the logcat.
|
||||
*/
|
||||
private void dumpPerUidProxyCounts() {
|
||||
SparseIntArray counts = BinderInternal.nGetBinderProxyPerUidCounts();
|
||||
if (counts.size() == 0) return;
|
||||
Log.d(Binder.TAG, "Per Uid Binder Proxy Counts:");
|
||||
for (int i = 0; i < counts.size(); i++) {
|
||||
final int uid = counts.keyAt(i);
|
||||
final int binderCount = counts.valueAt(i);
|
||||
Log.d(Binder.TAG, "UID : " + uid + " count = " + binderCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Corresponding ArrayLists in the following two arrays always have the same size.
|
||||
// They contain no empty entries. However WeakReferences in the values ArrayLists
|
||||
// may have been cleared.
|
||||
|
||||
// mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
|
||||
// The values ArrayList has the proper size(), the corresponding keys array
|
||||
// is always at least the same size, but may be larger.
|
||||
// If either a particular keys array, or the corresponding values ArrayList
|
||||
// are null, then they both are.
|
||||
private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
|
||||
private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
|
||||
new ArrayList[MAIN_INDEX_SIZE];
|
||||
}
|
||||
|
||||
@GuardedBy("sProxyMap")
|
||||
private static final ProxyMap sProxyMap = new ProxyMap();
|
||||
|
||||
/**
|
||||
* Dump proxy debug information.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private static void dumpProxyDebugInfo() {
|
||||
if (Build.IS_DEBUGGABLE) {
|
||||
synchronized (sProxyMap) {
|
||||
sProxyMap.dumpProxyInterfaceCounts();
|
||||
}
|
||||
// Note that we don't call dumpPerUidProxyCounts(); this is because this
|
||||
// method may be called as part of the uid limit being hit, and calling
|
||||
// back into the UID tracking code would cause us to try to acquire a mutex
|
||||
// that is held during that callback.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a BinderProxy for IBinder.
|
||||
* If we previously returned a BinderProxy bp for the same iBinder, and bp is still
|
||||
* in use, then we return the same bp.
|
||||
*
|
||||
* @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
|
||||
* Takes ownership of nativeData iff <result>.mNativeData == nativeData, or if
|
||||
* we exit via an exception. If neither applies, it's the callers responsibility to
|
||||
* recycle nativeData.
|
||||
* @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
private BinderProxy(long nativeData) {
|
||||
mNativeData = nativeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guestimate of native memory associated with a BinderProxy.
|
||||
* This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
|
||||
* that points back to us. We guess high since it includes a GlobalRef, which
|
||||
* may be in short supply.
|
||||
*/
|
||||
private static final int NATIVE_ALLOCATION_SIZE = 1000;
|
||||
|
||||
// Use a Holder to allow static initialization of BinderProxy in the boot image, and
|
||||
// to avoid some initialization ordering issues.
|
||||
private static class NoImagePreloadHolder {
|
||||
public static final long sNativeFinalizer = getNativeFinalizer();
|
||||
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
|
||||
BinderProxy.class.getClassLoader(), sNativeFinalizer, NATIVE_ALLOCATION_SIZE);
|
||||
}
|
||||
|
||||
public native boolean pingBinder();
|
||||
public native boolean isBinderAlive();
|
||||
|
||||
public IInterface queryLocalInterface(String descriptor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
|
||||
|
||||
if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) {
|
||||
// For now, avoid spamming the log by disabling after we've logged
|
||||
// about this interface at least once
|
||||
mWarnOnBlocking = false;
|
||||
Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
|
||||
new Throwable());
|
||||
}
|
||||
|
||||
final boolean tracingEnabled = Binder.isTracingEnabled();
|
||||
if (tracingEnabled) {
|
||||
final Throwable tr = new Throwable();
|
||||
Binder.getTransactionTracker().addTrace(tr);
|
||||
StackTraceElement stackTraceElement = tr.getStackTrace()[1];
|
||||
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
|
||||
stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
|
||||
}
|
||||
try {
|
||||
return transactNative(code, data, reply, flags);
|
||||
} finally {
|
||||
if (tracingEnabled) {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static native long getNativeFinalizer();
|
||||
public native String getInterfaceDescriptor() throws RemoteException;
|
||||
public native boolean transactNative(int code, Parcel data, Parcel reply,
|
||||
int flags) throws RemoteException;
|
||||
public native void linkToDeath(DeathRecipient recipient, int flags)
|
||||
throws RemoteException;
|
||||
public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
|
||||
|
||||
public void dump(FileDescriptor fd, String[] args) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeFileDescriptor(fd);
|
||||
data.writeStringArray(args);
|
||||
try {
|
||||
transact(DUMP_TRANSACTION, data, reply, 0);
|
||||
reply.readException();
|
||||
} finally {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeFileDescriptor(fd);
|
||||
data.writeStringArray(args);
|
||||
try {
|
||||
transact(DUMP_TRANSACTION, data, reply, FLAG_ONEWAY);
|
||||
} finally {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
|
||||
String[] args, ShellCallback callback,
|
||||
ResultReceiver resultReceiver) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeFileDescriptor(in);
|
||||
data.writeFileDescriptor(out);
|
||||
data.writeFileDescriptor(err);
|
||||
data.writeStringArray(args);
|
||||
ShellCallback.writeToParcel(callback, data);
|
||||
resultReceiver.writeToParcel(data, 0);
|
||||
try {
|
||||
transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
|
||||
reply.readException();
|
||||
} finally {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private static final void sendDeathNotice(DeathRecipient recipient) {
|
||||
if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
|
||||
try {
|
||||
recipient.binderDied();
|
||||
}
|
||||
catch (RuntimeException exc) {
|
||||
Log.w("BinderNative", "Uncaught exception from death notification",
|
||||
exc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
|
||||
* native IBinder object, and a DeathRecipientList.
|
||||
*/
|
||||
private final long mNativeData;
|
||||
}
|
||||
|
||||
588
core/java/android/os/BinderProxy.java
Normal file
588
core/java/android/os/BinderProxy.java
Normal file
@@ -0,0 +1,588 @@
|
||||
/*
|
||||
* Copyright (C) 2006 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.os;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.os.BinderInternal;
|
||||
|
||||
import libcore.util.NativeAllocationRegistry;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Java proxy for a native IBinder object.
|
||||
* Allocated and constructed by the native javaObjectforIBinder function. Never allocated
|
||||
* directly from Java code.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class BinderProxy implements IBinder {
|
||||
// See android_util_Binder.cpp for the native half of this.
|
||||
|
||||
// Assume the process-wide default value when created
|
||||
volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
|
||||
|
||||
/*
|
||||
* Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
|
||||
* We roll our own only because we need to lazily remove WeakReferences during accesses
|
||||
* to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
|
||||
* because we want weak values, not keys.
|
||||
* Our hash table is never resized, but the number of entries is unlimited;
|
||||
* performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
|
||||
* Not thread-safe. Client ensures there's a single access at a time.
|
||||
*/
|
||||
private static final class ProxyMap {
|
||||
private static final int LOG_MAIN_INDEX_SIZE = 8;
|
||||
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
|
||||
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
|
||||
// Debuggable builds will throw an AssertionError if the number of map entries exceeds:
|
||||
private static final int CRASH_AT_SIZE = 20_000;
|
||||
|
||||
/**
|
||||
* We next warn when we exceed this bucket size.
|
||||
*/
|
||||
private int mWarnBucketSize = 20;
|
||||
|
||||
/**
|
||||
* Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
|
||||
*/
|
||||
private static final int WARN_INCREMENT = 10;
|
||||
|
||||
/**
|
||||
* Hash function tailored to native pointers.
|
||||
* Returns a value < MAIN_INDEX_SIZE.
|
||||
*/
|
||||
private static int hash(long arg) {
|
||||
return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of pairs in the map.
|
||||
*/
|
||||
private int size() {
|
||||
int size = 0;
|
||||
for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
|
||||
if (a != null) {
|
||||
size += a.size();
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of pairs in the map containing values that have
|
||||
* not been cleared. More expensive than the above size function.
|
||||
*/
|
||||
private int unclearedSize() {
|
||||
int size = 0;
|
||||
for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
|
||||
if (a != null) {
|
||||
for (WeakReference<BinderProxy> ref : a) {
|
||||
if (ref.get() != null) {
|
||||
++size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove ith entry from the hash bucket indicated by hash.
|
||||
*/
|
||||
private void remove(int hash, int index) {
|
||||
Long[] keyArray = mMainIndexKeys[hash];
|
||||
ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
|
||||
int size = valueArray.size(); // KeyArray may have extra elements.
|
||||
// Move last entry into empty slot, and truncate at end.
|
||||
if (index != size - 1) {
|
||||
keyArray[index] = keyArray[size - 1];
|
||||
valueArray.set(index, valueArray.get(size - 1));
|
||||
}
|
||||
valueArray.remove(size - 1);
|
||||
// Just leave key array entry; it's unused. We only trust the valueArray size.
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the supplied key. If we have a non-cleared entry for it, return it.
|
||||
*/
|
||||
BinderProxy get(long key) {
|
||||
int myHash = hash(key);
|
||||
Long[] keyArray = mMainIndexKeys[myHash];
|
||||
if (keyArray == null) {
|
||||
return null;
|
||||
}
|
||||
ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
|
||||
int bucketSize = valueArray.size();
|
||||
for (int i = 0; i < bucketSize; ++i) {
|
||||
long foundKey = keyArray[i];
|
||||
if (key == foundKey) {
|
||||
WeakReference<BinderProxy> wr = valueArray.get(i);
|
||||
BinderProxy bp = wr.get();
|
||||
if (bp != null) {
|
||||
return bp;
|
||||
} else {
|
||||
remove(myHash, i);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int mRandom; // A counter used to generate a "random" index. World's 2nd worst RNG.
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
int myHash = hash(key);
|
||||
ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
|
||||
if (valueArray == null) {
|
||||
valueArray = mMainIndexValues[myHash] = new ArrayList<>();
|
||||
mMainIndexKeys[myHash] = new Long[1];
|
||||
}
|
||||
int size = valueArray.size();
|
||||
WeakReference<BinderProxy> newWr = new WeakReference<>(value);
|
||||
// First look for a cleared reference.
|
||||
// This ensures that ArrayList size is bounded by the maximum occupancy of
|
||||
// that bucket.
|
||||
for (int i = 0; i < size; ++i) {
|
||||
if (valueArray.get(i).get() == null) {
|
||||
valueArray.set(i, newWr);
|
||||
Long[] keyArray = mMainIndexKeys[myHash];
|
||||
keyArray[i] = key;
|
||||
if (i < size - 1) {
|
||||
// "Randomly" check one of the remaining entries in [i+1, size), so that
|
||||
// needlessly long buckets are eventually pruned.
|
||||
int rnd = Math.floorMod(++mRandom, size - (i + 1));
|
||||
if (valueArray.get(i + 1 + rnd).get() == null) {
|
||||
remove(myHash, i + 1 + rnd);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
valueArray.add(size, newWr);
|
||||
Long[] keyArray = mMainIndexKeys[myHash];
|
||||
if (keyArray.length == size) {
|
||||
// size >= 1, since we initially allocated one element
|
||||
Long[] newArray = new Long[size + size / 2 + 2];
|
||||
System.arraycopy(keyArray, 0, newArray, 0, size);
|
||||
newArray[size] = key;
|
||||
mMainIndexKeys[myHash] = newArray;
|
||||
} else {
|
||||
keyArray[size] = key;
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InterfaceCount[] getSortedInterfaceCounts(int maxToReturn) {
|
||||
if (maxToReturn < 0) {
|
||||
throw new IllegalArgumentException("negative interface count");
|
||||
}
|
||||
|
||||
Map<String, Integer> counts = new HashMap<>();
|
||||
for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
|
||||
if (a != null) {
|
||||
for (WeakReference<BinderProxy> weakRef : a) {
|
||||
BinderProxy bp = weakRef.get();
|
||||
String key;
|
||||
if (bp == null) {
|
||||
key = "<cleared weak-ref>";
|
||||
} else {
|
||||
try {
|
||||
key = bp.getInterfaceDescriptor();
|
||||
} catch (Throwable t) {
|
||||
key = "<exception during getDescriptor>";
|
||||
}
|
||||
}
|
||||
Integer i = counts.get(key);
|
||||
if (i == null) {
|
||||
counts.put(key, 1);
|
||||
} else {
|
||||
counts.put(key, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Map.Entry<String, Integer>[] sorted = counts.entrySet().toArray(
|
||||
new Map.Entry[counts.size()]);
|
||||
|
||||
Arrays.sort(sorted, (Map.Entry<String, Integer> a, Map.Entry<String, Integer> b)
|
||||
-> b.getValue().compareTo(a.getValue()));
|
||||
|
||||
int returnCount = Math.min(maxToReturn, sorted.length);
|
||||
InterfaceCount[] ifaceCounts = new InterfaceCount[returnCount];
|
||||
for (int i = 0; i < returnCount; i++) {
|
||||
ifaceCounts[i] = new InterfaceCount(sorted[i].getKey(), sorted[i].getValue());
|
||||
}
|
||||
return ifaceCounts;
|
||||
}
|
||||
|
||||
static final int MAX_NUM_INTERFACES_TO_DUMP = 10;
|
||||
|
||||
/**
|
||||
* Dump a histogram to the logcat. Used to diagnose abnormally large proxy maps.
|
||||
*/
|
||||
private void dumpProxyInterfaceCounts() {
|
||||
final InterfaceCount[] sorted = getSortedInterfaceCounts(MAX_NUM_INTERFACES_TO_DUMP);
|
||||
|
||||
Log.v(Binder.TAG, "BinderProxy descriptor histogram "
|
||||
+ "(top " + Integer.toString(MAX_NUM_INTERFACES_TO_DUMP) + "):");
|
||||
for (int i = 0; i < sorted.length; i++) {
|
||||
Log.v(Binder.TAG, " #" + (i + 1) + ": " + sorted[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump per uid binder proxy counts to the logcat.
|
||||
*/
|
||||
private void dumpPerUidProxyCounts() {
|
||||
SparseIntArray counts = BinderInternal.nGetBinderProxyPerUidCounts();
|
||||
if (counts.size() == 0) return;
|
||||
Log.d(Binder.TAG, "Per Uid Binder Proxy Counts:");
|
||||
for (int i = 0; i < counts.size(); i++) {
|
||||
final int uid = counts.keyAt(i);
|
||||
final int binderCount = counts.valueAt(i);
|
||||
Log.d(Binder.TAG, "UID : " + uid + " count = " + binderCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Corresponding ArrayLists in the following two arrays always have the same size.
|
||||
// They contain no empty entries. However WeakReferences in the values ArrayLists
|
||||
// may have been cleared.
|
||||
|
||||
// mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
|
||||
// The values ArrayList has the proper size(), the corresponding keys array
|
||||
// is always at least the same size, but may be larger.
|
||||
// If either a particular keys array, or the corresponding values ArrayList
|
||||
// are null, then they both are.
|
||||
private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
|
||||
private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
|
||||
new ArrayList[MAIN_INDEX_SIZE];
|
||||
}
|
||||
|
||||
@GuardedBy("sProxyMap")
|
||||
private static final ProxyMap sProxyMap = new ProxyMap();
|
||||
|
||||
/**
|
||||
* Simple pair-value class to store number of binder proxy interfaces live in this process.
|
||||
*/
|
||||
public static final class InterfaceCount {
|
||||
private final String mInterfaceName;
|
||||
private final int mCount;
|
||||
|
||||
InterfaceCount(String interfaceName, int count) {
|
||||
mInterfaceName = interfaceName;
|
||||
mCount = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mInterfaceName + " x" + Integer.toString(mCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sorted array with entries mapping proxy interface names to the number
|
||||
* of live proxies with those names.
|
||||
*
|
||||
* @param num maximum number of proxy interface counts to return. Use
|
||||
* Integer.MAX_VALUE to retrieve all
|
||||
* @hide
|
||||
*/
|
||||
public static InterfaceCount[] getSortedInterfaceCounts(int num) {
|
||||
synchronized (sProxyMap) {
|
||||
return sProxyMap.getSortedInterfaceCounts(num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump proxy debug information.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static void dumpProxyDebugInfo() {
|
||||
if (Build.IS_DEBUGGABLE) {
|
||||
synchronized (sProxyMap) {
|
||||
sProxyMap.dumpProxyInterfaceCounts();
|
||||
sProxyMap.dumpPerUidProxyCounts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a BinderProxy for IBinder.
|
||||
* If we previously returned a BinderProxy bp for the same iBinder, and bp is still
|
||||
* in use, then we return the same bp.
|
||||
*
|
||||
* @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
|
||||
* Takes ownership of nativeData iff <result>.mNativeData == nativeData, or if
|
||||
* we exit via an exception. If neither applies, it's the callers responsibility to
|
||||
* recycle nativeData.
|
||||
* @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
private BinderProxy(long nativeData) {
|
||||
mNativeData = nativeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guestimate of native memory associated with a BinderProxy.
|
||||
* This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
|
||||
* that points back to us. We guess high since it includes a GlobalRef, which
|
||||
* may be in short supply.
|
||||
*/
|
||||
private static final int NATIVE_ALLOCATION_SIZE = 1000;
|
||||
|
||||
// Use a Holder to allow static initialization of BinderProxy in the boot image, and
|
||||
// to avoid some initialization ordering issues.
|
||||
private static class NoImagePreloadHolder {
|
||||
public static final long sNativeFinalizer = getNativeFinalizer();
|
||||
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
|
||||
BinderProxy.class.getClassLoader(), sNativeFinalizer, NATIVE_ALLOCATION_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false if the hosting process is gone, otherwise whatever the remote returns
|
||||
*/
|
||||
public native boolean pingBinder();
|
||||
|
||||
/**
|
||||
* @return false if the hosting process is gone
|
||||
*/
|
||||
public native boolean isBinderAlive();
|
||||
|
||||
/**
|
||||
* Retrieve a local interface - always null in case of a proxy
|
||||
*/
|
||||
public IInterface queryLocalInterface(String descriptor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a binder transaction on a proxy.
|
||||
*
|
||||
* @param code The action to perform. This should
|
||||
* be a number between {@link #FIRST_CALL_TRANSACTION} and
|
||||
* {@link #LAST_CALL_TRANSACTION}.
|
||||
* @param data Marshalled data to send to the target. Must not be null.
|
||||
* If you are not sending any data, you must create an empty Parcel
|
||||
* that is given here.
|
||||
* @param reply Marshalled data to be received from the target. May be
|
||||
* null if you are not interested in the return value.
|
||||
* @param flags Additional operation flags. Either 0 for a normal
|
||||
* RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
|
||||
*
|
||||
* @return
|
||||
* @throws RemoteException
|
||||
*/
|
||||
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
|
||||
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
|
||||
|
||||
if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) {
|
||||
// For now, avoid spamming the log by disabling after we've logged
|
||||
// about this interface at least once
|
||||
mWarnOnBlocking = false;
|
||||
Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
|
||||
new Throwable());
|
||||
}
|
||||
|
||||
final boolean tracingEnabled = Binder.isTracingEnabled();
|
||||
if (tracingEnabled) {
|
||||
final Throwable tr = new Throwable();
|
||||
Binder.getTransactionTracker().addTrace(tr);
|
||||
StackTraceElement stackTraceElement = tr.getStackTrace()[1];
|
||||
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
|
||||
stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
|
||||
}
|
||||
try {
|
||||
return transactNative(code, data, reply, flags);
|
||||
} finally {
|
||||
if (tracingEnabled) {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the native free function */
|
||||
private static native long getNativeFinalizer();
|
||||
/**
|
||||
* See {@link IBinder#getInterfaceDescriptor()}
|
||||
*/
|
||||
public native String getInterfaceDescriptor() throws RemoteException;
|
||||
|
||||
/**
|
||||
* Native implementation of transact() for proxies
|
||||
*/
|
||||
public native boolean transactNative(int code, Parcel data, Parcel reply,
|
||||
int flags) throws RemoteException;
|
||||
/**
|
||||
* See {@link IBinder#linkToDeath(DeathRecipient, int)}
|
||||
*/
|
||||
public native void linkToDeath(DeathRecipient recipient, int flags)
|
||||
throws RemoteException;
|
||||
/**
|
||||
* See {@link IBinder#unlinkToDeath}
|
||||
*/
|
||||
public native boolean unlinkToDeath(DeathRecipient recipient, int flags);
|
||||
|
||||
/**
|
||||
* Perform a dump on the remote object
|
||||
*
|
||||
* @param fd The raw file descriptor that the dump is being sent to.
|
||||
* @param args additional arguments to the dump request.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
public void dump(FileDescriptor fd, String[] args) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeFileDescriptor(fd);
|
||||
data.writeStringArray(args);
|
||||
try {
|
||||
transact(DUMP_TRANSACTION, data, reply, 0);
|
||||
reply.readException();
|
||||
} finally {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an asynchronous dump on the remote object
|
||||
*
|
||||
* @param fd The raw file descriptor that the dump is being sent to.
|
||||
* @param args additional arguments to the dump request.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeFileDescriptor(fd);
|
||||
data.writeStringArray(args);
|
||||
try {
|
||||
transact(DUMP_TRANSACTION, data, reply, FLAG_ONEWAY);
|
||||
} finally {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link IBinder#shellCommand(FileDescriptor, FileDescriptor, FileDescriptor,
|
||||
* String[], ShellCallback, ResultReceiver)}
|
||||
*
|
||||
* @param in The raw file descriptor that an input data stream can be read from.
|
||||
* @param out The raw file descriptor that normal command messages should be written to.
|
||||
* @param err The raw file descriptor that command error messages should be written to.
|
||||
* @param args Command-line arguments.
|
||||
* @param callback Optional callback to the caller's shell to perform operations in it.
|
||||
* @param resultReceiver Called when the command has finished executing, with the result code.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
|
||||
String[] args, ShellCallback callback,
|
||||
ResultReceiver resultReceiver) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeFileDescriptor(in);
|
||||
data.writeFileDescriptor(out);
|
||||
data.writeFileDescriptor(err);
|
||||
data.writeStringArray(args);
|
||||
ShellCallback.writeToParcel(callback, data);
|
||||
resultReceiver.writeToParcel(data, 0);
|
||||
try {
|
||||
transact(SHELL_COMMAND_TRANSACTION, data, reply, 0);
|
||||
reply.readException();
|
||||
} finally {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendDeathNotice(DeathRecipient recipient) {
|
||||
if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
|
||||
try {
|
||||
recipient.binderDied();
|
||||
} catch (RuntimeException exc) {
|
||||
Log.w("BinderNative", "Uncaught exception from death notification",
|
||||
exc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
|
||||
* native IBinder object, and a DeathRecipientList.
|
||||
*/
|
||||
private final long mNativeData;
|
||||
}
|
||||
Reference in New Issue
Block a user