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