blob: e56bfb583a34724c271129d95044730238bc4068 [file] [log] [blame]
/*
* [The "BSD licence"]
* Copyright (c) 2010 Ben Gruver (JesusFreke)
* 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. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.jf.dexlib;
import org.jf.dexlib.Code.Format.*;
import org.jf.dexlib.Code.*;
import org.jf.dexlib.Debug.DebugInstructionIterator;
import org.jf.dexlib.Debug.DebugOpcode;
import org.jf.dexlib.Util.*;
import java.util.ArrayList;
import java.util.List;
public class CodeItem extends Item<CodeItem> {
private int registerCount;
private int inWords;
private int outWords;
private DebugInfoItem debugInfo;
private Instruction[] instructions;
private TryItem[] tries;
private EncodedCatchHandler[] encodedCatchHandlers;
private ClassDataItem.EncodedMethod parent;
/**
* Creates a new uninitialized <code>CodeItem</code>
* @param dexFile The <code>DexFile</code> that this item belongs to
*/
public CodeItem(DexFile dexFile) {
super(dexFile);
}
/**
* Creates a new <code>CodeItem</code> with the given values.
* @param dexFile The <code>DexFile</code> that this item belongs to
* @param registerCount the number of registers that the method containing this code uses
* @param inWords the number of 2-byte words that the parameters to the method containing this code take
* @param outWords the maximum number of 2-byte words for the arguments of any method call in this code
* @param debugInfo the debug information for this code/method
* @param instructions the instructions for this code item
* @param tries an array of the tries defined for this code/method
* @param encodedCatchHandlers an array of the exception handlers defined for this code/method
*/
private CodeItem(DexFile dexFile,
int registerCount,
int inWords,
int outWords,
DebugInfoItem debugInfo,
Instruction[] instructions,
TryItem[] tries,
EncodedCatchHandler[] encodedCatchHandlers) {
super(dexFile);
this.registerCount = registerCount;
this.inWords = inWords;
this.outWords = outWords;
this.debugInfo = debugInfo;
if (debugInfo != null) {
debugInfo.setParent(this);
}
this.instructions = instructions;
this.tries = tries;
this.encodedCatchHandlers = encodedCatchHandlers;
}
/**
* Returns a new <code>CodeItem</code> with the given values.
* @param dexFile The <code>DexFile</code> that this item belongs to
* @param registerCount the number of registers that the method containing this code uses
* @param inWords the number of 2-byte words that the parameters to the method containing this code take
* @param outWords the maximum number of 2-byte words for the arguments of any method call in this code
* @param debugInfo the debug information for this code/method
* @param instructions the instructions for this code item
* @param tries a list of the tries defined for this code/method or null if none
* @param encodedCatchHandlers a list of the exception handlers defined for this code/method or null if none
* @return a new <code>CodeItem</code> with the given values.
*/
public static CodeItem internCodeItem(DexFile dexFile,
int registerCount,
int inWords,
int outWords,
DebugInfoItem debugInfo,
List<Instruction> instructions,
List<TryItem> tries,
List<EncodedCatchHandler> encodedCatchHandlers) {
TryItem[] triesArray = null;
EncodedCatchHandler[] encodedCatchHandlersArray = null;
Instruction[] instructionsArray = null;
if (tries != null && tries.size() > 0) {
triesArray = new TryItem[tries.size()];
tries.toArray(triesArray);
}
if (encodedCatchHandlers != null && encodedCatchHandlers.size() > 0) {
encodedCatchHandlersArray = new EncodedCatchHandler[encodedCatchHandlers.size()];
encodedCatchHandlers.toArray(encodedCatchHandlersArray);
}
if (instructions != null && instructions.size() > 0) {
instructionsArray = new Instruction[instructions.size()];
instructions.toArray(instructionsArray);
}
CodeItem codeItem = new CodeItem(dexFile, registerCount, inWords, outWords, debugInfo, instructionsArray,
triesArray, encodedCatchHandlersArray);
return dexFile.CodeItemsSection.intern(codeItem);
}
/** {@inheritDoc} */
protected void readItem(Input in, ReadContext readContext) {
this.registerCount = in.readShort();
this.inWords = in.readShort();
this.outWords = in.readShort();
int triesCount = in.readShort();
this.debugInfo = (DebugInfoItem)readContext.getOptionalOffsettedItemByOffset(ItemType.TYPE_DEBUG_INFO_ITEM,
in.readInt());
if (this.debugInfo != null) {
this.debugInfo.setParent(this);
}
int instructionCount = in.readInt();
final ArrayList<Instruction> instructionList = new ArrayList<Instruction>();
byte[] encodedInstructions = in.readBytes(instructionCount * 2);
InstructionIterator.IterateInstructions(dexFile, encodedInstructions,
new InstructionIterator.ProcessInstructionDelegate() {
public void ProcessInstruction(int codeAddress, Instruction instruction) {
instructionList.add(instruction);
}
});
this.instructions = new Instruction[instructionList.size()];
instructionList.toArray(instructions);
if (triesCount > 0) {
in.alignTo(4);
//we need to read in the catch handlers first, so save the offset to the try items for future reference
int triesOffset = in.getCursor();
in.setCursor(triesOffset + 8 * triesCount);
//read in the encoded catch handlers
int encodedHandlerStart = in.getCursor();
int handlerCount = in.readUnsignedLeb128();
SparseArray<EncodedCatchHandler> handlerMap = new SparseArray<EncodedCatchHandler>(handlerCount);
encodedCatchHandlers = new EncodedCatchHandler[handlerCount];
for (int i=0; i<handlerCount; i++) {
try {
int position = in.getCursor() - encodedHandlerStart;
encodedCatchHandlers[i] = new EncodedCatchHandler(dexFile, in);
handlerMap.append(position, encodedCatchHandlers[i]);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex, "Error while reading EncodedCatchHandler at index " + i);
}
}
int codeItemEnd = in.getCursor();
//now go back and read the tries
in.setCursor(triesOffset);
tries = new TryItem[triesCount];
for (int i=0; i<triesCount; i++) {
try {
tries[i] = new TryItem(in, handlerMap);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex, "Error while reading TryItem at index " + i);
}
}
//and now back to the end of the code item
in.setCursor(codeItemEnd);
}
}
/** {@inheritDoc} */
protected int placeItem(int offset) {
offset += 16 + getInstructionsLength() * 2;
if (tries != null && tries.length > 0) {
offset = AlignmentUtils.alignOffset(offset, 4);
offset += tries.length * 8;
int encodedCatchHandlerBaseOffset = offset;
offset += Leb128Utils.unsignedLeb128Size(encodedCatchHandlers.length);
for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
offset = encodedCatchHandler.place(offset, encodedCatchHandlerBaseOffset);
}
}
return offset;
}
/** {@inheritDoc} */
protected void writeItem(final AnnotatedOutput out) {
int instructionsLength = getInstructionsLength();
if (out.annotates()) {
out.annotate(0, parent.method.getMethodString());
out.annotate(2, "registers_size: 0x" + Integer.toHexString(registerCount) + " (" + registerCount + ")");
out.annotate(2, "ins_size: 0x" + Integer.toHexString(inWords) + " (" + inWords + ")");
out.annotate(2, "outs_size: 0x" + Integer.toHexString(outWords) + " (" + outWords + ")");
int triesLength = tries==null?0:tries.length;
out.annotate(2, "tries_size: 0x" + Integer.toHexString(triesLength) + " (" + triesLength + ")");
if (debugInfo == null) {
out.annotate(4, "debug_info_off:");
} else {
out.annotate(4, "debug_info_off: 0x" + Integer.toHexString(debugInfo.getOffset()));
}
out.annotate(4, "insns_size: 0x" + Integer.toHexString(instructionsLength) + " (" +
(instructionsLength) + ")");
}
out.writeShort(registerCount);
out.writeShort(inWords);
out.writeShort(outWords);
if (tries == null) {
out.writeShort(0);
} else {
out.writeShort(tries.length);
}
if (debugInfo == null) {
out.writeInt(0);
} else {
out.writeInt(debugInfo.getOffset());
}
out.writeInt(instructionsLength);
int currentCodeAddress = 0;
for (Instruction instruction: instructions) {
currentCodeAddress = instruction.write(out, currentCodeAddress);
}
if (tries != null && tries.length > 0) {
if (out.annotates()) {
if ((currentCodeAddress % 2) != 0) {
out.annotate("padding");
out.writeShort(0);
}
int index = 0;
for (TryItem tryItem: tries) {
out.annotate(0, "[0x" + Integer.toHexString(index++) + "] try_item");
out.indent();
tryItem.writeTo(out);
out.deindent();
}
out.annotate("handler_count: 0x" + Integer.toHexString(encodedCatchHandlers.length) + "(" +
encodedCatchHandlers.length + ")");
out.writeUnsignedLeb128(encodedCatchHandlers.length);
index = 0;
for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
out.annotate(0, "[" + Integer.toHexString(index++) + "] encoded_catch_handler");
out.indent();
encodedCatchHandler.writeTo(out);
out.deindent();
}
} else {
if ((currentCodeAddress % 2) != 0) {
out.writeShort(0);
}
for (TryItem tryItem: tries) {
tryItem.writeTo(out);
}
out.writeUnsignedLeb128(encodedCatchHandlers.length);
for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
encodedCatchHandler.writeTo(out);
}
}
}
}
/** {@inheritDoc} */
public ItemType getItemType() {
return ItemType.TYPE_CODE_ITEM;
}
/** {@inheritDoc} */
public String getConciseIdentity() {
if (this.parent == null) {
return "code_item @0x" + Integer.toHexString(getOffset());
}
return "code_item @0x" + Integer.toHexString(getOffset()) + " (" + parent.method.getMethodString() + ")";
}
/** {@inheritDoc} */
public int compareTo(CodeItem other) {
if (parent == null) {
if (other.parent == null) {
return 0;
}
return -1;
}
if (other.parent == null) {
return 1;
}
return parent.method.compareTo(other.parent.method);
}
/**
* @return the register count
*/
public int getRegisterCount() {
return registerCount;
}
/**
* @return an array of the instructions in this code item
*/
public Instruction[] getInstructions() {
return instructions;
}
/**
* @return an array of the <code>TryItem</code> objects in this <code>CodeItem</code>
*/
public TryItem[] getTries() {
return tries;
}
/**
* @return an array of the <code>EncodedCatchHandler</code> objects in this <code>CodeItem</code>
*/
public EncodedCatchHandler[] getHandlers() {
return encodedCatchHandlers;
}
/**
* @return the <code>DebugInfoItem</code> associated with this <code>CodeItem</code>
*/
public DebugInfoItem getDebugInfo() {
return debugInfo;
}
/**
* @return the number of 2-byte words that the parameters to the method containing this code take
*/
public int getInWords() {
return inWords;
}
/**
* @return the maximum number of 2-byte words for the arguments of any method call in this code
*/
public int getOutWords() {
return outWords;
}
/**
* Sets the <code>MethodIdItem</code> of the method that this <code>CodeItem</code> is associated with
* @param encodedMethod the <code>EncodedMethod</code> of the method that this <code>CodeItem</code> is associated
* with
*/
protected void setParent(ClassDataItem.EncodedMethod encodedMethod) {
this.parent = encodedMethod;
}
/**
* @return the MethodIdItem of the method that this CodeItem belongs to
*/
public ClassDataItem.EncodedMethod getParent() {
return parent;
}
/**
* Used by OdexUtil to update this <code>CodeItem</code> with a deodexed version of the instructions
* @param newInstructions the new instructions to use for this code item
*/
public void updateCode(Instruction[] newInstructions) {
this.instructions = newInstructions;
}
/**
* @return The length of the instructions in this CodeItem, in 2-byte code blocks
*/
private int getInstructionsLength() {
int currentCodeAddress = 0;
for (Instruction instruction: instructions) {
currentCodeAddress += instruction.getSize(currentCodeAddress);
}
return currentCodeAddress;
}
/**
* Go through the instructions and perform any of the following fixes that are applicable
* - Replace const-string instruction with const-string/jumbo, when the string index is too big
* - Replace goto and goto/16 with a larger version of goto, when the target is too far away
* TODO: we should be able to replace if-* instructions with targets that are too far away with a negated if followed by a goto/32 to the original target
* TODO: remove multiple nops that occur before a switch/array data pseudo instruction. In some cases, multiple smali-baksmali cycles with changes in between could cause nops to start piling up
* TODO: in case of non-range invoke with a jumbo-sized method reference, we could check if the registers are sequential, and replace it with the jumbo variant (which only takes a register range)
*
* The above fixes are applied iteratively, until no more fixes have been performed
*/
public void fixInstructions(boolean fixJumbo, boolean fixGoto) {
try {
boolean didSomething = false;
do
{
didSomething = false;
int currentCodeAddress = 0;
for (int i=0; i<instructions.length; i++) {
Instruction instruction = instructions[i];
try {
if (fixGoto && instruction.opcode == Opcode.GOTO) {
int codeAddress = ((OffsetInstruction)instruction).getTargetAddressOffset();
if (((byte) codeAddress) != codeAddress) {
//the address doesn't fit within a byte, we need to upgrade to a goto/16 or goto/32
if ((short) codeAddress == codeAddress) {
//the address fits in a short, so upgrade to a goto/16
replaceInstructionAtAddress(currentCodeAddress,
new Instruction20t(Opcode.GOTO_16, codeAddress));
}
else {
//The address won't fit into a short, we have to upgrade to a goto/32
replaceInstructionAtAddress(currentCodeAddress,
new Instruction30t(Opcode.GOTO_32, codeAddress));
}
didSomething = true;
break;
}
} else if (fixGoto && instruction.opcode == Opcode.GOTO_16) {
int codeAddress = ((OffsetInstruction)instruction).getTargetAddressOffset();
if (((short) codeAddress) != codeAddress) {
//the address doesn't fit within a short, we need to upgrade to a goto/32
replaceInstructionAtAddress(currentCodeAddress,
new Instruction30t(Opcode.GOTO_32, codeAddress));
didSomething = true;
break;
}
} else if (fixJumbo && instruction.opcode.hasJumboOpcode()) {
InstructionWithReference referenceInstruction = (InstructionWithReference)instruction;
if (referenceInstruction.getReferencedItem().getIndex() > 0xFFFF) {
InstructionWithJumboVariant instructionWithJumboVariant =
(InstructionWithJumboVariant)referenceInstruction;
Instruction jumboInstruction = instructionWithJumboVariant.makeJumbo();
if (jumboInstruction != null) {
replaceInstructionAtAddress(currentCodeAddress,
instructionWithJumboVariant.makeJumbo());
didSomething = true;
break;
}
}
}
currentCodeAddress += instruction.getSize(currentCodeAddress);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex, "Error while attempting to fix " +
instruction.opcode.name + " instruction at address " + currentCodeAddress);
}
}
}while(didSomething);
} catch (Exception ex) {
throw this.addExceptionContext(ex);
}
}
private void replaceInstructionAtAddress(int codeAddress, Instruction replacementInstruction) {
Instruction originalInstruction = null;
int[] originalInstructionCodeAddresses = new int[instructions.length+1];
SparseIntArray originalSwitchAddressByOriginalSwitchDataAddress = new SparseIntArray();
int currentCodeAddress = 0;
int instructionIndex = 0;
int i;
for (i=0; i<instructions.length; i++) {
Instruction instruction = instructions[i];
if (currentCodeAddress == codeAddress) {
originalInstruction = instruction;
instructionIndex = i;
}
if (instruction.opcode == Opcode.PACKED_SWITCH || instruction.opcode == Opcode.SPARSE_SWITCH) {
OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
int switchDataAddress = currentCodeAddress + offsetInstruction.getTargetAddressOffset();
if (originalSwitchAddressByOriginalSwitchDataAddress.indexOfKey(switchDataAddress) < 0) {
originalSwitchAddressByOriginalSwitchDataAddress.put(switchDataAddress, currentCodeAddress);
}
}
originalInstructionCodeAddresses[i] = currentCodeAddress;
currentCodeAddress += instruction.getSize(currentCodeAddress);
}
//add the address just past the end of the last instruction, to help when fixing up try blocks that end
//at the end of the method
originalInstructionCodeAddresses[i] = currentCodeAddress;
if (originalInstruction == null) {
throw new RuntimeException("There is no instruction at address " + codeAddress);
}
instructions[instructionIndex] = replacementInstruction;
//if we're replacing the instruction with one of the same size, we don't have to worry about fixing
//up any address
if (originalInstruction.getSize(codeAddress) == replacementInstruction.getSize(codeAddress)) {
return;
}
final SparseIntArray originalAddressByNewAddress = new SparseIntArray();
final SparseIntArray newAddressByOriginalAddress = new SparseIntArray();
currentCodeAddress = 0;
for (i=0; i<instructions.length; i++) {
Instruction instruction = instructions[i];
int originalAddress = originalInstructionCodeAddresses[i];
originalAddressByNewAddress.append(currentCodeAddress, originalAddress);
newAddressByOriginalAddress.append(originalAddress, currentCodeAddress);
currentCodeAddress += instruction.getSize(currentCodeAddress);
}
//add the address just past the end of the last instruction, to help when fixing up try blocks that end
//at the end of the method
originalAddressByNewAddress.append(currentCodeAddress, originalInstructionCodeAddresses[i]);
newAddressByOriginalAddress.append(originalInstructionCodeAddresses[i], currentCodeAddress);
//update any "offset" instructions, or switch data instructions
currentCodeAddress = 0;
for (i=0; i<instructions.length; i++) {
Instruction instruction = instructions[i];
if (instruction instanceof OffsetInstruction) {
OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
assert originalAddressByNewAddress.indexOfKey(currentCodeAddress) >= 0;
int originalAddress = originalAddressByNewAddress.get(currentCodeAddress);
int originalInstructionTarget = originalAddress + offsetInstruction.getTargetAddressOffset();
assert newAddressByOriginalAddress.indexOfKey(originalInstructionTarget) >= 0;
int newInstructionTarget = newAddressByOriginalAddress.get(originalInstructionTarget);
int newCodeAddress = (newInstructionTarget - currentCodeAddress);
if (newCodeAddress != offsetInstruction.getTargetAddressOffset()) {
offsetInstruction.updateTargetAddressOffset(newCodeAddress);
}
} else if (instruction instanceof MultiOffsetInstruction) {
MultiOffsetInstruction multiOffsetInstruction = (MultiOffsetInstruction)instruction;
assert originalAddressByNewAddress.indexOfKey(currentCodeAddress) >= 0;
int originalDataAddress = originalAddressByNewAddress.get(currentCodeAddress);
int originalSwitchAddress =
originalSwitchAddressByOriginalSwitchDataAddress.get(originalDataAddress, -1);
if (originalSwitchAddress == -1) {
//TODO: maybe we could just remove the unreferenced switch data?
throw new RuntimeException("This method contains an unreferenced switch data block at address " +
+ currentCodeAddress + " and can't be automatically fixed.");
}
assert newAddressByOriginalAddress.indexOfKey(originalSwitchAddress) >= 0;
int newSwitchAddress = newAddressByOriginalAddress.get(originalSwitchAddress);
int[] targets = multiOffsetInstruction.getTargets();
for (int t=0; t<targets.length; t++) {
int originalTargetCodeAddress = originalSwitchAddress + targets[t];
assert newAddressByOriginalAddress.indexOfKey(originalTargetCodeAddress) >= 0;
int newTargetCodeAddress = newAddressByOriginalAddress.get(originalTargetCodeAddress);
int newCodeAddress = newTargetCodeAddress - newSwitchAddress;
if (newCodeAddress != targets[t]) {
multiOffsetInstruction.updateTarget(t, newCodeAddress);
}
}
}
currentCodeAddress += instruction.getSize(currentCodeAddress);
}
if (debugInfo != null) {
final byte[] encodedDebugInfo = debugInfo.getEncodedDebugInfo();
ByteArrayInput debugInput = new ByteArrayInput(encodedDebugInfo);
DebugInstructionFixer debugInstructionFixer = new DebugInstructionFixer(encodedDebugInfo,
newAddressByOriginalAddress);
DebugInstructionIterator.IterateInstructions(debugInput, debugInstructionFixer);
if (debugInstructionFixer.result != null) {
debugInfo.setEncodedDebugInfo(debugInstructionFixer.result);
}
}
if (encodedCatchHandlers != null) {
for (EncodedCatchHandler encodedCatchHandler: encodedCatchHandlers) {
if (encodedCatchHandler.catchAllHandlerAddress != -1) {
assert newAddressByOriginalAddress.indexOfKey(encodedCatchHandler.catchAllHandlerAddress) >= 0;
encodedCatchHandler.catchAllHandlerAddress =
newAddressByOriginalAddress.get(encodedCatchHandler.catchAllHandlerAddress);
}
for (EncodedTypeAddrPair handler: encodedCatchHandler.handlers) {
assert newAddressByOriginalAddress.indexOfKey(handler.handlerAddress) >= 0;
handler.handlerAddress = newAddressByOriginalAddress.get(handler.handlerAddress);
}
}
}
if (this.tries != null) {
for (TryItem tryItem: tries) {
int startAddress = tryItem.startCodeAddress;
int endAddress = tryItem.startCodeAddress + tryItem.tryLength;
assert newAddressByOriginalAddress.indexOfKey(startAddress) >= 0;
tryItem.startCodeAddress = newAddressByOriginalAddress.get(startAddress);
assert newAddressByOriginalAddress.indexOfKey(endAddress) >= 0;
tryItem.tryLength = newAddressByOriginalAddress.get(endAddress) - tryItem.startCodeAddress;
}
}
}
private class DebugInstructionFixer extends DebugInstructionIterator.ProcessRawDebugInstructionDelegate {
private int currentCodeAddress = 0;
private SparseIntArray newAddressByOriginalAddress;
private final byte[] originalEncodedDebugInfo;
public byte[] result = null;
public DebugInstructionFixer(byte[] originalEncodedDebugInfo, SparseIntArray newAddressByOriginalAddress) {
this.newAddressByOriginalAddress = newAddressByOriginalAddress;
this.originalEncodedDebugInfo = originalEncodedDebugInfo;
}
@Override
public void ProcessAdvancePC(int startDebugOffset, int debugInstructionLength, int codeAddressDelta) {
currentCodeAddress += codeAddressDelta;
if (result != null) {
return;
}
int newCodeAddress = newAddressByOriginalAddress.get(currentCodeAddress, -1);
//The address might not point to an actual instruction in some cases, for example, if an AdvancePC
//instruction was inserted just before a "special" instruction, to fix up the addresses for a previous
//instruction replacement.
//In this case, it should be safe to skip, because there will be another AdvancePC/SpecialOpcode that will
//bump up the address to point to a valid instruction before anything (line/local/etc.) is emitted
if (newCodeAddress == -1) {
return;
}
if (newCodeAddress != currentCodeAddress) {
int newCodeAddressDelta = newCodeAddress - (currentCodeAddress - codeAddressDelta);
assert newCodeAddressDelta > 0;
int codeAddressDeltaLeb128Size = Leb128Utils.unsignedLeb128Size(newCodeAddressDelta);
//if the length of the new code address delta is the same, we can use the existing buffer
if (codeAddressDeltaLeb128Size + 1 == debugInstructionLength) {
result = originalEncodedDebugInfo;
Leb128Utils.writeUnsignedLeb128(newCodeAddressDelta, result, startDebugOffset+1);
} else {
//The length of the new code address delta is different, so create a new buffer with enough
//additional space to accomodate the new code address delta value.
result = new byte[originalEncodedDebugInfo.length + codeAddressDeltaLeb128Size -
(debugInstructionLength - 1)];
System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startDebugOffset);
result[startDebugOffset] = DebugOpcode.DBG_ADVANCE_PC.value;
Leb128Utils.writeUnsignedLeb128(newCodeAddressDelta, result, startDebugOffset+1);
System.arraycopy(originalEncodedDebugInfo, startDebugOffset + debugInstructionLength, result,
startDebugOffset + codeAddressDeltaLeb128Size + 1,
originalEncodedDebugInfo.length - (startDebugOffset + codeAddressDeltaLeb128Size + 1));
}
}
}
@Override
public void ProcessSpecialOpcode(int startDebugOffset, int debugOpcode, int lineDelta,
int codeAddressDelta) {
currentCodeAddress += codeAddressDelta;
if (result != null) {
return;
}
int newCodeAddress = newAddressByOriginalAddress.get(currentCodeAddress, -1);
assert newCodeAddress != -1;
if (newCodeAddress != currentCodeAddress) {
int newCodeAddressDelta = newCodeAddress - (currentCodeAddress - codeAddressDelta);
assert newCodeAddressDelta > 0;
//if the new code address delta won't fit in the special opcode, we need to insert
//an additional DBG_ADVANCE_PC opcode
if (lineDelta < 2 && newCodeAddressDelta > 16 || lineDelta > 1 && newCodeAddressDelta > 15) {
int additionalCodeAddressDelta = newCodeAddress - currentCodeAddress;
int additionalCodeAddressDeltaLeb128Size = Leb128Utils.signedLeb128Size(additionalCodeAddressDelta);
//create a new buffer with enough additional space for the new opcode
result = new byte[originalEncodedDebugInfo.length + additionalCodeAddressDeltaLeb128Size + 1];
System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startDebugOffset);
result[startDebugOffset] = 0x01; //DBG_ADVANCE_PC
Leb128Utils.writeUnsignedLeb128(additionalCodeAddressDelta, result, startDebugOffset+1);
System.arraycopy(originalEncodedDebugInfo, startDebugOffset, result,
startDebugOffset+additionalCodeAddressDeltaLeb128Size+1,
result.length - (startDebugOffset+additionalCodeAddressDeltaLeb128Size+1));
} else {
result = originalEncodedDebugInfo;
result[startDebugOffset] = DebugInfoBuilder.calculateSpecialOpcode(lineDelta,
newCodeAddressDelta);
}
}
}
}
public static class TryItem {
/**
* The address (in 2-byte words) within the code where the try block starts
*/
private int startCodeAddress;
/**
* The number of 2-byte words that the try block covers
*/
private int tryLength;
/**
* The associated exception handler
*/
public final EncodedCatchHandler encodedCatchHandler;
/**
* Construct a new <code>TryItem</code> with the given values
* @param startCodeAddress the code address within the code where the try block starts
* @param tryLength the number of code blocks that the try block covers
* @param encodedCatchHandler the associated exception handler
*/
public TryItem(int startCodeAddress, int tryLength, EncodedCatchHandler encodedCatchHandler) {
this.startCodeAddress = startCodeAddress;
this.tryLength = tryLength;
this.encodedCatchHandler = encodedCatchHandler;
}
/**
* This is used internally to construct a new <code>TryItem</code> while reading in a <code>DexFile</code>
* @param in the Input object to read the <code>TryItem</code> from
* @param encodedCatchHandlers a SparseArray of the EncodedCatchHandlers for this <code>CodeItem</code>. The
* key should be the offset of the EncodedCatchHandler from the beginning of the encoded_catch_handler_list
* structure.
*/
private TryItem(Input in, SparseArray<EncodedCatchHandler> encodedCatchHandlers) {
startCodeAddress = in.readInt();
tryLength = in.readShort();
encodedCatchHandler = encodedCatchHandlers.get(in.readShort());
if (encodedCatchHandler == null) {
throw new RuntimeException("Could not find the EncodedCatchHandler referenced by this TryItem");
}
}
/**
* Writes the <code>TryItem</code> to the given <code>AnnotatedOutput</code> object
* @param out the <code>AnnotatedOutput</code> object to write to
*/
private void writeTo(AnnotatedOutput out) {
if (out.annotates()) {
out.annotate(4, "start_addr: 0x" + Integer.toHexString(startCodeAddress));
out.annotate(2, "try_length: 0x" + Integer.toHexString(tryLength) + " (" + tryLength +
")");
out.annotate(2, "handler_off: 0x" + Integer.toHexString(encodedCatchHandler.getOffsetInList()));
}
out.writeInt(startCodeAddress);
out.writeShort(tryLength);
out.writeShort(encodedCatchHandler.getOffsetInList());
}
/**
* @return The address (in 2-byte words) within the code where the try block starts
*/
public int getStartCodeAddress() {
return startCodeAddress;
}
/**
* @return The number of code blocks that the try block covers
*/
public int getTryLength() {
return tryLength;
}
}
public static class EncodedCatchHandler {
/**
* An array of the individual exception handlers
*/
public final EncodedTypeAddrPair[] handlers;
/**
* The address within the code (in 2-byte words) for the catch all handler, or -1 if there is no catch all
* handler
*/
private int catchAllHandlerAddress;
private int baseOffset;
private int offset;
/**
* Constructs a new <code>EncodedCatchHandler</code> with the given values
* @param handlers an array of the individual exception handlers
* @param catchAllHandlerAddress The address within the code (in 2-byte words) for the catch all handler, or -1
* if there is no catch all handler
*/
public EncodedCatchHandler(EncodedTypeAddrPair[] handlers, int catchAllHandlerAddress) {
this.handlers = handlers;
this.catchAllHandlerAddress = catchAllHandlerAddress;
}
/**
* This is used internally to construct a new <code>EncodedCatchHandler</code> while reading in a
* <code>DexFile</code>
* @param dexFile the <code>DexFile</code> that is being read in
* @param in the Input object to read the <code>EncodedCatchHandler</code> from
*/
private EncodedCatchHandler(DexFile dexFile, Input in) {
int handlerCount = in.readSignedLeb128();
if (handlerCount < 0) {
handlers = new EncodedTypeAddrPair[-1 * handlerCount];
} else {
handlers = new EncodedTypeAddrPair[handlerCount];
}
for (int i=0; i<handlers.length; i++) {
try {
handlers[i] = new EncodedTypeAddrPair(dexFile, in);
} catch (Exception ex) {
throw ExceptionWithContext.withContext(ex, "Error while reading EncodedTypeAddrPair at index " + i);
}
}
if (handlerCount <= 0) {
catchAllHandlerAddress = in.readUnsignedLeb128();
} else {
catchAllHandlerAddress = -1;
}
}
/**
* @return the "Catch All" handler address for this <code>EncodedCatchHandler</code>, or -1 if there
* is no "Catch All" handler
*/
public int getCatchAllHandlerAddress() {
return catchAllHandlerAddress;
}
/**
* @return the offset of this <code>EncodedCatchHandler</code> from the beginning of the
* encoded_catch_handler_list structure
*/
private int getOffsetInList() {
return offset-baseOffset;
}
/**
* Places the <code>EncodedCatchHandler</code>, storing the offset and baseOffset, and returning the offset
* immediately following this <code>EncodedCatchHandler</code>
* @param offset the offset of this <code>EncodedCatchHandler</code> in the <code>DexFile</code>
* @param baseOffset the offset of the beginning of the encoded_catch_handler_list structure in the
* <code>DexFile</code>
* @return the offset immediately following this <code>EncodedCatchHandler</code>
*/
private int place(int offset, int baseOffset) {
this.offset = offset;
this.baseOffset = baseOffset;
int size = handlers.length;
if (catchAllHandlerAddress > -1) {
size *= -1;
offset += Leb128Utils.unsignedLeb128Size(catchAllHandlerAddress);
}
offset += Leb128Utils.signedLeb128Size(size);
for (EncodedTypeAddrPair handler: handlers) {
offset += handler.getSize();
}
return offset;
}
/**
* Writes the <code>EncodedCatchHandler</code> to the given <code>AnnotatedOutput</code> object
* @param out the <code>AnnotatedOutput</code> object to write to
*/
private void writeTo(AnnotatedOutput out) {
if (out.annotates()) {
out.annotate("size: 0x" + Integer.toHexString(handlers.length) + " (" + handlers.length + ")");
int size = handlers.length;
if (catchAllHandlerAddress > -1) {
size = size * -1;
}
out.writeSignedLeb128(size);
int index = 0;
for (EncodedTypeAddrPair handler: handlers) {
out.annotate(0, "[" + index++ + "] encoded_type_addr_pair");
out.indent();
handler.writeTo(out);
out.deindent();
}
if (catchAllHandlerAddress > -1) {
out.annotate("catch_all_addr: 0x" + Integer.toHexString(catchAllHandlerAddress));
out.writeUnsignedLeb128(catchAllHandlerAddress);
}
} else {
int size = handlers.length;
if (catchAllHandlerAddress > -1) {
size = size * -1;
}
out.writeSignedLeb128(size);
for (EncodedTypeAddrPair handler: handlers) {
handler.writeTo(out);
}
if (catchAllHandlerAddress > -1) {
out.writeUnsignedLeb128(catchAllHandlerAddress);
}
}
}
@Override
public int hashCode() {
int hash = 0;
for (EncodedTypeAddrPair handler: handlers) {
hash = hash * 31 + handler.hashCode();
}
hash = hash * 31 + catchAllHandlerAddress;
return hash;
}
@Override
public boolean equals(Object o) {
if (this==o) {
return true;
}
if (o==null || !this.getClass().equals(o.getClass())) {
return false;
}
EncodedCatchHandler other = (EncodedCatchHandler)o;
if (handlers.length != other.handlers.length || catchAllHandlerAddress != other.catchAllHandlerAddress) {
return false;
}
for (int i=0; i<handlers.length; i++) {
if (!handlers[i].equals(other.handlers[i])) {
return false;
}
}
return true;
}
}
public static class EncodedTypeAddrPair {
/**
* The type of the <code>Exception</code> that this handler handles
*/
public final TypeIdItem exceptionType;
/**
* The address (in 2-byte words) in the code of the handler
*/
private int handlerAddress;
/**
* Constructs a new <code>EncodedTypeAddrPair</code> with the given values
* @param exceptionType the type of the <code>Exception</code> that this handler handles
* @param handlerAddress the address (in 2-byte words) in the code of the handler
*/
public EncodedTypeAddrPair(TypeIdItem exceptionType, int handlerAddress) {
this.exceptionType = exceptionType;
this.handlerAddress = handlerAddress;
}
/**
* This is used internally to construct a new <code>EncodedTypeAddrPair</code> while reading in a
* <code>DexFile</code>
* @param dexFile the <code>DexFile</code> that is being read in
* @param in the Input object to read the <code>EncodedCatchHandler</code> from
*/
private EncodedTypeAddrPair(DexFile dexFile, Input in) {
exceptionType = dexFile.TypeIdsSection.getItemByIndex(in.readUnsignedLeb128());
handlerAddress = in.readUnsignedLeb128();
}
/**
* @return the size of this <code>EncodedTypeAddrPair</code>
*/
private int getSize() {
return Leb128Utils.unsignedLeb128Size(exceptionType.getIndex()) +
Leb128Utils.unsignedLeb128Size(handlerAddress);
}
/**
* Writes the <code>EncodedTypeAddrPair</code> to the given <code>AnnotatedOutput</code> object
* @param out the <code>AnnotatedOutput</code> object to write to
*/
private void writeTo(AnnotatedOutput out) {
if (out.annotates()) {
out.annotate("exception_type: " + exceptionType.getTypeDescriptor());
out.writeUnsignedLeb128(exceptionType.getIndex());
out.annotate("handler_addr: 0x" + Integer.toHexString(handlerAddress));
out.writeUnsignedLeb128(handlerAddress);
} else {
out.writeUnsignedLeb128(exceptionType.getIndex());
out.writeUnsignedLeb128(handlerAddress);
}
}
public int getHandlerAddress() {
return handlerAddress;
}
@Override
public int hashCode() {
return exceptionType.hashCode() * 31 + handlerAddress;
}
@Override
public boolean equals(Object o) {
if (this==o) {
return true;
}
if (o==null || !this.getClass().equals(o.getClass())) {
return false;
}
EncodedTypeAddrPair other = (EncodedTypeAddrPair)o;
return exceptionType == other.exceptionType && handlerAddress == other.handlerAddress;
}
}
}