blob: 7d6aa0bea5cb48f3d51f61700e92596f86e53d04 [file] [log] [blame]
/*
* Copyright (C) 2014 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.ddmlib;
import com.android.annotations.NonNull;
import java.nio.ByteBuffer;
public class AllocationsParser {
/**
* Converts a VM class descriptor string ("Landroid/os/Debug;") to
* a dot-notation class name ("android.os.Debug").
*/
private static String descriptorToDot(String str) {
// count the number of arrays.
int array = 0;
while (str.startsWith("[")) {
str = str.substring(1);
array++;
}
int len = str.length();
/* strip off leading 'L' and trailing ';' if appropriate */
if (len >= 2 && str.charAt(0) == 'L' && str.charAt(len - 1) == ';') {
str = str.substring(1, len-1);
str = str.replace('/', '.');
} else {
// convert the basic types
if ("C".equals(str)) {
str = "char";
} else if ("B".equals(str)) {
str = "byte";
} else if ("Z".equals(str)) {
str = "boolean";
} else if ("S".equals(str)) {
str = "short";
} else if ("I".equals(str)) {
str = "int";
} else if ("J".equals(str)) {
str = "long";
} else if ("F".equals(str)) {
str = "float";
} else if ("D".equals(str)) {
str = "double";
}
}
// now add the array part
for (int a = 0 ; a < array; a++) {
str += "[]";
}
return str;
}
/**
* Reads a string table out of "data".
*
* This is just a serial collection of strings, each of which is a
* four-byte length followed by UTF-16 data.
*/
private static void readStringTable(ByteBuffer data, String[] strings) {
int count = strings.length;
int i;
for (i = 0; i < count; i++) {
int nameLen = data.getInt();
String descriptor = ByteBufferUtil.getString(data, nameLen);
strings[i] = descriptorToDot(descriptor);
}
}
/*
* Message format:
* Message header (all values big-endian):
* (1b) message header len (to allow future expansion); includes itself
* (1b) entry header len
* (1b) stack frame len
* (2b) number of entries
* (4b) offset to string table from start of message
* (2b) number of class name strings
* (2b) number of method name strings
* (2b) number of source file name strings
* For each entry:
* (4b) total allocation size
* (2b) threadId
* (2b) allocated object's class name index
* (1b) stack depth
* For each stack frame:
* (2b) method's class name
* (2b) method name
* (2b) method source file
* (2b) line number, clipped to 32767; -2 if native; -1 if no source
* (xb) class name strings
* (xb) method name strings
* (xb) source file strings
*
* As with other DDM traffic, strings are sent as a 4-byte length
* followed by UTF-16 data.
*/
@NonNull
public static AllocationInfo[] parse(@NonNull ByteBuffer data) {
int messageHdrLen, entryHdrLen, stackFrameLen;
int numEntries, offsetToStrings;
int numClassNames, numMethodNames, numFileNames;
/*
* Read the header.
*/
messageHdrLen = (data.get() & 0xff);
entryHdrLen = (data.get() & 0xff);
stackFrameLen = (data.get() & 0xff);
numEntries = (data.getShort() & 0xffff);
offsetToStrings = data.getInt();
numClassNames = (data.getShort() & 0xffff);
numMethodNames = (data.getShort() & 0xffff);
numFileNames = (data.getShort() & 0xffff);
/*
* Skip forward to the strings and read them.
*/
data.position(offsetToStrings);
String[] classNames = new String[numClassNames];
String[] methodNames = new String[numMethodNames];
String[] fileNames = new String[numFileNames];
readStringTable(data, classNames);
readStringTable(data, methodNames);
readStringTable(data, fileNames);
/*
* Skip back to a point just past the header and start reading
* entries.
*/
data.position(messageHdrLen);
AllocationInfo[] allocations = new AllocationInfo[numEntries];
for (int i = 0; i < numEntries; i++) {
int totalSize;
int threadId, classNameIndex, stackDepth;
totalSize = data.getInt();
threadId = (data.getShort() & 0xffff);
classNameIndex = (data.getShort() & 0xffff);
stackDepth = (data.get() & 0xff);
/* we've consumed 9 bytes; gobble up any extra */
for (int skip = 9; skip < entryHdrLen; skip++)
data.get();
StackTraceElement[] steArray = new StackTraceElement[stackDepth];
/*
* Pull out the stack trace.
*/
for (int sti = 0; sti < stackDepth; sti++) {
int methodClassNameIndex, methodNameIndex;
int methodSourceFileIndex;
short lineNumber;
String methodClassName, methodName, methodSourceFile;
methodClassNameIndex = (data.getShort() & 0xffff);
methodNameIndex = (data.getShort() & 0xffff);
methodSourceFileIndex = (data.getShort() & 0xffff);
lineNumber = data.getShort();
methodClassName = classNames[methodClassNameIndex];
methodName = methodNames[methodNameIndex];
methodSourceFile = fileNames[methodSourceFileIndex];
steArray[sti] = new StackTraceElement(methodClassName,
methodName, methodSourceFile, lineNumber);
/* we've consumed 8 bytes; gobble up any extra */
for (int skip = 8; skip < stackFrameLen; skip++)
data.get();
}
allocations[i] = new AllocationInfo(numEntries - i, classNames[classNameIndex], totalSize, (short) threadId, steArray);
}
return allocations;
}
}