changeset 46:bac8d9297f30

Added parser for debug information in DEX files. * dex/Code.java: Call the parser mentioned above. * (getRegistersSize): New method for internal use. * (findLabels): Added hack, maybe find a better solution. * dex/DebugInfo.java: New parser for decoding debug_info_item.
author Michael Starzinger <michi@complang.tuwien.ac.at>
date Sat, 19 Mar 2011 16:44:40 +0100
parents 1b283cb77127
children 3f0bf7420296
files src/main/java/org/icedrobot/daneel/dex/Code.java src/main/java/org/icedrobot/daneel/dex/DebugInfo.java
diffstat 2 files changed, 362 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/org/icedrobot/daneel/dex/Code.java	Sat Mar 19 15:37:50 2011 +0100
+++ b/src/main/java/org/icedrobot/daneel/dex/Code.java	Sat Mar 19 16:44:40 2011 +0100
@@ -43,6 +43,8 @@
 import java.util.List;
 import java.util.Map;
 
+import org.icedrobot.daneel.dex.DebugInfo.LineNumberLabel;
+import org.icedrobot.daneel.dex.DebugInfo.LocalVariable;
 import org.icedrobot.daneel.util.BufferUtil;
 import org.icedrobot.daneel.util.TypeUtil;
 
@@ -78,6 +80,8 @@
 
     private final ShortBuffer insns;
 
