Mercurial > hg > icedrobot > daneel
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); + } + }; +}