mirror of
https://github.com/revanced/smali.git
synced 2025-05-09 10:54:29 +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:
parent
2ba2d0f16b
commit
0c65e0f4f5
@ -43,16 +43,6 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
|
|||||||
protected final StringTemplateGroup stg;
|
protected final StringTemplateGroup stg;
|
||||||
protected final T instruction;
|
protected final T instruction;
|
||||||
|
|
||||||
/**
|
|
||||||
* Instructions that execution could pass on to next
|
|
||||||
*/
|
|
||||||
private LinkedList<InstructionMethodItem> successors = new LinkedList<InstructionMethodItem>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instructions that can pass on execution to this one
|
|
||||||
*/
|
|
||||||
private LinkedList<InstructionMethodItem> predecessors = new LinkedList<InstructionMethodItem>();
|
|
||||||
|
|
||||||
public InstructionMethodItem(CodeItem codeItem, int codeAddress, StringTemplateGroup stg, T instruction) {
|
public InstructionMethodItem(CodeItem codeItem, int codeAddress, StringTemplateGroup stg, T instruction) {
|
||||||
super(codeAddress);
|
super(codeAddress);
|
||||||
this.codeItem = codeItem;
|
this.codeItem = codeItem;
|
||||||
@ -97,6 +87,14 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
|
|||||||
if (instruction instanceof InstructionWithReference) {
|
if (instruction instanceof InstructionWithReference) {
|
||||||
setInstructionWithReferenceAttributes((InstructionWithReference)instruction, template);
|
setInstructionWithReferenceAttributes((InstructionWithReference)instruction, template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (instruction instanceof OdexedInvokeVirtual) {
|
||||||
|
setOdexedInvokeVirtualAttributes((OdexedInvokeVirtual)instruction, template);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instruction instanceof OdexedFieldAccess) {
|
||||||
|
setOdexedFieldAccessAttributes((OdexedFieldAccess)instruction, template);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLiteralAttributes(LiteralInstruction instruction, StringTemplate template) {
|
private void setLiteralAttributes(LiteralInstruction instruction, StringTemplate template) {
|
||||||
@ -170,4 +168,12 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
|
|||||||
template.setAttribute("Reference", Reference.createReference(template.getGroup(),
|
template.setAttribute("Reference", Reference.createReference(template.getGroup(),
|
||||||
instruction.getReferencedItem()));
|
instruction.getReferencedItem()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setOdexedInvokeVirtualAttributes(OdexedInvokeVirtual instruction, StringTemplate template) {
|
||||||
|
template.setAttribute("MethodIndex", instruction.getMethodIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOdexedFieldAccessAttributes(OdexedFieldAccess instruction, StringTemplate template) {
|
||||||
|
template.setAttribute("FieldOffset", instruction.getFieldOffset());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package org.jf.baksmali.Adaptors.Format;
|
|||||||
|
|
||||||
import org.antlr.stringtemplate.StringTemplateGroup;
|
import org.antlr.stringtemplate.StringTemplateGroup;
|
||||||
import org.jf.baksmali.Adaptors.MethodDefinition;
|
import org.jf.baksmali.Adaptors.MethodDefinition;
|
||||||
|
import org.jf.dexlib.Code.Analysis.AnalyzedInstruction;
|
||||||
import org.jf.dexlib.Code.Format.*;
|
import org.jf.dexlib.Code.Format.*;
|
||||||
import org.jf.dexlib.Code.Instruction;
|
import org.jf.dexlib.Code.Instruction;
|
||||||
import org.jf.dexlib.Code.OffsetInstruction;
|
import org.jf.dexlib.Code.OffsetInstruction;
|
||||||
@ -15,8 +16,8 @@ public class InstructionMethodItemFactory {
|
|||||||
CodeItem codeItem,
|
CodeItem codeItem,
|
||||||
int codeAddress,
|
int codeAddress,
|
||||||
StringTemplateGroup stg,
|
StringTemplateGroup stg,
|
||||||
Instruction instruction) {
|
Instruction instruction,
|
||||||
|
boolean isLastInstruction) {
|
||||||
if (instruction instanceof OffsetInstruction) {
|
if (instruction instanceof OffsetInstruction) {
|
||||||
return new OffsetInstructionFormatMethodItem(methodDefinition.getLabelCache(), codeItem, codeAddress, stg,
|
return new OffsetInstructionFormatMethodItem(methodDefinition.getLabelCache(), codeItem, codeAddress, stg,
|
||||||
instruction);
|
instruction);
|
||||||
@ -32,6 +33,9 @@ public class InstructionMethodItemFactory {
|
|||||||
case SparseSwitchData:
|
case SparseSwitchData:
|
||||||
return new SparseSwitchMethodItem(methodDefinition, codeItem, codeAddress, stg,
|
return new SparseSwitchMethodItem(methodDefinition, codeItem, codeAddress, stg,
|
||||||
(SparseSwitchDataPseudoInstruction)instruction);
|
(SparseSwitchDataPseudoInstruction)instruction);
|
||||||
|
case UnresolvedNullReference:
|
||||||
|
return new UnresolvedNullReferenceMethodItem(codeItem, codeAddress, stg,
|
||||||
|
(UnresolvedNullReference)instruction, isLastInstruction);
|
||||||
default:
|
default:
|
||||||
return new InstructionMethodItem(codeItem, codeAddress, stg, instruction);
|
return new InstructionMethodItem(codeItem, codeAddress, stg, instruction);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
package org.jf.baksmali.Adaptors.Format;
|
|
||||||
|
|
||||||
import org.antlr.stringtemplate.StringTemplateGroup;
|
|
||||||
import org.jf.dexlib.Code.Instruction;
|
|
||||||
import org.jf.dexlib.CodeItem;
|
|
||||||
|
|
||||||
public class OdexInstructionMethodItem<T extends Instruction> extends InstructionMethodItem<T> {
|
|
||||||
protected Instruction fixedInstruction = null;
|
|
||||||
|
|
||||||
public OdexInstructionMethodItem(CodeItem codeItem, int codeAddress, StringTemplateGroup stg, T ins) {
|
|
||||||
super(codeItem, codeAddress, stg, ins);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instruction getFixedInstruction() {
|
|
||||||
return fixedInstruction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFixedInstruction(Instruction fixedInstruction) {
|
|
||||||
this.fixedInstruction = fixedInstruction;
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,29 +26,33 @@
|
|||||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jf.dexlib.Code.Format;
|
package org.jf.baksmali.Adaptors.Format;
|
||||||
|
|
||||||
import org.jf.dexlib.Code.Instruction;
|
import org.jf.dexlib.Code.Format.UnresolvedNullReference;
|
||||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
import org.jf.dexlib.CodeItem;
|
||||||
|
import org.antlr.stringtemplate.StringTemplateGroup;
|
||||||
|
import org.antlr.stringtemplate.StringTemplate;
|
||||||
|
|
||||||
public class DeadInstruction extends Instruction {
|
public class UnresolvedNullReferenceMethodItem extends InstructionMethodItem<UnresolvedNullReference> {
|
||||||
public final Instruction OriginalInstruction;
|
public final boolean isLastInstruction;
|
||||||
|
|
||||||
public DeadInstruction(Instruction originalInstruction) {
|
public UnresolvedNullReferenceMethodItem(CodeItem codeItem, int codeAddress, StringTemplateGroup stg,
|
||||||
super(originalInstruction.opcode);
|
UnresolvedNullReference instruction, boolean isLastInstruction) {
|
||||||
this.OriginalInstruction = originalInstruction;
|
super(codeItem, codeAddress, stg, instruction);
|
||||||
|
this.isLastInstruction = isLastInstruction;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeInstruction(AnnotatedOutput out, int currentCodeAddress) {
|
protected void setAttributes(StringTemplate template) {
|
||||||
//don't write anything
|
template.setAttribute("Register", formatRegister(instruction.ObjectRegisterNum));
|
||||||
}
|
switch (instruction.OriginalInstruction.opcode)
|
||||||
|
{
|
||||||
|
case INVOKE_VIRTUAL_QUICK_RANGE:
|
||||||
|
case INVOKE_SUPER_QUICK_RANGE:
|
||||||
|
template.setAttribute("UseInvokeRange", 1);
|
||||||
|
if (isLastInstruction) {
|
||||||
|
template.setAttribute("AddGoto", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize(int codeAddress) {
|
|
||||||
return OriginalInstruction.getSize(codeAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Format getFormat() {
|
|
||||||
return Format.DeadInstruction;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -37,6 +37,7 @@ import org.jf.dexlib.Code.Analysis.AnalyzedInstruction;
|
|||||||
import org.jf.dexlib.Code.Analysis.MethodAnalyzer;
|
import org.jf.dexlib.Code.Analysis.MethodAnalyzer;
|
||||||
import org.jf.dexlib.Code.Analysis.RegisterType;
|
import org.jf.dexlib.Code.Analysis.RegisterType;
|
||||||
import org.jf.dexlib.Code.Analysis.ValidationException;
|
import org.jf.dexlib.Code.Analysis.ValidationException;
|
||||||
|
import org.jf.dexlib.Code.Format.Format;
|
||||||
import org.jf.dexlib.Debug.DebugInstructionIterator;
|
import org.jf.dexlib.Debug.DebugInstructionIterator;
|
||||||
import org.jf.dexlib.Util.AccessFlags;
|
import org.jf.dexlib.Util.AccessFlags;
|
||||||
import org.antlr.stringtemplate.StringTemplateGroup;
|
import org.antlr.stringtemplate.StringTemplateGroup;
|
||||||
@ -66,7 +67,7 @@ public class MethodDefinition {
|
|||||||
//TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
|
//TODO: what about try/catch blocks inside the dead code? those will need to be commented out too. ugh.
|
||||||
|
|
||||||
if (encodedMethod.codeItem != null) {
|
if (encodedMethod.codeItem != null) {
|
||||||
methodAnalyzer = new MethodAnalyzer(encodedMethod);
|
methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex);
|
||||||
AnalyzedInstruction[] instructions = methodAnalyzer.makeInstructionArray();
|
AnalyzedInstruction[] instructions = methodAnalyzer.makeInstructionArray();
|
||||||
|
|
||||||
packedSwitchMap = new SparseIntArray(1);
|
packedSwitchMap = new SparseIntArray(1);
|
||||||
@ -78,17 +79,17 @@ public class MethodDefinition {
|
|||||||
int currentCodeAddress = 0;
|
int currentCodeAddress = 0;
|
||||||
for (int i=0; i<instructions.length; i++) {
|
for (int i=0; i<instructions.length; i++) {
|
||||||
AnalyzedInstruction instruction = instructions[i];
|
AnalyzedInstruction instruction = instructions[i];
|
||||||
if (instruction.instruction.opcode == Opcode.PACKED_SWITCH) {
|
if (instruction.getInstruction().opcode == Opcode.PACKED_SWITCH) {
|
||||||
packedSwitchMap.append(
|
packedSwitchMap.append(
|
||||||
currentCodeAddress + ((OffsetInstruction)instruction.instruction).getTargetAddressOffset(),
|
currentCodeAddress + ((OffsetInstruction)instruction.getInstruction()).getTargetAddressOffset(),
|
||||||
currentCodeAddress);
|
currentCodeAddress);
|
||||||
} else if (instruction.instruction.opcode == Opcode.SPARSE_SWITCH) {
|
} else if (instruction.getInstruction().opcode == Opcode.SPARSE_SWITCH) {
|
||||||
sparseSwitchMap.append(
|
sparseSwitchMap.append(
|
||||||
currentCodeAddress + ((OffsetInstruction)instruction.instruction).getTargetAddressOffset(),
|
currentCodeAddress + ((OffsetInstruction)instruction.getInstruction()).getTargetAddressOffset(),
|
||||||
currentCodeAddress);
|
currentCodeAddress);
|
||||||
}
|
}
|
||||||
instructionMap.append(currentCodeAddress, i);
|
instructionMap.append(currentCodeAddress, i);
|
||||||
currentCodeAddress += instruction.instruction.getSize(currentCodeAddress);
|
currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
packedSwitchMap = null;
|
packedSwitchMap = null;
|
||||||
@ -248,7 +249,7 @@ public class MethodDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AnalyzedInstruction[] instructions;
|
AnalyzedInstruction[] instructions;
|
||||||
if (baksmali.registerInfo != 0) {
|
if (baksmali.registerInfo != 0 || baksmali.deodex) {
|
||||||
instructions = methodAnalyzer.analyze();
|
instructions = methodAnalyzer.analyze();
|
||||||
|
|
||||||
ValidationException validationException = methodAnalyzer.getValidationException();
|
ValidationException validationException = methodAnalyzer.getValidationException();
|
||||||
@ -261,6 +262,17 @@ public class MethodDefinition {
|
|||||||
instructions = methodAnalyzer.makeInstructionArray();
|
instructions = methodAnalyzer.makeInstructionArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnalyzedInstruction lastInstruction = null;
|
||||||
|
|
||||||
|
for (int i=instructions.length-1; i>=0; i--) {
|
||||||
|
AnalyzedInstruction instruction = instructions[i];
|
||||||
|
|
||||||
|
if (!instruction.isDead()) {
|
||||||
|
lastInstruction = instruction;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BitSet printPreRegister = new BitSet(registerCount);
|
BitSet printPreRegister = new BitSet(registerCount);
|
||||||
BitSet printPostRegister = new BitSet(registerCount);
|
BitSet printPostRegister = new BitSet(registerCount);
|
||||||
|
|
||||||
@ -268,8 +280,20 @@ public class MethodDefinition {
|
|||||||
for (int i=0; i<instructions.length; i++) {
|
for (int i=0; i<instructions.length; i++) {
|
||||||
AnalyzedInstruction instruction = instructions[i];
|
AnalyzedInstruction instruction = instructions[i];
|
||||||
|
|
||||||
methodItems.add(InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
|
MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
|
||||||
encodedMethod.codeItem, currentCodeAddress, stg, instruction.instruction));
|
encodedMethod.codeItem, currentCodeAddress, stg, instruction.getInstruction(),
|
||||||
|
instruction == lastInstruction);
|
||||||
|
|
||||||
|
if (instruction.isDead()) {
|
||||||
|
methodItems.add(new CommentedOutMethodItem(stg, methodItem));
|
||||||
|
} else {
|
||||||
|
methodItems.add(methodItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instruction.getInstruction().getFormat() == Format.UnresolvedNullReference) {
|
||||||
|
methodItems.add(new CommentedOutMethodItem(stg, InstructionMethodItemFactory.makeInstructionFormatMethodItem(this,
|
||||||
|
encodedMethod.codeItem, currentCodeAddress, stg, instruction.getOriginalInstruction(), false)));
|
||||||
|
}
|
||||||
|
|
||||||
if (i != instructions.length - 1) {
|
if (i != instructions.length - 1) {
|
||||||
methodItems.add(new BlankMethodItem(stg, currentCodeAddress));
|
methodItems.add(new BlankMethodItem(stg, currentCodeAddress));
|
||||||
@ -280,7 +304,7 @@ public class MethodDefinition {
|
|||||||
currentCodeAddress, -1000));
|
currentCodeAddress, -1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baksmali.registerInfo != 0 && !instruction.instruction.getFormat().variableSizeFormat) {
|
if (baksmali.registerInfo != 0 && !instruction.getInstruction().getFormat().variableSizeFormat) {
|
||||||
printPreRegister.clear();
|
printPreRegister.clear();
|
||||||
printPostRegister.clear();
|
printPostRegister.clear();
|
||||||
|
|
||||||
@ -314,19 +338,21 @@ public class MethodDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!printPreRegister.isEmpty()) {
|
if (!printPreRegister.isEmpty()) {
|
||||||
methodItems.add(new CommentMethodItem(stg,
|
String comment = getPreInstructionRegisterString(instruction, printPreRegister);
|
||||||
getPreInstructionRegisterString(instruction, printPreRegister),
|
if (comment != null && comment.length() > 0) {
|
||||||
currentCodeAddress, 99.9));
|
methodItems.add(new CommentMethodItem(stg, comment, currentCodeAddress, 99.9));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!printPostRegister.isEmpty()) {
|
if (!printPostRegister.isEmpty()) {
|
||||||
methodItems.add(new CommentMethodItem(stg,
|
String comment = getPostInstructionRegisterString(instruction, printPostRegister);
|
||||||
getPostInstructionRegisterString(instruction, printPostRegister),
|
if (comment != null && comment.length() > 0) {
|
||||||
currentCodeAddress, 100.1));
|
methodItems.add(new CommentMethodItem(stg, comment, currentCodeAddress, 100.1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentCodeAddress += instruction.instruction.getSize(currentCodeAddress);
|
currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
addTries(methodItems);
|
addTries(methodItems);
|
||||||
@ -351,13 +377,13 @@ public class MethodDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addArgsRegs(BitSet printPreRegister, AnalyzedInstruction analyzedInstruction) {
|
private void addArgsRegs(BitSet printPreRegister, AnalyzedInstruction analyzedInstruction) {
|
||||||
if (analyzedInstruction.instruction instanceof RegisterRangeInstruction) {
|
if (analyzedInstruction.getInstruction() instanceof RegisterRangeInstruction) {
|
||||||
RegisterRangeInstruction instruction = (RegisterRangeInstruction)analyzedInstruction.instruction;
|
RegisterRangeInstruction instruction = (RegisterRangeInstruction)analyzedInstruction.getInstruction();
|
||||||
|
|
||||||
printPreRegister.set(instruction.getStartRegister(),
|
printPreRegister.set(instruction.getStartRegister(),
|
||||||
instruction.getStartRegister() + instruction.getRegCount());
|
instruction.getStartRegister() + instruction.getRegCount());
|
||||||
} else if (analyzedInstruction.instruction instanceof FiveRegisterInstruction) {
|
} else if (analyzedInstruction.getInstruction() instanceof FiveRegisterInstruction) {
|
||||||
FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.instruction;
|
FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.getInstruction();
|
||||||
int regCount = instruction.getRegCount();
|
int regCount = instruction.getRegCount();
|
||||||
switch (regCount) {
|
switch (regCount) {
|
||||||
case 5:
|
case 5:
|
||||||
@ -375,17 +401,17 @@ public class MethodDefinition {
|
|||||||
case 1:
|
case 1:
|
||||||
printPreRegister.set(instruction.getRegisterD());
|
printPreRegister.set(instruction.getRegisterD());
|
||||||
}
|
}
|
||||||
} else if (analyzedInstruction.instruction instanceof ThreeRegisterInstruction) {
|
} else if (analyzedInstruction.getInstruction() instanceof ThreeRegisterInstruction) {
|
||||||
ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.instruction;
|
ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.getInstruction();
|
||||||
printPreRegister.set(instruction.getRegisterA());
|
printPreRegister.set(instruction.getRegisterA());
|
||||||
printPreRegister.set(instruction.getRegisterB());
|
printPreRegister.set(instruction.getRegisterB());
|
||||||
printPreRegister.set(instruction.getRegisterC());
|
printPreRegister.set(instruction.getRegisterC());
|
||||||
} else if (analyzedInstruction.instruction instanceof TwoRegisterInstruction) {
|
} else if (analyzedInstruction.getInstruction() instanceof TwoRegisterInstruction) {
|
||||||
TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction;
|
TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.getInstruction();
|
||||||
printPreRegister.set(instruction.getRegisterA());
|
printPreRegister.set(instruction.getRegisterA());
|
||||||
printPreRegister.set(instruction.getRegisterB());
|
printPreRegister.set(instruction.getRegisterB());
|
||||||
} else if (analyzedInstruction.instruction instanceof SingleRegisterInstruction) {
|
} else if (analyzedInstruction.getInstruction() instanceof SingleRegisterInstruction) {
|
||||||
SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.instruction;
|
SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.getInstruction();
|
||||||
printPreRegister.set(instruction.getRegisterA());
|
printPreRegister.set(instruction.getRegisterA());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,7 +440,7 @@ public class MethodDefinition {
|
|||||||
sb.append(instruction.getPreInstructionRegisterType(registerNum));
|
sb.append(instruction.getPreInstructionRegisterType(registerNum));
|
||||||
sb.append(":merge{");
|
sb.append(":merge{");
|
||||||
|
|
||||||
RegisterType mergedRegisterType = instruction.getPreInstructionRegisterType(registerNum);
|
RegisterType mergedRegisterType = null;
|
||||||
boolean addRegister = false;
|
boolean addRegister = false;
|
||||||
|
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
@ -422,10 +448,15 @@ public class MethodDefinition {
|
|||||||
RegisterType predecessorRegisterType = predecessor.getPostInstructionRegisterType(registerNum);
|
RegisterType predecessorRegisterType = predecessor.getPostInstructionRegisterType(registerNum);
|
||||||
|
|
||||||
if (!first) {
|
if (!first) {
|
||||||
sb.append(',');
|
if (!addRegister) {
|
||||||
if (!addRegister && mergedRegisterType != predecessorRegisterType) {
|
sb.append(',');
|
||||||
addRegister = true;
|
if (mergedRegisterType != predecessorRegisterType) {
|
||||||
|
addRegister = true;
|
||||||
|
}
|
||||||
|
mergedRegisterType = mergedRegisterType.merge(predecessorRegisterType);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
mergedRegisterType = predecessorRegisterType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (predecessor.getInstructionIndex() == -1) {
|
if (predecessor.getInstructionIndex() == -1) {
|
||||||
@ -436,7 +467,7 @@ public class MethodDefinition {
|
|||||||
sb.append(Integer.toHexString(methodAnalyzer.getInstructionAddress(predecessor)));
|
sb.append(Integer.toHexString(methodAnalyzer.getInstructionAddress(predecessor)));
|
||||||
sb.append(':');
|
sb.append(':');
|
||||||
}
|
}
|
||||||
sb.append(predecessor.getPostInstructionRegisterType(registerNum).toString());
|
sb.append(predecessorRegisterType.toString());
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +0,0 @@
|
|||||||
package org.jf.baksmali.Deodex;
|
|
||||||
|
|
||||||
public class DeodexUtil2 {
|
|
||||||
}
|
|
@ -1,590 +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.baksmali.Deodex;
|
|
||||||
|
|
||||||
import org.jf.dexlib.*;
|
|
||||||
import org.jf.dexlib.Util.SparseArray;
|
|
||||||
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
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 final HashMap<String, ClassData> vtableMap = new HashMap<String, ClassData>();
|
|
||||||
private final HashMap<CommonSuperclassLookup, String> cachedCommonSuperclassLookup =
|
|
||||||
new HashMap<CommonSuperclassLookup, String>();
|
|
||||||
private InlineMethod[] inlineMethods;
|
|
||||||
|
|
||||||
public final DexFile dexFile;
|
|
||||||
|
|
||||||
private Socket socket = null;
|
|
||||||
private PrintWriter out = null;
|
|
||||||
private BufferedReader in = null;
|
|
||||||
|
|
||||||
public Deodexerant(DexFile dexFile, String host, int port) {
|
|
||||||
this.dexFile = dexFile;
|
|
||||||
this.host = host;
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadInlineMethods() {
|
|
||||||
List<String> responseLines = sendMultilineCommand("I");
|
|
||||||
|
|
||||||
inlineMethods = new InlineMethod[responseLines.size()];
|
|
||||||
for (int i=0; i<inlineMethods.length; i++) {
|
|
||||||
String response = responseLines.get(i);
|
|
||||||
if (!response.startsWith("inline: ")) {
|
|
||||||
throw new RuntimeException("Invalid response from deodexerant");
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] parts = response.substring(8).split(" ");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
throw new RuntimeException("Invalid response from deodexerant");
|
|
||||||
}
|
|
||||||
|
|
||||||
String methodType = parts[0];
|
|
||||||
InlineMethodType type;
|
|
||||||
if (methodType.equals("virtual")) {
|
|
||||||
type = InlineMethodType.Virtual;
|
|
||||||
} else if (methodType.equals("direct")) {
|
|
||||||
type = InlineMethodType.Direct;
|
|
||||||
} else if (methodType.equals("static")) {
|
|
||||||
type = InlineMethodType.Static;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Invalid response from deodexerant");
|
|
||||||
}
|
|
||||||
|
|
||||||
String methodDescriptor = parts[1];
|
|
||||||
inlineMethods[i] = new InlineMethod(methodDescriptor, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public InlineMethod lookupInlineMethod(int inlineMethodIndex) {
|
|
||||||
if (inlineMethods == null) {
|
|
||||||
loadInlineMethods();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inlineMethodIndex >= inlineMethods.length) {
|
|
||||||
throw new RuntimeException("Invalid inline method index " + inlineMethodIndex + ". Too big.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return inlineMethods[inlineMethodIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeIdItem resolveTypeOrSupertype(String type) {
|
|
||||||
TypeIdItem typeItem = TypeIdItem.internTypeIdItem(dexFile, type);
|
|
||||||
|
|
||||||
while (typeItem == null) {
|
|
||||||
type = lookupSuperclass(type);
|
|
||||||
if (type == null) {
|
|
||||||
throw new RuntimeException("Could not find the type or a supertype of " + type + " in the dex file");
|
|
||||||
}
|
|
||||||
|
|
||||||
typeItem = TypeIdItem.internTypeIdItem(dexFile, type);
|
|
||||||
}
|
|
||||||
return typeItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FieldIdItem lookupField(String type, int fieldOffset) {
|
|
||||||
ClassData classData = getClassData(type);
|
|
||||||
return classData.lookupField(fieldOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClassData getClassData(String type) {
|
|
||||||
ClassData classData = vtableMap.get(type);
|
|
||||||
if (classData == null) {
|
|
||||||
classData = new ClassData(type);
|
|
||||||
vtableMap.put(type, classData);
|
|
||||||
}
|
|
||||||
return classData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodIdItem lookupVirtualMethod(String classType, int methodIndex, boolean lookupSuper) {
|
|
||||||
if (lookupSuper) {
|
|
||||||
classType = lookupSuperclass(classType);
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassData classData = getClassData(classType);
|
|
||||||
|
|
||||||
return classData.lookupMethod(methodIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String lookupSuperclass(String typeDescriptor) {
|
|
||||||
connectIfNeeded();
|
|
||||||
|
|
||||||
String response = sendCommand("P " + typeDescriptor);
|
|
||||||
int colon = response.indexOf(':');
|
|
||||||
if (colon == -1) {
|
|
||||||
throw new RuntimeException("Invalid response from deodexerant");
|
|
||||||
}
|
|
||||||
|
|
||||||
String type = response.substring(colon+2);
|
|
||||||
if (type.length() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String lookupCommonSuperclass(String typeDescriptor1, String typeDescriptor2) {
|
|
||||||
CommonSuperclassLookup lookup = new CommonSuperclassLookup(typeDescriptor1, typeDescriptor2);
|
|
||||||
String result = cachedCommonSuperclassLookup.get(lookup);
|
|
||||||
if (result == null) {
|
|
||||||
String response = sendCommand("C " + typeDescriptor1 + " " + typeDescriptor2);
|
|
||||||
int colon = response.indexOf(':');
|
|
||||||
if (colon == -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = response.substring(colon+2);
|
|
||||||
|
|
||||||
cachedCommonSuperclassLookup.put(lookup, result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 List<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));
|
|
||||||
}
|
|
||||||
|
|
||||||
responseLines.add(response);
|
|
||||||
response = in.readLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseLines;
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodIdItem parseAndResolveMethod(String classType, String methodName, String methodParams,
|
|
||||||
String methodRet) {
|
|
||||||
TypeIdItem classTypeItem = resolveTypeOrSupertype(classType);
|
|
||||||
|
|
||||||
StringIdItem methodNameItem = StringIdItem.internStringIdItem(dexFile, methodName);
|
|
||||||
if (methodNameItem == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkedList<TypeIdItem> paramList = new LinkedList<TypeIdItem>();
|
|
||||||
|
|
||||||
for (int i=0; i<methodParams.length(); i++) {
|
|
||||||
switch (methodParams.charAt(i)) {
|
|
||||||
case 'Z':
|
|
||||||
case 'B':
|
|
||||||
case 'S':
|
|
||||||
case 'C':
|
|
||||||
case 'I':
|
|
||||||
case 'J':
|
|
||||||
case 'F':
|
|
||||||
case 'D':
|
|
||||||
paramList.add(getType(Character.toString(methodParams.charAt(i))));
|
|
||||||
break;
|
|
||||||
case 'L':
|
|
||||||
{
|
|
||||||
int end = methodParams.indexOf(';', i);
|
|
||||||
if (end == -1) {
|
|
||||||
throw new RuntimeException("invalid parameter in the method");
|
|
||||||
}
|
|
||||||
|
|
||||||
paramList.add(getType(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");
|
|
||||||
}
|
|
||||||
|
|
||||||
paramList.add(getType(methodParams.substring(i, end+1)));
|
|
||||||
i = end;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new RuntimeException("invalid parameter in the method");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeListItem paramListItem = null;
|
|
||||||
if (paramList.size() > 0) {
|
|
||||||
paramListItem = TypeListItem.internTypeListItem(dexFile, paramList);
|
|
||||||
if (paramListItem == null) {
|
|
||||||
throw new RuntimeException("Could not find type list item in dex file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeIdItem retType = getType(methodRet);
|
|
||||||
|
|
||||||
ProtoIdItem protoItem = ProtoIdItem.internProtoIdItem(dexFile, retType, paramListItem);
|
|
||||||
if (protoItem == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodIdItem methodIdItem;
|
|
||||||
|
|
||||||
do {
|
|
||||||
methodIdItem = MethodIdItem.internMethodIdItem(dexFile, classTypeItem, protoItem, methodNameItem);
|
|
||||||
if (methodIdItem != null) {
|
|
||||||
return methodIdItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
String superclassDescriptor = lookupSuperclass(classTypeItem.getTypeDescriptor());
|
|
||||||
if (superclassDescriptor == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
classTypeItem = TypeIdItem.internTypeIdItem(dexFile, superclassDescriptor);
|
|
||||||
|
|
||||||
while (classTypeItem == null && superclassDescriptor != null) {
|
|
||||||
superclassDescriptor = lookupSuperclass(superclassDescriptor);
|
|
||||||
classTypeItem = TypeIdItem.internTypeIdItem(dexFile, superclassDescriptor);
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Pattern fullMethodPattern = Pattern.compile("(\\[*(?:L[^;]+;|[ZBSCIJFD]))->([^(]+)\\(([^)]*)\\)(.+)");
|
|
||||||
private static final Pattern shortMethodPattern = Pattern.compile("([^(]+)\\(([^)]*)\\)(.+)");
|
|
||||||
//private static final Pattern fieldPattern = Pattern.compile("(\\[*L[^;]+;)->([^:]+):(.+)");
|
|
||||||
|
|
||||||
|
|
||||||
private FieldIdItem parseAndResolveField(String classType, String field) {
|
|
||||||
//expecting a string like someField:Lfield/type;
|
|
||||||
String[] parts = field.split(":");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
throw new RuntimeException("Invalid field descriptor " + field);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeIdItem classTypeItem = resolveTypeOrSupertype(classType);
|
|
||||||
if (classTypeItem == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String fieldName = parts[0];
|
|
||||||
String fieldType = parts[1];
|
|
||||||
|
|
||||||
StringIdItem fieldNameItem = StringIdItem.internStringIdItem(dexFile, fieldName);
|
|
||||||
if (fieldNameItem == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeIdItem fieldTypeItem = TypeIdItem.internTypeIdItem(dexFile, fieldType);
|
|
||||||
if (fieldTypeItem == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
FieldIdItem fieldIdItem;
|
|
||||||
|
|
||||||
do {
|
|
||||||
fieldIdItem = FieldIdItem.internFieldIdItem(dexFile, classTypeItem, fieldTypeItem, fieldNameItem);
|
|
||||||
if (fieldIdItem != null) {
|
|
||||||
return fieldIdItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
String superclassDescriptor = lookupSuperclass(classTypeItem.getTypeDescriptor());
|
|
||||||
classTypeItem = TypeIdItem.internTypeIdItem(dexFile, superclassDescriptor);
|
|
||||||
|
|
||||||
while (classTypeItem == null && superclassDescriptor != null) {
|
|
||||||
superclassDescriptor = lookupSuperclass(superclassDescriptor);
|
|
||||||
classTypeItem = TypeIdItem.internTypeIdItem(dexFile, superclassDescriptor);
|
|
||||||
}
|
|
||||||
} while (classTypeItem != null);
|
|
||||||
throw new RuntimeException("Could not find field in dex file");
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum InlineMethodType {
|
|
||||||
Virtual,
|
|
||||||
Direct,
|
|
||||||
Static
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InlineMethod {
|
|
||||||
public final String inlineMethodDescriptor;
|
|
||||||
private final InlineMethodType methodType;
|
|
||||||
private MethodIdItem methodIdItem = null;
|
|
||||||
|
|
||||||
public InlineMethod(String inlineMethodDescriptor, InlineMethodType methodType) {
|
|
||||||
this.inlineMethodDescriptor = inlineMethodDescriptor;
|
|
||||||
this.methodType = methodType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodIdItem getMethodIdItem() {
|
|
||||||
if (methodIdItem == null) {
|
|
||||||
loadMethod();
|
|
||||||
}
|
|
||||||
return methodIdItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public InlineMethodType getMethodType() {
|
|
||||||
return methodType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadMethod() {
|
|
||||||
Matcher m = fullMethodPattern.matcher(inlineMethodDescriptor);
|
|
||||||
if (!m.matches()) {
|
|
||||||
throw new RuntimeException("Invalid method descriptor: " + inlineMethodDescriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
String classType = m.group(1);
|
|
||||||
String methodName = m.group(2);
|
|
||||||
String methodParams = m.group(3);
|
|
||||||
String methodRet = m.group(4);
|
|
||||||
|
|
||||||
MethodIdItem method = parseAndResolveMethod(classType, methodName, methodParams, methodRet);
|
|
||||||
if (method == null) {
|
|
||||||
throw new RuntimeException("Could not resolve method " + inlineMethodDescriptor);
|
|
||||||
}
|
|
||||||
this.methodIdItem = method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeIdItem getType(String typeDescriptor) {
|
|
||||||
TypeIdItem type = TypeIdItem.internTypeIdItem(dexFile, typeDescriptor);
|
|
||||||
if (type == null) {
|
|
||||||
throw new RuntimeException("Could not find type \"" + typeDescriptor + "\" in dex file");
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private class ClassData {
|
|
||||||
private final String ClassType;
|
|
||||||
|
|
||||||
private boolean vtableLoaded = false;
|
|
||||||
private String[] methodNames;
|
|
||||||
private String[] methodParams;
|
|
||||||
private String[] methodRets;
|
|
||||||
private MethodIdItem[] resolvedMethods;
|
|
||||||
|
|
||||||
private boolean fieldsLoaded = false;
|
|
||||||
private SparseArray<String> instanceFields;
|
|
||||||
private SparseArray<FieldIdItem> resolvedFields;
|
|
||||||
|
|
||||||
|
|
||||||
public ClassData(String classType) {
|
|
||||||
this.ClassType = classType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MethodIdItem lookupMethod(int index) {
|
|
||||||
if (!vtableLoaded) {
|
|
||||||
loadvtable();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= resolvedMethods.length) {
|
|
||||||
throw new RuntimeException("Invalid vtable index " + index + ". Too large.");
|
|
||||||
}
|
|
||||||
if (resolvedMethods[index] == null) {
|
|
||||||
resolvedMethods[index] = parseAndResolveMethod(ClassType, methodNames[index], methodParams[index],
|
|
||||||
methodRets[index]);
|
|
||||||
}
|
|
||||||
return resolvedMethods[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
public FieldIdItem lookupField(int fieldOffset) {
|
|
||||||
if (!fieldsLoaded) {
|
|
||||||
loadFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
FieldIdItem fieldIdItem = resolvedFields.get(fieldOffset);
|
|
||||||
if (fieldIdItem == null) {
|
|
||||||
String field = instanceFields.get(fieldOffset);
|
|
||||||
if (field == null) {
|
|
||||||
throw new RuntimeException("Invalid field offset " + fieldOffset);
|
|
||||||
}
|
|
||||||
fieldIdItem = parseAndResolveField(ClassType, field);
|
|
||||||
if (fieldIdItem != null) {
|
|
||||||
resolvedFields.put(fieldOffset, fieldIdItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fieldIdItem;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadvtable() {
|
|
||||||
List<String> responseLines = sendMultilineCommand("V " + ClassType);
|
|
||||||
|
|
||||||
methodNames = new String[responseLines.size()];
|
|
||||||
methodParams = new String[responseLines.size()];
|
|
||||||
methodRets = new String[responseLines.size()];
|
|
||||||
resolvedMethods = new MethodIdItem[responseLines.size()];
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
for (String vtableEntry: responseLines) {
|
|
||||||
if (!vtableEntry.startsWith("vtable: ")) {
|
|
||||||
throw new RuntimeException("Invalid response from deodexerant");
|
|
||||||
}
|
|
||||||
|
|
||||||
String method = vtableEntry.substring(8);
|
|
||||||
Matcher m = shortMethodPattern.matcher(method);
|
|
||||||
if (!m.matches()) {
|
|
||||||
throw new RuntimeException("invalid method string: " + method);
|
|
||||||
}
|
|
||||||
|
|
||||||
methodNames[index] = m.group(1);
|
|
||||||
methodParams[index] = m.group(2);
|
|
||||||
methodRets[index] = m.group(3);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
vtableLoaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadFields() {
|
|
||||||
List<String> responseLines = sendMultilineCommand("F " + ClassType);
|
|
||||||
|
|
||||||
instanceFields = new SparseArray<String>(responseLines.size());
|
|
||||||
resolvedFields = new SparseArray<FieldIdItem>(responseLines.size());
|
|
||||||
|
|
||||||
for (String fieldLine: responseLines) {
|
|
||||||
if (!fieldLine.startsWith("field: ")) {
|
|
||||||
throw new RuntimeException("Invalid response from deodexerant");
|
|
||||||
}
|
|
||||||
|
|
||||||
String field = fieldLine.substring(7);
|
|
||||||
String[] parts = field.split(" ");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
throw new RuntimeException("Invalid response from deodexerant");
|
|
||||||
}
|
|
||||||
|
|
||||||
int fieldOffset = Integer.parseInt(parts[0]);
|
|
||||||
instanceFields.put(fieldOffset, parts[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldsLoaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CommonSuperclassLookup {
|
|
||||||
public final String Type1;
|
|
||||||
public final String Type2;
|
|
||||||
|
|
||||||
public CommonSuperclassLookup(String type1, String type2) {
|
|
||||||
this.Type1 = type1;
|
|
||||||
this.Type2 = type2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
CommonSuperclassLookup that = (CommonSuperclassLookup) o;
|
|
||||||
|
|
||||||
return Type1.equals(that.Type1) && Type2.equals(that.Type2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Type1.hashCode() + 31 * Type2.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,7 +31,6 @@ package org.jf.baksmali;
|
|||||||
import org.antlr.stringtemplate.StringTemplate;
|
import org.antlr.stringtemplate.StringTemplate;
|
||||||
import org.antlr.stringtemplate.StringTemplateGroup;
|
import org.antlr.stringtemplate.StringTemplateGroup;
|
||||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||||
import org.jf.baksmali.Deodex.*;
|
|
||||||
import org.jf.baksmali.Renderers.*;
|
import org.jf.baksmali.Renderers.*;
|
||||||
import org.jf.dexlib.Code.Analysis.ClassPath;
|
import org.jf.dexlib.Code.Analysis.ClassPath;
|
||||||
import org.jf.dexlib.DexFile;
|
import org.jf.dexlib.DexFile;
|
||||||
@ -46,11 +45,11 @@ public class baksmali {
|
|||||||
public static boolean useSequentialLabels = false;
|
public static boolean useSequentialLabels = false;
|
||||||
public static boolean outputDebugInfo = true;
|
public static boolean outputDebugInfo = true;
|
||||||
public static boolean addCodeOffsets = false;
|
public static boolean addCodeOffsets = false;
|
||||||
|
public static boolean deodex = false;
|
||||||
public static int registerInfo = 0;
|
public static int registerInfo = 0;
|
||||||
public static String bootClassPath;
|
public static String bootClassPath;
|
||||||
public static DeodexUtil deodexUtil = null;
|
|
||||||
|
|
||||||
public static void disassembleDexFile(DexFile dexFile, Deodexerant deodexerant, String outputDirectory,
|
public static void disassembleDexFile(DexFile dexFile, boolean deodex, String outputDirectory,
|
||||||
String bootClassPathDir, String bootClassPath, boolean noParameterRegisters,
|
String bootClassPathDir, String bootClassPath, boolean noParameterRegisters,
|
||||||
boolean useLocalsDirective, boolean useSequentialLabels,
|
boolean useLocalsDirective, boolean useSequentialLabels,
|
||||||
boolean outputDebugInfo, boolean addCodeOffsets, int registerInfo)
|
boolean outputDebugInfo, boolean addCodeOffsets, int registerInfo)
|
||||||
@ -60,17 +59,14 @@ public class baksmali {
|
|||||||
baksmali.useSequentialLabels = useSequentialLabels;
|
baksmali.useSequentialLabels = useSequentialLabels;
|
||||||
baksmali.outputDebugInfo = outputDebugInfo;
|
baksmali.outputDebugInfo = outputDebugInfo;
|
||||||
baksmali.addCodeOffsets = addCodeOffsets;
|
baksmali.addCodeOffsets = addCodeOffsets;
|
||||||
|
baksmali.deodex = deodex;
|
||||||
baksmali.registerInfo = registerInfo;
|
baksmali.registerInfo = registerInfo;
|
||||||
baksmali.bootClassPath = bootClassPath;
|
baksmali.bootClassPath = bootClassPath;
|
||||||
|
|
||||||
if (registerInfo != 0) {
|
if (registerInfo != 0 || deodex) {
|
||||||
ClassPath.InitializeClassPath(bootClassPathDir, bootClassPath==null?null:bootClassPath.split(":"), dexFile);
|
ClassPath.InitializeClassPath(bootClassPathDir, bootClassPath==null?null:bootClassPath.split(":"), dexFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deodexerant != null) {
|
|
||||||
baksmali.deodexUtil = new DeodexUtil(deodexerant);
|
|
||||||
}
|
|
||||||
|
|
||||||
File outputDirectoryFile = new File(outputDirectory);
|
File outputDirectoryFile = new File(outputDirectory);
|
||||||
if (!outputDirectoryFile.exists()) {
|
if (!outputDirectoryFile.exists()) {
|
||||||
if (!outputDirectoryFile.mkdirs()) {
|
if (!outputDirectoryFile.mkdirs()) {
|
||||||
|
150
baksmali/src/main/java/org/jf/baksmali/deodexCheck.java
Normal file
150
baksmali/src/main/java/org/jf/baksmali/deodexCheck.java
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package org.jf.baksmali;
|
||||||
|
|
||||||
|
import org.apache.commons.cli.*;
|
||||||
|
import org.jf.dexlib.Code.Analysis.ClassPath;
|
||||||
|
|
||||||
|
public class deodexCheck {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
CommandLineParser parser = new PosixParser();
|
||||||
|
CommandLine commandLine;
|
||||||
|
|
||||||
|
Options options = buildOptions();
|
||||||
|
|
||||||
|
try {
|
||||||
|
commandLine = parser.parse(options, args);
|
||||||
|
} catch (ParseException ex) {
|
||||||
|
usage(options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
|
||||||
|
String bootClassPathDir = ".";
|
||||||
|
String deodexerantHost = null;
|
||||||
|
int deodexerantPort = 0;
|
||||||
|
int classStartIndex = 0;
|
||||||
|
|
||||||
|
|
||||||
|
String[] remainingArgs = commandLine.getArgs();
|
||||||
|
|
||||||
|
|
||||||
|
if (commandLine.hasOption("v")) {
|
||||||
|
main.version();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandLine.hasOption("?")) {
|
||||||
|
usage(options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingArgs.length > 0) {
|
||||||
|
usage(options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (commandLine.hasOption("c")) {
|
||||||
|
String bcp = commandLine.getOptionValue("c");
|
||||||
|
if (bcp.charAt(0) == ':') {
|
||||||
|
bootClassPath = bootClassPath + bcp;
|
||||||
|
} else {
|
||||||
|
bootClassPath = bcp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandLine.hasOption("i")) {
|
||||||
|
try {
|
||||||
|
classStartIndex = Integer.parseInt(commandLine.getOptionValue("i"));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandLine.hasOption("C")) {
|
||||||
|
bootClassPathDir = commandLine.getOptionValue("C");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commandLine.hasOption("x")) {
|
||||||
|
String deodexerantAddress = commandLine.getOptionValue("x");
|
||||||
|
String[] parts = deodexerantAddress.split(":");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
System.err.println("Invalid deodexerant address. Expecting :<port> or <host>:<port>");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
deodexerantHost = parts[0];
|
||||||
|
if (deodexerantHost.length() == 0) {
|
||||||
|
deodexerantHost = "localhost";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
deodexerantPort = Integer.parseInt(parts[1]);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
System.err.println("Invalid port \"" + deodexerantPort + "\" for deodexerant address");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassPath.InitializeClassPath(bootClassPathDir, bootClassPath==null?null:bootClassPath.split(":"), null);
|
||||||
|
|
||||||
|
ClassPath.validateAgainstDeodexerant(deodexerantHost, deodexerantPort, classStartIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the usage message.
|
||||||
|
*/
|
||||||
|
private static void usage(Options options) {
|
||||||
|
HelpFormatter formatter = new HelpFormatter();
|
||||||
|
formatter.setWidth(100);
|
||||||
|
formatter.printHelp("java -classpath baksmali.jar deodexCheck -x HOST:PORT [options]",
|
||||||
|
"disassembles and/or dumps a dex file", options, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Options buildOptions() {
|
||||||
|
Options options = new Options();
|
||||||
|
|
||||||
|
Option versionOption = OptionBuilder.withLongOpt("version")
|
||||||
|
.withDescription("prints the version then exits")
|
||||||
|
.create("v");
|
||||||
|
|
||||||
|
Option helpOption = OptionBuilder.withLongOpt("help")
|
||||||
|
.withDescription("prints the help message then exits")
|
||||||
|
.create("?");
|
||||||
|
|
||||||
|
Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
|
||||||
|
.withDescription("the bootclasspath jars to use, for analysis. Defaults to " +
|
||||||
|
"core.jar:ext.jar:framework.jar:android.policy.jar:services.jar. If you specify a value that " +
|
||||||
|
"begins with a :, it will be appended to the default bootclasspath")
|
||||||
|
.hasOptionalArg()
|
||||||
|
.withArgName("BOOTCLASSPATH")
|
||||||
|
.create("c");
|
||||||
|
|
||||||
|
Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
|
||||||
|
.withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
|
||||||
|
"directory")
|
||||||
|
.hasArg()
|
||||||
|
.withArgName("DIR")
|
||||||
|
.create("C");
|
||||||
|
|
||||||
|
Option deodexerantOption = OptionBuilder.withLongOpt("deodexerant")
|
||||||
|
.isRequired()
|
||||||
|
.withDescription("connect to deodexerant on the specified HOST:PORT, and validate the virtual method " +
|
||||||
|
"indexes, field offsets and inline methods against what dexlib calculates")
|
||||||
|
.hasArg()
|
||||||
|
.withArgName("HOST:PORT")
|
||||||
|
.create("x");
|
||||||
|
|
||||||
|
Option classStartOption = OptionBuilder.withLongOpt("class-start-index")
|
||||||
|
.withDescription("Start checking classes at the given class index")
|
||||||
|
.hasArg()
|
||||||
|
.withArgName("CLASSINDEX")
|
||||||
|
.create("i");
|
||||||
|
|
||||||
|
options.addOption(versionOption);
|
||||||
|
options.addOption(helpOption);
|
||||||
|
options.addOption(deodexerantOption);
|
||||||
|
options.addOption(classPathOption);
|
||||||
|
options.addOption(classPathDirOption);
|
||||||
|
options.addOption(classStartOption);
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,6 @@
|
|||||||
package org.jf.baksmali;
|
package org.jf.baksmali;
|
||||||
|
|
||||||
import org.apache.commons.cli.*;
|
import org.apache.commons.cli.*;
|
||||||
import org.jf.baksmali.Deodex.Deodexerant;
|
|
||||||
import org.jf.dexlib.DexFile;
|
import org.jf.dexlib.DexFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -88,6 +87,7 @@ public class main {
|
|||||||
boolean useSequentialLabels = false;
|
boolean useSequentialLabels = false;
|
||||||
boolean outputDebugInfo = true;
|
boolean outputDebugInfo = true;
|
||||||
boolean addCodeOffsets = false;
|
boolean addCodeOffsets = false;
|
||||||
|
boolean deodex = false;
|
||||||
|
|
||||||
int registerInfo = 0;
|
int registerInfo = 0;
|
||||||
|
|
||||||
@ -95,10 +95,9 @@ public class main {
|
|||||||
String dumpFileName = null;
|
String dumpFileName = null;
|
||||||
String outputDexFileName = null;
|
String outputDexFileName = null;
|
||||||
String inputDexFileName = null;
|
String inputDexFileName = null;
|
||||||
String deodexerantHost = null;
|
|
||||||
String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
|
String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
|
||||||
String bootClassPathDir = ".";
|
String bootClassPathDir = ".";
|
||||||
int deodexerantPort = 0;
|
|
||||||
|
|
||||||
String[] remainingArgs = commandLine.getArgs();
|
String[] remainingArgs = commandLine.getArgs();
|
||||||
|
|
||||||
@ -176,30 +175,14 @@ public class main {
|
|||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
String bcp = commandLine.getOptionValue("c");
|
String bcp = commandLine.getOptionValue("c");
|
||||||
if (bcp.charAt(0) == ':') {
|
if (bcp != null && bcp.charAt(0) == ':') {
|
||||||
bootClassPath = bootClassPath + bcp;
|
bootClassPath = bootClassPath + bcp;
|
||||||
} else {
|
} else {
|
||||||
bootClassPath = bcp;
|
bootClassPath = bcp;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'x':
|
case 'x':
|
||||||
String deodexerantAddress = commandLine.getOptionValue("x");
|
deodex = true;
|
||||||
String[] parts = deodexerantAddress.split(":");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
System.err.println("Invalid deodexerant address. Expecting :<port> or <host>:<port>");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
deodexerantHost = parts[0];
|
|
||||||
if (deodexerantHost.length() == 0) {
|
|
||||||
deodexerantHost = "localhost";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
deodexerantPort = Integer.parseInt(parts[1]);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
System.err.println("Invalid port \"" + deodexerantPort + "\" for deodexerant address");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'N':
|
case 'N':
|
||||||
disassemble = false;
|
disassemble = false;
|
||||||
@ -240,16 +223,6 @@ public class main {
|
|||||||
//Read in and parse the dex file
|
//Read in and parse the dex file
|
||||||
DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false);
|
DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false);
|
||||||
|
|
||||||
Deodexerant deodexerant = null;
|
|
||||||
|
|
||||||
|
|
||||||
if (deodexerantHost != null) {
|
|
||||||
if (!dexFile.isOdex()) {
|
|
||||||
System.err.println("-x cannot be used with a normal dex file. Ignoring -x");
|
|
||||||
}
|
|
||||||
deodexerant = new Deodexerant(dexFile, deodexerantHost, deodexerantPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dexFile.isOdex()) {
|
if (dexFile.isOdex()) {
|
||||||
if (doDump) {
|
if (doDump) {
|
||||||
System.err.println("-d cannot be used with on odex file. Ignoring -d");
|
System.err.println("-d cannot be used with on odex file. Ignoring -d");
|
||||||
@ -257,15 +230,17 @@ public class main {
|
|||||||
if (write) {
|
if (write) {
|
||||||
System.err.println("-w cannot be used with an odex file. Ignoring -w");
|
System.err.println("-w cannot be used with an odex file. Ignoring -w");
|
||||||
}
|
}
|
||||||
if (deodexerant == null) {
|
if (!deodex) {
|
||||||
System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
|
System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
|
||||||
System.err.println("won't be able to re-assemble the results unless you use deodexerant, and");
|
System.err.println("won't be able to re-assemble the results unless you deodex it with the -x");
|
||||||
System.err.println("the -x option for baksmali");
|
System.err.println("option");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
deodex = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disassemble) {
|
if (disassemble) {
|
||||||
baksmali.disassembleDexFile(dexFile, deodexerant, outputDirectory, bootClassPathDir, bootClassPath,
|
baksmali.disassembleDexFile(dexFile, deodex, outputDirectory, bootClassPathDir, bootClassPath,
|
||||||
noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets,
|
noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets,
|
||||||
registerInfo);
|
registerInfo);
|
||||||
}
|
}
|
||||||
@ -317,7 +292,7 @@ public class main {
|
|||||||
/**
|
/**
|
||||||
* Prints the version message.
|
* Prints the version message.
|
||||||
*/
|
*/
|
||||||
private static void version() {
|
protected static void version() {
|
||||||
System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
|
System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
|
||||||
System.out.println("Copyright (C) 2009 Ben Gruver");
|
System.out.println("Copyright (C) 2009 Ben Gruver");
|
||||||
System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
|
System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
|
||||||
@ -366,15 +341,13 @@ public class main {
|
|||||||
.create("F");
|
.create("F");
|
||||||
|
|
||||||
Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
|
Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
|
||||||
.withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method" +
|
.withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method " +
|
||||||
" parameters")
|
"parameters")
|
||||||
.create("p");
|
.create("p");
|
||||||
|
|
||||||
Option deodexerantOption = OptionBuilder.withLongOpt("deodexerant")
|
Option deodexerantOption = OptionBuilder.withLongOpt("deodex")
|
||||||
.withDescription("connect to deodexerant on the specified HOST:PORT, and deodex the input odex"
|
.withDescription("deodex the given odex file. This option is ignored if the input file is not an " +
|
||||||
+ " file. This option is ignored if the input file is a dex file instead of an odex file")
|
"odex file.")
|
||||||
.hasArg()
|
|
||||||
.withArgName("HOST:PORT")
|
|
||||||
.create("x");
|
.create("x");
|
||||||
|
|
||||||
Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
|
Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
|
||||||
|
@ -188,11 +188,6 @@ Format22cs(Opcode, RegisterA, RegisterB, FieldOffset) ::=
|
|||||||
<Opcode> <RegisterA>, <RegisterB>, field@<FieldOffset>
|
<Opcode> <RegisterA>, <RegisterB>, field@<FieldOffset>
|
||||||
>>
|
>>
|
||||||
|
|
||||||
Format22csf(Opcode, RegisterA, RegisterB, Reference) ::=
|
|
||||||
<<
|
|
||||||
<Opcode> <RegisterA>, <RegisterB>, <Reference>
|
|
||||||
>>
|
|
||||||
|
|
||||||
Format22s(Opcode, RegisterA, RegisterB, Literal) ::=
|
Format22s(Opcode, RegisterA, RegisterB, Literal) ::=
|
||||||
<<
|
<<
|
||||||
<Opcode> <RegisterA>, <RegisterB>, <Literal>
|
<Opcode> <RegisterA>, <RegisterB>, <Literal>
|
||||||
@ -248,21 +243,11 @@ Format35s(Opcode, Registers, Reference) ::=
|
|||||||
<Opcode> {<Registers; separator=", ">}, <Reference>
|
<Opcode> {<Registers; separator=", ">}, <Reference>
|
||||||
>>
|
>>
|
||||||
|
|
||||||
Format35sf(Opcode, Registers, Reference) ::=
|
|
||||||
<<
|
|
||||||
<Opcode> {<Registers; separator=", ">}, <Reference>
|
|
||||||
>>
|
|
||||||
|
|
||||||
Format35ms(Opcode, Registers, MethodIndex) ::=
|
Format35ms(Opcode, Registers, MethodIndex) ::=
|
||||||
<<
|
<<
|
||||||
<Opcode> {<Registers; separator=", ">}, vtable@<MethodIndex>
|
<Opcode> {<Registers; separator=", ">}, vtable@<MethodIndex>
|
||||||
>>
|
>>
|
||||||
|
|
||||||
Format35msf(Opcode, Registers, Reference) ::=
|
|
||||||
<<
|
|
||||||
<Opcode> {<Registers; separator=", ">}, <Reference>
|
|
||||||
>>
|
|
||||||
|
|
||||||
Format3rc(Opcode, StartRegister, LastRegister, Reference) ::=
|
Format3rc(Opcode, StartRegister, LastRegister, Reference) ::=
|
||||||
<<
|
<<
|
||||||
<Opcode> {<StartRegister> .. <LastRegister>}, <Reference>
|
<Opcode> {<StartRegister> .. <LastRegister>}, <Reference>
|
||||||
@ -273,11 +258,6 @@ Format3rms(Opcode, StartRegister, LastRegister, MethodIndex) ::=
|
|||||||
<Opcode> {<StartRegister> .. <LastRegister>}, vtable@<MethodIndex>
|
<Opcode> {<StartRegister> .. <LastRegister>}, vtable@<MethodIndex>
|
||||||
>>
|
>>
|
||||||
|
|
||||||
Format3rmsf(Opcode, StartRegister, LastRegister, Reference) ::=
|
|
||||||
<<
|
|
||||||
<Opcode> {<StartRegister> .. <LastRegister>}, <Reference>
|
|
||||||
>>
|
|
||||||
|
|
||||||
Format51l(Opcode, RegisterA, Literal) ::=
|
Format51l(Opcode, RegisterA, Literal) ::=
|
||||||
<<
|
<<
|
||||||
<Opcode> <RegisterA>, <Literal>
|
<Opcode> <RegisterA>, <Literal>
|
||||||
@ -401,7 +381,6 @@ TypeReference(TypeDescriptor) ::=
|
|||||||
>>
|
>>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SimpleEncodedValue(Value) ::=
|
SimpleEncodedValue(Value) ::=
|
||||||
<<
|
<<
|
||||||
<Value>
|
<Value>
|
||||||
|
@ -20,11 +20,7 @@
|
|||||||
#and this will always throw an exception. Everything below
|
#and this will always throw an exception. Everything below
|
||||||
#here, until the here2: label is dead code, and should be
|
#here, until the here2: label is dead code, and should be
|
||||||
#commented out. This instruction itself should be be replaced
|
#commented out. This instruction itself should be be replaced
|
||||||
#with a call to Ljava/lang/Object;->hashCode()I, followed
|
#with a call to Ljava/lang/Object;->hashCode()I
|
||||||
#by a goto/32 0, which is just there to prevent dexopt from
|
|
||||||
#thinking that execution will fall off the end of the method
|
|
||||||
#i.e. if all the code following this was dead (and thus commented
|
|
||||||
#out)
|
|
||||||
invoke-virtual {v0}, Lrandomclass;->getSuperclass()Lsuperclass;
|
invoke-virtual {v0}, Lrandomclass;->getSuperclass()Lsuperclass;
|
||||||
move-result-object v1
|
move-result-object v1
|
||||||
|
|
||||||
|
@ -6,15 +6,13 @@ import org.jf.dexlib.ItemType;
|
|||||||
import org.jf.dexlib.MethodIdItem;
|
import org.jf.dexlib.MethodIdItem;
|
||||||
import org.jf.dexlib.Util.ExceptionWithContext;
|
import org.jf.dexlib.Util.ExceptionWithContext;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class AnalyzedInstruction {
|
public class AnalyzedInstruction implements Comparable<AnalyzedInstruction> {
|
||||||
/**
|
/**
|
||||||
* The actual instruction
|
* 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
|
* 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
|
* 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
|
* Instructions that can execution could pass on to next during normal execution
|
||||||
*/
|
*/
|
||||||
protected final LinkedList<AnalyzedInstruction> successors = new LinkedList<AnalyzedInstruction>();
|
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
|
* This contains the register types *after* the instruction has executed
|
||||||
*/
|
*/
|
||||||
protected final RegisterType[] postRegisterMap;
|
protected final RegisterType[] postRegisterMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is set to true when this instruction follows an odexed instruction that couldn't be deodexed. In this case
|
* When deodexing, we might need to deodex this instruction multiple times, when we merge in new register
|
||||||
* the unodexable instruction is guaranteed to throw an NPE, so anything following it is dead, up until a non-dead
|
* information. When this happens, we need to restore the original (odexed) instruction, so we can deodex it again
|
||||||
* 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"
|
protected final Instruction originalInstruction;
|
||||||
* 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.
|
/**
|
||||||
|
* 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;
|
protected boolean dead = false;
|
||||||
|
|
||||||
public AnalyzedInstruction(Instruction instruction, int instructionIndex, int registerCount) {
|
public AnalyzedInstruction(Instruction instruction, int instructionIndex, int registerCount) {
|
||||||
this.instruction = instruction;
|
this.instruction = instruction;
|
||||||
|
this.originalInstruction = instruction;
|
||||||
this.instructionIndex = instructionIndex;
|
this.instructionIndex = instructionIndex;
|
||||||
this.postRegisterMap = new RegisterType[registerCount];
|
this.postRegisterMap = new RegisterType[registerCount];
|
||||||
|
this.preRegisterMap = new RegisterType[registerCount];
|
||||||
RegisterType unknown = RegisterType.getRegisterType(RegisterType.Category.Unknown, null);
|
RegisterType unknown = RegisterType.getRegisterType(RegisterType.Category.Unknown, null);
|
||||||
for (int i=0; i<registerCount; i++) {
|
for (int i=0; i<registerCount; i++) {
|
||||||
|
preRegisterMap[i] = unknown;
|
||||||
postRegisterMap[i] = unknown;
|
postRegisterMap[i] = unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,38 +87,26 @@ public class AnalyzedInstruction {
|
|||||||
return predecessors.size();
|
return predecessors.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AnalyzedInstruction> getPredecessors() {
|
public SortedSet<AnalyzedInstruction> getPredecessors() {
|
||||||
return Collections.unmodifiableList(predecessors);
|
return Collections.unmodifiableSortedSet(predecessors);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkPredecessorSorted(AnalyzedInstruction predecessor) {
|
protected boolean addPredecessor(AnalyzedInstruction predecessor) {
|
||||||
if (predecessors.size() == 0) {
|
return predecessors.add(predecessor);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (predecessor.getInstructionIndex() <= predecessors.getLast().getInstructionIndex()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addPredecessor(AnalyzedInstruction predecessor) {
|
protected void addSuccessor(AnalyzedInstruction successor) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
successors.add(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() {
|
public int getSuccessorCount() {
|
||||||
@ -107,6 +117,18 @@ public class AnalyzedInstruction {
|
|||||||
return Collections.unmodifiableList(successors);
|
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
|
* 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
|
* 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,
|
* 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
|
* 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.
|
* 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
|
* @return a boolean value indicating whether this instruction is a beginning instruction
|
||||||
*/
|
*/
|
||||||
public boolean isBeginningInstruction() {
|
public boolean isBeginningInstruction() {
|
||||||
@ -121,34 +146,50 @@ public class AnalyzedInstruction {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (predecessors.getFirst().instructionIndex == -1) {
|
if (predecessors.first().instructionIndex == -1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sets the "post-instruction" register type as indicated. This should only be used to set
|
* Merges the given register type into the specified pre-instruction register, and also sets the post-instruction
|
||||||
* the method parameter types for the "start of method" instruction, or to set the register
|
* register type accordingly if it isn't a destination register for this instruction
|
||||||
* type of the destination register during verification. The change to the register type
|
|
||||||
* will
|
|
||||||
* @param registerNumber Which register to set
|
* @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 registerNumber >= 0 && registerNumber < postRegisterMap.length;
|
||||||
assert registerType != null;
|
assert registerType != null;
|
||||||
|
|
||||||
RegisterType oldRegisterType = postRegisterMap[registerNumber];
|
RegisterType oldRegisterType = preRegisterMap[registerNumber];
|
||||||
if (oldRegisterType == registerType) {
|
RegisterType mergedRegisterType = oldRegisterType.merge(registerType);
|
||||||
|
|
||||||
|
if (mergedRegisterType == oldRegisterType) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
postRegisterMap[registerNumber] = registerType;
|
preRegisterMap[registerNumber] = mergedRegisterType;
|
||||||
return true;
|
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;
|
RegisterType mergedRegisterType = null;
|
||||||
for (AnalyzedInstruction predecessor: predecessors) {
|
for (AnalyzedInstruction predecessor: predecessors) {
|
||||||
RegisterType predecessorRegisterType = predecessor.postRegisterMap[registerNumber];
|
RegisterType predecessorRegisterType = predecessor.postRegisterMap[registerNumber];
|
||||||
@ -158,9 +199,30 @@ public class AnalyzedInstruction {
|
|||||||
return mergedRegisterType;
|
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() {
|
protected boolean isInvokeInit() {
|
||||||
if (instruction == null ||
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +249,6 @@ public class AnalyzedInstruction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean setsRegister(int registerNumber) {
|
public boolean setsRegister(int registerNumber) {
|
||||||
|
|
||||||
//When constructing a new object, the register type will be an uninitialized reference after the new-instance
|
//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
|
//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
|
//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) {
|
if (registerNumber == destinationRegister) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
RegisterType preInstructionDestRegisterType = getMergedRegisterTypeFromPredecessors(registerNumber);
|
RegisterType preInstructionDestRegisterType = getPreInstructionRegisterType(registerNumber);
|
||||||
if (preInstructionDestRegisterType.category != RegisterType.Category.UninitRef &&
|
if (preInstructionDestRegisterType.category != RegisterType.Category.UninitRef &&
|
||||||
preInstructionDestRegisterType.category != RegisterType.Category.UninitThis) {
|
preInstructionDestRegisterType.category != RegisterType.Category.UninitThis) {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//check if the uninit ref has been copied to another register
|
//check if the uninit ref has been copied to another register
|
||||||
if (getMergedRegisterTypeFromPredecessors(registerNumber) == preInstructionDestRegisterType) {
|
if (getPreInstructionRegisterType(registerNumber) == preInstructionDestRegisterType) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -251,14 +312,17 @@ public class AnalyzedInstruction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public RegisterType getPreInstructionRegisterType(int registerNumber) {
|
public RegisterType getPreInstructionRegisterType(int registerNumber) {
|
||||||
//if the specific register is not a destination register, then the stored post-instruction register type will
|
return preRegisterMap[registerNumber];
|
||||||
//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
|
|
||||||
|
|
||||||
if (this.setsRegister(registerNumber)) {
|
public int compareTo(AnalyzedInstruction analyzedInstruction) {
|
||||||
return getMergedRegisterTypeFromPredecessors(registerNumber);
|
//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 {
|
} else {
|
||||||
return postRegisterMap[registerNumber];
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,24 @@ public class ClassPath {
|
|||||||
private final HashMap<String, ClassDef> classDefs;
|
private final HashMap<String, ClassDef> classDefs;
|
||||||
protected ClassDef javaLangObjectClassDef; //Ljava/lang/Object;
|
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) {
|
public static void InitializeClassPath(String bootClassPathDir, String[] bootClassPath, DexFile dexFile) {
|
||||||
if (theClassPath != null) {
|
if (theClassPath != null) {
|
||||||
throw new ExceptionWithContext("Cannot initialize ClassPath multiple times");
|
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"}) {
|
for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) {
|
||||||
ClassDef classDef = new PrimitiveClassDef(primitiveType);
|
ClassDef classDef = new PrimitiveClassDef(primitiveType);
|
||||||
@ -76,8 +96,6 @@ public class ClassPath {
|
|||||||
if (classDefItem.getClassType().getTypeDescriptor().equals("Ljava/lang/Object;")) {
|
if (classDefItem.getClassType().getTypeDescriptor().equals("Ljava/lang/Object;")) {
|
||||||
theClassPath.javaLangObjectClassDef = classDef;
|
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);
|
ClassDef classDef = theClassPath.classDefs.get(classType);
|
||||||
if (classDef == null) {
|
if (classDef == null) {
|
||||||
//if it's an array class, try to create it
|
//if it's an array class, try to create it
|
||||||
if (classType.charAt(0) == '[') {
|
if (classType.charAt(0) == '[') {
|
||||||
return theClassPath.createArrayClassDef(classType);
|
return theClassPath.createArrayClassDef(classType);
|
||||||
} else {
|
} else {
|
||||||
//TODO: we should output a warning
|
if (createUnresolvedClassDef) {
|
||||||
return theClassPath.createUnresolvedClassDef(classType);
|
//TODO: we should output a warning
|
||||||
|
return theClassPath.createUnresolvedClassDef(classType);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return classDef;
|
return classDef;
|
||||||
@ -105,6 +131,10 @@ public class ClassPath {
|
|||||||
return getClassDef(classType.getTypeDescriptor());
|
return getClassDef(classType.getTypeDescriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ClassDef getClassDef(TypeIdItem classType, boolean creatUnresolvedClassDef) {
|
||||||
|
return getClassDef(classType.getTypeDescriptor(), creatUnresolvedClassDef);
|
||||||
|
}
|
||||||
|
|
||||||
//256 [ characters
|
//256 [ characters
|
||||||
private static final String arrayPrefix = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" +
|
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)
|
//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)) {
|
if (class1.implementsInterface(class2)) {
|
||||||
return class2;
|
return class2;
|
||||||
}
|
}
|
||||||
return theClassPath.javaLangObjectClassDef;
|
return theClassPath.javaLangObjectClassDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!class2.isInterface && class1.isInterface) {
|
if (class1.isInterface) {
|
||||||
if (class2.implementsInterface(class1)) {
|
if (class2.implementsInterface(class1)) {
|
||||||
return class1;
|
return class1;
|
||||||
}
|
}
|
||||||
@ -220,6 +250,15 @@ public class ClassPath {
|
|||||||
return getArrayClassDefByElementClassAndDimension(theClassPath.javaLangObjectClassDef, dimensions);
|
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 {
|
public static class ArrayClassDef extends ClassDef {
|
||||||
private final ClassDef elementClass;
|
private final ClassDef elementClass;
|
||||||
private final int arrayDimensions;
|
private final int arrayDimensions;
|
||||||
@ -382,6 +421,18 @@ public class ClassPath {
|
|||||||
public final static int PrimitiveClassDef = 1;
|
public final static int PrimitiveClassDef = 1;
|
||||||
public final static int UnresolvedClassDef = 2;
|
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
|
* This constructor is used for the ArrayClassDef, PrimitiveClassDef and UnresolvedClassDef subclasses
|
||||||
* @param classType the class type
|
* @param classType the class type
|
||||||
@ -403,6 +454,9 @@ public class ClassPath {
|
|||||||
instanceFields = superclass.instanceFields;
|
instanceFields = superclass.instanceFields;
|
||||||
instanceFieldLookup = superclass.instanceFieldLookup;
|
instanceFieldLookup = superclass.instanceFieldLookup;
|
||||||
classDepth = 1; //1 off from java.lang.Object
|
classDepth = 1; //1 off from java.lang.Object
|
||||||
|
|
||||||
|
virtualMethods = null;
|
||||||
|
interfaceTable = null;
|
||||||
} else if (classFlavor == PrimitiveClassDef) {
|
} else if (classFlavor == PrimitiveClassDef) {
|
||||||
//primitive type
|
//primitive type
|
||||||
assert classType.charAt(0) != '[' && classType.charAt(0) != 'L';
|
assert classType.charAt(0) != '[' && classType.charAt(0) != 'L';
|
||||||
@ -416,6 +470,9 @@ public class ClassPath {
|
|||||||
instanceFields = null;
|
instanceFields = null;
|
||||||
instanceFieldLookup = null;
|
instanceFieldLookup = null;
|
||||||
classDepth = 0; //TODO: maybe use -1 to indicate not applicable?
|
classDepth = 0; //TODO: maybe use -1 to indicate not applicable?
|
||||||
|
|
||||||
|
virtualMethods = null;
|
||||||
|
interfaceTable = null;
|
||||||
} else /*if (classFlavor == UnresolvedClassDef)*/ {
|
} else /*if (classFlavor == UnresolvedClassDef)*/ {
|
||||||
assert classType.charAt(0) == 'L';
|
assert classType.charAt(0) == 'L';
|
||||||
this.classType = classType;
|
this.classType = classType;
|
||||||
@ -429,6 +486,9 @@ public class ClassPath {
|
|||||||
instanceFields = superclass.instanceFields;
|
instanceFields = superclass.instanceFields;
|
||||||
instanceFieldLookup = superclass.instanceFieldLookup;
|
instanceFieldLookup = superclass.instanceFieldLookup;
|
||||||
classDepth = 1; //1 off from java.lang.Object
|
classDepth = 1; //1 off from java.lang.Object
|
||||||
|
|
||||||
|
virtualMethods = null;
|
||||||
|
interfaceTable = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,6 +506,9 @@ public class ClassPath {
|
|||||||
|
|
||||||
implementedInterfaces = loadAllImplementedInterfaces(classDefItem);
|
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);
|
vtable = loadVtable(classDefItem);
|
||||||
virtualMethodLookup = new HashMap<String, Integer>((int)Math.ceil(vtable.length / .7f), .75f);
|
virtualMethodLookup = new HashMap<String, Integer>((int)Math.ceil(vtable.length / .7f), .75f);
|
||||||
for (int i=0; i<vtable.length; i++) {
|
for (int i=0; i<vtable.length; i++) {
|
||||||
@ -513,6 +576,17 @@ public class ClassPath {
|
|||||||
return virtualMethodLookup.containsKey(method);
|
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) {
|
private void swap(byte[] fieldTypes, String[] fields, int position1, int position2) {
|
||||||
byte tempType = fieldTypes[position1];
|
byte tempType = fieldTypes[position1];
|
||||||
fieldTypes[position1] = fieldTypes[position2];
|
fieldTypes[position1] = fieldTypes[position2];
|
||||||
@ -586,6 +660,56 @@ public class ClassPath {
|
|||||||
return implementedInterfaceSet;
|
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) {
|
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
|
//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>();
|
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
|
//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)
|
//method (i.e. if it was implemented by the superclass)
|
||||||
ClassDataItem classDataItem = classDefItem.getClassData();
|
if (!this.isInterface) {
|
||||||
if (classDataItem != null) {
|
ClassDataItem classDataItem = classDefItem.getClassData();
|
||||||
EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods();
|
if (classDataItem != null) {
|
||||||
if (virtualMethods != null) {
|
EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods();
|
||||||
for (EncodedMethod virtualMethod: virtualMethods) {
|
if (virtualMethods != null) {
|
||||||
String methodString = virtualMethod.method.getVirtualMethodString();
|
for (EncodedMethod virtualMethod: virtualMethods) {
|
||||||
if (tempVirtualMethodLookup.get(methodString) == null) {
|
String methodString = virtualMethod.method.getVirtualMethodString();
|
||||||
virtualMethodList.add(methodString);
|
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;
|
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) {
|
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
|
//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).
|
//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 REFERENCE = 0;
|
||||||
final byte WIDE = 1;
|
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
|
//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
|
//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.
|
//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 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) {
|
if (fieldTypes[front] == WIDE) {
|
||||||
//we need to swap in a 32-bit field, so the wide fields will be correctly aligned
|
//we need to swap in a 32-bit field, so the wide fields will be correctly aligned
|
||||||
back = fields.length - 1;
|
back = fields.length - 1;
|
||||||
@ -725,7 +901,7 @@ public class ClassPath {
|
|||||||
|
|
||||||
int superFieldCount = 0;
|
int superFieldCount = 0;
|
||||||
if (superclass != null) {
|
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
|
//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);
|
String lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1);
|
||||||
assert lastSuperField.indexOf(':') >= 0;
|
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);
|
char fieldType = lastSuperField.charAt(lastSuperField.indexOf(':') + 1);
|
||||||
if (fieldType == 'J' || fieldType == 'D') {
|
if (fieldType == 'J' || fieldType == 'D') {
|
||||||
fieldOffset += 8;
|
fieldOffset += 8;
|
||||||
@ -819,4 +995,95 @@ public class ClassPath {
|
|||||||
return classType.compareTo(classDef.classType);
|
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
@ -44,7 +44,6 @@ public enum Format {
|
|||||||
Format22b(Instruction22b.Factory, 4),
|
Format22b(Instruction22b.Factory, 4),
|
||||||
Format22c(Instruction22c.Factory, 4),
|
Format22c(Instruction22c.Factory, 4),
|
||||||
Format22cs(Instruction22cs.Factory, 4),
|
Format22cs(Instruction22cs.Factory, 4),
|
||||||
Format22csf(null, 4),
|
|
||||||
Format22s(Instruction22s.Factory, 4),
|
Format22s(Instruction22s.Factory, 4),
|
||||||
Format22t(Instruction22t.Factory, 4),
|
Format22t(Instruction22t.Factory, 4),
|
||||||
Format22x(Instruction22x.Factory, 4),
|
Format22x(Instruction22x.Factory, 4),
|
||||||
@ -56,18 +55,14 @@ public enum Format {
|
|||||||
Format32x(Instruction32x.Factory, 6),
|
Format32x(Instruction32x.Factory, 6),
|
||||||
Format35c(Instruction35c.Factory, 6),
|
Format35c(Instruction35c.Factory, 6),
|
||||||
Format35s(Instruction35s.Factory, 6),
|
Format35s(Instruction35s.Factory, 6),
|
||||||
Format35sf(null, 6),
|
|
||||||
Format35ms(Instruction35ms.Factory, 6),
|
Format35ms(Instruction35ms.Factory, 6),
|
||||||
Format35msf(null, 6),
|
|
||||||
Format3rc(Instruction3rc.Factory, 6),
|
Format3rc(Instruction3rc.Factory, 6),
|
||||||
Format3rms(Instruction3rms.Factory, 6),
|
Format3rms(Instruction3rms.Factory, 6),
|
||||||
Format3rmsf(null, 6),
|
|
||||||
Format51l(Instruction51l.Factory, 10),
|
Format51l(Instruction51l.Factory, 10),
|
||||||
ArrayData(null, -1, true),
|
ArrayData(null, -1, true),
|
||||||
PackedSwitchData(null, -1, true),
|
PackedSwitchData(null, -1, true),
|
||||||
SparseSwitchData(null, -1, true),
|
SparseSwitchData(null, -1, true),
|
||||||
UnresolvedNullReference(null, -1, false),
|
UnresolvedNullReference(null, -1, false),
|
||||||
DeadInstruction(null, -1, false)
|
|
||||||
;
|
;
|
||||||
|
|
||||||
public final Instruction.InstructionFactory Factory;
|
public final Instruction.InstructionFactory Factory;
|
||||||
|
@ -29,13 +29,14 @@
|
|||||||
package org.jf.dexlib.Code.Format;
|
package org.jf.dexlib.Code.Format;
|
||||||
|
|
||||||
import org.jf.dexlib.Code.Instruction;
|
import org.jf.dexlib.Code.Instruction;
|
||||||
|
import org.jf.dexlib.Code.OdexedFieldAccess;
|
||||||
import org.jf.dexlib.Code.Opcode;
|
import org.jf.dexlib.Code.Opcode;
|
||||||
import org.jf.dexlib.Code.TwoRegisterInstruction;
|
import org.jf.dexlib.Code.TwoRegisterInstruction;
|
||||||
import org.jf.dexlib.Util.NumberUtils;
|
import org.jf.dexlib.Util.NumberUtils;
|
||||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||||
import org.jf.dexlib.DexFile;
|
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();
|
public static final Instruction.InstructionFactory Factory = new Factory();
|
||||||
private byte regA;
|
private byte regA;
|
||||||
private byte regB;
|
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.FiveRegisterInstruction;
|
||||||
import org.jf.dexlib.Code.Instruction;
|
import org.jf.dexlib.Code.Instruction;
|
||||||
|
import org.jf.dexlib.Code.OdexedInvokeVirtual;
|
||||||
import org.jf.dexlib.Code.Opcode;
|
import org.jf.dexlib.Code.Opcode;
|
||||||
import org.jf.dexlib.Util.NumberUtils;
|
import org.jf.dexlib.Util.NumberUtils;
|
||||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||||
import org.jf.dexlib.DexFile;
|
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();
|
public static final Instruction.InstructionFactory Factory = new Factory();
|
||||||
private byte regCount;
|
private byte regCount;
|
||||||
private byte regA;
|
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;
|
package org.jf.dexlib.Code.Format;
|
||||||
|
|
||||||
import org.jf.dexlib.Code.Instruction;
|
import org.jf.dexlib.Code.Instruction;
|
||||||
|
import org.jf.dexlib.Code.OdexedInvokeVirtual;
|
||||||
import org.jf.dexlib.Code.Opcode;
|
import org.jf.dexlib.Code.Opcode;
|
||||||
import org.jf.dexlib.Code.RegisterRangeInstruction;
|
import org.jf.dexlib.Code.RegisterRangeInstruction;
|
||||||
import org.jf.dexlib.Util.NumberUtils;
|
import org.jf.dexlib.Util.NumberUtils;
|
||||||
import org.jf.dexlib.Util.AnnotatedOutput;
|
import org.jf.dexlib.Util.AnnotatedOutput;
|
||||||
import org.jf.dexlib.DexFile;
|
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();
|
public static final Instruction.InstructionFactory Factory = new Factory();
|
||||||
private byte regCount;
|
private byte regCount;
|
||||||
private short startReg;
|
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),
|
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),
|
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_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT),
|
||||||
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_THROW | 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),
|
|
||||||
INVOKE_DIRECT_EMPTY((byte)0xf0, "invoke-direct-empty", ReferenceType.method, Format.Format35s, 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_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_WIDE_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),
|
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_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_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),
|
IPUT_OBJECT_QUICK((byte)0xf7, "iput-object-quick", ReferenceType.none, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user