All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.mat.hprof.Pass1Parser Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2008 SAP AG.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    SAP AG - initial API and implementation
 *******************************************************************************/
package org.eclipse.mat.hprof;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.HashMapLongObject;
import org.eclipse.mat.parser.io.PositionInputStream;
import org.eclipse.mat.parser.model.ClassImpl;
import org.eclipse.mat.snapshot.model.Field;
import org.eclipse.mat.snapshot.model.FieldDescriptor;
import org.eclipse.mat.snapshot.model.GCRootInfo;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IPrimitiveArray;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.SimpleMonitor;
import org.eclipse.mat.util.IProgressListener.Severity;

public class Pass1Parser extends AbstractParser {
    private static final Pattern PATTERN_OBJ_ARRAY = Pattern.compile("^(\\[+)L(.*);$"); //$NON-NLS-1$
    private static final Pattern PATTERN_PRIMITIVE_ARRAY = Pattern.compile("^(\\[+)(.)$"); //$NON-NLS-1$

    private HashMapLongObject class2name = new HashMapLongObject();
    private HashMapLongObject thread2id = new HashMapLongObject();
    private HashMapLongObject id2frame = new HashMapLongObject();
    private HashMapLongObject serNum2stackTrace = new HashMapLongObject();
    private HashMapLongObject classSerNum2id = new HashMapLongObject();
    private HashMapLongObject> thread2locals = new HashMapLongObject>();
    private IHprofParserHandler handler;
    private SimpleMonitor.Listener monitor;

    public Pass1Parser(IHprofParserHandler handler, SimpleMonitor.Listener monitor) {
        this.handler = handler;
        this.monitor = monitor;
    }

    public void read(File file) throws SnapshotException, IOException {
        in = new PositionInputStream(new BufferedInputStream(new FileInputStream(file)));

        final int dumpNrToRead = determineDumpNumber();
        int currentDumpNr = 0;

        try {
            // header & version
            version = readVersion(in);
            handler.addProperty(IHprofParserHandler.VERSION, version.toString());

            // identifierSize (32 or 64 bit)
            idSize = in.readInt();
            if (idSize != 4 && idSize != 8)
                throw new SnapshotException(Messages.Pass1Parser_Error_SupportedDumps);
            handler.addProperty(IHprofParserHandler.IDENTIFIER_SIZE, String.valueOf(idSize));

            // creation date
            long date = in.readLong();
            handler.addProperty(IHprofParserHandler.CREATION_DATE, String.valueOf(date));

            long fileSize = file.length();
            long curPos = in.position();

            while (curPos < fileSize) {
                if (monitor.isProbablyCanceled())
                    throw new IProgressListener.OperationCanceledException();
                monitor.totalWorkDone(curPos / 1000);

                int record = in.readUnsignedByte();

                in.skipBytes(4); // time stamp

                long length = readUnsignedInt();
                if (length < 0)
                    throw new SnapshotException(MessageUtil.format(Messages.Pass1Parser_Error_IllegalRecordLength, in
                            .position()));

                if (curPos + length - 9 > fileSize)
                    monitor.sendUserMessage(Severity.WARNING, MessageUtil.format(
                            Messages.Pass1Parser_Error_invalidHPROFFile, length, fileSize - curPos - 9), null);

                switch (record) {
                    case Constants.Record.STRING_IN_UTF8:
                        readString(length);
                        break;
                    case Constants.Record.LOAD_CLASS:
                        readLoadClass();
                        break;
                    case Constants.Record.STACK_FRAME:
                        readStackFrame(length);
                        break;
                    case Constants.Record.STACK_TRACE:
                        readStackTrace(length);
                        break;
                    case Constants.Record.HEAP_DUMP:
                    case Constants.Record.HEAP_DUMP_SEGMENT:
                        if (dumpNrToRead == currentDumpNr)
                            readDumpSegments(length);
                        else
                            in.skipBytes(length);

                        if (record == Constants.Record.HEAP_DUMP)
                            currentDumpNr++;

                        break;
                    case Constants.Record.HEAP_DUMP_END:
                        currentDumpNr++;
                    default:
                        in.skipBytes(length);
                        break;
                }

                curPos = in.position();
            }
        } finally {
            try {
                in.close();
            } catch (IOException ignore) {
            }
        }

        if (currentDumpNr <= dumpNrToRead)
            throw new SnapshotException(MessageUtil.format(
                    Messages.Pass1Parser_Error_NoHeapDumpIndexFound,
                    currentDumpNr, file.getName(), dumpNrToRead));

        if (currentDumpNr > 1)
            monitor.sendUserMessage(Severity.INFO, MessageUtil.format(
                    Messages.Pass1Parser_Info_UsingDumpIndex, currentDumpNr,
                    file.getName(), dumpNrToRead), null);

        if (serNum2stackTrace.size() > 0)
            dumpThreads();

    }

