/* Software Name : AsmDex * Version : 1.0 * * Copyright © 2012 France Télécom * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ package org.ow2.asmdex.structureWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.TreeSet; import org.ow2.asmdex.Opcodes; import org.ow2.asmdex.instruction.DebugInstruction; import org.ow2.asmdex.instruction.DebugInstructionAdvanceLine; import org.ow2.asmdex.instruction.DebugInstructionAdvancePC; import org.ow2.asmdex.instruction.DebugInstructionEndLocal; import org.ow2.asmdex.instruction.DebugInstructionRestartLocal; import org.ow2.asmdex.instruction.DebugInstructionSetPrologueEnd; import org.ow2.asmdex.instruction.DebugInstructionSpecialOpcode; import org.ow2.asmdex.instruction.DebugInstructionStartLocal; import org.ow2.asmdex.instruction.DebugInstructionStartLocalExtended; import org.ow2.asmdex.instruction.Instruction; import org.ow2.asmdex.lowLevelUtils.ByteVector; import org.ow2.asmdex.lowLevelUtils.DalvikValueReader; import org.ow2.asmdex.lowLevelUtils.IDalvikValueReader; import org.ow2.asmdex.structureCommon.Label; import org.ow2.asmdex.structureCommon.LocalVariable; /** * This Class represents the debug_info_item structure of one code_item. * * It also holds the method to write to a byte array the debug bytecode. * * NOTE : * - Epilogue : Not encoded, as I don't know how it is handled (I never saw it encoded). * - Source file : Not encoded. It would require a new event from AsmDex, not very important. * * @author Julien Névo */ public class DebugInfoItem { /** * Vector that contains the opcodes of the debug_info_item, including the header. */ private ByteVector debugCode; /** * Indicates if at least one index is encoded in the debug code. We use it to optimize the mapping * with the resolved indexes : it doesn't need do be done if no index was found. */ private boolean areSymbolicIndexesUsed; /** * Current Debug Address. One of the debug_info_item internal variables. */ private int debugAddress = 0; /** * Current Debug Line. One of the debug_info_item internal variables. */ private int debugLine; /** * Indicates if the next Instruction is going to be the first. Used to write the DBG_PROLOGUE_END * Debug Instruction. */ private boolean isFirstInstruction = true; /** * The Constant Pool of the Application. */ final private ConstantPool constantPool; /** * Map linking an offset to one or more Debug Instruction. */ private HashMap<Integer, List<DebugInstruction>> offsetToDebugInstructions = new HashMap<Integer, List<DebugInstruction>>(); /** * Constructor of the Debug Info Item. * @param constantPool the Constant Pool of the Application. */ public DebugInfoItem(ConstantPool constantPool) { this.constantPool = constantPool; } // ------------------------------------ // Public methods. // ------------------------------------ /** * Frees all the structures (list of debug instructions...) so that they don't * consume memory. This should be done <i>after</i> having generated the bytecode, once the * method has been parsed and its end visited. */ public void free() { offsetToDebugInstructions = null; // Parameters not Nulled, as we need them when generating the final Dex file. } private enum LocalVariableType { end(0), local(1), restart(2); private final int id; LocalVariableType(int id) { this.id = id; } }; /** * Simple intermediary class in order to help sorting the local variable. * * @author Julien Névo */ private static class LocalVariableItem implements Comparable<LocalVariableItem>{ public LocalVariableType localVariableType; public int register; public String name; public Label label; public String signature; // Only used by "starting" local variables. public String type; // Descriptor. public LocalVariableItem(LocalVariableType localVariableType, int register, String name, Label label, String signature, String type) { this.localVariableType = localVariableType; this.register = register; this.name = name; this.label = label; this.signature = signature; this.type = type; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof LocalVariableItem) { LocalVariableItem lvi = (LocalVariableItem)obj; boolean result = (name.equals(lvi.name)) && (register == lvi.register) && (localVariableType == lvi.localVariableType) && (label.equals(lvi.label)) && (type.equals(lvi.type)); if (result) { result = (signature != null ? signature.equals(lvi.signature) : lvi.signature == null); } return result; } return false; } @Override public int hashCode() { int result = (signature != null ? signature.hashCode() : 0); result += localVariableType.hashCode() + register * 33 + name.hashCode() + label.hashCode() + type.hashCode(); return result; } @Override public int compareTo(LocalVariableItem var) { if (this == var) { return 0; } int result; if (localVariableType == var.localVariableType) { result = (register == var.register ? 0 : (register > var.register ? 1 : -1)); if (result == 0) { int offset1 = label.getOffset(); int offset2 = var.label.getOffset(); result = (offset1 == offset2 ? 0 : (offset1 > offset2 ? 1 : -1)); } } else { int type1 = localVariableType.id; int type2 = var.localVariableType.id; result = (type1 == type2 ? 0 : (type1 > type2 ? 1 : -1)); } return result; } } /** * Initializes the debug_info_item vector by creating its header. It is <i>not</i> useable as-is though, * but it can only be when the whole application has been parsed, as it encodes the parameters index * from its symbolic index. It will have to be parsed again later. * * @param parameters the parameters of the Method, or null. * @param codeItem the Code Item the Debug Info item is related to. * @param localVariables the Local Variables of the current Method. */ public void initializeDebugInfoItem(String[] parameters, CodeItem codeItem, List<LocalVariable> localVariables) { debugCode = new ByteVector(); // Initializes the Debug Instructions. Links offsets to Debug Instructions. offsetToDebugInstructions.clear(); areSymbolicIndexesUsed = localVariables.size() > 0; // First of all, we need to sort the Local Variables. This is not mandatory in the dex format, // but dx does it and it allows a better output with baksmali (no diffs) when comparing files // when debugging. // To do that, we create intermediary objects that we sort. if (localVariables.size() > 0) { TreeSet<LocalVariableItem> items = new TreeSet<LocalVariableItem>(); for (LocalVariable lv : localVariables) { // Is there any variable that starts ? Label start = lv.getStart(); if (start != null) { LocalVariableItem item = new LocalVariableItem(LocalVariableType.local, lv.getRegister(), lv.getName(), start, lv.getSignature(), lv.getType() ); items.add(item); } // Is there any variable that ends ? if (lv.getEnds() != null) { for (Label end : lv.getEnds()) { LocalVariableItem item = new LocalVariableItem(LocalVariableType.end, lv.getRegister(), lv.getName(), end, lv.getSignature(), lv.getType() ); items.add(item); } } // Is there any variable that restarts ? if (lv.getRestarts() != null) { for (Label restart : lv.getRestarts()) { LocalVariableItem item = new LocalVariableItem(LocalVariableType.restart, lv.getRegister(), lv.getName(), restart, lv.getSignature(), lv.getType() ); items.add(item); } } } // Nows creates the Debug Instructions from the sorted list. for (LocalVariableItem item : items) { switch (item.localVariableType) { case end: { DebugInstruction insn = new DebugInstructionEndLocal(item.register, item.label); addDebugInstruction(item.label.getOffset(), insn); break; } case local: { String signature = item.signature; DebugInstruction insn; if (signature == null) { insn = new DebugInstructionStartLocal(item.register, item.name, item.type, item.label); } else { insn = new DebugInstructionStartLocalExtended(item.register, item.name, item.type, signature, item.label); } addDebugInstruction(item.label.getOffset(), insn); break; } case restart: { DebugInstruction insn = new DebugInstructionRestartLocal(item.register, item.label); addDebugInstruction(item.label.getOffset(), insn); break; } default: try { throw new IllegalArgumentException("Unknown Local Variable type."); } catch (Exception e) { e.printStackTrace(); } } } } // Gets the line_start, which we may have found while parsing the code. // If not, we set it to 1, as a line_number should be > 0. int firstLineFound = codeItem.getFirstLineNumber(); debugLine = (firstLineFound == 0) ? 1 : firstLineFound; debugCode.putUleb128(debugLine); // Parameters_size. int parameterCount = (parameters == null) ? 0 : parameters.length; debugCode.putUleb128(parameterCount); // Parameter_names. if (parameterCount > 0) { areSymbolicIndexesUsed = true; for (int i = 0; i < parameterCount; i++) { int parameterIndex = parameters[i].equals("") ? Opcodes.NO_INDEX_SIGNED : constantPool.getStringIndex(parameters[i]); debugCode.putUleb128p1(parameterIndex); } } } /** * Parses the Debug Information that an Instruction to be encoded contains (or not), and fills * the debug_code_item accordingly. This one is however not encoded for now in the Dex file. * @param instruction the Instruction to be encoded. * @param address address in bytes of the Instruction from the beginning of the byte code itself. */ public void parseDebugInformation(Instruction instruction, int address) { // If this instruction is the first one, marks the end of the Prologue. if (isFirstInstruction) { encodeDebugSetPrologueEnd(); isFirstInstruction = false; } // If the Instruction has a line number, we encode it. int newLineNumber = instruction.getLineNumber(); if (newLineNumber > 0) { int deltaLine = newLineNumber - debugLine; int deltaOffset = address - debugAddress; int deltaOffsetInWord = deltaOffset / 2; // In order to be encoded as a Special Opcodes, the delta line must be from (inclusive) // -4 and 10 (10 = DBG_LINE_RANGE + DBG_LINE_BASE - 1), and the delta offset from (inclusive) // 0 to 16 (16 because 16 * DBG_LINE_RANGE + DBG_FIRST_SPECIAL is the maximum number to fit in // a byte). boolean isDeltaLineWithinLimit = ((deltaLine <= (Opcodes.DBG_LINE_RANGE + Opcodes.DBG_LINE_BASE - 1)) && (deltaLine >= Opcodes.DBG_LINE_BASE)); boolean isDeltaOffsetWithinLimit = ((deltaOffsetInWord >= 0) && (deltaOffsetInWord <= Opcodes.DBG_LINE_RANGE)); // If one of the value is out of limit, we encode the change now (BEFORE the Special Opcode). // It will be taken in account for the next Instruction, when the Special Opcode is encountered. if (!isDeltaOffsetWithinLimit) { encodeDebugAdvancePC(deltaOffsetInWord); } if (!isDeltaLineWithinLimit) { encodeDebugAdvanceLine(deltaLine); } // We must encode a Special Opcode anyway, because it emits the new position. The out of // limit values are set as 0. encodeDebugSpecialOpcode(isDeltaOffsetWithinLimit ? deltaOffsetInWord : 0, isDeltaLineWithinLimit ? deltaLine : 0); debugLine = newLineNumber; debugAddress = address; } // Checks if this offset matches a Debug Instruction. if (offsetToDebugInstructions.containsKey(address)) { for (DebugInstruction debugInstruction : offsetToDebugInstructions.get(address)) { if (address != debugAddress) { encodeDebugAdvancePC((address - debugAddress) / 2); } debugAddress = address; debugInstruction.write(debugCode, constantPool); } } } /** * Closes the debug_info_item by adding a End_Sequence opcode. */ public void closeDebugInfoItem() { if (debugCode.getLength() > 0) { debugCode.putByte(Opcodes.DBG_END_SEQUENCE); } } /** * Parses the debug bytecode and maps the resolved indexes (Strings, Types) from the * symbolic ones. The mapping between the two, done through two tables in the Constant Pool, * must have been performed before. * @param in input buffer to parse. * @param offsetInputBuffer offset inside the input buffer from where to start the parsing. * @return the parsed and modified debug bytecode. */ public ByteVector mapResolvedIndexes(ByteVector in, int offsetInputBuffer) { // A different byte array is used in input and output, because odds are that different sized // index will be encoded. byte[] data = in.getBuffer(); IDalvikValueReader reader = new DalvikValueReader(data); reader.seek(offsetInputBuffer); ByteVector out = new ByteVector(); out.putUleb128(reader.uleb128()); // Encodes line_start. int nbParameters = reader.uleb128(); out.putUleb128(nbParameters); // Encodes parameters_size. // Maps symbolic parameters, if any, to resolved parameters. for (int i = 0; i < nbParameters; i++) { mapResolvedIndexFromSymbolicIndex(out, reader.uleb128_p1(), true); } boolean continueParsing = true; // Encodes the debug instructions proper. while (continueParsing) { int opcode = reader.ubyte(); out.putByte(opcode); // Always encodes the opcode. Also takes care of the special opcodes. switch (opcode) { case Opcodes.DBG_ADVANCE_PC: case Opcodes.DBG_END_LOCAL: case Opcodes.DBG_RESTART_LOCAL: out.putUleb128(reader.uleb128()); break; case Opcodes.DBG_ADVANCE_LINE: out.putSleb128(reader.sleb128()); break; case Opcodes.DBG_SET_FILE: mapResolvedIndexFromSymbolicIndex(out, reader.uleb128_p1(), true); break; case Opcodes.DBG_START_LOCAL: out.putUleb128(reader.uleb128()); // register_num. mapResolvedIndexFromSymbolicIndex(out, reader.uleb128_p1(), true); // name_idx. mapResolvedIndexFromSymbolicIndex(out, reader.uleb128_p1(), false); // type_idx. break; case Opcodes.DBG_START_LOCAL_EXTENDED: out.putUleb128(reader.uleb128()); // register_num. mapResolvedIndexFromSymbolicIndex(out, reader.uleb128_p1(), true); // name_idx. mapResolvedIndexFromSymbolicIndex(out, reader.uleb128_p1(), false); // type_idx. mapResolvedIndexFromSymbolicIndex(out, reader.uleb128_p1(), true); // sig_idx. break; case Opcodes.DBG_END_SEQUENCE: continueParsing = false; } } return out; } /** * Encodes a resolved String or Type index from a symbolic index, in the uleb128p1 format. * If the symbolic index is NO_INDEX, then the same NO_INDEX is encoded. * @param out the ByteVector where to encode the index. * @param symbolicIndex the symbolic index, or NO_INDEX. * @param isStringIndex true for String index, false for Type index. */ private void mapResolvedIndexFromSymbolicIndex(ByteVector out, int symbolicIndex, boolean isStringIndex) { int resolvedIndex; if (symbolicIndex != Opcodes.NO_INDEX_SIGNED) { if (isStringIndex) { resolvedIndex = constantPool.getResolvedStringIndexFromSymbolicStringIndex(symbolicIndex); } else { resolvedIndex = constantPool.getResolvedTypeIndexFromSymbolicTypeIndex(symbolicIndex); } } else { resolvedIndex = Opcodes.NO_INDEX_SIGNED; } out.putUleb128p1(resolvedIndex); } // ------------------------------------ // Getters and setters. // ------------------------------------ /** * Returns the bytecode of the Debug Info Item, still using symbolic indexes. * The method bytecode must have been generated first. * @return the bytecode of the Debug Info Item, still using symbolic indexes. */ public ByteVector getDebugInfoItemCode() { return debugCode; } /** * Indicates if at least one index is encoded in the debug code. We use it to optimize the mapping * with the resolved indexes : it doesn't need do be done if no index was found. * @return true if at least one index is encoded in the debug code. */ public boolean areSymbolicIndexesUsed() { return areSymbolicIndexesUsed; } // ------------------------------------ // Private methods. // ------------------------------------ /** * Adds a Debug Instruction to the list linked to the given offset. * @param offset the offset to which is linked the Debug Instruction. * @param insn the debug instruction to add. */ private void addDebugInstruction(int offset, DebugInstruction insn) { List<DebugInstruction> list; if (!offsetToDebugInstructions.containsKey(offset)) { list = new ArrayList<DebugInstruction>(1); offsetToDebugInstructions.put(offset, list); } else { list = offsetToDebugInstructions.get(offset); } list.add(insn); } /** * Encodes a Debug_Advance_PC Instruction. * @param deltaOffsetInWord delta offset in word to encode. */ private void encodeDebugAdvancePC(int deltaOffsetInWord) { DebugInstruction insn = new DebugInstructionAdvancePC(deltaOffsetInWord); insn.write(debugCode, constantPool); } /** * Encodes a Debug_Advance_Line Instruction. * @param deltaLine delta line to encode. */ private void encodeDebugAdvanceLine(int deltaLine) { DebugInstruction insn = new DebugInstructionAdvanceLine(deltaLine); insn.write(debugCode, constantPool); } /** * Encodes a Debug_Set_Prologue_End Instruction. */ private void encodeDebugSetPrologueEnd() { DebugInstruction insn = new DebugInstructionSetPrologueEnd(); insn.write(debugCode, constantPool); } /** * Encodes a Special Opcode (>= 0xa) according to the given delta offset and delta line. * This code doesn't test if the deltas are inside the authorized limits. * @param deltaOffsetInWord the delta offset in Word. * @param deltaLine the delta line. */ private void encodeDebugSpecialOpcode(int deltaOffsetInWord, int deltaLine) { DebugInstruction insn = new DebugInstructionSpecialOpcode(deltaOffsetInWord, deltaLine); insn.write(debugCode, constantPool); } }