mirror of
https://github.com/revanced/smali.git
synced 2025-06-13 04:27:38 +02:00
Initial commit of analysis stuff.
So far, it only builds the AnalyzedInstruction list, and populates the predecessors/successors of each instruction git-svn-id: https://smali.googlecode.com/svn/trunk@566 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
@ -0,0 +1,65 @@
|
|||||||
|
package org.jf.dexlib.Code.Analysis;
|
||||||
|
|
||||||
|
import org.jf.dexlib.Code.Instruction;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
public class AnalyzedInstruction {
|
||||||
|
/**
|
||||||
|
* The actual instruction
|
||||||
|
*/
|
||||||
|
protected final Instruction instruction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The address of the instruction, in 2-byte code blocks
|
||||||
|
*/
|
||||||
|
protected final int codeAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions that can pass on execution to this one during normal execution
|
||||||
|
*/
|
||||||
|
protected LinkedList<AnalyzedInstruction> predecessors = new LinkedList<AnalyzedInstruction>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions that can execution could pass on to next during normal execution
|
||||||
|
*/
|
||||||
|
protected LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
protected boolean dead = false;
|
||||||
|
|
||||||
|
public AnalyzedInstruction(Instruction instruction, int codeAddress) {
|
||||||
|
this.instruction = instruction;
|
||||||
|
this.codeAddress = codeAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCodeAddress() {
|
||||||
|
return codeAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addPredecessor(AnalyzedInstruction 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
successors.add(successor);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,212 @@
|
|||||||
|
package org.jf.dexlib.Code.Analysis;
|
||||||
|
|
||||||
|
import org.jf.dexlib.ClassDataItem;
|
||||||
|
import org.jf.dexlib.Code.Instruction;
|
||||||
|
import org.jf.dexlib.Code.MultiOffsetInstruction;
|
||||||
|
import org.jf.dexlib.Code.OffsetInstruction;
|
||||||
|
import org.jf.dexlib.Code.Opcode;
|
||||||
|
import org.jf.dexlib.CodeItem;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public class MethodAnalyzer {
|
||||||
|
private final HashSet<RegisterInfo> registerInfoCache = new HashSet<RegisterInfo>();
|
||||||
|
private final ClassDataItem.EncodedMethod encodedMethod;
|
||||||
|
|
||||||
|
private AnalyzedInstruction[] instructions;
|
||||||
|
|
||||||
|
//This is a dummy instruction that occurs immediately before the first real instruction. We can initialize the
|
||||||
|
//register types for this instruction to the parameter types, in order to have them propagate to all of its
|
||||||
|
//successors, e.g. the first real instruction, the first instructions in any exception handlers covering the first
|
||||||
|
//instruction, etc.
|
||||||
|
private AnalyzedInstruction startOfMethod;
|
||||||
|
|
||||||
|
public MethodAnalyzer(ClassDataItem.EncodedMethod encodedMethod) {
|
||||||
|
if (encodedMethod == null) {
|
||||||
|
throw new IllegalArgumentException("encodedMethod cannot be null");
|
||||||
|
}
|
||||||
|
if (encodedMethod.codeItem == null) {
|
||||||
|
throw new IllegalArgumentException("The method has no code");
|
||||||
|
}
|
||||||
|
this.encodedMethod = encodedMethod;
|
||||||
|
buildInstructionList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AnalyzedInstruction[] analyze() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildInstructionList() {
|
||||||
|
assert encodedMethod != null;
|
||||||
|
assert encodedMethod.codeItem != null;
|
||||||
|
|
||||||
|
startOfMethod = new AnalyzedInstruction(null, -1);
|
||||||
|
|
||||||
|
Instruction[] insns = encodedMethod.codeItem.getInstructions();
|
||||||
|
|
||||||
|
instructions = new AnalyzedInstruction[insns.length];
|
||||||
|
|
||||||
|
//first, create all the instructions and populate the instructionAddresses array
|
||||||
|
int currentCodeAddress = 0;
|
||||||
|
for (int i=0; i<insns.length; i++) {
|
||||||
|
instructions[i] = new AnalyzedInstruction(insns[i], currentCodeAddress);
|
||||||
|
currentCodeAddress += insns[i].getSize(currentCodeAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
//next, populate the exceptionHandlers array. The array item for each instruction that can throw an exception
|
||||||
|
//and is covered by a try block should be set to a list of the first instructions of each exception handler
|
||||||
|
//for the try block covering the instruction
|
||||||
|
CodeItem.TryItem[] tries = encodedMethod.codeItem.getTries();
|
||||||
|
int triesIndex = 0;
|
||||||
|
CodeItem.TryItem currentTry = null;
|
||||||
|
int[] currentExceptionHandlers = null;
|
||||||
|
int[][] exceptionHandlers = new int[insns.length][];
|
||||||
|
|
||||||
|
for (int i=0; i<instructions.length; i++) {
|
||||||
|
AnalyzedInstruction instruction = instructions[i];
|
||||||
|
Opcode instructionOpcode = instruction.instruction.opcode;
|
||||||
|
|
||||||
|
//check if we have gone past the end of the current try
|
||||||
|
if (currentTry != null) {
|
||||||
|
if (currentTry.getStartCodeAddress() + currentTry.getTryLength() <= currentCodeAddress) {
|
||||||
|
currentTry = null;
|
||||||
|
triesIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if the next try is applicable yet
|
||||||
|
if (currentTry == null && triesIndex < tries.length) {
|
||||||
|
CodeItem.TryItem tryItem = tries[triesIndex];
|
||||||
|
if (tryItem.getStartCodeAddress() <= currentCodeAddress) {
|
||||||
|
assert(tryItem.getStartCodeAddress() + tryItem.getTryLength() > currentCodeAddress);
|
||||||
|
|
||||||
|
currentTry = tryItem;
|
||||||
|
|
||||||
|
currentExceptionHandlers = buildExceptionHandlerArray(tryItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//if we're inside a try block, and the instruction can throw an exception, then add the exception handlers
|
||||||
|
//for the current instruction
|
||||||
|
if (currentTry != null && instructionOpcode.canThrow()) {
|
||||||
|
exceptionHandlers[i] = currentExceptionHandlers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//finally, populate the successors and predecessors for each instruction
|
||||||
|
for (int i=0; i<instructions.length; i++) {
|
||||||
|
AnalyzedInstruction instruction = instructions[i];
|
||||||
|
Opcode instructionOpcode = instruction.instruction.opcode;
|
||||||
|
int currentCodeOffset = instruction.codeAddress;
|
||||||
|
|
||||||
|
if (instruction.instruction.opcode.canContinue()) {
|
||||||
|
if (i == instructions.length - 1) {
|
||||||
|
throw new ValidationException("Execution can continue past the last instruction");
|
||||||
|
}
|
||||||
|
AnalyzedInstruction nextInstruction = instructions[i+1];
|
||||||
|
addPredecessorSuccessor(instruction, nextInstruction, exceptionHandlers, i+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instruction instanceof OffsetInstruction) {
|
||||||
|
OffsetInstruction offsetInstruction = (OffsetInstruction)instruction;
|
||||||
|
|
||||||
|
if (instructionOpcode == Opcode.PACKED_SWITCH || instructionOpcode == Opcode.SPARSE_SWITCH) {
|
||||||
|
MultiOffsetInstruction switchDataInstruction =
|
||||||
|
(MultiOffsetInstruction)getInstructionByAddress(currentCodeOffset +
|
||||||
|
offsetInstruction.getTargetAddressOffset()).instruction;
|
||||||
|
for (int targetAddressOffset: switchDataInstruction.getTargets()) {
|
||||||
|
int targetInstructionIndex = getInstructionIndexByAddress(currentCodeOffset +
|
||||||
|
targetAddressOffset);
|
||||||
|
AnalyzedInstruction targetInstruction = instructions[targetInstructionIndex];
|
||||||
|
|
||||||
|
addPredecessorSuccessor(instruction, targetInstruction, exceptionHandlers,
|
||||||
|
targetInstructionIndex);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int targetAddressOffset = offsetInstruction.getTargetAddressOffset();
|
||||||
|
int targetInstructionIndex = getInstructionIndexByAddress(currentCodeOffset + targetAddressOffset);
|
||||||
|
AnalyzedInstruction targetInstruction = instructions[targetInstructionIndex];
|
||||||
|
|
||||||
|
addPredecessorSuccessor(instruction, targetInstruction, exceptionHandlers, targetInstructionIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPredecessorSuccessor(AnalyzedInstruction predecessor, AnalyzedInstruction successor,
|
||||||
|
int[][] exceptionHandlers,
|
||||||
|
int successorInstructionIndex) {
|
||||||
|
|
||||||
|
if (!predecessor.addSuccessor(successor)) {
|
||||||
|
//if predecessor already had successor as a successor, then there's nothing else to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
successor.addPredecessor(predecessor);
|
||||||
|
|
||||||
|
//if the successor can throw an instruction, then we need to add the exception handlers as additional
|
||||||
|
//successors to the predecessor (and then apply this same logic recursively if needed)
|
||||||
|
int[] exceptionHandlersForSuccessor = exceptionHandlers[successorInstructionIndex];
|
||||||
|
if (exceptionHandlersForSuccessor != null) {
|
||||||
|
//the item for this instruction in exceptionHandlersForSuccessor should only be set if this instruction
|
||||||
|
//can throw an exception
|
||||||
|
assert predecessor.instruction.opcode.canThrow();
|
||||||
|
|
||||||
|
for (int exceptionHandlerIndex: exceptionHandlersForSuccessor) {
|
||||||
|
AnalyzedInstruction exceptionHandler = instructions[exceptionHandlerIndex];
|
||||||
|
addPredecessorSuccessor(predecessor, exceptionHandler, exceptionHandlers, exceptionHandlerIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] buildExceptionHandlerArray(CodeItem.TryItem tryItem) {
|
||||||
|
int exceptionHandlerCount = tryItem.encodedCatchHandler.handlers.length;
|
||||||
|
int catchAllHandler = tryItem.encodedCatchHandler.getCatchAllHandlerAddress();
|
||||||
|
if (catchAllHandler != -1) {
|
||||||
|
exceptionHandlerCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] exceptionHandlers = new int[exceptionHandlerCount];
|
||||||
|
for (int i=0; i<tryItem.encodedCatchHandler.handlers.length; i++) {
|
||||||
|
exceptionHandlers[i] = getInstructionIndexByAddress(
|
||||||
|
tryItem.encodedCatchHandler.handlers[i].getHandlerAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catchAllHandler != -1) {
|
||||||
|
exceptionHandlers[exceptionHandlers.length - 1] = getInstructionIndexByAddress(
|
||||||
|
catchAllHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
return exceptionHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getInstructionIndexByAddress(int address) {
|
||||||
|
int start=0;
|
||||||
|
int end=instructions.length;
|
||||||
|
|
||||||
|
while (end > (start + 1)) {
|
||||||
|
int index = (start + end) / 2;
|
||||||
|
|
||||||
|
if (instructions[index].codeAddress < address) {
|
||||||
|
start = index;
|
||||||
|
} else {
|
||||||
|
end = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end < instructions.length && instructions[end].codeAddress == address) {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("No instruction at address " + address);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnalyzedInstruction getInstructionByAddress(int address) {
|
||||||
|
int index = getInstructionIndexByAddress(address);
|
||||||
|
|
||||||
|
assert index < instructions.length;
|
||||||
|
assert instructions[index] != null;
|
||||||
|
return instructions[index];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.jf.dexlib.Code.Analysis;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
public class RegisterInfo {
|
||||||
|
private final LinkedList<RegisterType> possibleRegisterTypes = new LinkedList<RegisterType>();
|
||||||
|
|
||||||
|
protected RegisterInfo() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package org.jf.dexlib.Code.Analysis;
|
||||||
|
|
||||||
|
import org.jf.dexlib.TypeIdItem;
|
||||||
|
|
||||||
|
public class RegisterType {
|
||||||
|
public final Category category;
|
||||||
|
public final TypeIdItem type;
|
||||||
|
|
||||||
|
protected RegisterType(Category category, TypeIdItem type) {
|
||||||
|
this.category = category;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
RegisterType that = (RegisterType) o;
|
||||||
|
|
||||||
|
if (category != that.category) return false;
|
||||||
|
if (type != null ? !type.equals(that.type) : that.type != null) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = category.hashCode();
|
||||||
|
result = 31 * result + (type != null ? type.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*private static RegisterType[][] mergeTable =
|
||||||
|
{
|
||||||
|
//Unknown Null Nonreference Reference Conflicted
|
||||||
|
{Unknown, Null, NonReference, Reference, Conflicted}, //Unknown
|
||||||
|
{Null, Null, NonReference, Reference, Conflicted}, //Null
|
||||||
|
{NonReference, NonReference, NonReference, Conflicted, Conflicted}, //NonReference
|
||||||
|
{Reference, Reference, Conflicted, Reference, Conflicted}, //Referenced
|
||||||
|
{Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, //Conflicted
|
||||||
|
};*/
|
||||||
|
|
||||||
|
/*public static RegisterType mergeRegisterTypes(RegisterType type1, RegisterType type2) {
|
||||||
|
return mergeTable[type1.ordinal()][type2.ordinal()];
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public static enum Category {
|
||||||
|
Unknown,
|
||||||
|
Null,
|
||||||
|
NonReference,
|
||||||
|
Reference,
|
||||||
|
Conflicted
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.jf.dexlib.Code.Analysis;
|
||||||
|
|
||||||
|
public class ValidationException extends RuntimeException {
|
||||||
|
public ValidationException(String errorMessage) {
|
||||||
|
super(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user