Mercurial > hg > icedrobot > daneel
view src/main/java/org/icedrobot/daneel/dex/DebugInfo.java @ 91:2711ac37fdfb
Fixed local variable information for DEX files.
* dex/Code.java: Added end label marking the instruction stream end.
* dex/DebugInfo.java (interpret): Optimistic approach for variable closing.
* rewriter/DexRewriter.java (visitLocalVariable): Implemented.
* rewriter/PatchMethodVisitor.java: Pass local variables to actual visitor.
* rewriter/GenericTest.java: Enabled test case.
author | Michael Starzinger <michi@complang.tuwien.ac.at> |
---|---|
date | Mon, 28 Mar 2011 00:29:55 +0200 |
parents | 817b7941153f |
children | 9813956b6840 |
line wrap: on
line source
/* * 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.dex.Code.DebugLabel; import org.icedrobot.daneel.util.BufferUtil; import org.icedrobot.daneel.util.TypeUtil; /** * 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 DexFile dex; 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.dex = dex; 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]; // Emit local variables for method parameters. String[] parameterTypes = code.getMethod().getProtoId().getParameters(); if (parameterTypes != null) { if (parametersSize != parameterTypes.length) throw new DexParseException("Parameter count does not match."); int regNum = maxRegs - 1; for (int i = parametersSize - 1; i >= 0; i--, regNum--) { String name = parameterNames[i]; String type = parameterTypes[i]; if (TypeUtil.isWideType(type)) regNum--; regs[regNum] = emitLocalVariable(regNum, 0, name, type, null); } } // Iterate over all state machine bytecodes. while (buffer.hasRemaining()) { int opcode = buffer.get() & 0xff; int regNum, nameIdx, typeIdx, sigIdx; String name, type, sig; LocalVariable local; // Switch over all possible bytecodes. switch (opcode) { case DBG_END_SEQUENCE: for (LocalVariable open : regs) if (open != null && open.endLabel == null) open.endLabel = code.getEndLabel(); 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; name = resolveString(nameIdx); type = resolveType(typeIdx); local = regs[regNum]; if (local != null && local.endLabel == null) closeLocalVariable(local, addr); regs[regNum] = emitLocalVariable(regNum, addr, name, type, null); 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; name = resolveString(nameIdx); type = resolveType(typeIdx); sig = resolveString(sigIdx); local = regs[regNum]; if (local != null && local.endLabel == null) closeLocalVariable(local, addr); regs[regNum] = emitLocalVariable(regNum, addr, name, type, sig); break; case DBG_END_LOCAL: regNum = BufferUtil.getULEB128(buffer); local = regs[regNum]; if (local == null || local.endLabel != null) throw new DexParseException("No live local in register."); closeLocalVariable(local, addr); break; case DBG_RESTART_LOCAL: regNum = BufferUtil.getULEB128(buffer); local = regs[regNum]; if (local == null) throw new DexParseException("No local to re-introduce."); if (local != null && local.endLabel == null) closeLocalVariable(local, addr); name = local.name; type = local.type; sig = local.sig; regs[regNum] = emitLocalVariable(regNum, addr, name, type, sig); 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."); } private String resolveString(int idx) { return (idx != DexFile.NO_INDEX) ? dex.getString(idx) : null; } private String resolveType(int idx) { return (idx != DexFile.NO_INDEX) ? dex.getTypeDescriptor(idx) : null; } /** * 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 name The local variable's name or {@code null}. * @param type The local variable's type descriptor or {@code null}. * @param sig The local variable's type signature or {@code null}. * @return The object representing the local variable information. */ private LocalVariable emitLocalVariable(int regNum, int startAddr, String name, String type, String sig) { Label startLabel = code.putLabel(startAddr, false); LocalVariable local = new LocalVariable(regNum, name, type, sig, startLabel); localVariables.add(local); return local; } /** * Closes a previously emitted local variable information. The label at * which the local variable runs out of scope is set here. * * @param local The local variable information. * @param endAddr The instruction offset where the local variable ends. */ private void closeLocalVariable(LocalVariable local, int endAddr) { Label endLabel = code.putLabel(endAddr, false); if (endLabel == local.startLabel) localVariables.remove(local); else local.endLabel = endLabel; } /** * 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 { final int regNum; final String name; final String type; final String sig; final Label startLabel; Label endLabel; public LocalVariable(int regNum, String name, String type, String sig, Label startLabel) { this.regNum = regNum; this.name = name; this.type = type; this.sig = sig; this.startLabel = startLabel; } }; /** * A label as defined by the interface, but enriched with additional debug * and line number information. */ static class LineNumberLabel extends DebugLabel { final int line; public LineNumberLabel(int pos, int line) { super(pos, false); this.line = line; } @Override public String toString() { return String.format("%s[line=%d]", super.toString(), line); } }; }