mirror of
https://github.com/revanced/smali.git
synced 2025-05-28 11:50:12 +02:00
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:
parent
7b89cbdf6b
commit
15ae0affc4
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user