Annotate binder services and tokens.
For all instances of android.os.Binder, try to annotate them by
retrieving their descriptor. If the instance is a also (sub)class of
'descriptor$Stub', mark it as a binder service. Otherwise, mark it as
a binder token.
Example:
android.server.wm.Session@1aa85368 binder service (android.view.IWindowSession)
Bug: 114365505
Test: ahat-tests (including new tests) passes, visual inspection
Change-Id: I7343467be5b745960c6e87c850c4125945de2f85
diff --git a/tools/ahat/etc/ahat_api.txt b/tools/ahat/etc/ahat_api.txt
index c82b314..01e00e9 100644
--- a/tools/ahat/etc/ahat_api.txt
+++ b/tools/ahat/etc/ahat_api.txt
@@ -74,6 +74,8 @@
method public com.android.ahat.heapdump.AhatClassObj getAssociatedClassForOverhead();
method public com.android.ahat.heapdump.AhatInstance getBaseline();
method public java.lang.String getBinderProxyInterfaceName();
+ method public java.lang.String getBinderStubInterfaceName();
+ method public java.lang.String getBinderTokenDescriptor();
method public java.lang.String getClassName();
method public com.android.ahat.heapdump.AhatClassObj getClassObj();
method public java.lang.String getDexCacheLocation(int);
diff --git a/tools/ahat/src/main/com/android/ahat/Summarizer.java b/tools/ahat/src/main/com/android/ahat/Summarizer.java
index 877ecf4..df3b577 100644
--- a/tools/ahat/src/main/com/android/ahat/Summarizer.java
+++ b/tools/ahat/src/main/com/android/ahat/Summarizer.java
@@ -114,9 +114,20 @@
}
// Annotate BinderProxy with its interface name.
- String binderInterface = inst.getBinderProxyInterfaceName();
- if (binderInterface != null) {
- formatted.appendFormat(" for %s", binderInterface);
+ String binderProxyInterface = inst.getBinderProxyInterfaceName();
+ if (binderProxyInterface != null) {
+ formatted.appendFormat(" for %s", binderProxyInterface);
+ }
+
+ // Annotate Binder tokens with their descriptor
+ String binderTokenDescriptor = inst.getBinderTokenDescriptor();
+ if (binderTokenDescriptor != null) {
+ formatted.appendFormat(" binder token (%s)", binderTokenDescriptor);
+ }
+ // Annotate Binder services with their interface name.
+ String binderStubInterface = inst.getBinderStubInterfaceName();
+ if (binderStubInterface != null) {
+ formatted.appendFormat(" binder service (%s)", binderStubInterface);
}
return formatted;
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
index 141bdd9..d2ba68d 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
@@ -160,6 +160,37 @@
return null;
}
+ @Override public String getBinderTokenDescriptor() {
+ String descriptor = getBinderDescriptor();
+ if (descriptor == null) {
+ return null;
+ }
+
+ if (isInstanceOfClass(descriptor + "$Stub")) {
+ // This is an instance of an auto-generated interface class, and
+ // therefore not a binder token.
+ return null;
+ }
+
+ return descriptor;
+ }
+
+ @Override public String getBinderStubInterfaceName() {
+ String descriptor = getBinderDescriptor();
+ if (descriptor == null || descriptor.isEmpty()) {
+ // Binder interface stubs always have a non-empty descriptor
+ return null;
+ }
+
+ // We only consider something a binder service if it's an instance of the
+ // auto-generated descriptor$Stub class.
+ if (isInstanceOfClass(descriptor + "$Stub")) {
+ return descriptor;
+ }
+
+ return null;
+ }
+
@Override public AhatInstance getAssociatedBitmapInstance() {
return getBitmapInfo() == null ? null : this;
}
@@ -177,6 +208,25 @@
}
/**
+ * Returns the descriptor of an android.os.Binder object.
+ * If no descriptor is set, returns an empty string.
+ * If the object is not an android.os.Binder object, returns null.
+ */
+ private String getBinderDescriptor() {
+ if (isInstanceOfClass("android.os.Binder")) {
+ Value value = getField("mDescriptor");;
+
+ if (value == null) {
+ return "";
+ } else {
+ return value.asAhatInstance().asString();
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Read the given field from the given instance.
* The field is assumed to be a byte[] field.
* Returns null if the field value is null, not a byte[] or could not be read.
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
index 3aae11e..281c977 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -490,9 +490,10 @@
}
/**
- * Returns the name of the Binder proxy interface associated with this object. Only applies to
- * instances of android.os.BinderProxy. If this is an instance of BinderProxy,
- * returns the fully qualified binder interface name, otherwise returns null.
+ * Returns the name of the Binder proxy interface associated with this object.
+ * Only applies to instances of android.os.BinderProxy. If this is an
+ * instance of BinderProxy, returns the fully qualified binder interface name,
+ * otherwise returns null.
*
* @return the name of the binder interface associated with this object
*/
@@ -501,6 +502,32 @@
}
/**
+ * Returns the descriptor of the Binder token associated with this object.
+ * Only applies to instances of android.os.Binder. If this is an instance of
+ * android.os.Binder with a subclass of the name "descriptor$Stub", the
+ * object in question is a binder stub, and this function will return null.
+ * In that case, @see AhatInstance#getBinderStubInterfaceName
+ *
+ * @return the descriptor of this object, if it's a binder token
+ */
+ public String getBinderTokenDescriptor() {
+ return null;
+ }
+
+ /**
+ * Returns the name of the Binder stub interface associated with this object.
+ * Only applies to instances which are a subclass of android.os.Binder,
+ * and are an instance of class 'descriptor$Stub', where descriptor
+ * is the descriptor of the android.os.Binder object.
+ *
+ * @return the name of the binder interface associated with this object,
+ * or null if this is not a binder stub interface.
+ */
+ public String getBinderStubInterfaceName() {
+ return null;
+ }
+
+ /**
* Returns the android.graphics.Bitmap instance associated with this object.
* Instances of android.graphics.Bitmap return themselves. If this is a
* byte[] array containing pixel data for an instance of
diff --git a/tools/ahat/src/test-dump/DumpedStuff.java b/tools/ahat/src/test-dump/DumpedStuff.java
index 50b8878..de2968f 100644
--- a/tools/ahat/src/test-dump/DumpedStuff.java
+++ b/tools/ahat/src/test-dump/DumpedStuff.java
@@ -124,9 +124,13 @@
}
}
- private static class IDumpedManager {
- public static class Stub {
- public static class Proxy {
+ public interface IDumpedManager {
+ public static class Stub extends android.os.Binder implements IDumpedManager {
+ private static final java.lang.String DESCRIPTOR = "DumpedStuff$IDumpedManager";
+ public Stub() {
+ super(DESCRIPTOR);
+ }
+ public static class Proxy implements IDumpedManager {
android.os.IBinder mRemote;
Proxy(android.os.IBinder binderProxy) {
mRemote = binderProxy;
@@ -135,9 +139,9 @@
}
}
- private static class IBinderInterfaceImpostor {
+ public interface IBinderInterfaceImpostor {
public static class Stub {
- public static class Proxy {
+ public static class Proxy implements IBinderInterfaceImpostor {
android.os.IBinder mFakeRemote = new android.os.BinderProxy();
Proxy(android.os.IBinder binderProxy) {
mFakeRemote = binderProxy;
@@ -153,6 +157,14 @@
}
}
+ private static class BinderService extends IDumpedManager.Stub {
+ // Intentionally empty
+ };
+
+ private static class FakeBinderService extends IBinderInterfaceImpostor.Stub {
+ // Intentionally empty
+ };
+
public String basicString = "hello, world";
public String nonAscii = "Sigma (Ʃ) is not ASCII";
public String embeddedZero = "embedded\0..."; // Non-ASCII for string compression purposes.
@@ -194,6 +206,11 @@
Object impostorBinderProxyObject = new IBinderInterfaceImpostor.Stub.Proxy(imposedBinderProxy);
Object carrierBinderProxyObject = new BinderProxyCarrier(carriedBinderProxy);
+ Object binderService = new BinderService();
+ Object fakeBinderService = new FakeBinderService();
+ Object binderToken = new android.os.Binder();
+ Object namedBinderToken = new android.os.Binder("awesomeToken");
+
// Allocate those objects that we need to not be GC'd before taking the heap
// dump.
public void shouldNotGc() {
diff --git a/tools/ahat/src/test-dump/android/os/Binder.java b/tools/ahat/src/test-dump/android/os/Binder.java
new file mode 100644
index 0000000..e89bb74
--- /dev/null
+++ b/tools/ahat/src/test-dump/android/os/Binder.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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 java.lang.String;
+
+/** Fake android.os.Binder class that just holds a descriptor.
+ *
+ * Note that having this class will cause Proguard to issue warnings when
+ * building ahat-test-dump with 'mm' or 'mma':
+ *
+ * Warning: Library class android.net.wifi.rtt.IWifiRttManager$Stub extends
+ * program class android.os.Binder
+ *
+ * This is because when building for the device, proguard will include the
+ * framework jars, which contain Stub classes that extend android.os.Binder,
+ * which is defined again here.
+ *
+ * Since we don't actually run this code on the device, these warnings can
+ * be ignored.
+ */
+public class Binder implements IBinder {
+ public Binder() {
+ mDescriptor = null;
+ }
+
+ public Binder(String descriptor) {
+ mDescriptor = descriptor;
+ }
+
+ private String mDescriptor;
+}
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 57aa31f..af0a73b 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -563,4 +563,46 @@
AhatInstance carriedObj = dump.getDumpedAhatInstance("carriedBinderProxy");
assertNull(carriedObj.getBinderProxyInterfaceName());
}
+
+ @Test
+ public void binderToken() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ // Tokens without a descriptor return an empty string
+ AhatInstance binderToken = dump.getDumpedAhatInstance("binderToken");
+ assertEquals("", binderToken.getBinderTokenDescriptor());
+
+ // Named binder tokens return their descriptor
+ AhatInstance namedBinderToken = dump.getDumpedAhatInstance("namedBinderToken");
+ assertEquals("awesomeToken", namedBinderToken.getBinderTokenDescriptor());
+
+ // Binder stubs aren't considered binder tokens
+ AhatInstance binderService = dump.getDumpedAhatInstance("binderService");
+ assertEquals(null, binderService.getBinderTokenDescriptor());
+ }
+
+ @Test
+ public void binderStub() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+
+ // Regular binder service returns the interface name and no token descriptor
+ AhatInstance binderService = dump.getDumpedAhatInstance("binderService");
+ assertEquals("DumpedStuff$IDumpedManager", binderService.getBinderStubInterfaceName());
+
+ // Binder tokens aren't considered binder services
+ AhatInstance binderToken = dump.getDumpedAhatInstance("binderToken");
+ assertEquals(null, binderToken.getBinderStubInterfaceName());
+
+ // Named binder tokens aren't considered binder services
+ AhatInstance namedBinderToken = dump.getDumpedAhatInstance("namedBinderToken");
+ assertEquals(null, namedBinderToken.getBinderStubInterfaceName());
+
+ // Fake service returns null
+ AhatInstance fakeService = dump.getDumpedAhatInstance("fakeBinderService");
+ assertNull(fakeService.getBinderStubInterfaceName());
+
+ // Random non-binder object returns null
+ AhatInstance nonBinderObject = dump.getDumpedAhatInstance("anObject");
+ assertNull(nonBinderObject.getBinderStubInterfaceName());
+ }
}