    private void readString(long length) throws IOException {
        long id = readID();
        byte[] chars = new byte[(int) (length - idSize)];
        in.readFully(chars);
        handler.getConstantPool().put(id, new String(chars));
    }

    private void readLoadClass() throws IOException {
        long classSerNum = readUnsignedInt(); // used in stacks frames
        long classID = readID();
        in.skipBytes(4);
        long nameID = readID();

        String className = getStringConstant(nameID).replace('/', '.');
        class2name.put(classID, className);
        classSerNum2id.put(classSerNum, classID);
    }

    private void readStackFrame(long length) throws IOException {
        long frameId = readID();
        long methodName = readID();
        long methodSig = readID();
        long srcFile = readID();
        long classSerNum = readUnsignedInt();
        int lineNr = in.readInt(); // can be negative
        StackFrame frame = new StackFrame(frameId, lineNr, getStringConstant(methodName), getStringConstant(methodSig),
                getStringConstant(srcFile), classSerNum);
        id2frame.put(frameId, frame);
    }

    private void readStackTrace(long length) throws IOException {
        long stackTraceNr = readUnsignedInt();
        long threadNr = readUnsignedInt();
        long frameCount = readUnsignedInt();
        long[] frameIds = new long[(int) frameCount];
        for (int i = 0; i < frameCount; i++) {
            frameIds[i] = readID();
        }
        StackTrace stackTrace = new StackTrace(stackTraceNr, threadNr, frameIds);
        serNum2stackTrace.put(stackTraceNr, stackTrace);
    }

    private void readDumpSegments(long length) throws IOException, SnapshotException {
        long segmentStartPos = in.position();
        long segmentsEndPos = segmentStartPos + length;

        while (segmentStartPos < segmentsEndPos) {
            long workDone = segmentStartPos / 1000;
            if (this.monitor.getWorkDone() < workDone) {
                if (this.monitor.isProbablyCanceled())
                    throw new IProgressListener.OperationCanceledException();
                this.monitor.totalWorkDone(workDone);
            }

            int segmentType = in.readUnsignedByte();
            switch (segmentType) {
                case Constants.DumpSegment.ROOT_UNKNOWN:
                    readGC(GCRootInfo.Type.UNKNOWN, 0);
                    break;
                case Constants.DumpSegment.ROOT_THREAD_OBJECT:
                    readGCThreadObject(GCRootInfo.Type.THREAD_OBJ);
                    break;
                case Constants.DumpSegment.ROOT_JNI_GLOBAL:
                    readGC(GCRootInfo.Type.NATIVE_STACK, idSize);
                    break;
                case Constants.DumpSegment.ROOT_JNI_LOCAL:
                    readGCWithThreadContext(GCRootInfo.Type.NATIVE_LOCAL, true);
                    break;
                case Constants.DumpSegment.ROOT_JAVA_FRAME:
                    readGCWithThreadContext(GCRootInfo.Type.JAVA_LOCAL, true);
                    break;
                case Constants.DumpSegment.ROOT_NATIVE_STACK:
                    readGCWithThreadContext(GCRootInfo.Type.NATIVE_STACK, false);
                    break;
                case Constants.DumpSegment.ROOT_STICKY_CLASS:
                    readGC(GCRootInfo.Type.SYSTEM_CLASS, 0);
                    break;
                case Constants.DumpSegment.ROOT_THREAD_BLOCK:
                    readGC(GCRootInfo.Type.THREAD_BLOCK, 4);
                    break;
                case Constants.DumpSegment.ROOT_MONITOR_USED:
                    readGC(GCRootInfo.Type.BUSY_MONITOR, 0);
                    break;
                case Constants.DumpSegment.CLASS_DUMP:
                    readClassDump(segmentStartPos);
                    break;
                case Constants.DumpSegment.INSTANCE_DUMP:
                    readInstanceDump(segmentStartPos);
                    break;
                case Constants.DumpSegment.OBJECT_ARRAY_DUMP:
                    readObjectArrayDump(segmentStartPos);
                    break;
                case Constants.DumpSegment.PRIMITIVE_ARRAY_DUMP:
                    readPrimitiveArrayDump(segmentStartPos);
                    break;

                /* these were added for Android in 1.0.3 */
                case Constants.DumpSegment.ANDROID_HEAP_DUMP_INFO:
                    // no 1.0.2 equivalent for this
                    in.skipBytes(idSize + 4);
                    break;
                case Constants.DumpSegment.ANDROID_ROOT_INTERNED_STRING:
                    readGC(GCRootInfo.Type.UNKNOWN, 0);
                    break;
                case Constants.DumpSegment.ANDROID_ROOT_FINALIZING:
                    readGC(GCRootInfo.Type.UNKNOWN, 0);
                    break;
                case Constants.DumpSegment.ANDROID_ROOT_DEBUGGER:
                    readGC(GCRootInfo.Type.UNKNOWN, 0);
                    break;
                case Constants.DumpSegment.ANDROID_ROOT_REFERENCE_CLEANUP:
                    readGC(GCRootInfo.Type.UNKNOWN, 0);
                    break;
                case Constants.DumpSegment.ANDROID_ROOT_VM_INTERNAL:
                    readGC(GCRootInfo.Type.UNKNOWN, 0);
                    break;
                case Constants.DumpSegment.ANDROID_ROOT_JNI_MONITOR:
                    /* keep the ident, drop the next 8 bytes */
                    readGC(GCRootInfo.Type.UNKNOWN, 8);
                    break;
                case Constants.DumpSegment.ANDROID_UNREACHABLE:
                    readGC(GCRootInfo.Type.UNKNOWN, 0);
                    break;
                case Constants.DumpSegment.ANDROID_PRIMITIVE_ARRAY_NODATA_DUMP:
                    readPrimitiveArrayNoDataDump(segmentStartPos);
                    break;

                default:
                    throw new SnapshotException(MessageUtil.format(Messages.Pass1Parser_Error_InvalidHeapDumpFile,
                            segmentType, segmentStartPos));
            }

            segmentStartPos = in.position();
        }
    }

