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
17package com.android.hit;
18
19import java.io.ByteArrayOutputStream;
20import java.io.DataInputStream;
21import java.io.InputStream;
22import java.io.EOFException;
23import java.io.IOException;
24import java.util.HashMap;
25
26public 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