diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/CodeItemPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/CodeItemPool.java index 909aa177..3a67ad3f 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/CodeItemPool.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/CodeItemPool.java @@ -43,9 +43,9 @@ import org.jf.dexlib2.iface.instruction.ReferenceInstruction; import org.jf.dexlib2.iface.instruction.SwitchElement; import org.jf.dexlib2.iface.instruction.formats.*; import org.jf.dexlib2.iface.reference.*; -import org.jf.dexlib2.util.InstructionUtil; import org.jf.dexlib2.util.MethodUtil; import org.jf.dexlib2.util.ReferenceUtil; +import org.jf.dexlib2.writer.util.InstructionWriteUtil; import org.jf.util.ExceptionWithContext; import javax.annotation.Nonnull; @@ -149,30 +149,15 @@ public class CodeItemPool { writer.writeUshort(methodImpl.getRegisterCount()); writer.writeUshort(MethodUtil.getParameterRegisterCount(method, MethodUtil.isStatic(method))); - int maxOutParamCount = 0; - int codeUnitCount = 0; - for (Instruction instruction: methodImpl.getInstructions()) { - codeUnitCount += instruction.getCodeUnits(); - if (instruction.getOpcode().referenceType == ReferenceType.METHOD) { - ReferenceInstruction refInsn = (ReferenceInstruction)instruction; - MethodReference methodRef = (MethodReference)refInsn.getReference(); - int paramCount = MethodUtil.getParameterRegisterCount(methodRef, - InstructionUtil.isInvokeStatic(instruction.getOpcode())); - if (paramCount > maxOutParamCount) { - maxOutParamCount = paramCount; - } - } - } - writer.writeUshort(maxOutParamCount); + InstructionWriteUtil instrWriteUtil = new InstructionWriteUtil(methodImpl, dexFile.stringPool); + writer.writeUshort(instrWriteUtil.getOutParamCount()); List tryBlocks = methodImpl.getTryBlocks(); writer.writeUshort(tryBlocks.size()); writer.writeInt(dexFile.debugInfoPool.getOffset(method)); - writer.writeInt(codeUnitCount); + writer.writeInt(instrWriteUtil.getCodeUnitCount()); - // TODO: need to fix up instructions. Add alignment nops, convert to const-string/jumbos, etc. - - for (Instruction instruction: methodImpl.getInstructions()) { + for (Instruction instruction: instrWriteUtil.getInstructions()) { switch (instruction.getOpcode().format) { case Format10t: writeFormat10t(writer, (Instruction10t)instruction); @@ -274,8 +259,15 @@ public class CodeItemPool { DexWriter.writeUleb128(ehBuf, exceptionHandlerOffsetMap.size()); for (TryBlock tryBlock: tryBlocks) { - writer.writeInt(tryBlock.getStartCodeAddress()); - writer.writeUshort(tryBlock.getCodeUnitCount()); + int startAddress = tryBlock.getStartCodeAddress(); + int endAddress = startAddress + tryBlock.getCodeUnitCount(); + + startAddress += instrWriteUtil.codeOffsetShift(startAddress); + endAddress += instrWriteUtil.codeOffsetShift(endAddress); + int tbCodeUnitCount = endAddress - startAddress; + + writer.writeInt(startAddress); + writer.writeUshort(tbCodeUnitCount); if (tryBlock.getExceptionHandlers().size() == 0) { throw new ExceptionWithContext("No exception handlers for the try block!"); @@ -303,6 +295,7 @@ public class CodeItemPool { for (ExceptionHandler eh : tryBlock.getExceptionHandlers()) { String exceptionType = eh.getExceptionType(); int codeAddress = eh.getHandlerCodeAddress(); + codeAddress += instrWriteUtil.codeOffsetShift(codeAddress); if (exceptionType != null) { //regular exception handling diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/util/InstructionWriteUtil.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/util/InstructionWriteUtil.java new file mode 100644 index 00000000..438a5c57 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/util/InstructionWriteUtil.java @@ -0,0 +1,375 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. 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.jf.dexlib2.writer.util; + +import com.google.common.collect.Lists; +import org.jf.dexlib2.Format; +import org.jf.dexlib2.Opcode; +import org.jf.dexlib2.ReferenceType; +import org.jf.dexlib2.iface.MethodImplementation; +import org.jf.dexlib2.iface.instruction.Instruction; +import org.jf.dexlib2.iface.instruction.ReferenceInstruction; +import org.jf.dexlib2.iface.instruction.SwitchElement; +import org.jf.dexlib2.iface.instruction.SwitchPayload; +import org.jf.dexlib2.iface.instruction.formats.*; +import org.jf.dexlib2.iface.reference.*; +import org.jf.dexlib2.immutable.instruction.*; +import org.jf.dexlib2.util.InstructionUtil; +import org.jf.dexlib2.util.MethodUtil; +import org.jf.dexlib2.writer.StringPool; +import org.jf.util.ExceptionWithContext; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; + +public class InstructionWriteUtil { + private final StringPool stringPool; + MethodImplementation methodImplementation; + + private List instructions; + private ArrayList codeOffsetShifts; + + private int codeUnitCount; + private int outParamCount; + + public InstructionWriteUtil(@Nonnull MethodImplementation methodImpl, @Nonnull StringPool stringPool) { + this.stringPool = stringPool; + methodImplementation = methodImpl; + + calculateMaxOutParamCount(); + findCodeOffsetShifts(); + modifyInstructions(); + } + + private void calculateMaxOutParamCount() { + for (Instruction instruction: methodImplementation.getInstructions()) { + codeUnitCount += instruction.getCodeUnits(); + if (instruction.getOpcode().referenceType == ReferenceType.METHOD) { + ReferenceInstruction refInsn = (ReferenceInstruction)instruction; + MethodReference methodRef = (MethodReference)refInsn.getReference(); + int paramCount = MethodUtil.getParameterRegisterCount(methodRef, InstructionUtil.isInvokeStatic(instruction.getOpcode())); + if (paramCount > outParamCount) { + outParamCount = paramCount; + } + } + } + } + + public Iterable getInstructions() { + if (instructions != null) { + return instructions; + } else { + return methodImplementation.getInstructions(); + } + } + + public int getCodeUnitCount() { + return codeUnitCount; + } + + public int getOutParamCount() { + return outParamCount; + } + + private int targetOffsetShift(int instrOffset, int targetOffset) { + int targetOffsetShift = 0; + if (codeOffsetShifts != null) { + int instrShift = codeOffsetShift(instrOffset); + int targetShift = codeOffsetShift(instrOffset+targetOffset); + targetOffsetShift = targetShift - instrShift; + } + return targetOffsetShift; + } + + public int codeOffsetShift(int offset) { + int shift = 0; + if (codeOffsetShifts != null) { + int numCodeOffsetShifts = codeOffsetShifts.size(); + if (numCodeOffsetShifts > 0) { + if (offset >= codeOffsetShifts.get(numCodeOffsetShifts-1)) { + shift = numCodeOffsetShifts; + } else if (numCodeOffsetShifts>1) { + for (int i=1;i= codeOffsetShifts.get(i-1) && offset < codeOffsetShifts.get(i)) { + shift = i; + break; + } + } + } + } + } + return shift; + } + + /** + * This method creates a list of code offsets of instructions, whose (and subsequent instructions') + * code offset will get shifted by one code unit with respect to previous instruction(s). + * This happens when the previous instruction has to be changed to a larger sized one + * to fit the new value or payload instruction has to be prepended by nop to ensure alignment. + */ + private void findCodeOffsetShifts() { + // first, process const-string to const-string/jumbo conversions + int currentCodeOffset = 0; + for (Instruction instruction: methodImplementation.getInstructions()) { + if (instruction.getOpcode().equals(Opcode.CONST_STRING)) { + ReferenceInstruction refInstr = (ReferenceInstruction) instruction; + int referenceIndex = stringPool.getIndex((StringReference)refInstr.getReference()); + if (referenceIndex > 0xFFFF) { + if (codeOffsetShifts == null) { + codeOffsetShifts = new ArrayList(); + } + codeOffsetShifts.add(currentCodeOffset+instruction.getCodeUnits()); + } + } + currentCodeOffset += instruction.getCodeUnits(); + } + + if (codeOffsetShifts == null) { + return; + } + + // next, let's check if this caused any conversions in goto instructions due to changes in offset values + // since code offset delta is equivalent to the position of instruction's code offset in the shift list, + // we use it as a position here + // we also check if we will have to insert nops to ensure 4-byte alignment for switch statements and packed arrays + currentCodeOffset = 0; + for (Instruction instruction: methodImplementation.getInstructions()) { + if (instruction.getOpcode().format.equals(Format.Format10t)) { + int targetOffset = ((Instruction10t)instruction).getCodeOffset(); + int codeOffsetDelta = codeOffsetShift(currentCodeOffset); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if ((byte)newTargetOffset != newTargetOffset) { + if ((short)newTargetOffset != newTargetOffset) { + // handling very small (negligible) possibility of goto becoming goto/32 + // we insert extra 1 code unit shift referring to the same position + // this will cause subsequent code offsets to be shifted by 2 code units + codeOffsetShifts.add(codeOffsetDelta, currentCodeOffset+instruction.getCodeUnits()); + } + codeOffsetShifts.add(codeOffsetDelta, currentCodeOffset+instruction.getCodeUnits()); + } + } else if (instruction.getOpcode().format.equals(Format.Format20t)) { + int targetOffset = ((Instruction20t)instruction).getCodeOffset(); + int codeOffsetDelta = codeOffsetShift(currentCodeOffset); + int newTargetOffset = targetOffsetShift(currentCodeOffset, targetOffset); + if ((short)newTargetOffset != newTargetOffset) { + codeOffsetShifts.add(codeOffsetDelta, currentCodeOffset+instruction.getCodeUnits()); + } + } else if (instruction.getOpcode().format.equals(Format.ArrayPayload) + || instruction.getOpcode().format.equals(Format.SparseSwitchPayload) + || instruction.getOpcode().format.equals(Format.PackedSwitchPayload)) { + int codeOffsetDelta = codeOffsetShift(currentCodeOffset); + if ((currentCodeOffset+codeOffsetDelta)%2 != 0) { + codeOffsetShifts.add(codeOffsetDelta, currentCodeOffset); + } + } + currentCodeOffset += instruction.getCodeUnits(); + } + + codeUnitCount += codeOffsetShifts.size(); + } + + private void modifyInstructions() { + if (codeOffsetShifts == null) { + return; + } + + instructions = Lists.newArrayList(); + int currentCodeOffset = 0; + for (Instruction instruction: methodImplementation.getInstructions()) { + Instruction modifiedInstruction = null; + switch (instruction.getOpcode().format) { + case Format10t: { + Instruction10t instr = (Instruction10t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + if ((byte)newTargetOffset != newTargetOffset) { + if ((short)newTargetOffset != newTargetOffset) { + modifiedInstruction = new ImmutableInstruction30t(Opcode.GOTO_32, newTargetOffset); + } else { + modifiedInstruction = new ImmutableInstruction20t(Opcode.GOTO_16, newTargetOffset); + } + } else { + modifiedInstruction = new ImmutableInstruction10t(instr.getOpcode(), newTargetOffset); + } + } + break; + } + case Format20t: { + Instruction20t instr = (Instruction20t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + if ((short)newTargetOffset != newTargetOffset) { + modifiedInstruction = new ImmutableInstruction30t(Opcode.GOTO_32, newTargetOffset); + } else { + modifiedInstruction = new ImmutableInstruction20t(Opcode.GOTO_16, newTargetOffset); + } + } + break; + } + case Format21c: { + Instruction21c instr = (Instruction21c)instruction; + if (instr.getOpcode().equals(Opcode.CONST_STRING)) { + int referenceIndex = stringPool.getIndex((StringReference)instr.getReference()); + if (referenceIndex > 0xFFFF) { + modifiedInstruction = new ImmutableInstruction31c(Opcode.CONST_STRING_JUMBO, instr.getRegisterA(), instr.getReference()); + } + } + break; + } + case Format21t: { + Instruction21t instr = (Instruction21t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction21t(instr.getOpcode(), instr.getRegisterA(), newTargetOffset); + } + break; + } + case Format22t: { + Instruction22t instr = (Instruction22t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction22t(instr.getOpcode(), instr.getRegisterA(), instr.getRegisterB(), newTargetOffset); + } + break; + } + case Format30t: { + Instruction30t instr = (Instruction30t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction30t(instr.getOpcode(), newTargetOffset); + } + break; + } + case Format31t: { + Instruction31t instr = (Instruction31t)instruction; + int targetOffset = instr.getCodeOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(currentCodeOffset, targetOffset); + if (newTargetOffset != targetOffset) { + modifiedInstruction = new ImmutableInstruction31t(instr.getOpcode(), instr.getRegisterA(), newTargetOffset); + } + break; + } + case SparseSwitchPayload: { + alignPayload(currentCodeOffset); + int switchInstructionOffset = findSwitchInstructionOffset(currentCodeOffset); + SwitchPayload payload = (SwitchPayload)instruction; + if (isSwitchTargetOffsetChanged(payload, switchInstructionOffset)) { + List newSwitchElements = modifySwitchElements(payload, switchInstructionOffset); + modifiedInstruction = new ImmutableSparseSwitchPayload(newSwitchElements); + } + break; + } + case PackedSwitchPayload: { + alignPayload(currentCodeOffset); + int switchInstructionOffset = findSwitchInstructionOffset(currentCodeOffset); + SwitchPayload payload = (SwitchPayload)instruction; + if (isSwitchTargetOffsetChanged(payload, switchInstructionOffset)) { + List newSwitchElements = modifySwitchElements(payload, switchInstructionOffset); + modifiedInstruction = new ImmutablePackedSwitchPayload(newSwitchElements); + } + break; + } + case ArrayPayload: { + alignPayload(currentCodeOffset); + break; + } + } + + if (modifiedInstruction != null) { + instructions.add(modifiedInstruction); + } else { + instructions.add(instruction); + } + + currentCodeOffset += instruction.getCodeUnits(); + } + } + + private void alignPayload(int codeOffset) { + if (codeOffsetShifts.contains(codeOffset)) { + instructions.add(new ImmutableInstruction10x(Opcode.NOP)); + } + } + + private int findSwitchInstructionOffset(int payloadOffset) { + int currentCodeOffset = 0; + int switchInstructionOffset = -1; + for (Instruction instruction: methodImplementation.getInstructions()) { + if (instruction.getOpcode().equals(Opcode.PACKED_SWITCH) + || instruction.getOpcode().equals(Opcode.SPARSE_SWITCH)) { + int targetOffset = currentCodeOffset + ((Instruction31t)instruction).getCodeOffset(); + if (targetOffset == payloadOffset) { + if (switchInstructionOffset < 0) { + switchInstructionOffset = currentCodeOffset; + } else { + throw new ExceptionWithContext("Multiple switch instructions refer to the same switch payload!"); + } + } + } + currentCodeOffset += instruction.getCodeUnits(); + } + return switchInstructionOffset; + } + + private boolean isSwitchTargetOffsetChanged(SwitchPayload payload, int switchInstructionOffset) { + for (SwitchElement switchElement: payload.getSwitchElements()) { + if (targetOffsetShift(switchInstructionOffset, switchElement.getOffset()) != 0) { + return true; + } + } + return false; + } + + private ArrayList modifySwitchElements(SwitchPayload payload, int switchInstructionOffset) { + ArrayList switchElements = Lists.newArrayList(); + for (SwitchElement switchElement: payload.getSwitchElements()) { + int targetOffset = switchElement.getOffset(); + int newTargetOffset = targetOffset + targetOffsetShift(switchInstructionOffset, targetOffset); + if (newTargetOffset != targetOffset) { + ImmutableSwitchElement immuSwitchElement = new ImmutableSwitchElement(switchElement.getKey(), newTargetOffset); + switchElements.add(immuSwitchElement); + } else { + switchElements.add(switchElement); + } + } + return switchElements; + } + +} + +