Home | History | Annotate | Download | only in hit
      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