blob: c7f7b4b763671ed420c8ad0753ab1fb97cbcf496 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.ahat.heapdump;
import com.android.ahat.progress.NullProgress;
import com.android.ahat.progress.Progress;
import com.android.ahat.proguard.ProguardMap;
import java.io.File;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Provides methods for parsing heap dumps.
* <p>
* The heap dump should be a heap dump in the J2SE HPROF format optionally
* with Android extensions and satisfying the following additional
* constraints:
* <ul>
* <li>
* Class serial numbers, stack frames, and stack traces individually satisfy
* the following:
* <ul>
* <li> All elements are defined before they are referenced.
* <li> Ids are densely packed in some range [a, b] where a is not necessarily 0.
* <li> There are not more than 2^31 elements defined.
* </ul>
* <li> All classes are defined via a LOAD CLASS record before the first
* heap dump segment.
* </ul>
*/
public class Parser {
private HprofBuffer hprof = null;
private ProguardMap map = new ProguardMap();
private Progress progress = new NullProgress();
private Reachability retained = Reachability.SOFT;
/**
* Creates an hprof Parser that parses a heap dump from a byte buffer.
*
* @param hprof byte buffer to parse the heap dump from.
*/
public Parser(ByteBuffer hprof) {
this.hprof = new HprofBuffer(hprof);
}
/**
* Creates an hprof Parser that parses a heap dump from a file.
*
* @param hprof file to parse the heap dump from.
* @throws IOException if the file cannot be accessed.
*/
public Parser(File hprof) throws IOException {
this.hprof = new HprofBuffer(hprof);
}
/**
* Sets the proguard map to use for deobfuscating the heap.
*
* @param map proguard map to use to deobfuscate the heap.
* @return this Parser instance.
*/
public Parser map(ProguardMap map) {
if (map == null) {
throw new NullPointerException("map == null");
}
this.map = map;
return this;
}
/**
* Sets the progress indicator to use when parsing the heap.
*
* @param progress progress indicator to use when parsing the heap.
* @return this Parser instance.
*/
public Parser progress(Progress progress) {
if (progress == null) {
throw new NullPointerException("progress == null");
}
this.progress = progress;
return this;
}
/**
* Specify the weakest reachability of instances to treat as retained.
*
* @param retained the weakest reachability of instances to treat as retained.
* @return this Parser instance.
*/
public Parser retained(Reachability retained) {
this.retained = retained;
return this;
}
/**
* Parse the heap dump.
*
* @throws IOException if the heap dump could not be read
* @throws HprofFormatException if the heap dump is not properly formatted
* @return the parsed heap dump
*/
public AhatSnapshot parse() throws IOException, HprofFormatException {
try {
return parseInternal();
} catch (BufferUnderflowException e) {
throw new HprofFormatException("Unexpected end of file", e);
}
}
/**
* Parses a heap dump from a File with given proguard map.
*
* @param hprof the hprof file to parse
* @param map the proguard map for deobfuscation
* @return the parsed heap dump
* @throws IOException if the heap dump could not be read
* @throws HprofFormatException if the heap dump is not properly formatted
*/
public static AhatSnapshot parseHeapDump(File hprof, ProguardMap map)
throws IOException, HprofFormatException {
return new Parser(hprof).map(map).parse();
}
/**
* Parses a heap dump from a byte buffer with given proguard map.
*
* @param hprof the bytes of the hprof file to parse
* @param map the proguard map for deobfuscation
* @return the parsed heap dump
* @throws IOException if the heap dump could not be read
* @throws HprofFormatException if the heap dump is not properly formatted
*/
public static AhatSnapshot parseHeapDump(ByteBuffer hprof, ProguardMap map)
throws IOException, HprofFormatException {
return new Parser(hprof).map(map).parse();
}
private AhatSnapshot parseInternal() throws IOException, HprofFormatException {
// Read, and mostly ignore, the hprof header info.
int idSize;
{
StringBuilder format = new StringBuilder();
int b;
while ((b = hprof.getU1()) != 0) {
format.append((char)b);
}
idSize = hprof.getU4();
if (idSize == 8) {
hprof.setIdSize8();
} else if (idSize != 4) {
throw new HprofFormatException("Id size " + idSize + " not supported.");
}
int hightime = hprof.getU4();
int lowtime = hprof.getU4();
}
// First pass: Read through all the heap dump records. Construct the
// AhatInstances, initialize them as much as possible and save any
// additional temporary data we need to complete their initialization in
// the fixup pass.
Site rootSite = new Site("ROOT");
List<AhatInstance> instances = new ArrayList<AhatInstance>();
List<RootData> roots = new ArrayList<RootData>();
HeapList heaps = new HeapList();
{
// Note: Strings do not satisfy the DenseMap requirements on heap dumps
// from Android K. And the RI seems to use string id 0 to refer to a
// null string?
UnDenseMap<String> strings = new UnDenseMap<String>("String");
strings.put(0, "???");
DenseMap<ProguardMap.Frame> frames = new DenseMap<ProguardMap.Frame>("Stack Frame");
DenseMap<Site> sites = new DenseMap<Site>("Stack Trace");
DenseMap<String> classNamesBySerial = new DenseMap<String>("Class Serial Number");
AhatClassObj javaLangClass = null;
AhatClassObj[] primArrayClasses = new AhatClassObj[Type.values().length];
ArrayList<AhatClassObj> classes = new ArrayList<AhatClassObj>();
Instances<AhatClassObj> classById = null;
progress.start("Reading hprof", hprof.size());
while (hprof.hasRemaining()) {
progress.update(hprof.tell());
int tag = hprof.getU1();
int time = hprof.getU4();
int recordLength = hprof.getU4();
switch (tag) {
case 0x01: { // STRING
long id = hprof.getId();
byte[] bytes = new byte[recordLength - idSize];
hprof.getBytes(bytes);
String str = new String(bytes, StandardCharsets.UTF_8);
strings.put(id, str);
break;
}
case 0x02: { // LOAD CLASS
int classSerialNumber = hprof.getU4();
long objectId = hprof.getId();
int stackSerialNumber = hprof.getU4();
long classNameStringId = hprof.getId();
String rawClassName = strings.get(classNameStringId);
String obfClassName = normalizeClassName(rawClassName);
String clrClassName = map.getClassName(obfClassName);
AhatClassObj classObj = new AhatClassObj(objectId, clrClassName);
classNamesBySerial.put(classSerialNumber, clrClassName);
classes.add(classObj);
// Check whether this class is one of the special classes we are
// interested in, and if so, save it for later use.
if ("java.lang.Class".equals(clrClassName)) {
javaLangClass = classObj;
}
for (Type type : Type.values()) {
if (clrClassName.equals(type.name + "[]")) {
primArrayClasses[type.ordinal()] = classObj;
}
}
break;
}
case 0x04: { // STACK FRAME
long frameId = hprof.getId();
long methodNameStringId = hprof.getId();
long methodSignatureStringId = hprof.getId();
long methodFileNameStringId = hprof.getId();
int classSerialNumber = hprof.getU4();
int lineNumber = hprof.getU4();
ProguardMap.Frame frame = map.getFrame(
classNamesBySerial.get(classSerialNumber),
strings.get(methodNameStringId),
strings.get(methodSignatureStringId),
strings.get(methodFileNameStringId),
lineNumber);
frames.put(frameId, frame);
break;
}
case 0x05: { // STACK TRACE
int stackSerialNumber = hprof.getU4();
int threadSerialNumber = hprof.getU4();
int numFrames = hprof.getU4();
ProguardMap.Frame[] trace = new ProguardMap.Frame[numFrames];
for (int i = 0; i < numFrames; i++) {
long frameId = hprof.getId();
trace[i] = frames.get(frameId);
}
sites.put(stackSerialNumber, rootSite.getSite(trace));
break;
}
case 0x0C: // HEAP DUMP
case 0x1C: { // HEAP DUMP SEGMENT
int endOfRecord = hprof.tell() + recordLength;
if (classById == null) {
classById = new Instances<AhatClassObj>(classes);
}
while (hprof.tell() < endOfRecord) {
progress.update(hprof.tell());
int subtag = hprof.getU1();
switch (subtag) {
case 0x01: { // ROOT JNI GLOBAL
long objectId = hprof.getId();
long refId = hprof.getId();
roots.add(new RootData(objectId, RootType.JNI_GLOBAL));
break;
}
case 0x02: { // ROOT JNI LOCAL
long objectId = hprof.getId();
int threadSerialNumber = hprof.getU4();
int frameNumber = hprof.getU4();
roots.add(new RootData(objectId, RootType.JNI_LOCAL));
break;
}
case 0x03: { // ROOT JAVA FRAME
long objectId = hprof.getId();
int threadSerialNumber = hprof.getU4();
int frameNumber = hprof.getU4();
roots.add(new RootData(objectId, RootType.JAVA_FRAME));
break;
}
case 0x04: { // ROOT NATIVE STACK
long objectId = hprof.getId();
int threadSerialNumber = hprof.getU4();
roots.add(new RootData(objectId, RootType.NATIVE_STACK));
break;
}
case 0x05: { // ROOT STICKY CLASS
long objectId = hprof.getId();
roots.add(new RootData(objectId, RootType.STICKY_CLASS));
break;
}
case 0x06: { // ROOT THREAD BLOCK
long objectId = hprof.getId();
int threadSerialNumber = hprof.getU4();
roots.add(new RootData(objectId, RootType.THREAD_BLOCK));
break;
}
case 0x07: { // ROOT MONITOR USED
long objectId = hprof.getId();
roots.add(new RootData(objectId, RootType.MONITOR));
break;
}
case 0x08: { // ROOT THREAD OBJECT
long objectId = hprof.getId();
int threadSerialNumber = hprof.getU4();
int stackSerialNumber = hprof.getU4();
roots.add(new RootData(objectId, RootType.THREAD));
break;
}
case 0x20: { // CLASS DUMP
ClassObjData data = new ClassObjData();
long objectId = hprof.getId();
int stackSerialNumber = hprof.getU4();
long superClassId = hprof.getId();
data.classLoaderId = hprof.getId();
long signersId = hprof.getId();
long protectionId = hprof.getId();
long reserved1 = hprof.getId();
long reserved2 = hprof.getId();
int instanceSize = hprof.getU4();
int constantPoolSize = hprof.getU2();
for (int i = 0; i < constantPoolSize; ++i) {
int index = hprof.getU2();
Type type = hprof.getType();
hprof.skip(type.size(idSize));
}
int numStaticFields = hprof.getU2();
data.staticFields = new FieldValue[numStaticFields];
AhatClassObj obj = classById.get(objectId);
String clrClassName = obj.getName();
long staticFieldsSize = 0;
for (int i = 0; i < numStaticFields; ++i) {
String obfName = strings.get(hprof.getId());
String clrName = map.getFieldName(clrClassName, obfName);
Type type = hprof.getType();
Value value = hprof.getDeferredValue(type);
staticFieldsSize += type.size(idSize);
data.staticFields[i] = new FieldValue(clrName, type, value);
}
AhatClassObj superClass = classById.get(superClassId);
int numInstanceFields = hprof.getU2();
Field[] ifields = new Field[numInstanceFields];
for (int i = 0; i < numInstanceFields; ++i) {
String name = map.getFieldName(obj.getName(), strings.get(hprof.getId()));
ifields[i] = new Field(name, hprof.getType());
}
Site site = sites.get(stackSerialNumber);
if (javaLangClass == null) {
throw new HprofFormatException("No class definition found for java.lang.Class");
}
obj.initialize(heaps.getCurrentHeap(), site, javaLangClass);
obj.initialize(superClass, instanceSize, ifields, staticFieldsSize);
obj.setTemporaryUserData(data);
break;
}
case 0x21: { // INSTANCE DUMP
long objectId = hprof.getId();
int stackSerialNumber = hprof.getU4();
long classId = hprof.getId();
int numBytes = hprof.getU4();
ClassInstData data = new ClassInstData(hprof.tell());
hprof.skip(numBytes);
Site site = sites.get(stackSerialNumber);
AhatClassObj classObj = classById.get(classId);
AhatClassInstance obj = new AhatClassInstance(objectId);
obj.initialize(heaps.getCurrentHeap(), site, classObj);
obj.setTemporaryUserData(data);
instances.add(obj);
break;
}
case 0x22: { // OBJECT ARRAY DUMP
long objectId = hprof.getId();
int stackSerialNumber = hprof.getU4();
int length = hprof.getU4();
long classId = hprof.getId();
ObjArrayData data = new ObjArrayData(length, hprof.tell());
hprof.skip(length * idSize);
Site site = sites.get(stackSerialNumber);
AhatClassObj classObj = classById.get(classId);
AhatArrayInstance obj = new AhatArrayInstance(objectId, idSize);
obj.initialize(heaps.getCurrentHeap(), site, classObj);
obj.setTemporaryUserData(data);
instances.add(obj);
break;
}
case 0x23: { // PRIMITIVE ARRAY DUMP
long objectId = hprof.getId();
int stackSerialNumber = hprof.getU4();
int length = hprof.getU4();
Type type = hprof.getPrimitiveType();
Site site = sites.get(stackSerialNumber);
AhatClassObj classObj = primArrayClasses[type.ordinal()];
if (classObj == null) {
throw new HprofFormatException(
"No class definition found for " + type.name + "[]");
}
AhatArrayInstance obj = new AhatArrayInstance(objectId, idSize);
obj.initialize(heaps.getCurrentHeap(), site, classObj);
instances.add(obj);
switch (type) {
case BOOLEAN: {
boolean[] data = new boolean[length];
for (int i = 0; i < length; ++i) {
data[i] = hprof.getBool();
}
obj.initialize(data);
break;
}
case CHAR: {
char[] data = new char[length];
for (int i = 0; i < length; ++i) {
data[i] = hprof.getChar();
}
obj.initialize(data);
break;
}
case FLOAT: {
float[] data = new float[length];
for (int i = 0; i < length; ++i) {
data[i] = hprof.getFloat();
}
obj.initialize(data);
break;
}
case DOUBLE: {
double[] data = new double[length];
for (int i = 0; i < length; ++i) {
data[i] = hprof.getDouble();
}
obj.initialize(data);
break;
}
case BYTE: {
byte[] data = new byte[length];
hprof.getBytes(data);
obj.initialize(data);
break;
}
case SHORT: {
short[] data = new short[length];
for (int i = 0; i < length; ++i) {
data[i] = hprof.getShort();
}
obj.initialize(data);
break;
}
case INT: {
int[] data = new int[length];
for (int i = 0; i < length; ++i) {
data[i] = hprof.getInt();
}
obj.initialize(data);
break;
}
case LONG: {
long[] data = new long[length];
for (int i = 0; i < length; ++i) {
data[i] = hprof.getLong();
}
obj.initialize(data);
break;
}
default: throw new AssertionError("unsupported enum member");
}
break;
}
case 0x89: { // ROOT INTERNED STRING (ANDROID)
long objectId = hprof.getId();
roots.add(new RootData(objectId, RootType.INTERNED_STRING));
break;
}
case 0x8a: { // ROOT FINALIZING (ANDROID)
long objectId = hprof.getId();
roots.add(new RootData(objectId, RootType.FINALIZING));
break;
}
case 0x8b: { // ROOT DEBUGGER (ANDROID)
long objectId = hprof.getId();
roots.add(new RootData(objectId, RootType.DEBUGGER));
break;
}
case 0x8d: { // ROOT VM INTERNAL (ANDROID)
long objectId = hprof.getId();
roots.add(new RootData(objectId, RootType.VM_INTERNAL));
break;
}
case 0x8e: { // ROOT JNI MONITOR (ANDROID)
long objectId = hprof.getId();
int threadSerialNumber = hprof.getU4();
int frameNumber = hprof.getU4();
roots.add(new RootData(objectId, RootType.JNI_MONITOR));
break;
}
case 0xfe: { // HEAP DUMP INFO (ANDROID)
int type = hprof.getU4();
long stringId = hprof.getId();
heaps.setCurrentHeap(strings.get(stringId));
break;
}
case 0xff: { // ROOT UNKNOWN
long objectId = hprof.getId();
roots.add(new RootData(objectId, RootType.UNKNOWN));
break;
}
default:
throw new HprofFormatException(
String.format("Unsupported heap dump sub tag 0x%02x", subtag));
}
}
break;
}
default:
// Ignore any other tags that we either don't know about or don't
// care about.
hprof.skip(recordLength);
break;
}
}
progress.done();
instances.addAll(classes);
}
// Sort roots and instances by id in preparation for the fixup pass.
Instances<AhatInstance> mInstances = new Instances<AhatInstance>(instances);
roots.sort(new Comparator<RootData>() {
@Override
public int compare(RootData a, RootData b) {
return Long.compare(a.id, b.id);
}
});
roots.add(null);
// Fixup pass: Label the root instances and fix up references to instances
// that we couldn't previously resolve.
SuperRoot superRoot = new SuperRoot();
{
progress.start("Resolving references", mInstances.size());
Iterator<RootData> ri = roots.iterator();
RootData root = ri.next();
for (AhatInstance inst : mInstances) {
progress.advance();
long id = inst.getId();
// Skip past any roots that don't have associated instances.
// It's not clear why there would be a root without an associated
// instance dump, but it does happen in practice, for example when
// taking heap dumps using the RI.
while (root != null && root.id < id) {
root = ri.next();
}
// Check if this instance is a root, and if so, update its root types.
if (root != null && root.id == id) {
superRoot.addRoot(inst);
while (root != null && root.id == id) {
inst.addRootType(root.type);
root = ri.next();
}
}
// Fixup the instance based on its type using the temporary data we
// saved during the first pass over the heap dump.
if (inst instanceof AhatClassInstance) {
ClassInstData data = (ClassInstData)inst.getTemporaryUserData();
inst.setTemporaryUserData(null);
// Compute the size of the fields array in advance to avoid
// extra allocations and copies that would come from using an array
// list to collect the field values.
int numFields = 0;
for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) {
numFields += cls.getInstanceFields().length;
}
Value[] fields = new Value[numFields];
int i = 0;
hprof.seek(data.position);
for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) {
for (Field field : cls.getInstanceFields()) {
fields[i++] = hprof.getValue(field.type, mInstances);
}
}
((AhatClassInstance)inst).initialize(fields);
} else if (inst instanceof AhatClassObj) {
ClassObjData data = (ClassObjData)inst.getTemporaryUserData();
inst.setTemporaryUserData(null);
AhatInstance loader = mInstances.get(data.classLoaderId);
for (int i = 0; i < data.staticFields.length; ++i) {
FieldValue field = data.staticFields[i];
if (field.value instanceof DeferredInstanceValue) {
DeferredInstanceValue deferred = (DeferredInstanceValue)field.value;
data.staticFields[i] = new FieldValue(
field.name, field.type, Value.pack(mInstances.get(deferred.getId())));
}
}
((AhatClassObj)inst).initialize(loader, data.staticFields);
} else if (inst instanceof AhatArrayInstance && inst.getTemporaryUserData() != null) {
// TODO: Have specialized object array instance and check for that
// rather than checking for the presence of user data?
ObjArrayData data = (ObjArrayData)inst.getTemporaryUserData();
inst.setTemporaryUserData(null);
AhatInstance[] array = new AhatInstance[data.length];
hprof.seek(data.position);
for (int i = 0; i < data.length; i++) {
array[i] = mInstances.get(hprof.getId());
}
((AhatArrayInstance)inst).initialize(array);
}
}
progress.done();
}
hprof = null;
roots = null;
return new AhatSnapshot(superRoot, mInstances, heaps.heaps, rootSite, progress, retained);
}
private static class RootData {
public long id;
public RootType type;
public RootData(long id, RootType type) {
this.id = id;
this.type = type;
}
}
private static class ClassInstData {
// The byte position in the hprof file where instance field data starts.
public int position;
public ClassInstData(int position) {
this.position = position;
}
}
private static class ObjArrayData {
public int length; // Number of array elements.
public int position; // Position in hprof file containing element data.
public ObjArrayData(int length, int position) {
this.length = length;
this.position = position;
}
}
private static class ClassObjData {
public long classLoaderId;
public FieldValue[] staticFields; // Contains DeferredInstanceValues.
}
/**
* Dummy value representing a reference to an instance that has not yet been
* resolved.
* When first initializing class static fields, we don't yet know what kinds
* of objects Object references refer to. We use DeferredInstanceValue as
* a dummy kind of value to store the id of an object. In the fixup pass we
* resolve all the DeferredInstanceValues into their proper InstanceValues.
*/
private static class DeferredInstanceValue extends Value {
private long mId;
public DeferredInstanceValue(long id) {
mId = id;
}
public long getId() {
return mId;
}
@Override
Type getType() {
return Type.OBJECT;
}
@Override
public String toString() {
return String.format("0x%08x", mId);
}
@Override public int hashCode() {
return Objects.hash(mId);
}
@Override public boolean equals(Object other) {
if (other instanceof DeferredInstanceValue) {
DeferredInstanceValue value = (DeferredInstanceValue)other;
return mId == value.mId;
}
return false;
}
}
/**
* A convenient abstraction for lazily building up the list of heaps seen in
* the heap dump.
*/
private static class HeapList {
public List<AhatHeap> heaps = new ArrayList<AhatHeap>();
private AhatHeap current;
public AhatHeap getCurrentHeap() {
if (current == null) {
setCurrentHeap("default");
}
return current;
}
public void setCurrentHeap(String name) {
for (AhatHeap heap : heaps) {
if (name.equals(heap.getName())) {
current = heap;
return;
}
}
current = new AhatHeap(name, heaps.size());
heaps.add(current);
}
}
/**
* A mapping from id to elements, where certain conditions are
* satisfied. The conditions are:
* - all elements are defined before they are referenced.
* - ids are densely packed in some range [a, b] where a is not
* necessarily 0.
* - there are not more than 2^31 elements defined.
*/
private static class DenseMap<T> {
private String mElementType;
// mValues behaves like a circular buffer.
// mKeyAt0 is the key corresponding to index 0 of mValues. Values with
// smaller keys will wrap around to the end of the mValues buffer. The
// buffer is expanded when it is no longer big enough to hold all the keys
// from mMinKey to mMaxKey.
private Object[] mValues;
private long mKeyAt0;
private long mMaxKey;
private long mMinKey;
/**
* Constructs a DenseMap.
* @param elementType Human readable name describing the type of
* elements for error message if the required
* conditions are found not to hold.
*/
public DenseMap(String elementType) {
mElementType = elementType;
}
public void put(long key, T value) {
if (mValues == null) {
mValues = new Object[8];
mValues[0] = value;
mKeyAt0 = key;
mMaxKey = key;
mMinKey = key;
return;
}
long max = Math.max(mMaxKey, key);
long min = Math.min(mMinKey, key);
int count = (int)(max + 1 - min);
if (count > mValues.length) {
Object[] values = new Object[2 * count];
// Copy over the values into the newly allocated larger buffer. It is
// convenient to move the value with mMinKey to index 0 when we make
// the copy.
for (int i = 0; i < mValues.length; ++i) {
values[i] = mValues[indexOf(i + mMinKey)];
}
mValues = values;
mKeyAt0 = mMinKey;
}
mMinKey = min;
mMaxKey = max;
mValues[indexOf(key)] = value;
}
/**
* Returns the value for the given key.
* @throws HprofFormatException if there is no value with the key in the
* given map.
*/
public T get(long key) throws HprofFormatException {
T value = null;
if (mValues != null && key >= mMinKey && key <= mMaxKey) {
value = (T)mValues[indexOf(key)];
}
if (value == null) {
throw new HprofFormatException(String.format(
"%s with id 0x%x referenced before definition", mElementType, key));
}
return value;
}
private int indexOf(long key) {
return ((int)(key - mKeyAt0) + mValues.length) % mValues.length;
}
}
/**
* A mapping from id to elements, where we don't have nice conditions to
* work with.
*/
private static class UnDenseMap<T> {
private String mElementType;
private Map<Long, T> mValues = new HashMap<Long, T>();
/**
* Constructs an UnDenseMap.
* @param elementType Human readable name describing the type of
* elements for error message if the required
* conditions are found not to hold.
*/
public UnDenseMap(String elementType) {
mElementType = elementType;
}
public void put(long key, T value) {
mValues.put(key, value);
}
/**
* Returns the value for the given key.
* @throws HprofFormatException if there is no value with the key in the
* given map.
*/
public T get(long key) throws HprofFormatException {
T value = mValues.get(key);
if (value == null) {
throw new HprofFormatException(String.format(
"%s with id 0x%x referenced before definition", mElementType, key));
}
return value;
}
}
/**
* Wrapper around a ByteBuffer that presents a uniform interface for
* accessing data from an hprof file.
*/
private static class HprofBuffer {
private boolean mIdSize8;
private final ByteBuffer mBuffer;
public HprofBuffer(File path) throws IOException {
FileChannel channel = FileChannel.open(path.toPath(), StandardOpenOption.READ);
mBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
channel.close();
}
public HprofBuffer(ByteBuffer buffer) {
mBuffer = buffer;
}
public void setIdSize8() {
mIdSize8 = true;
}
public boolean hasRemaining() {
return mBuffer.hasRemaining();
}
/**
* Returns the size of the file in bytes.
*/
public int size() {
return mBuffer.capacity();
}
/**
* Return the current absolution position in the file.
*/
public int tell() {
return mBuffer.position();
}
/**
* Seek to the given absolution position in the file.
*/
public void seek(int position) {
mBuffer.position(position);
}
/**
* Skip ahead in the file by the given delta bytes. Delta may be negative
* to skip backwards in the file.
*/
public void skip(int delta) {
seek(tell() + delta);
}
public int getU1() {
return mBuffer.get() & 0xFF;
}
public int getU2() {
return mBuffer.getShort() & 0xFFFF;
}
public int getU4() {
return mBuffer.getInt();
}
public long getId() {
if (mIdSize8) {
return mBuffer.getLong();
} else {
return mBuffer.getInt() & 0xFFFFFFFFL;
}
}
public boolean getBool() {
return mBuffer.get() != 0;
}
public char getChar() {
return mBuffer.getChar();
}
public float getFloat() {
return mBuffer.getFloat();
}
public double getDouble() {
return mBuffer.getDouble();
}
public byte getByte() {
return mBuffer.get();
}
public void getBytes(byte[] bytes) {
mBuffer.get(bytes);
}
public short getShort() {
return mBuffer.getShort();
}
public int getInt() {
return mBuffer.getInt();
}
public long getLong() {
return mBuffer.getLong();
}
private static Type[] TYPES = new Type[] {
null, null, Type.OBJECT, null,
Type.BOOLEAN, Type.CHAR, Type.FLOAT, Type.DOUBLE,
Type.BYTE, Type.SHORT, Type.INT, Type.LONG
};
public Type getType() throws HprofFormatException {
int id = getU1();
Type type = id < TYPES.length ? TYPES[id] : null;
if (type == null) {
throw new HprofFormatException("Invalid basic type id: " + id);
}
return type;
}
public Type getPrimitiveType() throws HprofFormatException {
Type type = getType();
if (type == Type.OBJECT) {
throw new HprofFormatException("Expected primitive type, but found type 'Object'");
}
return type;
}
/**
* Get a value from the hprof file, using the given instances map to
* convert instance ids to their corresponding AhatInstance objects.
*/
public Value getValue(Type type, Instances instances) {
switch (type) {
case OBJECT: return Value.pack(instances.get(getId()));
case BOOLEAN: return Value.pack(getBool());
case CHAR: return Value.pack(getChar());
case FLOAT: return Value.pack(getFloat());
case DOUBLE: return Value.pack(getDouble());
case BYTE: return Value.pack(getByte());
case SHORT: return Value.pack(getShort());
case INT: return Value.pack(getInt());
case LONG: return Value.pack(getLong());
default: throw new AssertionError("unsupported enum member");
}
}
/**
* Get a value from the hprof file. AhatInstance values are returned as
* DefferredInstanceValues rather than their corresponding AhatInstance
* objects.
*/
public Value getDeferredValue(Type type) {
switch (type) {
case OBJECT: return new DeferredInstanceValue(getId());
case BOOLEAN: return Value.pack(getBool());
case CHAR: return Value.pack(getChar());
case FLOAT: return Value.pack(getFloat());
case DOUBLE: return Value.pack(getDouble());
case BYTE: return Value.pack(getByte());
case SHORT: return Value.pack(getShort());
case INT: return Value.pack(getInt());
case LONG: return Value.pack(getLong());
default: throw new AssertionError("unsupported enum member");
}
}
}
// ART outputs class names such as:
// "java.lang.Class", "java.lang.Class[]", "byte", "byte[]"
// RI outputs class names such as:
// "java/lang/Class", '[Ljava/lang/Class;", N/A, "[B"
//
// This function converts all class names to match the ART format, which is
// assumed elsewhere in ahat.
private static String normalizeClassName(String name) throws HprofFormatException {
int numDimensions = 0;
while (name.startsWith("[")) {
numDimensions++;
name = name.substring(1);
}
if (numDimensions > 0) {
// If there was an array type signature to start, then interpret the
// class name as a type signature.
switch (name.charAt(0)) {
case 'Z': name = "boolean"; break;
case 'B': name = "byte"; break;
case 'C': name = "char"; break;
case 'S': name = "short"; break;
case 'I': name = "int"; break;
case 'J': name = "long"; break;
case 'F': name = "float"; break;
case 'D': name = "double"; break;
case 'L': name = name.substring(1, name.length() - 1); break;
default: throw new HprofFormatException("Invalid type signature in class name: " + name);
}
}
name = name.replace('/', '.');
for (int i = 0; i < numDimensions; ++i) {
name += "[]";
}
return name;
}
}