    private void readGCThreadObject(int gcType) throws IOException {
        long id = readID();
        int threadSerialNo = in.readInt();
        thread2id.put(threadSerialNo, id);
        handler.addGCRoot(id, 0, gcType);

        in.skipBytes(4);
    }

    private void readGC(int gcType, int skip) throws IOException {
        long id = readID();
        handler.addGCRoot(id, 0, gcType);

        if (skip > 0)
            in.skipBytes(skip);
    }

    private void readGCWithThreadContext(int gcType, boolean hasLineInfo) throws IOException {
        long id = readID();
        int threadSerialNo = in.readInt();
        Long tid = thread2id.get(threadSerialNo);
        if (tid != null) {
            handler.addGCRoot(id, tid, gcType);
        } else {
            handler.addGCRoot(id, 0, gcType);
        }

        if (hasLineInfo) {
            int lineNumber = in.readInt();
            List locals = thread2locals.get(threadSerialNo);
            if (locals == null) {
                locals = new ArrayList();
                thread2locals.put(threadSerialNo, locals);
            }
            locals.add(new JavaLocal(id, lineNumber, gcType));
        }
    }

    private void readClassDump(long segmentStartPos) throws IOException {
        long address = readID();
        in.skipBytes(4); // stack trace serial number
        long superClassObjectId = readID();
        long classLoaderObjectId = readID();

        // skip signers, protection domain, reserved ids (2), instance size
        in.skipBytes(this.idSize * 4 + 4);

        // constant pool: u2 ( u2 u1 value )*
        int constantPoolSize = in.readUnsignedShort();
        for (int ii = 0; ii < constantPoolSize; ii++) {
            in.skipBytes(2); // index
            skipValue(); // value
        }

        // static fields: u2 num ( name ID, u1 type, value)
        int numStaticFields = in.readUnsignedShort();
        Field[] statics = new Field[numStaticFields];

        for (int ii = 0; ii < numStaticFields; ii++) {
            long nameId = readID();
            String name = getStringConstant(nameId);

            byte type = in.readByte();

            Object value = readValue(null, type);
            statics[ii] = new Field(name, type, value);
        }

        // instance fields: u2 num ( name ID, u1 type )
        int numInstanceFields = in.readUnsignedShort();
        FieldDescriptor[] fields = new FieldDescriptor[numInstanceFields];

        for (int ii = 0; ii < numInstanceFields; ii++) {
            long nameId = readID();
            String name = getStringConstant(nameId);

            byte type = in.readByte();
            fields[ii] = new FieldDescriptor(name, type);
        }

        // get name
        String className = class2name.get(address);
        if (className == null)
            className = "unknown-name@0x" + Long.toHexString(address); //$NON-NLS-1$

        if (className.charAt(0) == '[') // quick check if array at hand
        {
            // fix object class names
            Matcher matcher = PATTERN_OBJ_ARRAY.matcher(className);
            if (matcher.matches()) {
                int l = matcher.group(1).length();
                className = matcher.group(2);
                for (int ii = 0; ii < l; ii++)
                    className += "[]"; //$NON-NLS-1$
            }

            // primitive arrays
            matcher = PATTERN_PRIMITIVE_ARRAY.matcher(className);
            if (matcher.matches()) {
                int count = matcher.group(1).length() - 1;
                className = "unknown[]"; //$NON-NLS-1$

                char signature = matcher.group(2).charAt(0);
                for (int ii = 0; ii < IPrimitiveArray.SIGNATURES.length; ii++) {
                    if (IPrimitiveArray.SIGNATURES[ii] == (byte) signature) {
                        className = IPrimitiveArray.TYPE[ii];
                        break;
                    }
                }

                for (int ii = 0; ii < count; ii++)
                    className += "[]"; //$NON-NLS-1$
            }
        }

        ClassImpl clazz = new ClassImpl(address, className, superClassObjectId, classLoaderObjectId, statics, fields);
        handler.addClass(clazz, segmentStartPos);
    }

