1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ahat;
18 
19 import com.android.tools.perflib.heap.ArrayInstance;
20 import com.android.tools.perflib.heap.ClassInstance;
21 import com.android.tools.perflib.heap.ClassObj;
22 import com.android.tools.perflib.heap.Instance;
23 import com.android.tools.perflib.heap.Heap;
24 import com.android.tools.perflib.heap.Type;
25 import java.awt.image.BufferedImage;
26 
27 /**
28  * Utilities for extracting information from hprof instances.
29  */
30 class InstanceUtils {
31   /**
32    * Returns true if the given instance is an instance of a class with the
33    * given name.
34    */
isInstanceOfClass(Instance inst, String className)35   private static boolean isInstanceOfClass(Instance inst, String className) {
36     ClassObj cls = (inst == null) ? null : inst.getClassObj();
37     return (cls != null && className.equals(cls.getClassName()));
38   }
39 
40   /**
41    * Read the byte[] value from an hprof Instance.
42    * Returns null if the instance is not a byte array.
43    */
asByteArray(Instance inst)44   private static byte[] asByteArray(Instance inst) {
45     if (! (inst instanceof ArrayInstance)) {
46       return null;
47     }
48 
49     ArrayInstance array = (ArrayInstance)inst;
50     if (array.getArrayType() != Type.BYTE) {
51       return null;
52     }
53 
54     Object[] objs = array.getValues();
55     byte[] bytes = new byte[objs.length];
56     for (int i = 0; i < objs.length; i++) {
57       Byte b = (Byte)objs[i];
58       bytes[i] = b.byteValue();
59     }
60     return bytes;
61   }
62 
63 
64   /**
65    * Read the string value from an hprof Instance.
66    * Returns null if the object can't be interpreted as a string.
67    */
asString(Instance inst)68   public static String asString(Instance inst) {
69     return asString(inst, -1);
70   }
71 
72   /**
73    * Read the string value from an hprof Instance.
74    * Returns null if the object can't be interpreted as a string.
75    * The returned string is truncated to maxChars characters.
76    * If maxChars is negative, the returned string is not truncated.
77    */
asString(Instance inst, int maxChars)78   public static String asString(Instance inst, int maxChars) {
79     // The inst object could either be a java.lang.String or a char[]. If it
80     // is a char[], use that directly as the value, otherwise use the value
81     // field of the string object. The field accesses for count and offset
82     // later on will work okay regardless of what type the inst object is.
83     Object value = inst;
84     if (isInstanceOfClass(inst, "java.lang.String")) {
85       value = getField(inst, "value");
86     }
87 
88     if (!(value instanceof ArrayInstance)) {
89       return null;
90     }
91 
92     ArrayInstance chars = (ArrayInstance) value;
93     if (chars.getArrayType() != Type.CHAR) {
94       return null;
95     }
96 
97     // TODO: When perflib provides a better way to get the length of the
98     // array, we should use that here.
99     int numChars = chars.getValues().length;
100     int count = getIntField(inst, "count", numChars);
101     if (count == 0) {
102       return "";
103     }
104     if (0 <= maxChars && maxChars < count) {
105       count = maxChars;
106     }
107 
108     int offset = getIntField(inst, "offset", 0);
109     int end = offset + count - 1;
110     if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
111       return new String(chars.asCharArray(offset, count));
112     }
113     return null;
114   }
115 
116   /**
117    * Read the bitmap data for the given android.graphics.Bitmap object.
118    * Returns null if the object isn't for android.graphics.Bitmap or the
119    * bitmap data couldn't be read.
120    */
asBitmap(Instance inst)121   public static BufferedImage asBitmap(Instance inst) {
122     if (!isInstanceOfClass(inst, "android.graphics.Bitmap")) {
123       return null;
124     }
125 
126     Integer width = getIntField(inst, "mWidth", null);
127     if (width == null) {
128       return null;
129     }
130 
131     Integer height = getIntField(inst, "mHeight", null);
132     if (height == null) {
133       return null;
134     }
135 
136     byte[] buffer = getByteArrayField(inst, "mBuffer");
137     if (buffer == null) {
138       return null;
139     }
140 
141     // Convert the raw data to an image
142     // Convert BGRA to ABGR
143     int[] abgr = new int[height * width];
144     for (int i = 0; i < abgr.length; i++) {
145       abgr[i] = (
146           (((int)buffer[i * 4 + 3] & 0xFF) << 24) +
147           (((int)buffer[i * 4 + 0] & 0xFF) << 16) +
148           (((int)buffer[i * 4 + 1] & 0xFF) << 8) +
149           ((int)buffer[i * 4 + 2] & 0xFF));
150     }
151 
152     BufferedImage bitmap = new BufferedImage(
153         width, height, BufferedImage.TYPE_4BYTE_ABGR);
154     bitmap.setRGB(0, 0, width, height, abgr, 0, width);
155     return bitmap;
156   }
157 
158   /**
159    * Read a field of an instance.
160    * Returns null if the field value is null or if the field couldn't be read.
161    */
getField(Instance inst, String fieldName)162   public static Object getField(Instance inst, String fieldName) {
163     if (!(inst instanceof ClassInstance)) {
164       return null;
165     }
166 
167     ClassInstance clsinst = (ClassInstance) inst;
168     Object value = null;
169     int count = 0;
170     for (ClassInstance.FieldValue field : clsinst.getValues()) {
171       if (fieldName.equals(field.getField().getName())) {
172         value = field.getValue();
173         count++;
174       }
175     }
176     return count == 1 ? value : null;
177   }
178 
179   /**
180    * Read a reference field of an instance.
181    * Returns null if the field value is null, or if the field couldn't be read.
182    */
getRefField(Instance inst, String fieldName)183   private static Instance getRefField(Instance inst, String fieldName) {
184     Object value = getField(inst, fieldName);
185     if (!(value instanceof Instance)) {
186       return null;
187     }
188     return (Instance)value;
189   }
190 
191   /**
192    * Read an int field of an instance.
193    * The field is assumed to be an int type.
194    * Returns <code>def</code> if the field value is not an int or could not be
195    * read.
196    */
getIntField(Instance inst, String fieldName, Integer def)197   private static Integer getIntField(Instance inst, String fieldName, Integer def) {
198     Object value = getField(inst, fieldName);
199     if (!(value instanceof Integer)) {
200       return def;
201     }
202     return (Integer)value;
203   }
204 
205   /**
206    * Read a long field of an instance.
207    * The field is assumed to be a long type.
208    * Returns <code>def</code> if the field value is not an long or could not
209    * be read.
210    */
getLongField(Instance inst, String fieldName, Long def)211   private static Long getLongField(Instance inst, String fieldName, Long def) {
212     Object value = getField(inst, fieldName);
213     if (!(value instanceof Long)) {
214       return def;
215     }
216     return (Long)value;
217   }
218 
219   /**
220    * Read the given field from the given instance.
221    * The field is assumed to be a byte[] field.
222    * Returns null if the field value is null, not a byte[] or could not be read.
223    */
getByteArrayField(Instance inst, String fieldName)224   private static byte[] getByteArrayField(Instance inst, String fieldName) {
225     Object value = getField(inst, fieldName);
226     if (!(value instanceof Instance)) {
227       return null;
228     }
229     return asByteArray((Instance)value);
230   }
231 
232   // Return the bitmap instance associated with this object, or null if there
233   // is none. This works for android.graphics.Bitmap instances and their
234   // underlying Byte[] instances.
getAssociatedBitmapInstance(Instance inst)235   public static Instance getAssociatedBitmapInstance(Instance inst) {
236     ClassObj cls = inst.getClassObj();
237     if (cls == null) {
238       return null;
239     }
240 
241     if ("android.graphics.Bitmap".equals(cls.getClassName())) {
242       return inst;
243     }
244 
245     if (inst instanceof ArrayInstance) {
246       ArrayInstance array = (ArrayInstance)inst;
247       if (array.getArrayType() == Type.BYTE && inst.getHardReferences().size() == 1) {
248         Instance ref = inst.getHardReferences().get(0);
249         ClassObj clsref = ref.getClassObj();
250         if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) {
251           return ref;
252         }
253       }
254     }
255     return null;
256   }
257 
isJavaLangRefReference(Instance inst)258   private static boolean isJavaLangRefReference(Instance inst) {
259     ClassObj cls = (inst == null) ? null : inst.getClassObj();
260     while (cls != null) {
261       if ("java.lang.ref.Reference".equals(cls.getClassName())) {
262         return true;
263       }
264       cls = cls.getSuperClassObj();
265     }
266     return false;
267   }
268 
getReferent(Instance inst)269   public static Instance getReferent(Instance inst) {
270     if (isJavaLangRefReference(inst)) {
271       return getRefField(inst, "referent");
272     }
273     return null;
274   }
275 
276   /**
277    * Assuming inst represents a DexCache object, return the dex location for
278    * that dex cache. Returns null if the given instance doesn't represent a
279    * DexCache object or the location could not be found.
280    * If maxChars is non-negative, the returned location is truncated to
281    * maxChars in length.
282    */
getDexCacheLocation(Instance inst, int maxChars)283   public static String getDexCacheLocation(Instance inst, int maxChars) {
284     if (isInstanceOfClass(inst, "java.lang.DexCache")) {
285       Instance location = getRefField(inst, "location");
286       if (location != null) {
287         return asString(location, maxChars);
288       }
289     }
290     return null;
291   }
292 
293   public static class NativeAllocation {
294     public long size;
295     public Heap heap;
296     public long pointer;
297     public Instance referent;
298 
NativeAllocation(long size, Heap heap, long pointer, Instance referent)299     public NativeAllocation(long size, Heap heap, long pointer, Instance referent) {
300       this.size = size;
301       this.heap = heap;
302       this.pointer = pointer;
303       this.referent = referent;
304     }
305   }
306 
307   /**
308    * Assuming inst represents a NativeAllocation, return information about the
309    * native allocation. Returns null if the given instance doesn't represent a
310    * native allocation.
311    */
getNativeAllocation(Instance inst)312   public static NativeAllocation getNativeAllocation(Instance inst) {
313     if (!isInstanceOfClass(inst, "libcore.util.NativeAllocationRegistry$CleanerThunk")) {
314       return null;
315     }
316 
317     Long pointer = InstanceUtils.getLongField(inst, "nativePtr", null);
318     if (pointer == null) {
319       return null;
320     }
321 
322     // Search for the registry field of inst.
323     // Note: We know inst as an instance of ClassInstance because we already
324     // read the nativePtr field from it.
325     Instance registry = null;
326     for (ClassInstance.FieldValue field : ((ClassInstance)inst).getValues()) {
327       Object fieldValue = field.getValue();
328       if (fieldValue instanceof Instance) {
329         Instance fieldInst = (Instance)fieldValue;
330         if (isInstanceOfClass(fieldInst, "libcore.util.NativeAllocationRegistry")) {
331           registry = fieldInst;
332           break;
333         }
334       }
335     }
336 
337     if (registry == null) {
338       return null;
339     }
340 
341     Long size = InstanceUtils.getLongField(registry, "size", null);
342     if (size == null) {
343       return null;
344     }
345 
346     Instance referent = null;
347     for (Instance ref : inst.getHardReferences()) {
348       if (isInstanceOfClass(ref, "sun.misc.Cleaner")) {
349         referent = InstanceUtils.getReferent(ref);
350         if (referent != null) {
351           break;
352         }
353       }
354     }
355 
356     if (referent == null) {
357       return null;
358     }
359     return new NativeAllocation(size, inst.getHeap(), pointer, referent);
360   }
361 }
362