| /* |
| * 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; |
| } |
| } |