    private void readInstanceDump(long segmentStartPos) throws IOException {
        long address = readID();
        handler.reportInstance(address, segmentStartPos);
        in.skipBytes(idSize + 4);
        int payload = in.readInt();
        in.skipBytes(payload);
    }

    private void readObjectArrayDump(long segmentStartPos) throws IOException {
        long address = readID();
        handler.reportInstance(address, segmentStartPos);

        in.skipBytes(4);
        int size = in.readInt();
        long arrayClassObjectID = readID();

        // check if class needs to be created
        IClass arrayType = handler.lookupClass(arrayClassObjectID);
        if (arrayType == null)
            handler.reportRequiredObjectArray(arrayClassObjectID);

        in.skipBytes(size * idSize);
    }

    private void readPrimitiveArrayDump(long segmentStartPos) throws SnapshotException, IOException {
        long address = readID();
        handler.reportInstance(address, segmentStartPos);

        in.skipBytes(4);
        int size = in.readInt();
        byte elementType = in.readByte();

        if ((elementType < IPrimitiveArray.Type.BOOLEAN) || (elementType > IPrimitiveArray.Type.LONG))
            throw new SnapshotException(Messages.Pass1Parser_Error_IllegalType);

        // check if class needs to be created
        String name = IPrimitiveArray.TYPE[elementType];
        IClass clazz = handler.lookupClassByName(name, true);
        if (clazz == null)
            handler.reportRequiredPrimitiveArray(elementType);

        int elementSize = IPrimitiveArray.ELEMENT_SIZE[elementType];
        in.skipBytes(elementSize * size);
    }

    /* Added for Android in 1.0.3 */
    private void readPrimitiveArrayNoDataDump(long segmentStartPos)
            throws SnapshotException, IOException {

        long address = readID();
        handler.reportInstance(address, segmentStartPos);

        in.skipBytes(4);
        int size = in.readInt();
        byte elementType = in.readByte();

        if ((elementType < IPrimitiveArray.Type.BOOLEAN)
                || (elementType > IPrimitiveArray.Type.LONG)) {
            throw new SnapshotException(Messages.Pass1Parser_Error_IllegalType);
        }

        // check if class needs to be created
        String name = IPrimitiveArray.TYPE[elementType];
        IClass clazz = handler.lookupClassByName(name, true);
        if (clazz == null) {
            handler.reportRequiredPrimitiveArray(elementType);
        }
    }

