/* * Copyright (C) 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.hit; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.InputStream; import java.io.EOFException; import java.io.IOException; import java.util.HashMap; public class HprofParser { private static final int STRING_IN_UTF8 = 0x01; private static final int LOAD_CLASS = 0x02; private static final int UNLOAD_CLASS = 0x03; // unused private static final int STACK_FRAME = 0x04; private static final int STACK_TRACE = 0x05; private static final int ALLOC_SITES = 0x06; // unused private static final int HEAP_SUMMARY = 0x07; private static final int START_THREAD = 0x0a; // unused private static final int END_THREAD = 0x0b; // unused private static final int HEAP_DUMP = 0x0c; private static final int HEAP_DUMP_SEGMENT = 0x1c; private static final int HEAP_DUMP_END = 0x2c; private static final int CPU_SAMPLES = 0x0d; // unused private static final int CONTROL_SETTINGS = 0x0e; // unused private static final int ROOT_UNKNOWN = 0xff; private static final int ROOT_JNI_GLOBAL = 0x01; private static final int ROOT_JNI_LOCAL = 0x02; private static final int ROOT_JAVA_FRAME = 0x03; private static final int ROOT_NATIVE_STACK = 0x04; private static final int ROOT_STICKY_CLASS = 0x05; private static final int ROOT_THREAD_BLOCK = 0x06; private static final int ROOT_MONITOR_USED = 0x07; private static final int ROOT_THREAD_OBJECT = 0x08; private static final int ROOT_CLASS_DUMP = 0x20; private static final int ROOT_INSTANCE_DUMP = 0x21; private static final int ROOT_OBJECT_ARRAY_DUMP = 0x22; private static final int ROOT_PRIMITIVE_ARRAY_DUMP = 0x23; /** * Android format addition * * Specifies information about which heap certain objects came from. * When a sub-tag of this type appears in a HPROF_HEAP_DUMP or * HPROF_HEAP_DUMP_SEGMENT record, entries that follow it will be * associated with the specified heap. The HEAP_DUMP_INFO data is reset * at the end of the HEAP_DUMP[_SEGMENT]. Multiple HEAP_DUMP_INFO entries * may appear in a single HEAP_DUMP[_SEGMENT]. * * Format: * u1: Tag value (0xFE) * u4: heap ID * ID: heap name string ID */ private static final int ROOT_HEAP_DUMP_INFO = 0xfe; private static final int ROOT_INTERNED_STRING = 0x89; private static final int ROOT_FINALIZING = 0x8a; private static final int ROOT_DEBUGGER = 0x8b; private static final int ROOT_REFERENCE_CLEANUP = 0x8c; private static final int ROOT_VM_INTERNAL = 0x8d; private static final int ROOT_JNI_MONITOR = 0x8e; private static final int ROOT_UNREACHABLE = 0x90; private static final int ROOT_PRIMITIVE_ARRAY_NODATA= 0xc3; DataInputStream mInput; int mIdSize; State mState; byte[] mFieldBuffer = new byte[8]; /* * These are only needed while parsing so are not kept as part of the * heap data. */ HashMap mStrings = new HashMap(); HashMap mClassNames = new HashMap(); public HprofParser(DataInputStream in) { mInput = in; } public final State parse() { State state = new State(); mState = state; try { String s = readNullTerminatedString(); DataInputStream in = mInput; mIdSize = in.readInt(); Types.setIdSize(mIdSize); in.readLong(); // Timestamp, ignored for now while (true) { int tag = in.readUnsignedByte(); int timestamp = in.readInt(); int length = in.readInt(); switch (tag) { case STRING_IN_UTF8: loadString(length - 4); break; case LOAD_CLASS: loadClass(); break; case STACK_FRAME: loadStackFrame(); break; case STACK_TRACE: loadStackTrace(); break; case HEAP_DUMP: loadHeapDump(length); mState.setToDefaultHeap(); break; case HEAP_DUMP_SEGMENT: loadHeapDump(length); mState.setToDefaultHeap(); break; default: skipFully(length); } } } catch (EOFException eof) { // this is fine } catch (Exception e) { e.printStackTrace(); } mState.resolveReferences(); return state; } private String readNullTerminatedString() throws IOException { StringBuilder s = new StringBuilder(); DataInputStream in = mInput; for (int c = in.read(); c != 0; c = in.read()) { s.append((char) c); } return s.toString(); } private long readId() throws IOException { switch (mIdSize) { case 1: return mInput.readUnsignedByte(); case 2: return mInput.readUnsignedShort(); case 4: return ((long) mInput.readInt()) & 0x00000000ffffffffL; case 8: return mInput.readLong(); } throw new IllegalArgumentException("ID Length must be 1, 2, 4, or 8"); } private String readUTF8(int length) throws IOException { byte[] b = new byte[length]; mInput.read(b); return new String(b, "utf-8"); } private void loadString(int length) throws IOException { long id = readId(); String string = readUTF8(length); mStrings.put(id, string); } private void loadClass() throws IOException { DataInputStream in = mInput; int serial = in.readInt(); long id = readId(); int stackTrace = in.readInt(); // unused String name = mStrings.get(readId()); mClassNames.put(id, name); } private void loadStackFrame() throws IOException { long id = readId(); String methodName = mStrings.get(readId()); String methodSignature = mStrings.get(readId()); String sourceFile = mStrings.get(readId()); int serial = mInput.readInt(); int lineNumber = mInput.readInt(); StackFrame frame = new StackFrame(id, methodName, methodSignature, sourceFile, serial, lineNumber); mState.addStackFrame(frame); } private void loadStackTrace() throws IOException { int serialNumber = mInput.readInt(); int threadSerialNumber = mInput.readInt(); final int numFrames = mInput.readInt(); StackFrame[] frames = new StackFrame[numFrames]; for (int i = 0; i < numFrames; i++) { frames[i] = mState.getStackFrame(readId()); } StackTrace trace = new StackTrace(serialNumber, threadSerialNumber, frames); mState.addStackTrace(trace); } private void loadHeapDump(int length) throws IOException { DataInputStream in = mInput; while (length > 0) { int tag = in.readUnsignedByte(); length--; switch (tag) { case ROOT_UNKNOWN: length -= loadBasicObj(RootType.UNKNOWN); break; case ROOT_JNI_GLOBAL: length -= loadBasicObj(RootType.NATIVE_STATIC); readId(); // ignored length -= mIdSize; break; case ROOT_JNI_LOCAL: length -= loadJniLocal(); break; case ROOT_JAVA_FRAME: length -= loadJavaFrame(); break; case ROOT_NATIVE_STACK: length -= loadNativeStack(); break; case ROOT_STICKY_CLASS: length -= loadBasicObj(RootType.SYSTEM_CLASS); break; case ROOT_THREAD_BLOCK: length -= loadThreadBlock(); break; case ROOT_MONITOR_USED: length -= loadBasicObj(RootType.BUSY_MONITOR); break; case ROOT_THREAD_OBJECT: length -= loadThreadObject(); break; case ROOT_CLASS_DUMP: length -= loadClassDump(); break; case ROOT_INSTANCE_DUMP: length -= loadInstanceDump(); break; case ROOT_OBJECT_ARRAY_DUMP: length -= loadObjectArrayDump(); break; case ROOT_PRIMITIVE_ARRAY_DUMP: length -= loadPrimitiveArrayDump(); break; case ROOT_PRIMITIVE_ARRAY_NODATA: System.err.println("+--- PRIMITIVE ARRAY NODATA DUMP"); length -= loadPrimitiveArrayDump(); throw new IllegalArgumentException( "Don't know how to load a nodata array"); case ROOT_HEAP_DUMP_INFO: int heapId = mInput.readInt(); long heapNameId = readId(); String heapName = mStrings.get(heapNameId); mState.setHeapTo(heapId, heapName); length -= 4 + mIdSize; break; case ROOT_INTERNED_STRING: length -= loadBasicObj(RootType.INTERNED_STRING); break; case ROOT_FINALIZING: length -= loadBasicObj(RootType.FINALIZING); break; case ROOT_DEBUGGER: length -= loadBasicObj(RootType.DEBUGGER); break; case ROOT_REFERENCE_CLEANUP: length -= loadBasicObj(RootType.REFERENCE_CLEANUP); break; case ROOT_VM_INTERNAL: length -= loadBasicObj(RootType.VM_INTERNAL); break; case ROOT_JNI_MONITOR: length -= loadJniMonitor(); break; case ROOT_UNREACHABLE: length -= loadBasicObj(RootType.UNREACHABLE); break; default: throw new IllegalArgumentException( "loadHeapDump loop with unknown tag " + tag + " with " + mInput.available() + " bytes possibly remaining"); } } } private int loadJniLocal() throws IOException { long id = readId(); int threadSerialNumber = mInput.readInt(); int stackFrameNumber = mInput.readInt(); ThreadObj thread = mState.getThread(threadSerialNumber); StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, stackFrameNumber); RootObj root = new RootObj(RootType.NATIVE_LOCAL, id, threadSerialNumber, trace); root.setHeap(mState.mCurrentHeap); mState.addRoot(root); return mIdSize + 4 + 4; } private int loadJavaFrame() throws IOException { long id = readId(); int threadSerialNumber = mInput.readInt(); int stackFrameNumber = mInput.readInt(); ThreadObj thread = mState.getThread(threadSerialNumber); StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, stackFrameNumber); RootObj root = new RootObj(RootType.JAVA_LOCAL, id, threadSerialNumber, trace); root.setHeap(mState.mCurrentHeap); mState.addRoot(root); return mIdSize + 4 + 4; } private int loadNativeStack() throws IOException { long id = readId(); int threadSerialNumber = mInput.readInt(); ThreadObj thread = mState.getThread(threadSerialNumber); StackTrace trace = mState.getStackTrace(thread.mStackTrace); RootObj root = new RootObj(RootType.NATIVE_STACK, id, threadSerialNumber, trace); root.setHeap(mState.mCurrentHeap); mState.addRoot(root); return mIdSize + 4; } private int loadBasicObj(RootType type) throws IOException { long id = readId(); RootObj root = new RootObj(type, id); root.setHeap(mState.mCurrentHeap); mState.addRoot(root); return mIdSize; } private int loadThreadBlock() throws IOException { long id = readId(); int threadSerialNumber = mInput.readInt(); ThreadObj thread = mState.getThread(threadSerialNumber); StackTrace stack = mState.getStackTrace(thread.mStackTrace); RootObj root = new RootObj(RootType.THREAD_BLOCK, id, threadSerialNumber, stack); root.setHeap(mState.mCurrentHeap); mState.addRoot(root); return mIdSize + 4; } private int loadThreadObject() throws IOException { long id = readId(); int threadSerialNumber = mInput.readInt(); int stackSerialNumber = mInput.readInt(); ThreadObj thread = new ThreadObj(id, stackSerialNumber); mState.addThread(thread, threadSerialNumber); return mIdSize + 4 + 4; } private int loadClassDump() throws IOException { int bytesRead = 0; DataInputStream in = mInput; long id = readId(); int stackSerialNumber = in.readInt(); StackTrace stack = mState.getStackTrace(stackSerialNumber); long superClassId = readId(); long classLoaderId = readId(); long signersId = readId(); long protectionDomainId = readId(); long reserved1 = readId(); long reserved2 = readId(); int instanceSize = in.readInt(); bytesRead = (7 * mIdSize) + 4 + 4; // Skip over the constant pool int numEntries = in.readUnsignedShort(); bytesRead += 2; for (int i = 0; i < numEntries; i++) { in.readUnsignedShort(); bytesRead += 2 + skipValue(); } // Static fields numEntries = in.readUnsignedShort(); bytesRead += 2; String[] staticFieldNames = new String[numEntries]; int[] staticFieldTypes = new int[numEntries]; ByteArrayOutputStream staticFieldValues = new ByteArrayOutputStream(); byte[] buffer = mFieldBuffer; for (int i = 0; i < numEntries; i++) { staticFieldNames[i] = mStrings.get(readId()); int fieldType = in.readByte(); int fieldSize = Types.getTypeSize(fieldType); staticFieldTypes[i] = fieldType; in.readFully(buffer, 0, fieldSize); staticFieldValues.write(buffer, 0, fieldSize); bytesRead += mIdSize + 1 + fieldSize; } // Instance fields numEntries = in.readUnsignedShort(); bytesRead += 2; String[] names = new String[numEntries]; int[] types = new int[numEntries]; for (int i = 0; i < numEntries; i++) { long fieldName = readId(); int type = in.readUnsignedByte(); names[i] = mStrings.get(fieldName); types[i] = type; bytesRead += mIdSize + 1; } ClassObj theClass = new ClassObj(id, stack, mClassNames.get(id)); theClass.setStaticFieldNames(staticFieldNames); theClass.setStaticFieldTypes(staticFieldTypes); theClass.setStaticFieldValues(staticFieldValues.toByteArray()); theClass.setSuperclassId(superClassId); theClass.setFieldNames(names); theClass.setFieldTypes(types); theClass.setSize(instanceSize); theClass.setHeap(mState.mCurrentHeap); mState.addClass(id, theClass); return bytesRead; } private int loadInstanceDump() throws IOException { long id = readId(); int stackId = mInput.readInt(); StackTrace stack = mState.getStackTrace(stackId); long classId = readId(); int remaining = mInput.readInt(); ClassInstance instance = new ClassInstance(id, stack, classId); instance.loadFieldData(mInput, remaining); instance.setHeap(mState.mCurrentHeap); mState.addInstance(id, instance); return mIdSize + 4 + mIdSize + 4 + remaining; } private int loadObjectArrayDump() throws IOException { long id = readId(); int stackId = mInput.readInt(); StackTrace stack = mState.getStackTrace(stackId); int numElements = mInput.readInt(); long classId = readId(); int totalBytes = numElements * mIdSize; byte[] data = new byte[totalBytes]; String className = mClassNames.get(classId); mInput.readFully(data); ArrayInstance array = new ArrayInstance(id, stack, Types.OBJECT, numElements, data); array.mClassId = classId; array.setHeap(mState.mCurrentHeap); mState.addInstance(id, array); return mIdSize + 4 + 4 + mIdSize + totalBytes; } private int loadPrimitiveArrayDump() throws IOException { long id = readId(); int stackId = mInput.readInt(); StackTrace stack = mState.getStackTrace(stackId); int numElements = mInput.readInt(); int type = mInput.readUnsignedByte(); int size = Types.getTypeSize(type); int totalBytes = numElements * size; byte[] data = new byte[totalBytes]; mInput.readFully(data); ArrayInstance array = new ArrayInstance(id, stack, type, numElements, data); array.setHeap(mState.mCurrentHeap); mState.addInstance(id, array); return mIdSize + 4 + 4 + 1 + totalBytes; } private int loadJniMonitor() throws IOException { long id = readId(); int threadSerialNumber = mInput.readInt(); int stackDepth = mInput.readInt(); ThreadObj thread = mState.getThread(threadSerialNumber); StackTrace trace = mState.getStackTraceAtDepth(thread.mStackTrace, stackDepth); RootObj root = new RootObj(RootType.NATIVE_MONITOR, id, threadSerialNumber, trace); root.setHeap(mState.mCurrentHeap); mState.addRoot(root); return mIdSize + 4 + 4; } private int skipValue() throws IOException { int type = mInput.readUnsignedByte(); int size = Types.getTypeSize(type); skipFully(size); return size + 1; } /* * BufferedInputStream will not skip(int) the entire requested number * of bytes if it extends past the current buffer boundary. So, this * routine is needed to actually skip over the requested number of bytes * using as many iterations as needed. */ private void skipFully(long numBytes) throws IOException { while (numBytes > 0) { long skipped = mInput.skip(numBytes); numBytes -= skipped; } } }