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