blob: 98fef1e12224e259ba480d49aa35b74fdc0e9bc2 [file] [log] [blame]
/*
* 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<Long, String> mStrings = new HashMap<Long, String>();
HashMap<Long, String> mClassNames = new HashMap<Long, String>();
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;
}
}
}