    private String getStringConstant(long address) {
        if (address == 0L)
            return ""; //$NON-NLS-1$

        String result = handler.getConstantPool().get(address);
        return result == null ? Messages.Pass1Parser_Error_UnresolvedName + Long.toHexString(address) : result;
    }

    private void dumpThreads() {
        // noticed that one stack trace with empty stack is always reported,
        // even if the dump has no call stacks info
        if (serNum2stackTrace == null || serNum2stackTrace.size() <= 1)
            return;

        PrintWriter out = null;
        String outputName = handler.getSnapshotInfo().getPrefix() + "threads"; //$NON-NLS-1$
        try {
            out = new PrintWriter(new FileWriter(outputName));

            Iterator it = serNum2stackTrace.values();
            while (it.hasNext()) {
                StackTrace stack = it.next();
                Long tid = thread2id.get(stack.threadSerialNr);
                if (tid == null)
                    continue;
                String threadId = tid == null ? "" : "0x" + Long.toHexString(tid); //$NON-NLS-1$ //$NON-NLS-2$
                out.println("Thread " + threadId); //$NON-NLS-1$
                out.println(stack);
                out.println("  locals:"); //$NON-NLS-1$
                List locals = thread2locals.get(stack.threadSerialNr);
                if (locals != null) {
                    for (JavaLocal javaLocal : locals) {
                        out
                                .println("    objecId=0x" + Long.toHexString(javaLocal.objectId) + ", line=" + javaLocal.lineNumber); //$NON-NLS-1$ //$NON-NLS-2$

                    }
                }
                out.println();
            }
            out.flush();
            this.monitor.sendUserMessage(Severity.INFO, MessageUtil.format(Messages.Pass1Parser_Info_WroteThreadsTo,
                    outputName), null);
        } catch (IOException e) {
            this.monitor.sendUserMessage(Severity.WARNING, MessageUtil
                    .format(Messages.Pass1Parser_Error_WritingThreadsInformation), e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (Exception ignore) {
                    // $JL-EXC$
                }
            }
        }

    }

    private class StackFrame {
        final long frameId;
        final String method;
        final String methodSignature;
        final String sourceFile;
        final long classSerNum;

        /*
         * > 0 line number
         * 0 no line info
         * -1 unknown location
         * -2 compiled method
         * -3 native method
         */
        final int lineNr;

        public StackFrame(long frameId, int lineNr, String method,
                          String methodSignature, String sourceFile, long classSerNum) {
            this.frameId = frameId;
            this.lineNr = lineNr;
            this.method = method;
            this.methodSignature = methodSignature;
            this.sourceFile = sourceFile;
            this.classSerNum = classSerNum;
        }

        @Override
        public String toString() {
            String className = null;
            Long classId = classSerNum2id.get(classSerNum);
            if (classId == null) {
                className = ""; //$NON-NLS-1$
            } else {
                className = class2name.get(classId);
            }

            String sourceLocation = ""; //$NON-NLS-1$
            if (lineNr > 0) {
                sourceLocation = "(" + sourceFile + ":" + String.valueOf(lineNr) + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            } else if (lineNr == 0 || lineNr == -1) {
                sourceLocation = "(Unknown Source)"; //$NON-NLS-1$
            } else if (lineNr == -2) {
                sourceLocation = "(Compiled method)"; //$NON-NLS-1$
            } else if (lineNr == -3) {
                sourceLocation = "(Native Method)"; //$NON-NLS-1$
            }

            return "  at " + className + "." + method + methodSignature + " " + sourceLocation; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        }

    }

    private class StackTrace {
        final long threadSerialNr;
        final long[] frameIds;

        public StackTrace(long serialNr, long threadSerialNr, long[] frameIds) {
            this.threadSerialNr = threadSerialNr;
            this.frameIds = frameIds;
        }

        @Override
        public String toString() {
            StringBuilder b = new StringBuilder();
            for (long frameId : frameIds) {
                StackFrame frame = id2frame.get(frameId);
                if (frame != null) {
                    b.append(frame).append("\r\n"); //$NON-NLS-1$
                }

            }
            return b.toString();
        }

    }

    private static class JavaLocal {

        final long objectId;
        final int lineNumber;
        final int type;

        public JavaLocal(long objectId, int lineNumber, int type) {
            this.objectId = objectId;
            this.lineNumber = lineNumber;
            this.type = type;
        }

        public int getType() {
            return type;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy