1 /* 2 * Copyright (C) 2017 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.heapdump; 18 19 import com.android.ahat.proguard.ProguardMap; 20 import java.io.File; 21 import java.io.IOException; 22 import java.nio.BufferUnderflowException; 23 import java.nio.ByteBuffer; 24 import java.nio.channels.FileChannel; 25 import java.nio.charset.StandardCharsets; 26 import java.nio.file.StandardOpenOption; 27 import java.util.ArrayList; 28 import java.util.Comparator; 29 import java.util.HashMap; 30 import java.util.Iterator; 31 import java.util.List; 32 import java.util.Map; 33 34 /** 35 * Provides methods for parsing heap dumps. 36 */ 37 public class Parser { 38 private static final int ID_SIZE = 4; 39 Parser()40 private Parser() { 41 } 42 43 /** 44 * Parses a heap dump from a File. 45 * <p> 46 * The heap dump should be a heap dump in the J2SE HPROF format optionally 47 * with Android extensions and satisfying the following additional 48 * constraints: 49 * <ul> 50 * <li> 51 * Class serial numbers, stack frames, and stack traces individually satisfy 52 * the following: 53 * <ul> 54 * <li> All elements are defined before they are referenced. 55 * <li> Ids are densely packed in some range [a, b] where a is not necessarily 0. 56 * <li> There are not more than 2^31 elements defined. 57 * </ul> 58 * <li> All classes are defined via a LOAD CLASS record before the first 59 * heap dump segment. 60 * <li> The ID size used in the heap dump is 4 bytes. 61 * </ul> 62 * <p> 63 * The given proguard map will be used to deobfuscate class names, field 64 * names, and stack traces in the heap dump. 65 * 66 * @param hprof the hprof file to parse 67 * @param map the proguard map for deobfuscation 68 * @return the parsed heap dump 69 * @throws IOException if the heap dump could not be read 70 * @throws HprofFormatException if the heap dump is not properly formatted 71 */ parseHeapDump(File hprof, ProguardMap map)72 public static AhatSnapshot parseHeapDump(File hprof, ProguardMap map) 73 throws IOException, HprofFormatException { 74 try { 75 return parseHeapDump(new HprofBuffer(hprof), map); 76 } catch (BufferUnderflowException e) { 77 throw new HprofFormatException("Unexpected end of file", e); 78 } 79 } 80 81 /** 82 * Parses a heap dump from a byte buffer. 83 * <p> 84 * The heap dump should be a heap dump in the J2SE HPROF format optionally 85 * with Android extensions and satisfying the following additional 86 * constraints: 87 * <ul> 88 * <li> 89 * Class serial numbers, stack frames, and stack traces individually satisfy 90 * the following: 91 * <ul> 92 * <li> All elements are defined before they are referenced. 93 * <li> Ids are densely packed in some range [a, b] where a is not necessarily 0. 94 * <li> There are not more than 2^31 elements defined. 95 * </ul> 96 * <li> All classes are defined via a LOAD CLASS record before the first 97 * heap dump segment. 98 * <li> The ID size used in the heap dump is 4 bytes. 99 * </ul> 100 * <p> 101 * The given proguard map will be used to deobfuscate class names, field 102 * names, and stack traces in the heap dump. 103 * 104 * @param hprof the bytes of the hprof file to parse 105 * @param map the proguard map for deobfuscation 106 * @return the parsed heap dump 107 * @throws IOException if the heap dump could not be read 108 * @throws HprofFormatException if the heap dump is not properly formatted 109 */ parseHeapDump(ByteBuffer hprof, ProguardMap map)110 public static AhatSnapshot parseHeapDump(ByteBuffer hprof, ProguardMap map) 111 throws IOException, HprofFormatException { 112 try { 113 return parseHeapDump(new HprofBuffer(hprof), map); 114 } catch (BufferUnderflowException e) { 115 throw new HprofFormatException("Unexpected end of file", e); 116 } 117 } 118 parseHeapDump(HprofBuffer hprof, ProguardMap map)119 private static AhatSnapshot parseHeapDump(HprofBuffer hprof, ProguardMap map) 120 throws IOException, HprofFormatException, BufferUnderflowException { 121 // Read, and mostly ignore, the hprof header info. 122 { 123 StringBuilder format = new StringBuilder(); 124 int b; 125 while ((b = hprof.getU1()) != 0) { 126 format.append((char)b); 127 } 128 129 int idSize = hprof.getU4(); 130 if (idSize != ID_SIZE) { 131 throw new HprofFormatException("Id size " + idSize + " not supported."); 132 } 133 int hightime = hprof.getU4(); 134 int lowtime = hprof.getU4(); 135 } 136 137 // First pass: Read through all the heap dump records. Construct the 138 // AhatInstances, initialize them as much as possible and save any 139 // additional temporary data we need to complete their initialization in 140 // the fixup pass. 141 Site rootSite = new Site("ROOT"); 142 List<AhatInstance> instances = new ArrayList<AhatInstance>(); 143 List<RootData> roots = new ArrayList<RootData>(); 144 HeapList heaps = new HeapList(); 145 { 146 // Note: Strings do not satisfy the DenseMap requirements on heap dumps 147 // from Android K. 148 UnDenseMap<String> strings = new UnDenseMap<String>("String"); 149 DenseMap<ProguardMap.Frame> frames = new DenseMap<ProguardMap.Frame>("Stack Frame"); 150 DenseMap<Site> sites = new DenseMap<Site>("Stack Trace"); 151 DenseMap<String> classNamesBySerial = new DenseMap<String>("Class Serial Number"); 152 AhatClassObj javaLangClass = null; 153 AhatClassObj[] primArrayClasses = new AhatClassObj[Type.values().length]; 154 ArrayList<AhatClassObj> classes = new ArrayList<AhatClassObj>(); 155 Instances<AhatClassObj> classById = null; 156 157 while (hprof.hasRemaining()) { 158 int tag = hprof.getU1(); 159 int time = hprof.getU4(); 160 int recordLength = hprof.getU4(); 161 switch (tag) { 162 case 0x01: { // STRING 163 long id = hprof.getId(); 164 byte[] bytes = new byte[recordLength - ID_SIZE]; 165 hprof.getBytes(bytes); 166 String str = new String(bytes, StandardCharsets.UTF_8); 167 strings.put(id, str); 168 break; 169 } 170 171 case 0x02: { // LOAD CLASS 172 int classSerialNumber = hprof.getU4(); 173 long objectId = hprof.getId(); 174 int stackSerialNumber = hprof.getU4(); 175 long classNameStringId = hprof.getId(); 176 String obfClassName = strings.get(classNameStringId); 177 String clrClassName = map.getClassName(obfClassName); 178 AhatClassObj classObj = new AhatClassObj(objectId, clrClassName); 179 classNamesBySerial.put(classSerialNumber, clrClassName); 180 classes.add(classObj); 181 182 // Check whether this class is one of the special classes we are 183 // interested in, and if so, save it for later use. 184 if ("java.lang.Class".equals(clrClassName)) { 185 javaLangClass = classObj; 186 } 187 188 for (Type type : Type.values()) { 189 if (clrClassName.equals(type.name + "[]")) { 190 primArrayClasses[type.ordinal()] = classObj; 191 } 192 } 193 break; 194 } 195 196 case 0x04: { // STACK FRAME 197 long frameId = hprof.getId(); 198 long methodNameStringId = hprof.getId(); 199 long methodSignatureStringId = hprof.getId(); 200 long methodFileNameStringId = hprof.getId(); 201 int classSerialNumber = hprof.getU4(); 202 int lineNumber = hprof.getU4(); 203 204 ProguardMap.Frame frame = map.getFrame( 205 classNamesBySerial.get(classSerialNumber), 206 strings.get(methodNameStringId), 207 strings.get(methodSignatureStringId), 208 strings.get(methodFileNameStringId), 209 lineNumber); 210 frames.put(frameId, frame); 211 break; 212 } 213 214 case 0x05: { // STACK TRACE 215 int stackSerialNumber = hprof.getU4(); 216 int threadSerialNumber = hprof.getU4(); 217 int numFrames = hprof.getU4(); 218 ProguardMap.Frame[] trace = new ProguardMap.Frame[numFrames]; 219 for (int i = 0; i < numFrames; i++) { 220 long frameId = hprof.getId(); 221 trace[i] = frames.get(frameId); 222 } 223 sites.put(stackSerialNumber, rootSite.getSite(trace)); 224 break; 225 } 226 227 case 0x1C: { // HEAP DUMP SEGMENT 228 if (classById == null) { 229 classById = new Instances<AhatClassObj>(classes); 230 } 231 int subtag; 232 while (!isEndOfHeapDumpSegment(subtag = hprof.getU1())) { 233 switch (subtag) { 234 case 0x01: { // ROOT JNI GLOBAL 235 long objectId = hprof.getId(); 236 long refId = hprof.getId(); 237 roots.add(new RootData(objectId, RootType.JNI_GLOBAL)); 238 break; 239 } 240 241 case 0x02: { // ROOT JNI LOCAL 242 long objectId = hprof.getId(); 243 int threadSerialNumber = hprof.getU4(); 244 int frameNumber = hprof.getU4(); 245 roots.add(new RootData(objectId, RootType.JNI_LOCAL)); 246 break; 247 } 248 249 case 0x03: { // ROOT JAVA FRAME 250 long objectId = hprof.getId(); 251 int threadSerialNumber = hprof.getU4(); 252 int frameNumber = hprof.getU4(); 253 roots.add(new RootData(objectId, RootType.JAVA_FRAME)); 254 break; 255 } 256 257 case 0x04: { // ROOT NATIVE STACK 258 long objectId = hprof.getId(); 259 int threadSerialNumber = hprof.getU4(); 260 roots.add(new RootData(objectId, RootType.NATIVE_STACK)); 261 break; 262 } 263 264 case 0x05: { // ROOT STICKY CLASS 265 long objectId = hprof.getId(); 266 roots.add(new RootData(objectId, RootType.STICKY_CLASS)); 267 break; 268 } 269 270 case 0x06: { // ROOT THREAD BLOCK 271 long objectId = hprof.getId(); 272 int threadSerialNumber = hprof.getU4(); 273 roots.add(new RootData(objectId, RootType.THREAD_BLOCK)); 274 break; 275 } 276 277 case 0x07: { // ROOT MONITOR USED 278 long objectId = hprof.getId(); 279 roots.add(new RootData(objectId, RootType.MONITOR)); 280 break; 281 } 282 283 case 0x08: { // ROOT THREAD OBJECT 284 long objectId = hprof.getId(); 285 int threadSerialNumber = hprof.getU4(); 286 int stackSerialNumber = hprof.getU4(); 287 roots.add(new RootData(objectId, RootType.THREAD)); 288 break; 289 } 290 291 case 0x20: { // CLASS DUMP 292 ClassObjData data = new ClassObjData(); 293 long objectId = hprof.getId(); 294 int stackSerialNumber = hprof.getU4(); 295 long superClassId = hprof.getId(); 296 data.classLoaderId = hprof.getId(); 297 long signersId = hprof.getId(); 298 long protectionId = hprof.getId(); 299 long reserved1 = hprof.getId(); 300 long reserved2 = hprof.getId(); 301 int instanceSize = hprof.getU4(); 302 int constantPoolSize = hprof.getU2(); 303 for (int i = 0; i < constantPoolSize; ++i) { 304 int index = hprof.getU2(); 305 Type type = hprof.getType(); 306 hprof.skip(type.size); 307 } 308 int numStaticFields = hprof.getU2(); 309 data.staticFields = new FieldValue[numStaticFields]; 310 AhatClassObj obj = classById.get(objectId); 311 String clrClassName = obj.getName(); 312 long staticFieldsSize = 0; 313 for (int i = 0; i < numStaticFields; ++i) { 314 String obfName = strings.get(hprof.getId()); 315 String clrName = map.getFieldName(clrClassName, obfName); 316 Type type = hprof.getType(); 317 Value value = hprof.getDeferredValue(type); 318 staticFieldsSize += type.size; 319 data.staticFields[i] = new FieldValue(clrName, type, value); 320 } 321 AhatClassObj superClass = classById.get(superClassId); 322 int numInstanceFields = hprof.getU2(); 323 Field[] ifields = new Field[numInstanceFields]; 324 for (int i = 0; i < numInstanceFields; ++i) { 325 String name = map.getFieldName(obj.getName(), strings.get(hprof.getId())); 326 ifields[i] = new Field(name, hprof.getType()); 327 } 328 Site site = sites.get(stackSerialNumber); 329 330 if (javaLangClass == null) { 331 throw new HprofFormatException("No class definition found for java.lang.Class"); 332 } 333 obj.initialize(heaps.getCurrentHeap(), site, javaLangClass); 334 obj.initialize(superClass, instanceSize, ifields, staticFieldsSize); 335 obj.setTemporaryUserData(data); 336 break; 337 } 338 339 case 0x21: { // INSTANCE DUMP 340 long objectId = hprof.getId(); 341 int stackSerialNumber = hprof.getU4(); 342 long classId = hprof.getId(); 343 int numBytes = hprof.getU4(); 344 ClassInstData data = new ClassInstData(hprof.tell()); 345 hprof.skip(numBytes); 346 347 Site site = sites.get(stackSerialNumber); 348 AhatClassObj classObj = classById.get(classId); 349 AhatClassInstance obj = new AhatClassInstance(objectId); 350 obj.initialize(heaps.getCurrentHeap(), site, classObj); 351 obj.setTemporaryUserData(data); 352 instances.add(obj); 353 break; 354 } 355 356 case 0x22: { // OBJECT ARRAY DUMP 357 long objectId = hprof.getId(); 358 int stackSerialNumber = hprof.getU4(); 359 int length = hprof.getU4(); 360 long classId = hprof.getId(); 361 ObjArrayData data = new ObjArrayData(length, hprof.tell()); 362 hprof.skip(length * ID_SIZE); 363 364 Site site = sites.get(stackSerialNumber); 365 AhatClassObj classObj = classById.get(classId); 366 AhatArrayInstance obj = new AhatArrayInstance(objectId); 367 obj.initialize(heaps.getCurrentHeap(), site, classObj); 368 obj.setTemporaryUserData(data); 369 instances.add(obj); 370 break; 371 } 372 373 case 0x23: { // PRIMITIVE ARRAY DUMP 374 long objectId = hprof.getId(); 375 int stackSerialNumber = hprof.getU4(); 376 int length = hprof.getU4(); 377 Type type = hprof.getPrimitiveType(); 378 Site site = sites.get(stackSerialNumber); 379 380 AhatClassObj classObj = primArrayClasses[type.ordinal()]; 381 if (classObj == null) { 382 throw new HprofFormatException( 383 "No class definition found for " + type.name + "[]"); 384 } 385 386 AhatArrayInstance obj = new AhatArrayInstance(objectId); 387 obj.initialize(heaps.getCurrentHeap(), site, classObj); 388 instances.add(obj); 389 switch (type) { 390 case BOOLEAN: { 391 boolean[] data = new boolean[length]; 392 for (int i = 0; i < length; ++i) { 393 data[i] = hprof.getBool(); 394 } 395 obj.initialize(data); 396 break; 397 } 398 399 case CHAR: { 400 char[] data = new char[length]; 401 for (int i = 0; i < length; ++i) { 402 data[i] = hprof.getChar(); 403 } 404 obj.initialize(data); 405 break; 406 } 407 408 case FLOAT: { 409 float[] data = new float[length]; 410 for (int i = 0; i < length; ++i) { 411 data[i] = hprof.getFloat(); 412 } 413 obj.initialize(data); 414 break; 415 } 416 417 case DOUBLE: { 418 double[] data = new double[length]; 419 for (int i = 0; i < length; ++i) { 420 data[i] = hprof.getDouble(); 421 } 422 obj.initialize(data); 423 break; 424 } 425 426 case BYTE: { 427 byte[] data = new byte[length]; 428 hprof.getBytes(data); 429 obj.initialize(data); 430 break; 431 } 432 433 case SHORT: { 434 short[] data = new short[length]; 435 for (int i = 0; i < length; ++i) { 436 data[i] = hprof.getShort(); 437 } 438 obj.initialize(data); 439 break; 440 } 441 442 case INT: { 443 int[] data = new int[length]; 444 for (int i = 0; i < length; ++i) { 445 data[i] = hprof.getInt(); 446 } 447 obj.initialize(data); 448 break; 449 } 450 451 case LONG: { 452 long[] data = new long[length]; 453 for (int i = 0; i < length; ++i) { 454 data[i] = hprof.getLong(); 455 } 456 obj.initialize(data); 457 break; 458 } 459 } 460 break; 461 } 462 463 case 0x89: { // ROOT INTERNED STRING (ANDROID) 464 long objectId = hprof.getId(); 465 roots.add(new RootData(objectId, RootType.INTERNED_STRING)); 466 break; 467 } 468 469 case 0x8a: { // ROOT FINALIZING (ANDROID) 470 long objectId = hprof.getId(); 471 roots.add(new RootData(objectId, RootType.FINALIZING)); 472 break; 473 } 474 475 case 0x8b: { // ROOT DEBUGGER (ANDROID) 476 long objectId = hprof.getId(); 477 roots.add(new RootData(objectId, RootType.DEBUGGER)); 478 break; 479 } 480 481 case 0x8d: { // ROOT VM INTERNAL (ANDROID) 482 long objectId = hprof.getId(); 483 roots.add(new RootData(objectId, RootType.VM_INTERNAL)); 484 break; 485 } 486 487 case 0x8e: { // ROOT JNI MONITOR (ANDROID) 488 long objectId = hprof.getId(); 489 int threadSerialNumber = hprof.getU4(); 490 int frameNumber = hprof.getU4(); 491 roots.add(new RootData(objectId, RootType.JNI_MONITOR)); 492 break; 493 } 494 495 case 0xfe: { // HEAP DUMP INFO (ANDROID) 496 int type = hprof.getU4(); 497 long stringId = hprof.getId(); 498 heaps.setCurrentHeap(strings.get(stringId)); 499 break; 500 } 501 502 case 0xff: { // ROOT UNKNOWN 503 long objectId = hprof.getId(); 504 roots.add(new RootData(objectId, RootType.UNKNOWN)); 505 break; 506 } 507 508 default: 509 throw new HprofFormatException( 510 String.format("Unsupported heap dump sub tag 0x%02x", subtag)); 511 } 512 } 513 514 // Reset the file pointer back because we read the first byte into 515 // the next record. 516 hprof.skip(-1); 517 break; 518 } 519 520 default: 521 // Ignore any other tags that we either don't know about or don't 522 // care about. 523 hprof.skip(recordLength); 524 break; 525 } 526 } 527 528 instances.addAll(classes); 529 } 530 531 // Sort roots and instances by id in preparation for the fixup pass. 532 Instances<AhatInstance> mInstances = new Instances<AhatInstance>(instances); 533 roots.sort(new Comparator<RootData>() { 534 @Override 535 public int compare(RootData a, RootData b) { 536 return Long.compare(a.id, b.id); 537 } 538 }); 539 roots.add(null); 540 541 // Fixup pass: Label the root instances and fix up references to instances 542 // that we couldn't previously resolve. 543 SuperRoot superRoot = new SuperRoot(); 544 { 545 Iterator<RootData> ri = roots.iterator(); 546 RootData root = ri.next(); 547 for (AhatInstance inst : mInstances) { 548 long id = inst.getId(); 549 550 // Skip past any roots that don't have associated instances. 551 // It's not clear why there would be a root without an associated 552 // instance dump, but it does happen in practice, for example when 553 // taking heap dumps using the RI. 554 while (root != null && root.id < id) { 555 root = ri.next(); 556 } 557 558 // Check if this instance is a root, and if so, update its root types. 559 if (root != null && root.id == id) { 560 superRoot.addRoot(inst); 561 while (root != null && root.id == id) { 562 inst.addRootType(root.type); 563 root = ri.next(); 564 } 565 } 566 567 // Fixup the instance based on its type using the temporary data we 568 // saved during the first pass over the heap dump. 569 if (inst instanceof AhatClassInstance) { 570 ClassInstData data = (ClassInstData)inst.getTemporaryUserData(); 571 inst.setTemporaryUserData(null); 572 573 // Compute the size of the fields array in advance to avoid 574 // extra allocations and copies that would come from using an array 575 // list to collect the field values. 576 int numFields = 0; 577 for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) { 578 numFields += cls.getInstanceFields().length; 579 } 580 581 Value[] fields = new Value[numFields]; 582 int i = 0; 583 hprof.seek(data.position); 584 for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) { 585 for (Field field : cls.getInstanceFields()) { 586 fields[i++] = hprof.getValue(field.type, mInstances); 587 } 588 } 589 ((AhatClassInstance)inst).initialize(fields); 590 } else if (inst instanceof AhatClassObj) { 591 ClassObjData data = (ClassObjData)inst.getTemporaryUserData(); 592 inst.setTemporaryUserData(null); 593 AhatInstance loader = mInstances.get(data.classLoaderId); 594 for (int i = 0; i < data.staticFields.length; ++i) { 595 FieldValue field = data.staticFields[i]; 596 if (field.value instanceof DeferredInstanceValue) { 597 DeferredInstanceValue deferred = (DeferredInstanceValue)field.value; 598 data.staticFields[i] = new FieldValue( 599 field.name, field.type, Value.pack(mInstances.get(deferred.getId()))); 600 } 601 } 602 ((AhatClassObj)inst).initialize(loader, data.staticFields); 603 } else if (inst instanceof AhatArrayInstance && inst.getTemporaryUserData() != null) { 604 // TODO: Have specialized object array instance and check for that 605 // rather than checking for the presence of user data? 606 ObjArrayData data = (ObjArrayData)inst.getTemporaryUserData(); 607 inst.setTemporaryUserData(null); 608 AhatInstance[] array = new AhatInstance[data.length]; 609 hprof.seek(data.position); 610 for (int i = 0; i < data.length; i++) { 611 array[i] = mInstances.get(hprof.getId()); 612 } 613 ((AhatArrayInstance)inst).initialize(array); 614 } 615 } 616 } 617 618 hprof = null; 619 roots = null; 620 return new AhatSnapshot(superRoot, mInstances, heaps.heaps, rootSite); 621 } 622 isEndOfHeapDumpSegment(int subtag)623 private static boolean isEndOfHeapDumpSegment(int subtag) { 624 return subtag == 0x1C || subtag == 0x2C; 625 } 626 627 private static class RootData { 628 public long id; 629 public RootType type; 630 RootData(long id, RootType type)631 public RootData(long id, RootType type) { 632 this.id = id; 633 this.type = type; 634 } 635 } 636 637 private static class ClassInstData { 638 // The byte position in the hprof file where instance field data starts. 639 public int position; 640 ClassInstData(int position)641 public ClassInstData(int position) { 642 this.position = position; 643 } 644 } 645 646 private static class ObjArrayData { 647 public int length; // Number of array elements. 648 public int position; // Position in hprof file containing element data. 649 ObjArrayData(int length, int position)650 public ObjArrayData(int length, int position) { 651 this.length = length; 652 this.position = position; 653 } 654 } 655 656 private static class ClassObjData { 657 public long classLoaderId; 658 public FieldValue[] staticFields; // Contains DeferredInstanceValues. 659 } 660 661 /** 662 * Dummy value representing a reference to an instance that has not yet been 663 * resolved. 664 * When first initializing class static fields, we don't yet know what kinds 665 * of objects Object references refer to. We use DeferredInstanceValue as 666 * a dummy kind of value to store the id of an object. In the fixup pass we 667 * resolve all the DeferredInstanceValues into their proper InstanceValues. 668 */ 669 private static class DeferredInstanceValue extends Value { 670 private long mId; 671 DeferredInstanceValue(long id)672 public DeferredInstanceValue(long id) { 673 mId = id; 674 } 675 getId()676 public long getId() { 677 return mId; 678 } 679 680 @Override getType()681 Type getType() { 682 return Type.OBJECT; 683 } 684 685 @Override toString()686 public String toString() { 687 return String.format("0x%08x", mId); 688 } 689 equals(Object other)690 @Override public boolean equals(Object other) { 691 if (other instanceof DeferredInstanceValue) { 692 DeferredInstanceValue value = (DeferredInstanceValue)other; 693 return mId == value.mId; 694 } 695 return false; 696 } 697 } 698 699 /** 700 * A convenient abstraction for lazily building up the list of heaps seen in 701 * the heap dump. 702 */ 703 private static class HeapList { 704 public List<AhatHeap> heaps = new ArrayList<AhatHeap>(); 705 private AhatHeap current; 706 getCurrentHeap()707 public AhatHeap getCurrentHeap() { 708 if (current == null) { 709 setCurrentHeap("default"); 710 } 711 return current; 712 } 713 setCurrentHeap(String name)714 public void setCurrentHeap(String name) { 715 for (AhatHeap heap : heaps) { 716 if (name.equals(heap.getName())) { 717 current = heap; 718 return; 719 } 720 } 721 722 current = new AhatHeap(name, heaps.size()); 723 heaps.add(current); 724 } 725 } 726 727 /** 728 * A mapping from id to elements, where certain conditions are 729 * satisfied. The conditions are: 730 * - all elements are defined before they are referenced. 731 * - ids are densely packed in some range [a, b] where a is not 732 * necessarily 0. 733 * - there are not more than 2^31 elements defined. 734 */ 735 private static class DenseMap<T> { 736 private String mElementType; 737 738 // mValues behaves like a circular buffer. 739 // mKeyAt0 is the key corresponding to index 0 of mValues. Values with 740 // smaller keys will wrap around to the end of the mValues buffer. The 741 // buffer is expanded when it is no longer big enough to hold all the keys 742 // from mMinKey to mMaxKey. 743 private Object[] mValues; 744 private long mKeyAt0; 745 private long mMaxKey; 746 private long mMinKey; 747 748 /** 749 * Constructs a DenseMap. 750 * @param elementType Human readable name describing the type of 751 * elements for error message if the required 752 * conditions are found not to hold. 753 */ DenseMap(String elementType)754 public DenseMap(String elementType) { 755 mElementType = elementType; 756 } 757 put(long key, T value)758 public void put(long key, T value) { 759 if (mValues == null) { 760 mValues = new Object[8]; 761 mValues[0] = value; 762 mKeyAt0 = key; 763 mMaxKey = key; 764 mMinKey = key; 765 return; 766 } 767 768 long max = Math.max(mMaxKey, key); 769 long min = Math.min(mMinKey, key); 770 int count = (int)(max + 1 - min); 771 if (count > mValues.length) { 772 Object[] values = new Object[2 * count]; 773 774 // Copy over the values into the newly allocated larger buffer. It is 775 // convenient to move the value with mMinKey to index 0 when we make 776 // the copy. 777 for (int i = 0; i < mValues.length; ++i) { 778 values[i] = mValues[indexOf(i + mMinKey)]; 779 } 780 mValues = values; 781 mKeyAt0 = mMinKey; 782 } 783 mMinKey = min; 784 mMaxKey = max; 785 mValues[indexOf(key)] = value; 786 } 787 788 /** 789 * Returns the value for the given key. 790 * @throws HprofFormatException if there is no value with the key in the 791 * given map. 792 */ get(long key)793 public T get(long key) throws HprofFormatException { 794 T value = null; 795 if (mValues != null && key >= mMinKey && key <= mMaxKey) { 796 value = (T)mValues[indexOf(key)]; 797 } 798 799 if (value == null) { 800 throw new HprofFormatException(String.format( 801 "%s with id 0x%x referenced before definition", mElementType, key)); 802 } 803 return value; 804 } 805 indexOf(long key)806 private int indexOf(long key) { 807 return ((int)(key - mKeyAt0) + mValues.length) % mValues.length; 808 } 809 } 810 811 /** 812 * A mapping from id to elements, where we don't have nice conditions to 813 * work with. 814 */ 815 private static class UnDenseMap<T> { 816 private String mElementType; 817 private Map<Long, T> mValues = new HashMap<Long, T>(); 818 819 /** 820 * Constructs an UnDenseMap. 821 * @param elementType Human readable name describing the type of 822 * elements for error message if the required 823 * conditions are found not to hold. 824 */ UnDenseMap(String elementType)825 public UnDenseMap(String elementType) { 826 mElementType = elementType; 827 } 828 put(long key, T value)829 public void put(long key, T value) { 830 mValues.put(key, value); 831 } 832 833 /** 834 * Returns the value for the given key. 835 * @throws HprofFormatException if there is no value with the key in the 836 * given map. 837 */ get(long key)838 public T get(long key) throws HprofFormatException { 839 T value = mValues.get(key); 840 if (value == null) { 841 throw new HprofFormatException(String.format( 842 "%s with id 0x%x referenced before definition", mElementType, key)); 843 } 844 return value; 845 } 846 } 847 848 /** 849 * Wrapper around a ByteBuffer that presents a uniform interface for 850 * accessing data from an hprof file. 851 */ 852 private static class HprofBuffer { 853 private ByteBuffer mBuffer; 854 HprofBuffer(File path)855 public HprofBuffer(File path) throws IOException { 856 FileChannel channel = FileChannel.open(path.toPath(), StandardOpenOption.READ); 857 mBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 858 channel.close(); 859 } 860 HprofBuffer(ByteBuffer buffer)861 public HprofBuffer(ByteBuffer buffer) { 862 mBuffer = buffer; 863 } 864 hasRemaining()865 public boolean hasRemaining() { 866 return mBuffer.hasRemaining(); 867 } 868 869 /** 870 * Return the current absolution position in the file. 871 */ tell()872 public int tell() { 873 return mBuffer.position(); 874 } 875 876 /** 877 * Seek to the given absolution position in the file. 878 */ seek(int position)879 public void seek(int position) { 880 mBuffer.position(position); 881 } 882 883 /** 884 * Skip ahead in the file by the given delta bytes. Delta may be negative 885 * to skip backwards in the file. 886 */ skip(int delta)887 public void skip(int delta) { 888 seek(tell() + delta); 889 } 890 getU1()891 public int getU1() { 892 return mBuffer.get() & 0xFF; 893 } 894 getU2()895 public int getU2() { 896 return mBuffer.getShort() & 0xFFFF; 897 } 898 getU4()899 public int getU4() { 900 return mBuffer.getInt(); 901 } 902 getId()903 public long getId() { 904 return mBuffer.getInt() & 0xFFFFFFFFL; 905 } 906 getBool()907 public boolean getBool() { 908 return mBuffer.get() != 0; 909 } 910 getChar()911 public char getChar() { 912 return mBuffer.getChar(); 913 } 914 getFloat()915 public float getFloat() { 916 return mBuffer.getFloat(); 917 } 918 getDouble()919 public double getDouble() { 920 return mBuffer.getDouble(); 921 } 922 getByte()923 public byte getByte() { 924 return mBuffer.get(); 925 } 926 getBytes(byte[] bytes)927 public void getBytes(byte[] bytes) { 928 mBuffer.get(bytes); 929 } 930 getShort()931 public short getShort() { 932 return mBuffer.getShort(); 933 } 934 getInt()935 public int getInt() { 936 return mBuffer.getInt(); 937 } 938 getLong()939 public long getLong() { 940 return mBuffer.getLong(); 941 } 942 943 private static Type[] TYPES = new Type[] { 944 null, null, Type.OBJECT, null, 945 Type.BOOLEAN, Type.CHAR, Type.FLOAT, Type.DOUBLE, 946 Type.BYTE, Type.SHORT, Type.INT, Type.LONG 947 }; 948 getType()949 public Type getType() throws HprofFormatException { 950 int id = getU1(); 951 Type type = id < TYPES.length ? TYPES[id] : null; 952 if (type == null) { 953 throw new HprofFormatException("Invalid basic type id: " + id); 954 } 955 return type; 956 } 957 958 public Type getPrimitiveType() throws HprofFormatException { 959 Type type = getType(); 960 if (type == Type.OBJECT) { 961 throw new HprofFormatException("Expected primitive type, but found type 'Object'"); 962 } 963 return type; 964 } 965 966 /** 967 * Get a value from the hprof file, using the given instances map to 968 * convert instance ids to their corresponding AhatInstance objects. 969 */ 970 public Value getValue(Type type, Instances instances) { 971 switch (type) { 972 case OBJECT: return Value.pack(instances.get(getId())); 973 case BOOLEAN: return Value.pack(getBool()); 974 case CHAR: return Value.pack(getChar()); 975 case FLOAT: return Value.pack(getFloat()); 976 case DOUBLE: return Value.pack(getDouble()); 977 case BYTE: return Value.pack(getByte()); 978 case SHORT: return Value.pack(getShort()); 979 case INT: return Value.pack(getInt()); 980 case LONG: return Value.pack(getLong()); 981 default: throw new AssertionError("unsupported enum member"); 982 } 983 } 984 985 /** 986 * Get a value from the hprof file. AhatInstance values are returned as 987 * DefferredInstanceValues rather than their corresponding AhatInstance 988 * objects. 989 */ 990 public Value getDeferredValue(Type type) { 991 switch (type) { 992 case OBJECT: return new DeferredInstanceValue(getId()); 993 case BOOLEAN: return Value.pack(getBool()); 994 case CHAR: return Value.pack(getChar()); 995 case FLOAT: return Value.pack(getFloat()); 996 case DOUBLE: return Value.pack(getDouble()); 997 case BYTE: return Value.pack(getByte()); 998 case SHORT: return Value.pack(getShort()); 999 case INT: return Value.pack(getInt()); 1000 case LONG: return Value.pack(getLong()); 1001 default: throw new AssertionError("unsupported enum member"); 1002 } 1003 } 1004 } 1005 } 1006