mirror of
https://github.com/revanced/smali.git
synced 2025-06-12 12:17:37 +02:00
Implemented deodex functionality
git-svn-id: https://smali.googlecode.com/svn/trunk@637 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
@ -6,15 +6,13 @@ import org.jf.dexlib.ItemType;
|
||||
import org.jf.dexlib.MethodIdItem;
|
||||
import org.jf.dexlib.Util.ExceptionWithContext;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
public class AnalyzedInstruction {
|
||||
public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||
/**
|
||||
* The actual instruction
|
||||
*/
|
||||
public final Instruction instruction;
|
||||
protected Instruction instruction;
|
||||
|
||||
/**
|
||||
* The index of the instruction, where the first instruction in the method is at index 0, and so on
|
||||
@ -24,35 +22,59 @@ public class AnalyzedInstruction {
|
||||
/**
|
||||
* Instructions that can pass on execution to this one during normal execution
|
||||
*/
|
||||
protected final LinkedList<AnalyzedInstruction> predecessors = new LinkedList<AnalyzedInstruction>();
|
||||
protected final TreeSet<AnalyzedInstruction> predecessors = new TreeSet<AnalyzedInstruction>();
|
||||
|
||||
/**
|
||||
* Instructions that can execution could pass on to next during normal execution
|
||||
*/
|
||||
protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
|
||||
|
||||
/**
|
||||
* This contains the register types *before* the instruction has executed
|
||||
*/
|
||||
protected final RegisterType[] preRegisterMap;
|
||||
|
||||
/**
|
||||
* This contains the register types *after* the instruction has executed
|
||||
*/
|
||||
protected final RegisterType[] postRegisterMap;
|
||||
|
||||
/**
|
||||
* This is set to true when this instruction follows an odexed instruction that couldn't be deodexed. In this case
|
||||
* the unodexable instruction is guaranteed to throw an NPE, so anything following it is dead, up until a non-dead
|
||||
* code path merges in. And more importantly, the code following the unodexable instruction isn't verifiable in
|
||||
* some cases, if it depends on the return/field type of the unodexeable instruction. Meaning that if the "dead"
|
||||
* code was left in, dalvik would reject it because it couldn't verify the register types. In some cases, this
|
||||
* dead code could be left in without ill-effect, but it's easier to always remove it, which is always valid. Since
|
||||
* it is dead code, removing it won't have any effect.
|
||||
* When deodexing, we might need to deodex this instruction multiple times, when we merge in new register
|
||||
* information. When this happens, we need to restore the original (odexed) instruction, so we can deodex it again
|
||||
*/
|
||||
protected final Instruction originalInstruction;
|
||||
|
||||
|
||||
/**
|
||||
* A dead instruction is one that is unreachable because it follows an odexed instruction that can't be deodexed
|
||||
* because it's object register is always null. In the non-odexed code that the odex was generated from, we would
|
||||
* have technically considered this code reachable and could verify it, even though the instruction that ended up
|
||||
* being odexed was always null, because we would assume both "paths" out of the instruction are valid - the one
|
||||
* where execution proceeds normally to the next instruction, and the one where an exception occurs and execution
|
||||
* either goes to a catch block, or out of the method.
|
||||
*
|
||||
* However, in the odexed case, we can't verify the code following an undeodexable instruction because we lack
|
||||
* the register information from the undeodexable instruction - because we don't know the actual method or field
|
||||
* that is being accessed.
|
||||
*
|
||||
* The undeodexable instruction is guaranteed to throw an NPE, so the following code is effectivetly unreachable.
|
||||
* Once we detect an undeodexeable instruction, the following code is marked as dead up until a non-dead execution
|
||||
* path merges in. Additionally, we remove the predecessors/successors of any dead instruction. For example, if
|
||||
* there is a dead goto instruction, then we would remove the target instruction as a successor, and we would
|
||||
* also remove the dead goto instruction as a predecessor to the target.
|
||||
*/
|
||||
protected boolean dead = false;
|
||||
|
||||
public AnalyzedInstruction(Instruction instruction, int instructionIndex, int registerCount) {
|
||||
this.instruction = instruction;
|
||||
this.originalInstruction = instruction;
|
||||
this.instructionIndex = instructionIndex;
|
||||
this.postRegisterMap = new RegisterType[registerCount];
|
||||
this.preRegisterMap = new RegisterType[registerCount];
|
||||
RegisterType unknown = RegisterType.getRegisterType(RegisterType.Category.Unknown, null);
|
||||
for (int i=0; i<registerCount; i++) {
|
||||
preRegisterMap[i] = unknown;
|
||||
postRegisterMap[i] = unknown;
|
||||
}
|
||||
}
|
||||
@ -65,38 +87,26 @@ public class AnalyzedInstruction {
|
||||
return predecessors.size();
|
||||
}
|
||||
|
||||
public List<AnalyzedInstruction> getPredecessors() {
|
||||
return Collections.unmodifiableList(predecessors);
|
||||
public SortedSet<AnalyzedInstruction> getPredecessors() {
|
||||
return Collections.unmodifiableSortedSet(predecessors);
|
||||
}
|
||||
|
||||
private boolean checkPredecessorSorted(AnalyzedInstruction predecessor) {
|
||||
if (predecessors.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (predecessor.getInstructionIndex() <= predecessors.getLast().getInstructionIndex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
protected boolean addPredecessor(AnalyzedInstruction predecessor) {
|
||||
return predecessors.add(predecessor);
|
||||
}
|
||||
|
||||
protected void addPredecessor(AnalyzedInstruction predecessor) {
|
||||
assert checkPredecessorSorted(predecessor);
|
||||
predecessors.add(predecessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the successor was added or false if it wasn't added because it already existed
|
||||
*/
|
||||
protected boolean addSuccessor(AnalyzedInstruction successor) {
|
||||
for (AnalyzedInstruction instruction: successors) {
|
||||
if (instruction == successor) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
protected void addSuccessor(AnalyzedInstruction successor) {
|
||||
successors.add(successor);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void setDeodexedInstruction(Instruction instruction) {
|
||||
assert originalInstruction.opcode.odexOnly();
|
||||
this.instruction = instruction;
|
||||
}
|
||||
|
||||
protected void restoreOdexedInstruction() {
|
||||
assert originalInstruction.opcode.odexOnly();
|
||||
instruction = originalInstruction;
|
||||
}
|
||||
|
||||
public int getSuccessorCount() {
|
||||
@ -107,6 +117,18 @@ public class AnalyzedInstruction {
|
||||
return Collections.unmodifiableList(successors);
|
||||
}
|
||||
|
||||
public Instruction getInstruction() {
|
||||
return instruction;
|
||||
}
|
||||
|
||||
public Instruction getOriginalInstruction() {
|
||||
return originalInstruction;
|
||||
}
|
||||
|
||||
public boolean isDead() {
|
||||
return dead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this instruction a "beginning instruction". A beginning instruction is defined to be an instruction
|
||||
* that can be the first successfully executed instruction in the method. The first instruction is always a
|
||||
@ -114,6 +136,9 @@ public class AnalyzedInstruction {
|
||||
* the first instruction of any exception handler for that try block is also a beginning instruction. And likewise,
|
||||
* if any of those instructions can throw an exception and are covered by try blocks, the first instruction of the
|
||||
* corresponding exception handler is a beginning instruction, etc.
|
||||
*
|
||||
* To determine this, we simply check if the first predecessor is the fake "StartOfMethod" instruction, which has
|
||||
* an instruction index of -1.
|
||||
* @return a boolean value indicating whether this instruction is a beginning instruction
|
||||
*/
|
||||
public boolean isBeginningInstruction() {
|
||||
@ -121,34 +146,50 @@ public class AnalyzedInstruction {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (predecessors.getFirst().instructionIndex == -1) {
|
||||
if (predecessors.first().instructionIndex == -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the "post-instruction" register type as indicated. This should only be used to set
|
||||
* the method parameter types for the "start of method" instruction, or to set the register
|
||||
* type of the destination register during verification. The change to the register type
|
||||
* will
|
||||
* Merges the given register type into the specified pre-instruction register, and also sets the post-instruction
|
||||
* register type accordingly if it isn't a destination register for this instruction
|
||||
* @param registerNumber Which register to set
|
||||
* @param registerType The "post-instruction" register type
|
||||
* @param registerType The register type
|
||||
* @returns true If the post-instruction register type was changed. This might be false if either the specified
|
||||
* register is a destination register for this instruction, or if the pre-instruction register type didn't change
|
||||
* after merging in the given register type
|
||||
*/
|
||||
protected boolean setPostRegisterType(int registerNumber, RegisterType registerType) {
|
||||
protected boolean mergeRegister(int registerNumber, RegisterType registerType, BitSet verifiedInstructions) {
|
||||
assert registerNumber >= 0 && registerNumber < postRegisterMap.length;
|
||||
assert registerType != null;
|
||||
|
||||
RegisterType oldRegisterType = postRegisterMap[registerNumber];
|
||||
if (oldRegisterType == registerType) {
|
||||
RegisterType oldRegisterType = preRegisterMap[registerNumber];
|
||||
RegisterType mergedRegisterType = oldRegisterType.merge(registerType);
|
||||
|
||||
if (mergedRegisterType == oldRegisterType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
postRegisterMap[registerNumber] = registerType;
|
||||
return true;
|
||||
preRegisterMap[registerNumber] = mergedRegisterType;
|
||||
verifiedInstructions.clear(instructionIndex);
|
||||
|
||||
if (!setsRegister(registerNumber)) {
|
||||
postRegisterMap[registerNumber] = mergedRegisterType;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected RegisterType getMergedRegisterTypeFromPredecessors(int registerNumber) {
|
||||
/**
|
||||
* Iterates over the predecessors of this instruction, and merges all the post-instruction register types for the
|
||||
* given register. Any dead, unreachable, or odexed predecessor is ignored
|
||||
* @param registerNumber
|
||||
* @return The register type resulting from merging the post-instruction register types from all predecessors
|
||||
*/
|
||||
protected RegisterType mergePreRegisterTypeFromPredecessors(int registerNumber) {
|
||||
RegisterType mergedRegisterType = null;
|
||||
for (AnalyzedInstruction predecessor: predecessors) {
|
||||
RegisterType predecessorRegisterType = predecessor.postRegisterMap[registerNumber];
|
||||
@ -158,9 +199,30 @@ public class AnalyzedInstruction {
|
||||
return mergedRegisterType;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the "post-instruction" register type as indicated.
|
||||
* @param registerNumber Which register to set
|
||||
* @param registerType The "post-instruction" register type
|
||||
* @returns true if the given register type is different than the existing post-instruction register type
|
||||
*/
|
||||
protected boolean setPostRegisterType(int registerNumber, RegisterType registerType) {
|
||||
assert registerNumber >= 0 && registerNumber < postRegisterMap.length;
|
||||
assert registerType != null;
|
||||
|
||||
RegisterType oldRegisterType = postRegisterMap[registerNumber];
|
||||
if (oldRegisterType == registerType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
postRegisterMap[registerNumber] = registerType;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected boolean isInvokeInit() {
|
||||
if (instruction == null ||
|
||||
(instruction.opcode != Opcode.INVOKE_DIRECT && instruction.opcode != Opcode.INVOKE_DIRECT_RANGE)) {
|
||||
(instruction.opcode != Opcode.INVOKE_DIRECT && instruction.opcode != Opcode.INVOKE_DIRECT_RANGE &&
|
||||
instruction.opcode != Opcode.INVOKE_DIRECT_EMPTY)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -187,7 +249,6 @@ public class AnalyzedInstruction {
|
||||
}
|
||||
|
||||
public boolean setsRegister(int registerNumber) {
|
||||
|
||||
//When constructing a new object, the register type will be an uninitialized reference after the new-instance
|
||||
//instruction, but becomes an initialized reference once the <init> method is called. So even though invoke
|
||||
//instructions don't normally change any registers, calling an <init> method will change the type of its
|
||||
@ -207,14 +268,14 @@ public class AnalyzedInstruction {
|
||||
if (registerNumber == destinationRegister) {
|
||||
return true;
|
||||
}
|
||||
RegisterType preInstructionDestRegisterType = getMergedRegisterTypeFromPredecessors(registerNumber);
|
||||
RegisterType preInstructionDestRegisterType = getPreInstructionRegisterType(registerNumber);
|
||||
if (preInstructionDestRegisterType.category != RegisterType.Category.UninitRef &&
|
||||
preInstructionDestRegisterType.category != RegisterType.Category.UninitThis) {
|
||||
|
||||
return false;
|
||||
}
|
||||
//check if the uninit ref has been copied to another register
|
||||
if (getMergedRegisterTypeFromPredecessors(registerNumber) == preInstructionDestRegisterType) {
|
||||
if (getPreInstructionRegisterType(registerNumber) == preInstructionDestRegisterType) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -251,14 +312,17 @@ public class AnalyzedInstruction {
|
||||
}
|
||||
|
||||
public RegisterType getPreInstructionRegisterType(int registerNumber) {
|
||||
//if the specific register is not a destination register, then the stored post-instruction register type will
|
||||
//be the same as the pre-instruction regsiter type, so we can use that.
|
||||
//otherwise, we need to merge the predecessor's post-instruction register types
|
||||
return preRegisterMap[registerNumber];
|
||||
}
|
||||
|
||||
if (this.setsRegister(registerNumber)) {
|
||||
return getMergedRegisterTypeFromPredecessors(registerNumber);
|
||||
public int compareTo(AnalyzedInstruction analyzedInstruction) {
|
||||
//TODO: out of curiosity, check the disassembly of this to see if it retrieves the value of analyzedInstruction.instructionIndex for every access. It should, because the field is final. What about if we set the field to non-final?
|
||||
if (instructionIndex < analyzedInstruction.instructionIndex) {
|
||||
return -1;
|
||||
} else if (instructionIndex == analyzedInstruction.instructionIndex) {
|
||||
return 0;
|
||||
} else {
|
||||
return postRegisterMap[registerNumber];
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,24 @@ public class ClassPath {
|
||||
private final HashMap<String, ClassDef> classDefs;
|
||||
protected ClassDef javaLangObjectClassDef; //Ljava/lang/Object;
|
||||
|
||||
private final static String[][] inlineMethods =
|
||||
new String[][] {
|
||||
{ "Lorg/apache/harmony/dalvik/NativeTestTarget;", "emptyInlineMethod()V"},
|
||||
{ "Ljava/lang/String;", "charAt(I)C"},
|
||||
{ "Ljava/lang/String;", "compareTo(Ljava/lang/String;)I"},
|
||||
{ "Ljava/lang/String;", "equals(Ljava/lang/Object;)Z"},
|
||||
{ "Ljava/lang/String;", "length()I"},
|
||||
{ "Ljava/lang/Math;", "abs(I)I"},
|
||||
{ "Ljava/lang/Math;", "abs(J)J"},
|
||||
{ "Ljava/lang/Math;", "abs(F)F"},
|
||||
{ "Ljava/lang/Math;", "abs(D)D"},
|
||||
{ "Ljava/lang/Math;", "min(I)I"},
|
||||
{ "Ljava/lang/Math;", "max(II)I"},
|
||||
{ "Ljava/lang/Math;", "sqrt(D)D"},
|
||||
{ "Ljava/lang/Math;", "cos(D)D"},
|
||||
{ "Ljava/lang/Math;", "sin(D)D"}
|
||||
};
|
||||
|
||||
public static void InitializeClassPath(String bootClassPathDir, String[] bootClassPath, DexFile dexFile) {
|
||||
if (theClassPath != null) {
|
||||
throw new ExceptionWithContext("Cannot initialize ClassPath multiple times");
|
||||
@ -37,7 +55,9 @@ public class ClassPath {
|
||||
}
|
||||
}
|
||||
|
||||
loadDexFile(dexFile);
|
||||
if (dexFile != null) {
|
||||
loadDexFile(dexFile);
|
||||
}
|
||||
|
||||
for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) {
|
||||
ClassDef classDef = new PrimitiveClassDef(primitiveType);
|
||||
@ -76,8 +96,6 @@ public class ClassPath {
|
||||
if (classDefItem.getClassType().getTypeDescriptor().equals("Ljava/lang/Object;")) {
|
||||
theClassPath.javaLangObjectClassDef = classDef;
|
||||
}
|
||||
/*classDef.dumpVtable();
|
||||
classDef.dumpFields();*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,15 +105,23 @@ public class ClassPath {
|
||||
}
|
||||
}
|
||||
|
||||
public static ClassDef getClassDef(String classType) {
|
||||
public static ClassDef getClassDef(String classType) {
|
||||
return getClassDef(classType, true);
|
||||
}
|
||||
|
||||
public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef) {
|
||||
ClassDef classDef = theClassPath.classDefs.get(classType);
|
||||
if (classDef == null) {
|
||||
//if it's an array class, try to create it
|
||||
if (classType.charAt(0) == '[') {
|
||||
return theClassPath.createArrayClassDef(classType);
|
||||
} else {
|
||||
//TODO: we should output a warning
|
||||
return theClassPath.createUnresolvedClassDef(classType);
|
||||
if (createUnresolvedClassDef) {
|
||||
//TODO: we should output a warning
|
||||
return theClassPath.createUnresolvedClassDef(classType);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return classDef;
|
||||
@ -105,6 +131,10 @@ public class ClassPath {
|
||||
return getClassDef(classType.getTypeDescriptor());
|
||||
}
|
||||
|
||||
public static ClassDef getClassDef(TypeIdItem classType, boolean creatUnresolvedClassDef) {
|
||||
return getClassDef(classType.getTypeDescriptor(), creatUnresolvedClassDef);
|
||||
}
|
||||
|
||||
//256 [ characters
|
||||
private static final String arrayPrefix = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" +
|
||||
"[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" +
|
||||
@ -149,14 +179,14 @@ public class ClassPath {
|
||||
|
||||
//TODO: do we want to handle primitive types here? I don't think so.. (if not, add assert)
|
||||
|
||||
if (!class1.isInterface && class2.isInterface) {
|
||||
if (class2.isInterface) {
|
||||
if (class1.implementsInterface(class2)) {
|
||||
return class2;
|
||||
}
|
||||
return theClassPath.javaLangObjectClassDef;
|
||||
}
|
||||
|
||||
if (!class2.isInterface && class1.isInterface) {
|
||||
if (class1.isInterface) {
|
||||
if (class2.implementsInterface(class1)) {
|
||||
return class1;
|
||||
}
|
||||
@ -220,6 +250,15 @@ public class ClassPath {
|
||||
return getArrayClassDefByElementClassAndDimension(theClassPath.javaLangObjectClassDef, dimensions);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String[] getInlineMethod(int inlineIndex) {
|
||||
if (inlineIndex < 0 || inlineIndex >= inlineMethods.length) {
|
||||
return null;
|
||||
}
|
||||
return inlineMethods[inlineIndex];
|
||||
}
|
||||
|
||||
public static class ArrayClassDef extends ClassDef {
|
||||
private final ClassDef elementClass;
|
||||
private final int arrayDimensions;
|
||||
@ -382,6 +421,18 @@ public class ClassPath {
|
||||
public final static int PrimitiveClassDef = 1;
|
||||
public final static int UnresolvedClassDef = 2;
|
||||
|
||||
|
||||
/**
|
||||
* The following fields are used only during the initial loading of classes, and are set to null afterwards
|
||||
* TODO: free these
|
||||
*/
|
||||
|
||||
//This is only the virtual methods that this class declares itself.
|
||||
private String[] virtualMethods;
|
||||
//this is a list of all the interfaces that the class implements directory, or any super interfaces of those
|
||||
//interfaces. It is generated in such a way that it is ordered in the same way as dalvik's ClassObject.iftable,
|
||||
private LinkedHashMap<String, ClassDef> interfaceTable;
|
||||
|
||||
/**
|
||||
* This constructor is used for the ArrayClassDef, PrimitiveClassDef and UnresolvedClassDef subclasses
|
||||
* @param classType the class type
|
||||
@ -403,6 +454,9 @@ public class ClassPath {
|
||||
instanceFields = superclass.instanceFields;
|
||||
instanceFieldLookup = superclass.instanceFieldLookup;
|
||||
classDepth = 1; //1 off from java.lang.Object
|
||||
|
||||
virtualMethods = null;
|
||||
interfaceTable = null;
|
||||
} else if (classFlavor == PrimitiveClassDef) {
|
||||
//primitive type
|
||||
assert classType.charAt(0) != '[' && classType.charAt(0) != 'L';
|
||||
@ -416,6 +470,9 @@ public class ClassPath {
|
||||
instanceFields = null;
|
||||
instanceFieldLookup = null;
|
||||
classDepth = 0; //TODO: maybe use -1 to indicate not applicable?
|
||||
|
||||
virtualMethods = null;
|
||||
interfaceTable = null;
|
||||
} else /*if (classFlavor == UnresolvedClassDef)*/ {
|
||||
assert classType.charAt(0) == 'L';
|
||||
this.classType = classType;
|
||||
@ -429,6 +486,9 @@ public class ClassPath {
|
||||
instanceFields = superclass.instanceFields;
|
||||
instanceFieldLookup = superclass.instanceFieldLookup;
|
||||
classDepth = 1; //1 off from java.lang.Object
|
||||
|
||||
virtualMethods = null;
|
||||
interfaceTable = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,6 +506,9 @@ public class ClassPath {
|
||||
|
||||
implementedInterfaces = loadAllImplementedInterfaces(classDefItem);
|
||||
|
||||
//TODO: we can probably get away with only creating the interface table for interface types
|
||||
interfaceTable = loadInterfaceTable(classDefItem);
|
||||
virtualMethods = loadVirtualMethods(classDefItem);
|
||||
vtable = loadVtable(classDefItem);
|
||||
virtualMethodLookup = new HashMap<String, Integer>((int)Math.ceil(vtable.length / .7f), .75f);
|
||||
for (int i=0; i<vtable.length; i++) {
|
||||
@ -513,6 +576,17 @@ public class ClassPath {
|
||||
return virtualMethodLookup.containsKey(method);
|
||||
}
|
||||
|
||||
public String getInstanceField(int fieldOffset) {
|
||||
return this.instanceFields.get(fieldOffset, null);
|
||||
}
|
||||
|
||||
public String getVirtualMethod(int vtableIndex) {
|
||||
if (vtableIndex < 0 || vtableIndex >= vtable.length) {
|
||||
return null;
|
||||
}
|
||||
return this.vtable[vtableIndex];
|
||||
}
|
||||
|
||||
private void swap(byte[] fieldTypes, String[] fields, int position1, int position2) {
|
||||
byte tempType = fieldTypes[position1];
|
||||
fieldTypes[position1] = fieldTypes[position2];
|
||||
@ -586,6 +660,56 @@ public class ClassPath {
|
||||
return implementedInterfaceSet;
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, ClassDef> loadInterfaceTable(ClassDefItem classDefItem) {
|
||||
TypeListItem typeListItem = classDefItem.getInterfaces();
|
||||
if (typeListItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LinkedHashMap<String, ClassDef> interfaceTable = new LinkedHashMap<String, ClassDef>();
|
||||
|
||||
for (TypeIdItem interfaceType: typeListItem.getTypes()) {
|
||||
if (!interfaceTable.containsKey(interfaceType.getTypeDescriptor())) {
|
||||
ClassDef classDef = getClassDef(interfaceType);
|
||||
if (classDef == null) {
|
||||
throw new ValidationException(String.format(
|
||||
"Could not resolve type %s", interfaceType.getTypeDescriptor()));
|
||||
}
|
||||
interfaceTable.put(interfaceType.getTypeDescriptor(), classDef);
|
||||
|
||||
if (classDef.interfaceTable != null) {
|
||||
for (ClassDef superInterface: classDef.interfaceTable.values()) {
|
||||
if (!interfaceTable.containsKey(superInterface.classType)) {
|
||||
interfaceTable.put(superInterface.classType, superInterface);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return interfaceTable;
|
||||
}
|
||||
|
||||
private String[] loadVirtualMethods(ClassDefItem classDefItem) {
|
||||
ClassDataItem classDataItem = classDefItem.getClassData();
|
||||
if (classDataItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EncodedMethod[] virtualEncodedMethods = classDataItem.getVirtualMethods();
|
||||
if (virtualEncodedMethods == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] virtualMethods = new String[virtualEncodedMethods.length];
|
||||
|
||||
for (int i=0; i<virtualEncodedMethods.length; i++) {
|
||||
virtualMethods[i] = virtualEncodedMethods[i].method.getVirtualMethodString();
|
||||
}
|
||||
|
||||
return virtualMethods;
|
||||
}
|
||||
|
||||
private String[] loadVtable(ClassDefItem classDefItem) {
|
||||
//TODO: it might be useful to keep track of which class's implementation is used for each virtual method. In other words, associate the implementing class type with each vtable entry
|
||||
List<String> virtualMethodList = new LinkedList<String>();
|
||||
@ -607,14 +731,32 @@ public class ClassPath {
|
||||
|
||||
//iterate over the virtual methods in the current class, and only add them when we don't already have the
|
||||
//method (i.e. if it was implemented by the superclass)
|
||||
ClassDataItem classDataItem = classDefItem.getClassData();
|
||||
if (classDataItem != null) {
|
||||
EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods();
|
||||
if (virtualMethods != null) {
|
||||
for (EncodedMethod virtualMethod: virtualMethods) {
|
||||
String methodString = virtualMethod.method.getVirtualMethodString();
|
||||
if (tempVirtualMethodLookup.get(methodString) == null) {
|
||||
virtualMethodList.add(methodString);
|
||||
if (!this.isInterface) {
|
||||
ClassDataItem classDataItem = classDefItem.getClassData();
|
||||
if (classDataItem != null) {
|
||||
EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods();
|
||||
if (virtualMethods != null) {
|
||||
for (EncodedMethod virtualMethod: virtualMethods) {
|
||||
String methodString = virtualMethod.method.getVirtualMethodString();
|
||||
if (tempVirtualMethodLookup.get(methodString) == null) {
|
||||
virtualMethodList.add(methodString);
|
||||
tempVirtualMethodLookup.put(methodString, methodIndex++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (interfaceTable != null) {
|
||||
for (ClassDef interfaceDef: interfaceTable.values()) {
|
||||
if (interfaceDef.virtualMethods == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String virtualMethod: interfaceDef.virtualMethods) {
|
||||
if (tempVirtualMethodLookup.get(virtualMethod) == null) {
|
||||
virtualMethodList.add(virtualMethod);
|
||||
tempVirtualMethodLookup.put(virtualMethod, methodIndex++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -628,9 +770,30 @@ public class ClassPath {
|
||||
return vtable;
|
||||
}
|
||||
|
||||
private int getNextFieldOffset() {
|
||||
if (instanceFields == null || instanceFields.size() == 0) {
|
||||
return 8;
|
||||
}
|
||||
|
||||
int lastItemIndex = instanceFields.size()-1;
|
||||
int fieldOffset = instanceFields.keyAt(lastItemIndex);
|
||||
String lastField = instanceFields.valueAt(lastItemIndex);
|
||||
|
||||
int fieldTypeIndex = lastField.indexOf(":") + 1;
|
||||
|
||||
switch (lastField.charAt(fieldTypeIndex)) {
|
||||
case 'J':
|
||||
case 'D':
|
||||
return fieldOffset + 8;
|
||||
default:
|
||||
return fieldOffset + 4;
|
||||
}
|
||||
}
|
||||
|
||||
private SparseArray<String> loadFields(ClassDefItem classDefItem) {
|
||||
//This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
|
||||
//arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
|
||||
//See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
|
||||
|
||||
final byte REFERENCE = 0;
|
||||
final byte WIDE = 1;
|
||||
@ -684,11 +847,24 @@ public class ClassPath {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int startFieldOffset = 8;
|
||||
if (this.superclass != null) {
|
||||
startFieldOffset = this.superclass.getNextFieldOffset();
|
||||
}
|
||||
|
||||
int fieldIndexMod;
|
||||
if ((startFieldOffset % 8) == 0) {
|
||||
fieldIndexMod = 0;
|
||||
} else {
|
||||
fieldIndexMod = 1;
|
||||
}
|
||||
|
||||
//next, we need to group all the wide fields after the reference fields. But the wide fields have to be
|
||||
//8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
|
||||
//is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
|
||||
//If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
|
||||
if (front < fields.length && (front % 2) != 0) {
|
||||
if (front < fields.length && (front % 2) != fieldIndexMod) {
|
||||
if (fieldTypes[front] == WIDE) {
|
||||
//we need to swap in a 32-bit field, so the wide fields will be correctly aligned
|
||||
back = fields.length - 1;
|
||||
@ -725,7 +901,7 @@ public class ClassPath {
|
||||
|
||||
int superFieldCount = 0;
|
||||
if (superclass != null) {
|
||||
superclass.instanceFields.size();
|
||||
superFieldCount = superclass.instanceFields.size();
|
||||
}
|
||||
|
||||
//now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
|
||||
@ -743,7 +919,7 @@ public class ClassPath {
|
||||
|
||||
String lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1);
|
||||
assert lastSuperField.indexOf(':') >= 0;
|
||||
assert lastSuperField.indexOf(':') < superFieldCount-1; //the ':' shouldn't be the last char
|
||||
assert lastSuperField.indexOf(':') < lastSuperField.length()-1; //the ':' shouldn't be the last char
|
||||
char fieldType = lastSuperField.charAt(lastSuperField.indexOf(':') + 1);
|
||||
if (fieldType == 'J' || fieldType == 'D') {
|
||||
fieldOffset += 8;
|
||||
@ -819,4 +995,95 @@ public class ClassPath {
|
||||
return classType.compareTo(classDef.classType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void validateAgainstDeodexerant(String host, int port, int skipClasses) {
|
||||
Deodexerant deodexerant = new Deodexerant(host, port);
|
||||
|
||||
int count = 0;
|
||||
|
||||
try {
|
||||
String[] inlineMethods = deodexerant.getInlineMethods();
|
||||
(new DeodexUtil(null)).checkInlineMethods(inlineMethods);
|
||||
} catch (Exception ex) {
|
||||
throw ExceptionWithContext.withContext(ex, "Error while checking inline methods");
|
||||
}
|
||||
|
||||
try {
|
||||
for (ClassDef classDef: theClassPath.classDefs.values()) {
|
||||
if (count < skipClasses) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
if (classDef instanceof UnresolvedClassDef || classDef instanceof ArrayClassDef ||
|
||||
classDef instanceof PrimitiveClassDef) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] vtable = deodexerant.getVirtualMethods(classDef.classType);
|
||||
|
||||
if (vtable.length != classDef.vtable.length) {
|
||||
throw new ValidationException(String.format("virtual table size mismatch for class %s",
|
||||
classDef.classType));
|
||||
}
|
||||
|
||||
for (int i=0; i<classDef.vtable.length; i++) {
|
||||
if (!classDef.vtable[i].equals(vtable[i])) {
|
||||
throw new ValidationException(String.format("virtual method mismatch for class %s at index %d",
|
||||
classDef.classType, i));
|
||||
}
|
||||
}
|
||||
|
||||
String[] fields = deodexerant.getInstanceFields(classDef.classType);
|
||||
|
||||
if (fields.length != classDef.instanceFields.size()) {
|
||||
throw new ValidationException(String.format("field count mismatch for class %s",
|
||||
classDef.classType));
|
||||
}
|
||||
|
||||
for (int i=0; i<classDef.instanceFields.size(); i++) {
|
||||
String[] fieldValues = fields[i].split(" ");
|
||||
if (fieldValues.length != 2) {
|
||||
throw new ValidationException("Could not parse field");
|
||||
}
|
||||
|
||||
int fieldOffset = Integer.parseInt(fieldValues[0]);
|
||||
|
||||
String field = classDef.instanceFields.get(fieldOffset);
|
||||
if (field == null) {
|
||||
throw new ValidationException("Could not find a field at the given offset");
|
||||
}
|
||||
|
||||
if (!field.equals(fieldValues[1])) {
|
||||
throw new ValidationException(String.format("field offset mismatch for class %s at index %d",
|
||||
classDef.classType, i));
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw ExceptionWithContext.withContext(ex, String.format("Error while checking class #%d", count));
|
||||
}
|
||||
}
|
||||
|
||||
public static String join(String[] vals)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String val: vals) {
|
||||
sb.append(val);
|
||||
sb.append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String join(List<String> vals)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String val: vals) {
|
||||
sb.append(val);
|
||||
sb.append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
323
dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java
Normal file
323
dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java
Normal file
@ -0,0 +1,323 @@
|
||||
package org.jf.dexlib.Code.Analysis;
|
||||
|
||||
import org.jf.dexlib.*;
|
||||
import org.jf.dexlib.Util.ExceptionWithContext;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DeodexUtil {
|
||||
public static final int Virtual = 0;
|
||||
public static final int Direct = 1;
|
||||
public static final int Static = 2;
|
||||
|
||||
private InlineMethod[] inlineMethods = new InlineMethod[] {
|
||||
new InlineMethod(Static, "Lorg/apache/harmony/dalvik/NativeTestTarget;", "emptyInlineMethod", "", "V"),
|
||||
new InlineMethod(Virtual, "Ljava/lang/String;", "charAt", "I", "C"),
|
||||
new InlineMethod(Virtual, "Ljava/lang/String;", "compareTo", "Ljava/lang/String;", "I"),
|
||||
new InlineMethod(Virtual, "Ljava/lang/String;", "equals", "Ljava/lang/Object;", "Z"),
|
||||
new InlineMethod(Virtual, "Ljava/lang/String;", "length", "", "I"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "abs", "I", "I"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "abs", "J", "J"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "abs", "F", "F"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "abs", "D", "D"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "min", "II", "I"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "max", "II", "I"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "sqrt", "D", "D"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "cos", "D", "D"),
|
||||
new InlineMethod(Static, "Ljava/lang/Math;", "sin", "D", "D")
|
||||
};
|
||||
|
||||
public final DexFile dexFile;
|
||||
|
||||
public DeodexUtil(DexFile dexFile) {
|
||||
this.dexFile = dexFile;
|
||||
}
|
||||
|
||||
public InlineMethod lookupInlineMethod(int inlineMethodIndex) {
|
||||
if (inlineMethodIndex >= inlineMethods.length) {
|
||||
throw new RuntimeException("Invalid inline method index " + inlineMethodIndex + ".");
|
||||
}
|
||||
|
||||
return inlineMethods[inlineMethodIndex];
|
||||
}
|
||||
|
||||
private TypeIdItem resolveTypeOrSupertype(ClassPath.ClassDef classDef) {
|
||||
ClassPath.ClassDef originalClassDef = classDef;
|
||||
|
||||
do {
|
||||
TypeIdItem typeItem = TypeIdItem.lookupTypeIdItem(dexFile, classDef.getClassType());
|
||||
|
||||
if (typeItem != null) {
|
||||
return typeItem;
|
||||
}
|
||||
|
||||
classDef = classDef.getSuperclass();
|
||||
} while (classDef != null);
|
||||
|
||||
throw new ExceptionWithContext(String.format("Cannot find type %s in the dex file",
|
||||
originalClassDef.getClassType()));
|
||||
}
|
||||
|
||||
public FieldIdItem lookupField(ClassPath.ClassDef classDef, int fieldOffset) {
|
||||
String field = classDef.getInstanceField(fieldOffset);
|
||||
if (field == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parseAndResolveField(classDef, field);
|
||||
}
|
||||
|
||||
private static final Pattern shortMethodPattern = Pattern.compile("([^(]+)\\(([^)]*)\\)(.+)");
|
||||
|
||||
public MethodIdItem lookupVirtualMethod(ClassPath.ClassDef classDef, int methodIndex) {
|
||||
String method = classDef.getVirtualMethod(methodIndex);
|
||||
|
||||
Matcher m = shortMethodPattern.matcher(method);
|
||||
if (!m.matches()) {
|
||||
assert false;
|
||||
throw new RuntimeException("Invalid method descriptor: " + method);
|
||||
}
|
||||
|
||||
String methodName = m.group(1);
|
||||
String methodParams = m.group(2);
|
||||
String methodRet = m.group(3);
|
||||
|
||||
if (classDef.isInterface()) {
|
||||
classDef = classDef.getSuperclass();
|
||||
assert classDef != null;
|
||||
}
|
||||
|
||||
return parseAndResolveMethod(classDef, methodName, methodParams, methodRet);
|
||||
}
|
||||
|
||||
private MethodIdItem parseAndResolveMethod(ClassPath.ClassDef classDef, String methodName, String methodParams,
|
||||
String methodRet) {
|
||||
StringIdItem methodNameItem = StringIdItem.lookupStringIdItem(dexFile, methodName);
|
||||
if (methodNameItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LinkedList<TypeIdItem> paramList = new LinkedList<TypeIdItem>();
|
||||
|
||||
for (int i=0; i<methodParams.length(); i++) {
|
||||
TypeIdItem typeIdItem;
|
||||
|
||||
switch (methodParams.charAt(i)) {
|
||||
case 'Z':
|
||||
case 'B':
|
||||
case 'S':
|
||||
case 'C':
|
||||
case 'I':
|
||||
case 'J':
|
||||
case 'F':
|
||||
case 'D':
|
||||
typeIdItem = TypeIdItem.lookupTypeIdItem(dexFile, methodParams.substring(i,i+1));
|
||||
break;
|
||||
case 'L':
|
||||
{
|
||||
int end = methodParams.indexOf(';', i);
|
||||
if (end == -1) {
|
||||
throw new RuntimeException("invalid parameter in the method");
|
||||
}
|
||||
|
||||
typeIdItem = TypeIdItem.lookupTypeIdItem(dexFile, methodParams.substring(i, end+1));
|
||||
i = end;
|
||||
break;
|
||||
}
|
||||
case '[':
|
||||
{
|
||||
int end;
|
||||
int typeStart = i+1;
|
||||
while (typeStart < methodParams.length() && methodParams.charAt(typeStart) == '[') {
|
||||
typeStart++;
|
||||
}
|
||||
switch (methodParams.charAt(typeStart)) {
|
||||
case 'Z':
|
||||
case 'B':
|
||||
case 'S':
|
||||
case 'C':
|
||||
case 'I':
|
||||
case 'J':
|
||||
case 'F':
|
||||
case 'D':
|
||||
end = typeStart;
|
||||
break;
|
||||
case 'L':
|
||||
end = methodParams.indexOf(';', typeStart);
|
||||
if (end == -1) {
|
||||
throw new RuntimeException("invalid parameter in the method");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("invalid parameter in the method");
|
||||
}
|
||||
|
||||
typeIdItem = TypeIdItem.lookupTypeIdItem(dexFile, methodParams.substring(i, end+1));
|
||||
i = end;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new RuntimeException("invalid parameter in the method");
|
||||
}
|
||||
|
||||
if (typeIdItem == null) {
|
||||
return null;
|
||||
}
|
||||
paramList.add(typeIdItem);
|
||||
}
|
||||
|
||||
TypeListItem paramListItem = null;
|
||||
if (paramList.size() > 0) {
|
||||
paramListItem = TypeListItem.lookupTypeListItem(dexFile, paramList);
|
||||
if (paramListItem == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
TypeIdItem retType = TypeIdItem.lookupTypeIdItem(dexFile, methodRet);
|
||||
if (retType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ProtoIdItem protoItem = ProtoIdItem.lookupProtoIdItem(dexFile, retType, paramListItem);
|
||||
if (protoItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassPath.ClassDef methodClassDef = classDef;
|
||||
|
||||
do {
|
||||
TypeIdItem classTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, methodClassDef.getClassType());
|
||||
|
||||
if (classTypeItem != null) {
|
||||
MethodIdItem methodIdItem = MethodIdItem.lookupMethodIdItem(dexFile, classTypeItem, protoItem, methodNameItem);
|
||||
if (methodIdItem != null) {
|
||||
return methodIdItem;
|
||||
}
|
||||
}
|
||||
|
||||
methodClassDef = methodClassDef.getSuperclass();
|
||||
} while (methodClassDef != null);
|
||||
return null;
|
||||
}
|
||||
|
||||
private FieldIdItem parseAndResolveField(ClassPath.ClassDef classDef, String field) {
|
||||
//expecting a string like someField:Lfield/type;
|
||||
String[] parts = field.split(":");
|
||||
if (parts.length != 2) {
|
||||
throw new RuntimeException("Invalid field descriptor " + field);
|
||||
}
|
||||
|
||||
String fieldName = parts[0];
|
||||
String fieldType = parts[1];
|
||||
|
||||
StringIdItem fieldNameItem = StringIdItem.lookupStringIdItem(dexFile, fieldName);
|
||||
if (fieldNameItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeIdItem fieldTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, fieldType);
|
||||
if (fieldTypeItem == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassPath.ClassDef fieldClass = classDef;
|
||||
|
||||
do {
|
||||
TypeIdItem classTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, fieldClass.getClassType());
|
||||
if (classTypeItem == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FieldIdItem fieldIdItem = FieldIdItem.lookupFieldIdItem(dexFile, classTypeItem, fieldTypeItem, fieldNameItem);
|
||||
if (fieldIdItem != null) {
|
||||
return fieldIdItem;
|
||||
}
|
||||
|
||||
fieldClass = fieldClass.getSuperclass();
|
||||
} while (fieldClass != null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the inline methods that we have against the given set of inline methods from deodexerant.
|
||||
* We want to make sure that each inline method in inlineMethods matches the method we have at the same
|
||||
* index. We may have more inline methods than we are given in inlineMethods - this shouldn't be a problem.
|
||||
* Newer versions of dalvik add additional inline methods, but (so far) have changed any existing ones.
|
||||
*
|
||||
* If anything doesn't look right, we just throw an exception
|
||||
* @param inlineMethods
|
||||
*/
|
||||
protected void checkInlineMethods(String[] inlineMethods) {
|
||||
if (inlineMethods.length > this.inlineMethods.length) {
|
||||
throw new ValidationException("Inline method count mismatch");
|
||||
}
|
||||
|
||||
for (int i=0; i<inlineMethods.length; i++) {
|
||||
String inlineMethod = inlineMethods[i];
|
||||
int methodType;
|
||||
|
||||
if (inlineMethod.startsWith("static")) {
|
||||
methodType = Static;
|
||||
inlineMethod = inlineMethod.substring(7);
|
||||
} else if (inlineMethod.startsWith("direct")) {
|
||||
methodType = Direct;
|
||||
inlineMethod = inlineMethod.substring(7);
|
||||
} else if (inlineMethod.startsWith("virtual")) {
|
||||
methodType = Virtual;
|
||||
inlineMethod = inlineMethod.substring(8);
|
||||
} else {
|
||||
throw new ValidationException("Could not parse inline method");
|
||||
}
|
||||
|
||||
if (!inlineMethod.equals(this.inlineMethods[i].getMethodString())) {
|
||||
throw new ValidationException(String.format("Inline method mismatch. %s vs. %s", inlineMethod,
|
||||
this.inlineMethods[i].getMethodString()));
|
||||
}
|
||||
|
||||
if (methodType != this.inlineMethods[i].methodType) {
|
||||
throw new ValidationException(String.format("Inline method type mismatch. %d vs. %d", methodType,
|
||||
this.inlineMethods[i].methodType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class InlineMethod {
|
||||
public final int methodType;
|
||||
public final String classType;
|
||||
public final String methodName;
|
||||
public final String parameters;
|
||||
public final String returnType;
|
||||
|
||||
private MethodIdItem methodIdItem = null;
|
||||
|
||||
protected InlineMethod(int methodType, String classType, String methodName, String parameters,
|
||||
String returnType) {
|
||||
this.methodType = methodType;
|
||||
this.classType = classType;
|
||||
this.methodName = methodName;
|
||||
this.parameters = parameters;
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public MethodIdItem getMethodIdItem() {
|
||||
if (methodIdItem == null) {
|
||||
loadMethod();
|
||||
}
|
||||
return methodIdItem;
|
||||
}
|
||||
|
||||
private void loadMethod() {
|
||||
ClassPath.ClassDef classDef = ClassPath.getClassDef(classType);
|
||||
|
||||
this.methodIdItem = parseAndResolveMethod(classDef, methodName, parameters, returnType);
|
||||
}
|
||||
|
||||
public String getMethodString() {
|
||||
return String.format("%s->%s(%s)%s", classType, methodName, parameters, returnType);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2009 Ben Gruver
|
||||
* 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.Code.Analysis;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This class handles communication with the deodexerant helper binary,
|
||||
* as well as caching the results of any deodexerant lookups
|
||||
*/
|
||||
public class Deodexerant {
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
private Socket socket = null;
|
||||
private PrintWriter out = null;
|
||||
private BufferedReader in = null;
|
||||
|
||||
public Deodexerant(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String[] getInlineMethods() {
|
||||
return sendMultilineCommand("I");
|
||||
}
|
||||
|
||||
public String[] getVirtualMethods(String classType) {
|
||||
return sendMultilineCommand(String.format("V %s", classType));
|
||||
}
|
||||
|
||||
public String[] getInstanceFields(String classType) {
|
||||
return sendMultilineCommand(String.format("F %s", classType));
|
||||
}
|
||||
|
||||
|
||||
private String sendCommand(String cmd) {
|
||||
try {
|
||||
connectIfNeeded();
|
||||
|
||||
out.println(cmd);
|
||||
out.flush();
|
||||
String response = in.readLine();
|
||||
if (response.startsWith("err")) {
|
||||
String error = response.substring(5);
|
||||
throw new RuntimeException(error);
|
||||
}
|
||||
return response;
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
//The command is still just a single line, but we're expecting a multi-line
|
||||
//response. The repsonse is considered finished when a line starting with "err"
|
||||
//or with "done" is encountered
|
||||
private String[] sendMultilineCommand(String cmd) {
|
||||
try {
|
||||
connectIfNeeded();
|
||||
|
||||
out.println(cmd);
|
||||
out.flush();
|
||||
|
||||
ArrayList<String> responseLines = new ArrayList<String>();
|
||||
String response = in.readLine();
|
||||
if (response == null) {
|
||||
throw new RuntimeException("Error talking to deodexerant");
|
||||
}
|
||||
while (!response.startsWith("done"))
|
||||
{
|
||||
if (response.startsWith("err")) {
|
||||
throw new RuntimeException(response.substring(5));
|
||||
}
|
||||
|
||||
int pos = response.indexOf(':') + 1;
|
||||
|
||||
responseLines.add(response.substring(pos+1));
|
||||
response = in.readLine();
|
||||
}
|
||||
|
||||
String[] lines = new String[responseLines.size()];
|
||||
|
||||
for (int i=0; i<lines.length; i++) {
|
||||
lines[i] = responseLines.get(i);
|
||||
}
|
||||
|
||||
return lines;
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void connectIfNeeded() {
|
||||
try {
|
||||
if (socket != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket = new Socket(host, port);
|
||||
|
||||
out = new PrintWriter(socket.getOutputStream(), true);
|
||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2009 Ben Gruver
|
||||
* 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.Code.Format;
|
||||
|
||||
import org.jf.dexlib.Code.Instruction;
|
||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
|
||||
public class DeadInstruction extends Instruction {
|
||||
public final Instruction OriginalInstruction;
|
||||
|
||||
public DeadInstruction(Instruction originalInstruction) {
|
||||
super(originalInstruction.opcode);
|
||||
this.OriginalInstruction = originalInstruction;
|
||||
}
|
||||
|
||||
protected void writeInstruction(AnnotatedOutput out, int currentCodeAddress) {
|
||||
//don't write anything
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(int codeAddress) {
|
||||
return OriginalInstruction.getSize(codeAddress);
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return Format.DeadInstruction;
|
||||
}
|
||||
}
|
@ -44,7 +44,6 @@ public enum Format {
|
||||
Format22b(Instruction22b.Factory, 4),
|
||||
Format22c(Instruction22c.Factory, 4),
|
||||
Format22cs(Instruction22cs.Factory, 4),
|
||||
Format22csf(null, 4),
|
||||
Format22s(Instruction22s.Factory, 4),
|
||||
Format22t(Instruction22t.Factory, 4),
|
||||
Format22x(Instruction22x.Factory, 4),
|
||||
@ -56,18 +55,14 @@ public enum Format {
|
||||
Format32x(Instruction32x.Factory, 6),
|
||||
Format35c(Instruction35c.Factory, 6),
|
||||
Format35s(Instruction35s.Factory, 6),
|
||||
Format35sf(null, 6),
|
||||
Format35ms(Instruction35ms.Factory, 6),
|
||||
Format35msf(null, 6),
|
||||
Format3rc(Instruction3rc.Factory, 6),
|
||||
Format3rms(Instruction3rms.Factory, 6),
|
||||
Format3rmsf(null, 6),
|
||||
Format51l(Instruction51l.Factory, 10),
|
||||
ArrayData(null, -1, true),
|
||||
PackedSwitchData(null, -1, true),
|
||||
SparseSwitchData(null, -1, true),
|
||||
UnresolvedNullReference(null, -1, false),
|
||||
DeadInstruction(null, -1, false)
|
||||
;
|
||||
|
||||
public final Instruction.InstructionFactory Factory;
|
||||
|
@ -29,13 +29,14 @@
|
||||
package org.jf.dexlib.Code.Format;
|
||||
|
||||
import org.jf.dexlib.Code.Instruction;
|
||||
import org.jf.dexlib.Code.OdexedFieldAccess;
|
||||
import org.jf.dexlib.Code.Opcode;
|
||||
import org.jf.dexlib.Code.TwoRegisterInstruction;
|
||||
import org.jf.dexlib.Util.NumberUtils;
|
||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
import org.jf.dexlib.DexFile;
|
||||
|
||||
public class Instruction22cs extends Instruction implements TwoRegisterInstruction {
|
||||
public class Instruction22cs extends Instruction implements TwoRegisterInstruction, OdexedFieldAccess {
|
||||
public static final Instruction.InstructionFactory Factory = new Factory();
|
||||
private byte regA;
|
||||
private byte regB;
|
||||
|
@ -1,66 +0,0 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2009 Ben Gruver
|
||||
* 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.Code.Format;
|
||||
|
||||
import org.jf.dexlib.Code.Opcode;
|
||||
import org.jf.dexlib.Code.TwoRegisterInstruction;
|
||||
import org.jf.dexlib.Code.InstructionWithReference;
|
||||
import org.jf.dexlib.FieldIdItem;
|
||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
|
||||
public class Instruction22csf extends InstructionWithReference implements TwoRegisterInstruction {
|
||||
private final Instruction22cs unfixedInstruction;
|
||||
|
||||
public Instruction22csf(Opcode opcode, Instruction22cs unfixedInstruction, FieldIdItem field) {
|
||||
//the opcode should be the "fixed" opcode. i.e. iget-object, etc. (NOT the "quick" version)
|
||||
super(opcode, field);
|
||||
this.unfixedInstruction = unfixedInstruction;
|
||||
}
|
||||
|
||||
protected void writeInstruction(AnnotatedOutput out, int currentCodeAddress) {
|
||||
byte regA = (byte)getRegisterA();
|
||||
byte regB = (byte)getRegisterB();
|
||||
|
||||
out.writeByte(opcode.value);
|
||||
out.writeByte((regB << 4) | regA);
|
||||
out.writeShort(this.getReferencedItem().getIndex());
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return Format.Format22csf;
|
||||
}
|
||||
|
||||
public int getRegisterA() {
|
||||
return unfixedInstruction.getRegisterA();
|
||||
}
|
||||
|
||||
public int getRegisterB() {
|
||||
return unfixedInstruction.getRegisterB();
|
||||
}
|
||||
}
|
@ -30,13 +30,14 @@ package org.jf.dexlib.Code.Format;
|
||||
|
||||
import org.jf.dexlib.Code.FiveRegisterInstruction;
|
||||
import org.jf.dexlib.Code.Instruction;
|
||||
import org.jf.dexlib.Code.OdexedInvokeVirtual;
|
||||
import org.jf.dexlib.Code.Opcode;
|
||||
import org.jf.dexlib.Util.NumberUtils;
|
||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
import org.jf.dexlib.DexFile;
|
||||
|
||||
|
||||
public class Instruction35ms extends Instruction implements FiveRegisterInstruction {
|
||||
public class Instruction35ms extends Instruction implements FiveRegisterInstruction, OdexedInvokeVirtual {
|
||||
public static final Instruction.InstructionFactory Factory = new Factory();
|
||||
private byte regCount;
|
||||
private byte regA;
|
||||
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2009 Ben Gruver
|
||||
* 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.Code.Format;
|
||||
|
||||
import org.jf.dexlib.Code.FiveRegisterInstruction;
|
||||
import org.jf.dexlib.Code.Opcode;
|
||||
import org.jf.dexlib.Code.InstructionWithReference;
|
||||
import org.jf.dexlib.MethodIdItem;
|
||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
|
||||
public class Instruction35msf extends InstructionWithReference implements FiveRegisterInstruction {
|
||||
private final Instruction35ms unfixedInstruction;
|
||||
|
||||
public Instruction35msf(Opcode opcode, Instruction35ms unfixedInstruction, MethodIdItem method) {
|
||||
//the opcode should be the "fixed" opcode. i.e. iget-object, etc. (NOT the "quick" version)
|
||||
super(opcode, method);
|
||||
this.unfixedInstruction = unfixedInstruction;
|
||||
}
|
||||
|
||||
protected void writeInstruction(AnnotatedOutput out, int currentCodeAddress) {
|
||||
byte regA = getRegisterA();
|
||||
byte regCount = getRegCount();
|
||||
byte regD = getRegisterD();
|
||||
byte regE = getRegisterE();
|
||||
byte regF = getRegisterF();
|
||||
byte regG = getRegisterG();
|
||||
|
||||
out.writeByte(opcode.value);
|
||||
out.writeByte((regCount << 4) | regA);
|
||||
out.writeShort(this.getReferencedItem().getIndex());
|
||||
out.writeByte((regE << 4) | regD);
|
||||
out.writeByte((regG << 4) | regF);
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return Format.Format35msf;
|
||||
}
|
||||
|
||||
public byte getRegCount() {
|
||||
return unfixedInstruction.getRegCount();
|
||||
}
|
||||
|
||||
public byte getRegisterA() {
|
||||
return unfixedInstruction.getRegisterA();
|
||||
}
|
||||
|
||||
public byte getRegisterD() {
|
||||
return unfixedInstruction.getRegisterD();
|
||||
}
|
||||
|
||||
public byte getRegisterE() {
|
||||
return unfixedInstruction.getRegisterE();
|
||||
}
|
||||
|
||||
public byte getRegisterF() {
|
||||
return unfixedInstruction.getRegisterF();
|
||||
}
|
||||
|
||||
public byte getRegisterG() {
|
||||
return unfixedInstruction.getRegisterG();
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2009 Ben Gruver
|
||||
* 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.Code.Format;
|
||||
|
||||
import org.jf.dexlib.Code.FiveRegisterInstruction;
|
||||
import org.jf.dexlib.Code.Opcode;
|
||||
import org.jf.dexlib.Code.InstructionWithReference;
|
||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
|
||||
public class Instruction35sf extends InstructionWithReference implements FiveRegisterInstruction {
|
||||
private final Instruction35s unfixedInstruction;
|
||||
|
||||
public Instruction35sf(Instruction35s unfixedInstruction) {
|
||||
super(Opcode.INVOKE_DIRECT, unfixedInstruction.getReferencedItem());
|
||||
this.unfixedInstruction = unfixedInstruction;
|
||||
}
|
||||
|
||||
protected void writeInstruction(AnnotatedOutput out, int currentCodeAddress) {
|
||||
byte regA = getRegisterA();
|
||||
byte regCount = getRegCount();
|
||||
byte regD = getRegisterD();
|
||||
byte regE = getRegisterE();
|
||||
byte regF = getRegisterF();
|
||||
byte regG = getRegisterG();
|
||||
|
||||
out.writeByte(opcode.value);
|
||||
out.writeByte((regCount << 4) | regA);
|
||||
out.writeShort(getReferencedItem().getIndex());
|
||||
out.writeByte((regE << 4) | regD);
|
||||
out.writeByte((regG << 4) | regF);
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return Format.Format35sf;
|
||||
}
|
||||
|
||||
public byte getRegCount() {
|
||||
return unfixedInstruction.getRegCount();
|
||||
}
|
||||
|
||||
public byte getRegisterA() {
|
||||
return unfixedInstruction.getRegisterA();
|
||||
}
|
||||
|
||||
public byte getRegisterD() {
|
||||
return unfixedInstruction.getRegisterD();
|
||||
}
|
||||
|
||||
public byte getRegisterE() {
|
||||
return unfixedInstruction.getRegisterE();
|
||||
}
|
||||
|
||||
public byte getRegisterF() {
|
||||
return unfixedInstruction.getRegisterF();
|
||||
}
|
||||
|
||||
public byte getRegisterG() {
|
||||
return unfixedInstruction.getRegisterG();
|
||||
}
|
||||
}
|
@ -29,13 +29,14 @@
|
||||
package org.jf.dexlib.Code.Format;
|
||||
|
||||
import org.jf.dexlib.Code.Instruction;
|
||||
import org.jf.dexlib.Code.OdexedInvokeVirtual;
|
||||
import org.jf.dexlib.Code.Opcode;
|
||||
import org.jf.dexlib.Code.RegisterRangeInstruction;
|
||||
import org.jf.dexlib.Util.NumberUtils;
|
||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
import org.jf.dexlib.DexFile;
|
||||
|
||||
public class Instruction3rms extends Instruction implements RegisterRangeInstruction {
|
||||
public class Instruction3rms extends Instruction implements RegisterRangeInstruction, OdexedInvokeVirtual {
|
||||
public static final Instruction.InstructionFactory Factory = new Factory();
|
||||
private byte regCount;
|
||||
private short startReg;
|
||||
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2009 Ben Gruver
|
||||
* 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.Code.Format;
|
||||
|
||||
import org.jf.dexlib.Code.Opcode;
|
||||
import org.jf.dexlib.Code.InstructionWithReference;
|
||||
import org.jf.dexlib.Code.RegisterRangeInstruction;
|
||||
import org.jf.dexlib.MethodIdItem;
|
||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||
|
||||
public class Instruction3rmsf extends InstructionWithReference implements RegisterRangeInstruction {
|
||||
private final Instruction3rms unfixedInstruction;
|
||||
|
||||
public Instruction3rmsf(Opcode opcode, Instruction3rms unfixedInstruction, MethodIdItem method) {
|
||||
//the opcode should be the "fixed" opcode. i.e. iget-object, etc. (NOT the "quick" version)
|
||||
super(opcode, method);
|
||||
this.unfixedInstruction = unfixedInstruction;
|
||||
}
|
||||
|
||||
protected void writeInstruction(AnnotatedOutput out, int currentCodeAddress) {
|
||||
short regCount = getRegCount();
|
||||
int startReg = getStartRegister();
|
||||
|
||||
out.writeByte(opcode.value);
|
||||
out.writeByte(regCount);
|
||||
out.writeShort(this.getReferencedItem().getIndex());
|
||||
out.writeShort(startReg);
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return Format.Format3rmsf;
|
||||
}
|
||||
|
||||
public int getStartRegister() {
|
||||
return unfixedInstruction.getStartRegister();
|
||||
}
|
||||
|
||||
public short getRegCount() {
|
||||
return unfixedInstruction.getRegCount();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.jf.dexlib.Code;
|
||||
|
||||
public interface OdexedFieldAccess {
|
||||
int getFieldOffset();
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.jf.dexlib.Code;
|
||||
|
||||
public interface OdexedInvokeVirtual {
|
||||
int getMethodIndex();
|
||||
}
|
@ -253,13 +253,12 @@ public enum Opcode
|
||||
SHR_INT_LIT8((byte)0xe1, "shr-int/lit8", ReferenceType.none, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
|
||||
USHR_INT_LIT8((byte)0xe2, "ushr-int/lit8", ReferenceType.none, Format.Format22b, Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
|
||||
|
||||
|
||||
EXECUTE_INLINE((byte)0xee, "execute-inline", ReferenceType.none, Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
|
||||
EXECUTE_INLINE_RANGE((byte)0xef, "execute-inline/range", ReferenceType.none, Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
|
||||
EXECUTE_INLINE((byte)0xee, "execute-inline", ReferenceType.none, Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
|
||||
EXECUTE_INLINE_RANGE((byte)0xef, "execute-inline/range", ReferenceType.none, Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
|
||||
INVOKE_DIRECT_EMPTY((byte)0xf0, "invoke-direct-empty", ReferenceType.method, Format.Format35s, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
|
||||
IGET_QUICK((byte)0xf2, "iget-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
|
||||
IGET_WIDE_QUICK((byte)0xf3, "iget-wide-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_WIDE_REGISTER),
|
||||
IGET_OBJECT_QUICK((byte)0xf4, "iget-object-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
|
||||
IGET_QUICK((byte)0xf2, "iget-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
|
||||
IGET_WIDE_QUICK((byte)0xf3, "iget-wide-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER),
|
||||
IGET_OBJECT_QUICK((byte)0xf4, "iget-object-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER),
|
||||
IPUT_QUICK((byte)0xf5, "iput-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
|
||||
IPUT_WIDE_QUICK((byte)0xf6, "iput-wide-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
|
||||
IPUT_OBJECT_QUICK((byte)0xf7, "iput-object-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
|
||||
|
Reference in New Issue
Block a user