1 /* 2 * Copyright (C) 2008 Google Inc. 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.hit; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.DataInputStream; 21 import java.io.InputStream; 22 import java.io.EOFException; 23 import java.io.IOException; 24 import java.util.HashMap; 25 26 public class HprofParser 27 { 28 private static final int STRING_IN_UTF8 = 0x01; 29 private static final int LOAD_CLASS = 0x02; 30 private static final int UNLOAD_CLASS = 0x03; // unused 31 private static final int STACK_FRAME = 0x04; 32 private static final int STACK_TRACE = 0x05; 33 private static final int ALLOC_SITES = 0x06; // unused 34 private static final int HEAP_SUMMARY = 0x07; 35 private static final int START_THREAD = 0x0a; // unused 36 private static final int END_THREAD = 0x0b; // unused 37 private static final int HEAP_DUMP = 0x0c; 38 private static final int HEAP_DUMP_SEGMENT = 0x1c; 39 private static final int HEAP_DUMP_END = 0x2c; 40 private static final int CPU_SAMPLES = 0x0d; // unused 41 private static final int CONTROL_SETTINGS = 0x0e; // unused 42 43 private static final int ROOT_UNKNOWN = 0xff; 44 private static final int ROOT_JNI_GLOBAL = 0x01; 45 private static final int ROOT_JNI_LOCAL = 0x02; 46 private static final int ROOT_JAVA_FRAME = 0x03; 47 private static final int ROOT_NATIVE_STACK = 0x04; 48 private static final int ROOT_STICKY_CLASS = 0x05; 49 private static final int ROOT_THREAD_BLOCK = 0x06; 50 private static final int ROOT_MONITOR_USED = 0x07; 51 private static final int ROOT_THREAD_OBJECT = 0x08; 52 private static final int ROOT_CLASS_DUMP = 0x20; 53 private static final int ROOT_INSTANCE_DUMP = 0x21; 54 private static final int ROOT_OBJECT_ARRAY_DUMP = 0x22; 55 private static final int ROOT_PRIMITIVE_ARRAY_DUMP = 0x23; 56 57 /** 58 * Android format addition 59 * 60 * Specifies information about which heap certain objects came from. 61 * When a sub-tag of this type appears in a HPROF_HEAP_DUMP or 62 * HPROF_HEAP_DUMP_SEGMENT record, entries that follow it will be 63 * associated with the specified heap. The HEAP_DUMP_INFO data is reset 64 * at the end of the HEAP_DUMP[_SEGMENT]. Multiple HEAP_DUMP_INFO entries 65 * may appear in a single HEAP_DUMP[_SEGMENT]. 66 * 67 * Format: 68 * u1: Tag value (0xFE) 69 * u4: heap ID 70 * ID: heap name string ID 71 */ 72 private static final int ROOT_HEAP_DUMP_INFO = 0xfe; 73 private static final int ROOT_INTERNED_STRING = 0x89; 74 private static final int ROOT_FINALIZING = 0x8a; 75 private static final int ROOT_DEBUGGER = 0x8b; 76 private static final int ROOT_REFERENCE_CLEANUP = 0x8c; 77 private static final int ROOT_VM_INTERNAL = 0x8d; 78 private static final int ROOT_JNI_MONITOR = 0x8e; 79 private static final int ROOT_UNREACHABLE = 0x90; 80 private static final int ROOT_PRIMITIVE_ARRAY_NODATA= 0xc3; 81 82 DataInputStream mInput; 83 int mIdSize; 84 State mState; 85 86 byte[] mFieldBuffer = new byte[8]; 87 88 /* 89 * These are only needed while parsing so are not kept as part of the 90 * heap data. 91 */ 92 HashMap<Long, String> mStrings = new HashMap<Long, String>(); 93 HashMap<Long, String> mClassNames = new HashMap<Long, String>(); 94 95 public HprofParser(DataInputStream in) { 96 mInput = in; 97 } 98 99 public final State parse() { 100 State state = new State(); 101 mState = state; 102 103 try { 104 String s = readNullTerminatedString(); 105 DataInputStream in = mInput; 106 107 mIdSize = in.readInt(); 108 Types.setIdSize(mIdSize); 109 110 in.readLong(); // Timestamp, ignored for now 111 112 while (true) { 113 int tag = in.readUnsignedByte(); 114 int timestamp = in.readInt(); 115 int length = in.readInt(); 116 117 switch (tag) { 118 case STRING_IN_UTF8: 119 loadString(length - 4); 120 break; 121 122 case LOAD_CLASS: 123 loadClass(); 124 break; 125 126 case STACK_FRAME: 127 loadStackFrame(); 128 break; 129 130 case STACK_TRACE: 131 loadStackTrace(); 132 break; 133 134 case HEAP_DUMP: 135 loadHeapDump(length); 136 mState.setToDefaultHeap(); 137 break; 138 139 case HEAP_DUMP_SEGMENT: 140 loadHeapDump(length); 141 mState.setToDefaultHeap(); 142 break; 143 144 default: 145 skipFully(length); 146 } 147 148 } 149 } catch (EOFException eof) { 150 // this is fine 151 } catch (Exception e) { 152 e.printStackTrace(); 153 } 154 155 mState.resolveReferences(); 156 157 return state; 158 } 159 160 private String readNullTerminatedString() throws IOException { 161 StringBuilder s = new StringBuilder(); 162 DataInputStream in = mInput; 163 164 for (int c = in.read(); c != 0; c = in.read()) { 165 s.append((char) c); 166 } 167 168 return s.toString(); 169 } 170 171 private long readId() throws IOException { 172 switch (mIdSize) { 173 case 1: return mInput.readUnsignedByte(); 174 case 2: return mInput.readUnsignedShort(); 175 case 4: return ((long) mInput.readInt()) & 0x00000000ffffffffL; 176 case 8: return mInput.readLong(); 177 } 178 179 throw new IllegalArgumentException("ID Length must be 1, 2, 4, or 8"); 180 } 181 182 private String readUTF8(int length) throws IOException { 183 byte[] b = new byte[length]; 184 185 mInput.read(b); 186 187 return new String(b, "utf-8"); 188 } 189 190 private void loadString(int length) throws IOException { 191 long id = readId(); 192 String string = readUTF8(length); 193 194 mStrings.put(id, string); 195 } 196 197 private void loadClass() throws IOException { 198 DataInputStream in = mInput; 199 int serial = in.readInt(); 200 long id = readId(); 201 int stackTrace = in.readInt(); // unused 202 String name = mStrings.get(readId()); 203 204 mClassNames.put(id, name); 205 } 206 207 private void loadStackFrame() throws IOException { 208 long id = readId(); 209 String methodName = mStrings.get(readId()); 210 String methodSignature = mStrings.get(readId()); 211 String sourceFile = mStrings.get(readId()); 212 int serial = mInput.readInt(); 213 int lineNumber = mInput.readInt(); 214 215 StackFrame frame = new StackFrame(id, methodName, methodSignature, 216 sourceFile, serial, lineNumber); 217 218 mState.addStackFrame(frame); 219 } 220 221 private void loadStackTrace() throws IOException { 222 int serialNumber = mInput.readInt(); 223 int threadSerialNumber = mInput.readInt(); 224 final int numFrames = mInput.readInt(); 225 StackFrame[] frames = new StackFrame[numFrames]; 226 227 for (int i = 0; i < numFrames; i++) { 228 frames[i] = mState.getStackFrame(readId()); 229 } 230 231 StackTrace trace = new StackTrace(serialNumber, threadSerialNumber, 232 frames); 233 234 mState.addStackTrace(trace); 235 } 236 237 private void loadHeapDump(int length) throws IOException { 238 DataInputStream in = mInput; 239 240 while (length > 0) { 241 int tag = in.readUnsignedByte(); 242 length--; 243 244 switch (tag) { 245 case ROOT_UNKNOWN: 246 length -= loadBasicObj(RootType.UNKNOWN); 247 break; 248 249 case ROOT_JNI_GLOBAL: 250 length -= loadBasicObj(RootType.NATIVE_STATIC); 251 readId(); // ignored 252 length -= mIdSize; 253 break; 254 255 case ROOT_JNI_LOCAL: 256 length -= loadJniLocal(); 257 break; 258 259 case ROOT_JAVA_FRAME: 260 length -= loadJavaFrame(); 261 break; 262 263 case ROOT_NATIVE_STACK: 264 length -= loadNativeStack(); 265 break; 266 267 case ROOT_STICKY_CLASS: 268 length -= loadBasicObj(RootType.SYSTEM_CLASS); 269 break; 270 271 case ROOT_THREAD_BLOCK: 272 length -= loadThreadBlock(); 273 break; 274 275 case ROOT_MONITOR_USED: 276 length -= loadBasicObj(RootType.BUSY_MONITOR); 277 break; 278 279 case ROOT_THREAD_OBJECT: 280 length -= loadThreadObject(); 281 break; 282 283 case ROOT_CLASS_DUMP: 284 length -= loadClassDump(); 285 break; 286 287 case ROOT_INSTANCE_DUMP: 288 length -= loadInstanceDump(); 289 break; 290 291 case ROOT_OBJECT_ARRAY_DUMP: 292 length -= loadObjectArrayDump(); 293 break; 294 295 case ROOT_PRIMITIVE_ARRAY_DUMP: 296 length -= loadPrimitiveArrayDump(); 297 break; 298 299 case ROOT_PRIMITIVE_ARRAY_NODATA: 300 System.err.println("+--- PRIMITIVE ARRAY NODATA DUMP"); 301 length -= loadPrimitiveArrayDump(); 302 303 throw new IllegalArgumentException( 304 "Don't know how to load a nodata array"); 305 306 case ROOT_HEAP_DUMP_INFO: 307 int heapId = mInput.readInt(); 308 long heapNameId = readId(); 309 String heapName = mStrings.get(heapNameId); 310 311 mState.setHeapTo(heapId, heapName); 312 length -= 4 + mIdSize; 313 break; 314 315 case ROOT_INTERNED_STRING: 316 length -= loadBasicObj(RootType.INTERNED_STRING); 317 break; 318 319 case ROOT_FINALIZING: 320 length -= loadBasicObj(RootType.FINALIZING); 321 break; 322 323 case ROOT_DEBUGGER: 324 length -= loadBasicObj(RootType.DEBUGGER); 325 break; 326 327 case ROOT_REFERENCE_CLEANUP: 328 length -= loadBasicObj(RootType.REFERENCE_CLEANUP); 329 break; 330 331 case ROOT_VM_INTERNAL: 332 length -= loadBasicObj(RootType.VM_INTERNAL); 333 break; 334 335 case ROOT_JNI_MONITOR: 336 length -= loadJniMonitor(); 337 break; 338 339 case ROOT_UNREACHABLE: 340 length -= loadBasicObj(RootType.UNREACHABLE); 341 break; 342 343 default: 344 throw new IllegalArgumentException( 345 "loadHeapDump loop with unknown tag " + tag 346 + " with " + mInput.available() 347 + " bytes possibly remaining"); 348 } 349 } 350 } 351 352 private int loadJniLocal() throws IOException { 353 long id = readId(); 354 int threadSerialNumber = mInput.readInt(); 355 int stackFrameNumber = mInput.readInt(); 356 ThreadObj thread = mState.getThread(threadSerialNumber); 357 StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, 358 stackFrameNumber); 359 RootObj root = new RootObj(RootType.NATIVE_LOCAL, id, 360 threadSerialNumber, trace); 361 362 root.setHeap(mState.mCurrentHeap); 363 mState.addRoot(root); 364 365 return mIdSize + 4 + 4; 366 } 367 368 private int loadJavaFrame() throws IOException { 369 long id = readId(); 370 int threadSerialNumber = mInput.readInt(); 371 int stackFrameNumber = mInput.readInt(); 372 ThreadObj thread = mState.getThread(threadSerialNumber); 373 StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, 374 stackFrameNumber); 375 RootObj root = new RootObj(RootType.JAVA_LOCAL, id, threadSerialNumber, 376 trace); 377 378 root.setHeap(mState.mCurrentHeap); 379 mState.addRoot(root); 380 381 return mIdSize + 4 + 4; 382 } 383 384 private int loadNativeStack() throws IOException { 385 long id = readId(); 386 int threadSerialNumber = mInput.readInt(); 387 ThreadObj thread = mState.getThread(threadSerialNumber); 388 StackTrace trace = mState.getStackTrace(thread.mStackTrace); 389 RootObj root = new RootObj(RootType.NATIVE_STACK, id, 390 threadSerialNumber, trace); 391 392 root.setHeap(mState.mCurrentHeap); 393 mState.addRoot(root); 394 395 return mIdSize + 4; 396 } 397 398 private int loadBasicObj(RootType type) throws IOException { 399 long id = readId(); 400 RootObj root = new RootObj(type, id); 401 402 root.setHeap(mState.mCurrentHeap); 403 mState.addRoot(root); 404 405 return mIdSize; 406 } 407 408 private int loadThreadBlock() throws IOException { 409 long id = readId(); 410 int threadSerialNumber = mInput.readInt(); 411 ThreadObj thread = mState.getThread(threadSerialNumber); 412 StackTrace stack = mState.getStackTrace(thread.mStackTrace); 413 RootObj root = new RootObj(RootType.THREAD_BLOCK, id, 414 threadSerialNumber, stack); 415 416 root.setHeap(mState.mCurrentHeap); 417 mState.addRoot(root); 418 419 return mIdSize + 4; 420 } 421 422 private int loadThreadObject() throws IOException { 423 long id = readId(); 424 int threadSerialNumber = mInput.readInt(); 425 int stackSerialNumber = mInput.readInt(); 426 ThreadObj thread = new ThreadObj(id, stackSerialNumber); 427 428 mState.addThread(thread, threadSerialNumber); 429 430 return mIdSize + 4 + 4; 431 } 432 433 private int loadClassDump() throws IOException { 434 int bytesRead = 0; 435 DataInputStream in = mInput; 436 long id = readId(); 437 int stackSerialNumber = in.readInt(); 438 StackTrace stack = mState.getStackTrace(stackSerialNumber); 439 long superClassId = readId(); 440 long classLoaderId = readId(); 441 long signersId = readId(); 442 long protectionDomainId = readId(); 443 long reserved1 = readId(); 444 long reserved2 = readId(); 445 int instanceSize = in.readInt(); 446 447 bytesRead = (7 * mIdSize) + 4 + 4; 448 449 // Skip over the constant pool 450 int numEntries = in.readUnsignedShort(); 451 bytesRead += 2; 452 453 for (int i = 0; i < numEntries; i++) { 454 in.readUnsignedShort(); 455 bytesRead += 2 + skipValue(); 456 } 457 458 // Static fields 459 numEntries = in.readUnsignedShort(); 460 bytesRead += 2; 461 462 String[] staticFieldNames = new String[numEntries]; 463 int[] staticFieldTypes = new int[numEntries]; 464 ByteArrayOutputStream staticFieldValues = new ByteArrayOutputStream(); 465 byte[] buffer = mFieldBuffer; 466 467 for (int i = 0; i < numEntries; i++) { 468 staticFieldNames[i] = mStrings.get(readId()); 469 470 int fieldType = in.readByte(); 471 int fieldSize = Types.getTypeSize(fieldType); 472 staticFieldTypes[i] = fieldType; 473 474 in.readFully(buffer, 0, fieldSize); 475 staticFieldValues.write(buffer, 0, fieldSize); 476 477 bytesRead += mIdSize + 1 + fieldSize; 478 } 479 480 // Instance fields 481 numEntries = in.readUnsignedShort(); 482 bytesRead += 2; 483 484 String[] names = new String[numEntries]; 485 int[] types = new int[numEntries]; 486 487 for (int i = 0; i < numEntries; i++) { 488 long fieldName = readId(); 489 int type = in.readUnsignedByte(); 490 491 names[i] = mStrings.get(fieldName); 492 types[i] = type; 493 494 bytesRead += mIdSize + 1; 495 } 496 497 ClassObj theClass = new ClassObj(id, stack, mClassNames.get(id)); 498 499 theClass.setStaticFieldNames(staticFieldNames); 500 theClass.setStaticFieldTypes(staticFieldTypes); 501 theClass.setStaticFieldValues(staticFieldValues.toByteArray()); 502 503 theClass.setSuperclassId(superClassId); 504 theClass.setFieldNames(names); 505 theClass.setFieldTypes(types); 506 theClass.setSize(instanceSize); 507 508 theClass.setHeap(mState.mCurrentHeap); 509 510 mState.addClass(id, theClass); 511 512 return bytesRead; 513 } 514 515 private int loadInstanceDump() throws IOException { 516 long id = readId(); 517 int stackId = mInput.readInt(); 518 StackTrace stack = mState.getStackTrace(stackId); 519 long classId = readId(); 520 int remaining = mInput.readInt(); 521 ClassInstance instance = new ClassInstance(id, stack, classId); 522 523 instance.loadFieldData(mInput, remaining); 524 instance.setHeap(mState.mCurrentHeap); 525 mState.addInstance(id, instance); 526 527 return mIdSize + 4 + mIdSize + 4 + remaining; 528 } 529 530 private int loadObjectArrayDump() throws IOException { 531 long id = readId(); 532 int stackId = mInput.readInt(); 533 StackTrace stack = mState.getStackTrace(stackId); 534 int numElements = mInput.readInt(); 535 long classId = readId(); 536 int totalBytes = numElements * mIdSize; 537 byte[] data = new byte[totalBytes]; 538 String className = mClassNames.get(classId); 539 540 mInput.readFully(data); 541 542 ArrayInstance array = new ArrayInstance(id, stack, Types.OBJECT, 543 numElements, data); 544 545 array.mClassId = classId; 546 array.setHeap(mState.mCurrentHeap); 547 mState.addInstance(id, array); 548 549 return mIdSize + 4 + 4 + mIdSize + totalBytes; 550 } 551 552 private int loadPrimitiveArrayDump() throws IOException { 553 long id = readId(); 554 int stackId = mInput.readInt(); 555 StackTrace stack = mState.getStackTrace(stackId); 556 int numElements = mInput.readInt(); 557 int type = mInput.readUnsignedByte(); 558 int size = Types.getTypeSize(type); 559 int totalBytes = numElements * size; 560 byte[] data = new byte[totalBytes]; 561 562 mInput.readFully(data); 563 564 ArrayInstance array = new ArrayInstance(id, stack, type, numElements, 565 data); 566 567 array.setHeap(mState.mCurrentHeap); 568 mState.addInstance(id, array); 569 570 return mIdSize + 4 + 4 + 1 + totalBytes; 571 } 572 573 private int loadJniMonitor() throws IOException { 574 long id = readId(); 575 int threadSerialNumber = mInput.readInt(); 576 int stackDepth = mInput.readInt(); 577 ThreadObj thread = mState.getThread(threadSerialNumber); 578 StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, 579 stackDepth); 580 RootObj root = new RootObj(RootType.NATIVE_MONITOR, id, 581 threadSerialNumber, trace); 582 583 root.setHeap(mState.mCurrentHeap); 584 mState.addRoot(root); 585 586 return mIdSize + 4 + 4; 587 } 588 589 private int skipValue() throws IOException { 590 int type = mInput.readUnsignedByte(); 591 int size = Types.getTypeSize(type); 592 593 skipFully(size); 594 595 return size + 1; 596 } 597 598 /* 599 * BufferedInputStream will not skip(int) the entire requested number 600 * of bytes if it extends past the current buffer boundary. So, this 601 * routine is needed to actually skip over the requested number of bytes 602 * using as many iterations as needed. 603 */ 604 private void skipFully(long numBytes) throws IOException { 605 while (numBytes > 0) { 606 long skipped = mInput.skip(numBytes); 607 608 numBytes -= skipped; 609 } 610 } 611 } 612