Handling jumbo string conversions and consequent offset adjustments for branch target instructions and 4-byte alignment enforcement for payload instructions (by prepending them with nops).

This commit is contained in:
Izzat Bahadirov 2013-02-20 23:28:37 -05:00
parent 7b89cbdf6b
commit 15ae0affc4
2 changed files with 390 additions and 22 deletions

View File

@ -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<? extends TryBlock> 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

View File

@ -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<Instruction> instructions;
private ArrayList<Integer> 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<? extends Instruction> 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<numCodeOffsetShifts;i++) {
if (offset >= 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<Integer>();
}
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<SwitchElement> 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<SwitchElement> 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<SwitchElement> modifySwitchElements(SwitchPayload payload, int switchInstructionOffset) {
ArrayList<SwitchElement> 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;
}
}