+    private final DebugInfo debugInfo;
+
     private final List<TryCatchInfo> tryCatchInfos;
 
     private Code(ByteBuffer buffer, DexFile dex) {
@@ -86,12 +90,20 @@
         insSize = buffer.getShort();
         outsSize = buffer.getShort();
         triesSize = buffer.getShort();
-        /* debugInfoOff = */buffer.getInt();
+        int debugInfoOff = buffer.getInt();
         insnsSize = buffer.getInt();
 
         // Keep a separate buffer for the instructions array.
         insns = (ShortBuffer) buffer.asShortBuffer().limit(insnsSize);
 
+        // Parse any associated debug info. Remember to do this before finding
+        // labels of branch targets, because it generates line number labels.
+        if (debugInfoOff != 0) {
+            ByteBuffer buf = dex.getDataBuffer(debugInfoOff);
+            debugInfo = DebugInfo.parse(buf, dex, this);
+        } else
+            debugInfo = null;
+
         // Find labels inside the instruction stream and also parse possible
         // in-code structures at such positions.
         findLabels(buffer);
@@ -139,6 +151,16 @@
     }
 
     /**
+     * Returns the number of registers used by the code as specified in the DEX
+     * file.
+     * 
+     * @return The number of registers.
+     */
+    public int getRegistersSize() {
+        return registersSize;
+    }
+
+    /**
      * Allows the given visitor to visit this code object.
      * 
      * @param visitor The given DEX method visitor object.
@@ -146,6 +168,13 @@
     public void accept(DexMethodVisitor visitor) {
         visitor.visitCode(registersSize, insSize, outsSize);
         acceptInsns(visitor);
+        if (debugInfo != null)
+            for (LocalVariable local : debugInfo.getLocalVariables())
+                // XXX Define method in DexMethodVisitor for that.
+                System.out.printf("LOCAL: r%02x : %s %s (%s-%s)\n",
+                        local.regNum, dex.getTypeDescriptor(local.typeIdx),
+                        dex.getString(local.nameIdx), local.startLabel,
+                        local.endLabel);
         for (TryCatchInfo tryCatch : tryCatchInfos)
             visitor.visitTryCatch(tryCatch.startLabel, tryCatch.endLabel,
                     tryCatch.handlerLabel, tryCatch.type);
@@ -190,6 +219,10 @@
             if ((label = labelMap.get(pos)) != null)
                 v.visitLabel(label);
 
+            // Visit a line number entry at the current position.
+            if (label instanceof LineNumberLabel)
+                /* XXX Define method in DexMethodVisitor for that. */;
+
             // Switch over all possible opcodes.
             switch (op) {
             case NOP:
@@ -731,7 +764,7 @@
      * @param label The label to be associated with that position.
      * @throws DexParseException In case there already is a label.
      */
-    private void putLabel(int pos, Label label) {
+    void putLabel(int pos, Label label) {
         if (pos < 0 || pos >= insnsSize)
             throw new DexParseException("Label position out of range: " + pos);
         if (labelMap.containsKey(pos))
@@ -823,6 +856,10 @@
                 // Syntax: op vAA, +BBBBBBBB
                 off = (s2 & 0xffff) | (s3 << 16);
                 buffer.position(buffer.position() + (pos + off) * 2);
+                // XXX This is a hack --->
+                if (labelMap.get(pos + off) instanceof LineNumberLabel)
+                    labelMap.remove(pos + off);
+                // XXX <--- This is a hack.
                 putLabel(pos + off, new PackedSwitchLabel(buffer, pos));
                 buffer.reset();
                 break;
@@ -832,6 +869,10 @@
                 // Syntax: op vAA, +BBBBBBBB
                 off = (s2 & 0xffff) | (s3 << 16);
                 buffer.position(buffer.position() + (pos + off) * 2);
+                // XXX This is a hack --->
+                if (labelMap.get(pos + off) instanceof LineNumberLabel)
+                    labelMap.remove(pos + off);
+                // XXX <--- This is a hack.
                 putLabel(pos + off, new SparseSwitchLabel(buffer, pos));
                 buffer.reset();
                 break;
@@ -967,6 +1008,7 @@
             this.pos = pos;
         }
 
+        @Override
         public String toString() {
             return String.format("L%03d", pos);
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/daneel/dex/DebugInfo.java	Sat Mar 19 16:44:40 2011 +0100
@@ -0,0 +1,318 @@
+/*
+ * Daneel - Dalvik to Java bytecode compiler
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * This file is subject to the "Classpath" exception:
+ *
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library.  Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give you
+ * permission to link this library with independent modules to produce an
+ * executable, regardless of the license terms of these independent
+ * modules, and to copy and distribute the resulting executable under terms
+ * of your choice, provided that you also meet, for each linked independent
+ * module, the terms and conditions of the license of that module.  An
+ * independent module is a module which is not derived from or based on
+ * this library.  If you modify this library, you may extend this exception
+ * to your version of the library, but you are not obligated to do so.  If
+ * you do not wish to do so, delete this exception statement from your
+ * version.
+ */
+
+package org.icedrobot.daneel.dex;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.icedrobot.daneel.util.BufferUtil;
+
+/**
+ * A parser class capable of parsing {@code debug_info_item} structures as part
+ * of DEX files. Keep package-private to hide internal API.
+ */
+class DebugInfo {
+
+    public static final int DBG_END_SEQUENCE         = 0x00;
+    public static final int DBG_ADVANCE_PC           = 0x01;
+    public static final int DBG_ADVANCE_LINE         = 0x02;
+    public static final int DBG_START_LOCAL          = 0x03;
+    public static final int DBG_START_LOCAL_EXTENDED = 0x04;
+    public static final int DBG_END_LOCAL            = 0x05;
+    public static final int DBG_RESTART_LOCAL        = 0x06;
+    public static final int DBG_SET_PROLOGUE_END     = 0x07;
+    public static final int DBG_SET_EPILOGUE_BEGIN   = 0x08;
+    public static final int DBG_SET_FILE             = 0x09;
+    public static final int DBG_FIRST_SPECIAL        = 0x0a;
+
+    /**
+     * Parses a {@code debug_info_item} structure in a DEX file at the buffer's
+     * current position.
+     * 
+     * @param buffer The byte buffer to read from.
+     * @param dex The DEX file currently being parsed.
+     * @param code The parsed {@code code_item} this debug info belongs to.
+     * @return An object representing the parsed data.
+     */
+    public static DebugInfo parse(ByteBuffer buffer, DexFile dex, Code code) {
+        return new DebugInfo(buffer, dex, code);
+    }
+
+    private final Code code;
+
+    private final int lineStart;
+
+    private final int parametersSize;
+
+    private final String[] parameterNames;
+
+    private final List<LocalVariable> localVariables;
+
+    private DebugInfo(ByteBuffer buffer, DexFile dex, Code code) {
+        this.code = code;
+        lineStart = BufferUtil.getULEB128(buffer);
+        parametersSize = BufferUtil.getULEB128(buffer);
+
+        // Parse parameter_names array and resolve string indices.
+        parameterNames = new String[parametersSize];
+        for (int i = 0; i < parametersSize; i++) {
+            int nameIdx = BufferUtil.getULEB128(buffer) - 1;
+            if (nameIdx != DexFile.NO_INDEX)
+                parameterNames[i] = dex.getString(nameIdx);
+        }
+
+        // Interpret all subsequent state machine bytecodes to compute local
+        // variable and line number information.
+        localVariables = new LinkedList<LocalVariable>();
+        interpret(buffer);
+    }
+
+    /**
+     * Returns the list of all parameter names (excluding an instance method's
+     * {@code this}). There should be one per method parameter, but the value
+     * can be {@code null} in case no name is available for the associated
+     * parameter.
+     * 
+     * @return The list as specified above as array.
+     */
+    public String[] getParameterNames() {
+        return parameterNames;
+    }
+
+    /**
+     * Returns the list of all local variable information as it was emitted by
+     * the byte-coded state machine.
+     * 
+     * @return The list as specified above.
+     */
+    public List<LocalVariable> getLocalVariables() {
+        return localVariables;
+    }
+
+    /**
+     * Interprets the byte-coded state machine that is part of a {@code
+     * debug_info_item} and emits all the local variable and line number
+     * information.
+     * 
+     * @param buffer The buffer positioned at the beginning of the bytecode.
+     */
+    private void interpret(ByteBuffer buffer) {
+
+        // The five state machine registers.
+        int addr = 0;
+        int line = lineStart;
+        // We ignore "sourceFile" for now.
+        // We ignore "prologueEnd" for now.
+        // We ignore "epilogueBegin" for now.
+
+        // Keep track of local variables in registers.
+        int maxRegs = code.getRegistersSize();
+        LocalVariable[] regs = new LocalVariable[maxRegs];
+
+        // Iterate over all state machine bytecodes.
+        while (buffer.hasRemaining()) {
+            int opcode = buffer.get() & 0xff;
+            int regNum, nameIdx, typeIdx, sigIdx;
+            LocalVariable local;
+
+            // Switch over all possible bytecodes.
+            switch (opcode) {
+            case DBG_END_SEQUENCE:
+                return;
+
+            case DBG_ADVANCE_PC:
+                addr += BufferUtil.getULEB128(buffer);
+                break;
+
+            case DBG_ADVANCE_LINE:
+                line += BufferUtil.getSLEB128(buffer);
+                break;
+
+            case DBG_START_LOCAL:
+                regNum = BufferUtil.getULEB128(buffer);
+                nameIdx = BufferUtil.getULEB128(buffer) - 1;
+                typeIdx = BufferUtil.getULEB128(buffer) - 1;
+                local = regs[regNum];
+                if (local != null && local.endLabel == null)
+                    throw new DexParseException("Live local in register.");
+                regs[regNum] = emitLocalVariable(regNum, addr, nameIdx,
+                        typeIdx, DexFile.NO_INDEX);
+                break;
+
+            case DBG_START_LOCAL_EXTENDED:
+                regNum = BufferUtil.getULEB128(buffer);
+                nameIdx = BufferUtil.getULEB128(buffer) - 1;
+                typeIdx = BufferUtil.getULEB128(buffer) - 1;
+                sigIdx = BufferUtil.getULEB128(buffer) - 1;
+                local = regs[regNum];
+                if (local != null && local.endLabel == null)
+                    throw new DexParseException("Live local in register.");
+                regs[regNum] = emitLocalVariable(regNum, addr, nameIdx,
+                        typeIdx, sigIdx);
+                break;
+
+            case DBG_END_LOCAL:
+                regNum = BufferUtil.getULEB128(buffer);
+                local = regs[regNum];
+                if (local == null || local.endLabel != null)
+                    // XXX Check why this happens, this shouldn't happen.
+                    //throw new DexParseException("No live local in register.");
+                    break;
+                local.setEnd(code.putLabel(addr));
+                break;
+
+            case DBG_RESTART_LOCAL:
+                regNum = BufferUtil.getULEB128(buffer);
+                local = regs[regNum];
+                if (local == null || local.endLabel == null)
+                    throw new DexParseException("No local to re-introduce.");
+                regs[regNum] = emitLocalVariable(regNum, addr, local.nameIdx,
+                        local.typeIdx, local.sigIdx);
+                break;
+
+            case DBG_SET_PROLOGUE_END:
+                break;
+            case DBG_SET_EPILOGUE_BEGIN:
+                break;
+
+            case DBG_SET_FILE:
+                // nameIdx = BufferUtil.getULEB128(buffer) - 1;
+                // break;
+                throw new UnsupportedOperationException("Not yet implemented!");
+
+            default:
+                int adjustedOpcode = opcode - DBG_FIRST_SPECIAL;
+                line += (adjustedOpcode % 15) - 4;
+                addr += (adjustedOpcode / 15);
+                emitLineNumber(line, addr);
+                break;
+            }
+        }
+
+        // If we fall through we ran out of bytecodes.
+        throw new DexParseException("Premature end of state machine.");
+    }
+
+    /**
+     * Emits a new local variable information. The returned object has all
+     * information set correctly, except for the label at which the local
+     * variable runs out of scope.
+     * 
+     * @param regNum The register number that will contain the local variable.
+     * @param startAddr The instruction offset where the local variable starts.
+     * @param nameIdx The index of the variable name or {@code NO_INDEX}.
+     * @param typeIdx The index of the type descriptor or {@code NO_INDEX}.
+     * @param sigIdx The index of the type signature or {@code NO_INDEX}.
+     * @return The object representing the local variable information.
+     */
+    private LocalVariable emitLocalVariable(int regNum, int startAddr,
+            int nameIdx, int typeIdx, int sigIdx) {
+        Label startLabel = code.putLabel(startAddr);
+        LocalVariable local = new LocalVariable(regNum, nameIdx, typeIdx,
+                sigIdx, startLabel);
+        localVariables.add(local);
+        return local;
+    }
+
+    /**
+     * Emits a new line number entry. Line numbers are stored as part of the
+     * label they are associated with.
+     * 
+     * @param line The given line number.
+     * @param addr The given instruction offset in 16-bit code units.
+     */
+    private void emitLineNumber(int line, int addr) {
+        Label label = new LineNumberLabel(addr, line);
+        try {
+            code.putLabel(addr, label);
+        } catch (DexParseException e) {
+            // XXX This can happen in case two line numbers are at the same
+            // address. Think about how to solve, fix it and remove this
+            // try-catch guard.
+            System.out.printf("DUPLICATE: line=%d, address=%d\n", line, addr);
+        }
+    }
+
+    /**
+     * An internal representation of local variable information as emitted by
+     * the {@code debug_info_item} state machine.
+     */
+    static class LocalVariable {
+        protected final int regNum;
+        protected final int nameIdx;
+        protected final int typeIdx;
+        protected final int sigIdx;
+        protected final Label startLabel;
+        protected Label endLabel;
+
+        public LocalVariable(int regNum, int nameIdx, int typeIdx, int sigIdx,
+                Label startLabel) {
+            this.regNum = regNum;
+            this.nameIdx = nameIdx;
+            this.typeIdx = typeIdx;
+            this.sigIdx = sigIdx;
+            this.startLabel = startLabel;
+        }
+
+        public void setEnd(Label endLabel) {
+            if (this.endLabel != null)
+                throw new DexParseException("Local variable is dead.");
+            this.endLabel = endLabel;
+        }
+    };
+
+    /**
+     * A label as defined by the interface, but enriched with additional debug
+     * and line number information.
+     */
+    static class LineNumberLabel extends Label {
+        private final int pos;
+        protected final int line;
+
+        public LineNumberLabel(int pos, int line) {
+            this.pos = pos;
+            this.line = line;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("L%03d[line=%d]", pos, line);
+        }
+    };
+}