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