From 0c65e0f4f54ead8fd2832c954d516367b3556ae3 Mon Sep 17 00:00:00 2001 From: "JesusFreke@JesusFreke.com" Date: Mon, 22 Feb 2010 00:57:15 +0000 Subject: [PATCH] Implemented deodex functionality git-svn-id: https://smali.googlecode.com/svn/trunk@637 55b6fa8a-2a1e-11de-a435-ffa8d773f76a --- .../Format/InstructionMethodItem.java | 26 +- .../Format/InstructionMethodItemFactory.java | 8 +- .../Format/OdexInstructionMethodItem.java | 21 - .../UnresolvedNullReferenceMethodItem.java | 40 +- .../baksmali/Adaptors/MethodDefinition.java | 95 +- .../org/jf/baksmali/Deodex/DeodexUtil.java | 1448 ----------------- .../org/jf/baksmali/Deodex/DeodexUtil2.java | 4 - .../org/jf/baksmali/Deodex/Deodexerant.java | 590 ------- .../main/java/org/jf/baksmali/baksmali.java | 12 +- .../java/org/jf/baksmali/deodexCheck.java | 150 ++ .../src/main/java/org/jf/baksmali/main.java | 59 +- .../templates/templates/baksmali.stg | 21 - .../src/test/smali/deodex_test1/main.smali | 6 +- .../Code/Analysis/AnalyzedInstruction.java | 188 ++- .../jf/dexlib/Code/Analysis/ClassPath.java | 305 +++- .../jf/dexlib/Code/Analysis/DeodexUtil.java | 323 ++++ .../jf/dexlib/Code/Analysis/Deodexerant.java | 139 ++ .../dexlib/Code/Analysis/MethodAnalyzer.java | 826 +++++++--- .../org/jf/dexlib/Code/Format/Format.java | 5 - .../dexlib/Code/Format/Instruction22cs.java | 3 +- .../dexlib/Code/Format/Instruction22csf.java | 66 - .../dexlib/Code/Format/Instruction35ms.java | 3 +- .../dexlib/Code/Format/Instruction35msf.java | 88 - .../dexlib/Code/Format/Instruction35sf.java | 86 - .../dexlib/Code/Format/Instruction3rms.java | 3 +- .../dexlib/Code/Format/Instruction3rmsf.java | 67 - .../org/jf/dexlib/Code/OdexedFieldAccess.java | 5 + .../jf/dexlib/Code/OdexedInvokeVirtual.java | 5 + .../main/java/org/jf/dexlib/Code/Opcode.java | 11 +- 29 files changed, 1796 insertions(+), 2807 deletions(-) delete mode 100644 baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OdexInstructionMethodItem.java rename dexlib/src/main/java/org/jf/dexlib/Code/Format/DeadInstruction.java => baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/UnresolvedNullReferenceMethodItem.java (55%) delete mode 100644 baksmali/src/main/java/org/jf/baksmali/Deodex/DeodexUtil.java delete mode 100644 baksmali/src/main/java/org/jf/baksmali/Deodex/DeodexUtil2.java delete mode 100644 baksmali/src/main/java/org/jf/baksmali/Deodex/Deodexerant.java create mode 100644 baksmali/src/main/java/org/jf/baksmali/deodexCheck.java create mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java create mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/Analysis/Deodexerant.java delete mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/Format/Instruction22csf.java delete mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/Format/Instruction35msf.java delete mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/Format/Instruction35sf.java delete mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/Format/Instruction3rmsf.java create mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/OdexedFieldAccess.java create mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/OdexedInvokeVirtual.java diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java index 42255680..95ddce7c 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java @@ -43,16 +43,6 @@ public class InstructionMethodItem extends MethodItem { protected final StringTemplateGroup stg; protected final T instruction; - /** - * Instructions that execution could pass on to next - */ - private LinkedList successors = new LinkedList(); - - /** - * Instructions that can pass on execution to this one - */ - private LinkedList predecessors = new LinkedList(); - public InstructionMethodItem(CodeItem codeItem, int codeAddress, StringTemplateGroup stg, T instruction) { super(codeAddress); this.codeItem = codeItem; @@ -97,6 +87,14 @@ public class InstructionMethodItem extends MethodItem { if (instruction instanceof InstructionWithReference) { 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) { @@ -170,4 +168,12 @@ public class InstructionMethodItem extends MethodItem { template.setAttribute("Reference", Reference.createReference(template.getGroup(), 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()); + } } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItemFactory.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItemFactory.java index 40052b5b..fbb04772 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItemFactory.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItemFactory.java @@ -2,6 +2,7 @@ package org.jf.baksmali.Adaptors.Format; import org.antlr.stringtemplate.StringTemplateGroup; import org.jf.baksmali.Adaptors.MethodDefinition; +import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; import org.jf.dexlib.Code.Format.*; import org.jf.dexlib.Code.Instruction; import org.jf.dexlib.Code.OffsetInstruction; @@ -15,8 +16,8 @@ public class InstructionMethodItemFactory { CodeItem codeItem, int codeAddress, StringTemplateGroup stg, - Instruction instruction) { - + Instruction instruction, + boolean isLastInstruction) { if (instruction instanceof OffsetInstruction) { return new OffsetInstructionFormatMethodItem(methodDefinition.getLabelCache(), codeItem, codeAddress, stg, instruction); @@ -32,6 +33,9 @@ public class InstructionMethodItemFactory { case SparseSwitchData: return new SparseSwitchMethodItem(methodDefinition, codeItem, codeAddress, stg, (SparseSwitchDataPseudoInstruction)instruction); + case UnresolvedNullReference: + return new UnresolvedNullReferenceMethodItem(codeItem, codeAddress, stg, + (UnresolvedNullReference)instruction, isLastInstruction); default: return new InstructionMethodItem(codeItem, codeAddress, stg, instruction); } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OdexInstructionMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OdexInstructionMethodItem.java deleted file mode 100644 index f3e07b87..00000000 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/OdexInstructionMethodItem.java +++ /dev/null @@ -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 extends InstructionMethodItem { - 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; - } -} diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Format/DeadInstruction.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/UnresolvedNullReferenceMethodItem.java similarity index 55% rename from dexlib/src/main/java/org/jf/dexlib/Code/Format/DeadInstruction.java rename to baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/UnresolvedNullReferenceMethodItem.java index 3061db57..3f6866b9 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Format/DeadInstruction.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/UnresolvedNullReferenceMethodItem.java @@ -26,29 +26,33 @@ * 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.Util.AnnotatedOutput; +import org.jf.dexlib.Code.Format.UnresolvedNullReference; +import org.jf.dexlib.CodeItem; +import org.antlr.stringtemplate.StringTemplateGroup; +import org.antlr.stringtemplate.StringTemplate; -public class DeadInstruction extends Instruction { - public final Instruction OriginalInstruction; +public class UnresolvedNullReferenceMethodItem extends InstructionMethodItem { + public final boolean isLastInstruction; - public DeadInstruction(Instruction originalInstruction) { - super(originalInstruction.opcode); - this.OriginalInstruction = originalInstruction; + public UnresolvedNullReferenceMethodItem(CodeItem codeItem, int codeAddress, StringTemplateGroup stg, + UnresolvedNullReference instruction, boolean isLastInstruction) { + super(codeItem, codeAddress, stg, instruction); + this.isLastInstruction = isLastInstruction; } - protected void writeInstruction(AnnotatedOutput out, int currentCodeAddress) { - //don't write anything - } + protected void setAttributes(StringTemplate template) { + 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; } } diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java index 8d6bfbfd..d9c12709 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java @@ -37,6 +37,7 @@ import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; import org.jf.dexlib.Code.Analysis.MethodAnalyzer; import org.jf.dexlib.Code.Analysis.RegisterType; import org.jf.dexlib.Code.Analysis.ValidationException; +import org.jf.dexlib.Code.Format.Format; import org.jf.dexlib.Debug.DebugInstructionIterator; import org.jf.dexlib.Util.AccessFlags; 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. if (encodedMethod.codeItem != null) { - methodAnalyzer = new MethodAnalyzer(encodedMethod); + methodAnalyzer = new MethodAnalyzer(encodedMethod, baksmali.deodex); AnalyzedInstruction[] instructions = methodAnalyzer.makeInstructionArray(); packedSwitchMap = new SparseIntArray(1); @@ -78,17 +79,17 @@ public class MethodDefinition { int currentCodeAddress = 0; for (int i=0; i=0; i--) { + AnalyzedInstruction instruction = instructions[i]; + + if (!instruction.isDead()) { + lastInstruction = instruction; + break; + } + } + BitSet printPreRegister = new BitSet(registerCount); BitSet printPostRegister = new BitSet(registerCount); @@ -268,8 +280,20 @@ public class MethodDefinition { for (int i=0; i 0) { + methodItems.add(new CommentMethodItem(stg, comment, currentCodeAddress, 99.9)); + } } if (!printPostRegister.isEmpty()) { - methodItems.add(new CommentMethodItem(stg, - getPostInstructionRegisterString(instruction, printPostRegister), - currentCodeAddress, 100.1)); + String comment = getPostInstructionRegisterString(instruction, printPostRegister); + if (comment != null && comment.length() > 0) { + methodItems.add(new CommentMethodItem(stg, comment, currentCodeAddress, 100.1)); + } } } - currentCodeAddress += instruction.instruction.getSize(currentCodeAddress); + currentCodeAddress += instruction.getInstruction().getSize(currentCodeAddress); } addTries(methodItems); @@ -351,13 +377,13 @@ public class MethodDefinition { } private void addArgsRegs(BitSet printPreRegister, AnalyzedInstruction analyzedInstruction) { - if (analyzedInstruction.instruction instanceof RegisterRangeInstruction) { - RegisterRangeInstruction instruction = (RegisterRangeInstruction)analyzedInstruction.instruction; + if (analyzedInstruction.getInstruction() instanceof RegisterRangeInstruction) { + RegisterRangeInstruction instruction = (RegisterRangeInstruction)analyzedInstruction.getInstruction(); printPreRegister.set(instruction.getStartRegister(), instruction.getStartRegister() + instruction.getRegCount()); - } else if (analyzedInstruction.instruction instanceof FiveRegisterInstruction) { - FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.instruction; + } else if (analyzedInstruction.getInstruction() instanceof FiveRegisterInstruction) { + FiveRegisterInstruction instruction = (FiveRegisterInstruction)analyzedInstruction.getInstruction(); int regCount = instruction.getRegCount(); switch (regCount) { case 5: @@ -375,17 +401,17 @@ public class MethodDefinition { case 1: printPreRegister.set(instruction.getRegisterD()); } - } else if (analyzedInstruction.instruction instanceof ThreeRegisterInstruction) { - ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.instruction; + } else if (analyzedInstruction.getInstruction() instanceof ThreeRegisterInstruction) { + ThreeRegisterInstruction instruction = (ThreeRegisterInstruction)analyzedInstruction.getInstruction(); printPreRegister.set(instruction.getRegisterA()); printPreRegister.set(instruction.getRegisterB()); printPreRegister.set(instruction.getRegisterC()); - } else if (analyzedInstruction.instruction instanceof TwoRegisterInstruction) { - TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.instruction; + } else if (analyzedInstruction.getInstruction() instanceof TwoRegisterInstruction) { + TwoRegisterInstruction instruction = (TwoRegisterInstruction)analyzedInstruction.getInstruction(); printPreRegister.set(instruction.getRegisterA()); printPreRegister.set(instruction.getRegisterB()); - } else if (analyzedInstruction.instruction instanceof SingleRegisterInstruction) { - SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.instruction; + } else if (analyzedInstruction.getInstruction() instanceof SingleRegisterInstruction) { + SingleRegisterInstruction instruction = (SingleRegisterInstruction)analyzedInstruction.getInstruction(); printPreRegister.set(instruction.getRegisterA()); } } @@ -414,7 +440,7 @@ public class MethodDefinition { sb.append(instruction.getPreInstructionRegisterType(registerNum)); sb.append(":merge{"); - RegisterType mergedRegisterType = instruction.getPreInstructionRegisterType(registerNum); + RegisterType mergedRegisterType = null; boolean addRegister = false; boolean first = true; @@ -422,10 +448,15 @@ public class MethodDefinition { RegisterType predecessorRegisterType = predecessor.getPostInstructionRegisterType(registerNum); if (!first) { - sb.append(','); - if (!addRegister && mergedRegisterType != predecessorRegisterType) { - addRegister = true; + if (!addRegister) { + sb.append(','); + if (mergedRegisterType != predecessorRegisterType) { + addRegister = true; + } + mergedRegisterType = mergedRegisterType.merge(predecessorRegisterType); } + } else { + mergedRegisterType = predecessorRegisterType; } if (predecessor.getInstructionIndex() == -1) { @@ -436,7 +467,7 @@ public class MethodDefinition { sb.append(Integer.toHexString(methodAnalyzer.getInstructionAddress(predecessor))); sb.append(':'); } - sb.append(predecessor.getPostInstructionRegisterType(registerNum).toString()); + sb.append(predecessorRegisterType.toString()); first = false; } diff --git a/baksmali/src/main/java/org/jf/baksmali/Deodex/DeodexUtil.java b/baksmali/src/main/java/org/jf/baksmali/Deodex/DeodexUtil.java deleted file mode 100644 index c12de70f..00000000 --- a/baksmali/src/main/java/org/jf/baksmali/Deodex/DeodexUtil.java +++ /dev/null @@ -1,1448 +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.Code.*; -import org.jf.dexlib.Code.Format.*; -import org.jf.dexlib.Util.AccessFlags; -import org.jf.dexlib.Util.SparseArray; - -import java.util.*; - -public class DeodexUtil { - private final Deodexerant deodexerant; - - public DeodexUtil(Deodexerant deodexerant) { - this.deodexerant = deodexerant; - } - - private List makeInsnList(final CodeItem codeItem) { - - final ArrayList insns = new ArrayList(); - final SparseArray insnsMap = new SparseArray(); - - int currentCodeAddress = 0; - for (Instruction instruction: codeItem.getInstructions()) { - insn ins = new insn(codeItem, instruction, insnsMap, currentCodeAddress); - insns.add(ins); - insnsMap.append(currentCodeAddress, ins); - currentCodeAddress += instruction.getSize(currentCodeAddress); - } - - if (codeItem.getTries() != null) { - for (CodeItem.TryItem tryItem: codeItem.getTries()) { - insn[] handlers; - - if (tryItem.encodedCatchHandler.getCatchAllHandlerAddress() != -1) { - handlers = new insn[tryItem.encodedCatchHandler.handlers.length + 1]; - handlers[handlers.length - 1] = - insnsMap.get(tryItem.encodedCatchHandler.getCatchAllHandlerAddress()); - } else { - handlers = new insn[tryItem.encodedCatchHandler.handlers.length]; - } - - for (int i=0; i deodexerizeCode(CodeItem codeItem) { - List insns = makeInsnList(codeItem); - - boolean didSomething; - boolean somethingLeftToDo; - do { - didSomething = false; - somethingLeftToDo = false; - for (insn i: insns) { - if (i.instruction.opcode.odexOnly() && i.fixedInstruction == null) { - if (deodexInstruction(i)) { - didSomething = true; - } else { - if (!i.dead) { - somethingLeftToDo = true; - } - } - } - } - } while (didSomething); - if (somethingLeftToDo) { - System.err.println("warning: could not fully deodex the method " + - codeItem.getParent().method.getMethodString()); - } - - List instructions = new ArrayList(insns.size()); - for (insn i: insns) { - if (i.dead) { - if (i.fixedInstruction != null) { - instructions.add(new DeadInstruction(i.fixedInstruction)); - } else { - instructions.add(new DeadInstruction(i.instruction)); - } - } else if (i.instruction.opcode.odexOnly()) { - assert i.fixedInstruction != null; - instructions.add(i.fixedInstruction); - } else { - instructions.add(i.instruction); - } - } - return instructions; - } - - private boolean deodexInstruction(insn i) { - switch (i.instruction.opcode) { - case EXECUTE_INLINE: - { - int inlineMethodIndex = ((Instruction35ms)i.instruction).getMethodIndex(); - Deodexerant.InlineMethod inlineMethod = - deodexerant.lookupInlineMethod(inlineMethodIndex); - if (inlineMethod == null) { - throw new RuntimeException("Could not find the inline method with index " + inlineMethodIndex); - } - assert inlineMethod != null; - assert inlineMethod.getMethodIdItem() != null; - - Opcode opcode = null; - switch (inlineMethod.getMethodType()) { - case Direct: - opcode = Opcode.INVOKE_DIRECT; - break; - case Static: - opcode = Opcode.INVOKE_STATIC; - break; - case Virtual: - opcode = Opcode.INVOKE_VIRTUAL; - break; - } - - i.fixedInstruction = new Instruction35msf(opcode, (Instruction35ms)i.instruction, - inlineMethod.getMethodIdItem()); - - insn nextInstruction = i.getInstructionAtAddress(i.address + i.instruction.getSize(i.address)); - assert nextInstruction != null; - if (nextInstruction.instruction.opcode == Opcode.MOVE_RESULT_OBJECT) { - nextInstruction.registerReferenceType = - inlineMethod.getMethodIdItem().getPrototype().getReturnType().getTypeDescriptor(); - } - - return true; - } - case EXECUTE_INLINE_RANGE: - { - int inlineMethodIndex = ((Instruction3rms)i.instruction).getMethodIndex(); - Deodexerant.InlineMethod inlineMethod = - deodexerant.lookupInlineMethod(inlineMethodIndex); - if (inlineMethod == null) { - throw new RuntimeException("Could not find the inline method with index " + inlineMethodIndex); - } - assert inlineMethod != null; - assert inlineMethod.getMethodIdItem() != null; - - Opcode opcode = null; - switch (inlineMethod.getMethodType()) { - case Direct: - opcode = Opcode.INVOKE_DIRECT_RANGE; - break; - case Static: - opcode = Opcode.INVOKE_STATIC_RANGE; - break; - case Virtual: - opcode = Opcode.INVOKE_VIRTUAL_RANGE; - break; - } - - i.fixedInstruction = new Instruction3rmsf(opcode, (Instruction3rms)i.instruction, - inlineMethod.getMethodIdItem()); - - insn nextInstruction = i.getInstructionAtAddress(i.address + i.instruction.getSize(i.address)); - assert nextInstruction != null; - if (nextInstruction.instruction.opcode == Opcode.MOVE_RESULT_OBJECT) { - nextInstruction.registerReferenceType = - inlineMethod.getMethodIdItem().getPrototype().getReturnType().getTypeDescriptor(); - } - - return true; - } - case INVOKE_DIRECT_EMPTY: - { - i.fixedInstruction = new Instruction35sf((Instruction35s)i.instruction); - return true; - } - case IGET_QUICK: - { - Instruction22cs ins = (Instruction22cs)i.instruction; - int registerNum = ins.getRegisterB(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what field is being accessed. What will really happen is that when it tries to access the field, it - //will obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction2csn, which doesn't have any method/field info associated with - //it, and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - i.propogateDeadness(); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - FieldIdItem field = deodexerant.lookupField(type, ins.getFieldOffset()); - if (field == null) { - throw new RuntimeException("Could not find the field with offset " + ins.getFieldOffset() + - " for class: " + type); - } - String fieldType = field.getFieldType().getTypeDescriptor(); - - Opcode opcode; - switch (fieldType.charAt(0)) { - case 'Z': - opcode = Opcode.IGET_BOOLEAN; - break; - case 'B': - opcode = Opcode.IGET_BYTE; - break; - case 'S': - opcode = Opcode.IGET_SHORT; - break; - case 'C': - opcode = Opcode.IGET_CHAR; - break; - case 'I': - case 'F': - opcode = Opcode.IGET; - break; - default: - throw new RuntimeException("Unexpected field type for iget-quick opcode: " + fieldType); - } - - i.fixedInstruction = new Instruction22csf(opcode, (Instruction22cs)i.instruction, field); - - return true; - } - case IGET_WIDE_QUICK: - { - Instruction22cs ins = (Instruction22cs)i.instruction; - int registerNum = ins.getRegisterB(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what field is being accessed. What will really happen is that when it tries to access the field, it - //will obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction2csn, which doesn't have any method/field info associated with - //it, and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - i.propogateDeadness(); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - FieldIdItem field = deodexerant.lookupField(type, ins.getFieldOffset()); - if (field == null) { - throw new RuntimeException("Could not find the field with offset " + ins.getFieldOffset() + - " for class: " + type); - } - - assert field.getFieldType().getTypeDescriptor().charAt(0) == 'J' || - field.getFieldType().getTypeDescriptor().charAt(0) == 'D'; - - i.fixedInstruction = new Instruction22csf(Opcode.IGET_WIDE, (Instruction22cs)i.instruction, field); - return true; - } - case IGET_OBJECT_QUICK: - { - Instruction22cs ins = (Instruction22cs)i.instruction; - int registerNum = ins.getRegisterB(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what field is being accessed. What will really happen is that when it tries to access the field, it - //will obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an UnresolvedNullReference, which doesn't have any method/field info - //associated with it, and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - i.propogateDeadness(); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - FieldIdItem field = deodexerant.lookupField(type, ins.getFieldOffset()); - if (field == null) { - throw new RuntimeException("Could not find the field with offset " + ins.getFieldOffset() + - " for class: " + type); - } - - assert field.getFieldType().getTypeDescriptor().charAt(0) == 'L' || - field.getFieldType().getTypeDescriptor().charAt(0) == '['; - - i.fixedInstruction = new Instruction22csf(Opcode.IGET_OBJECT, (Instruction22cs)i.instruction, field); - - i.updateRegisterReferenceType(field.getFieldType().getTypeDescriptor()); - return true; - } - case IPUT_QUICK: - { - Instruction22cs ins = (Instruction22cs)i.instruction; - int registerNum = ins.getRegisterB(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what field is being accessed. What will really happen is that when it tries to access the field, it - //will obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction2csn, which doesn't have any method/field info associated with - //it, and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - FieldIdItem field = deodexerant.lookupField(type, ins.getFieldOffset()); - if (field == null) { - throw new RuntimeException("Could not find the field with offset " + ins.getFieldOffset() + - " for class: " + type); - } - String fieldType = field.getFieldType().getTypeDescriptor(); - - Opcode opcode; - switch (fieldType.charAt(0)) { - case 'Z': - opcode = Opcode.IPUT_BOOLEAN; - break; - case 'B': - opcode = Opcode.IPUT_BYTE; - break; - case 'S': - opcode = Opcode.IPUT_SHORT; - break; - case 'C': - opcode = Opcode.IPUT_CHAR; - break; - case 'I': - case 'F': - opcode = Opcode.IPUT; - break; - default: - throw new RuntimeException("Unexpected field type for iput-quick opcode: " + fieldType); - } - - i.fixedInstruction = new Instruction22csf(opcode, (Instruction22cs)i.instruction, field); - - return true; - } - case IPUT_WIDE_QUICK: - { - Instruction22cs ins = (Instruction22cs)i.instruction; - int registerNum = ins.getRegisterB(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what field is being accessed. What will really happen is that when it tries to access the field, it - //will obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction2csn, which doesn't have any method/field info associated with - //it, and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - FieldIdItem field = deodexerant.lookupField(type, ins.getFieldOffset()); - if (field == null) { - throw new RuntimeException("Could not find the field with offset " + ins.getFieldOffset() + - " for class: " + type); - } - - assert field.getFieldType().getTypeDescriptor().charAt(0) == 'J' || - field.getFieldType().getTypeDescriptor().charAt(0) == 'D'; - - i.fixedInstruction = new Instruction22csf(Opcode.IPUT_WIDE, (Instruction22cs)i.instruction, field); - - return true; - } - case IPUT_OBJECT_QUICK: - { - Instruction22cs ins = (Instruction22cs)i.instruction; - int registerNum = ins.getRegisterB(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what field is being accessed. What will really happen is that when it tries to access the field, it - //will obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction2csn, which doesn't have any method/field info associated with - //it, and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - FieldIdItem field = deodexerant.lookupField(type, ins.getFieldOffset()); - if (field == null) { - throw new RuntimeException("Could not find the field with offset " + ins.getFieldOffset() + - " for class: " + type); - } - - assert field.getFieldType().getTypeDescriptor().charAt(0) == 'L' || - field.getFieldType().getTypeDescriptor().charAt(0) == '['; - - i.fixedInstruction = new Instruction22csf(Opcode.IPUT_OBJECT, (Instruction22cs)i.instruction, field); - - return true; - } - case INVOKE_VIRTUAL_QUICK: - { - Instruction35ms ins = ((Instruction35ms)i.instruction); - int registerNum = ins.getRegisterD(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what method to call. What will really happen is that when it tries to call the method, it will - //obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction3msn, which doesn't have any method info associated with it, - //and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - i.propogateDeadness(); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - MethodIdItem method = deodexerant.lookupVirtualMethod(type, ins.getMethodIndex(), false); - if (method == null) { - throw new RuntimeException("Could not find the virtual method with vtable index " + - ins.getMethodIndex() + " for class: " + type); - } - - i.fixedInstruction = new Instruction35msf(Opcode.INVOKE_VIRTUAL, (Instruction35ms)i.instruction, - method); - - insn nextInstruction = i.getInstructionAtAddress(i.address + i.instruction.getSize(i.address)); - assert nextInstruction != null; - if (nextInstruction.instruction.opcode == Opcode.MOVE_RESULT_OBJECT) { - nextInstruction.updateRegisterReferenceType( - method.getPrototype().getReturnType().getTypeDescriptor()); - } - return true; - } - case INVOKE_VIRTUAL_QUICK_RANGE: - { - Instruction3rms ins = ((Instruction3rms)i.instruction); - int registerNum = ins.getStartRegister(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what method to call. What will really happen is that when it tries to call the method, it will - //obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction3msn, which doesn't have any method info associated with it, - //and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - i.propogateDeadness(); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - MethodIdItem method = deodexerant.lookupVirtualMethod(type, ins.getMethodIndex(), false); - if (method == null) { - throw new RuntimeException("Could not find the virtual method with vtable index " + - ins.getMethodIndex() + " for class: " + type); - } - - i.fixedInstruction = new Instruction3rmsf(Opcode.INVOKE_VIRTUAL_RANGE, (Instruction3rms)i.instruction, - method); - - insn nextInstruction = i.getInstructionAtAddress(i.address + i.instruction.getSize(i.address)); - assert nextInstruction != null; - if (nextInstruction.instruction.opcode == Opcode.MOVE_RESULT_OBJECT) { - nextInstruction.updateRegisterReferenceType( - method.getPrototype().getReturnType().getTypeDescriptor()); - } - return true; - } - case INVOKE_SUPER_QUICK: - { - Instruction35ms ins = ((Instruction35ms)i.instruction); - int registerNum = ins.getRegisterD(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what method to call. What will really happen is that when it tries to call the method, it will - //obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction3msn, which doesn't have any method info associated with it, - //and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - //we need to mark any following instructions as dead - i.propogateDeadness(); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - MethodIdItem method = deodexerant.lookupVirtualMethod(type, ins.getMethodIndex(), true); - if (method == null) { - throw new RuntimeException("Could not find the super method with vtable index " + - ins.getMethodIndex() + " for class: " + type); - } - - i.fixedInstruction = new Instruction35msf(Opcode.INVOKE_SUPER, (Instruction35ms)i.instruction, - method); - - insn nextInstruction = i.getInstructionAtAddress(i.address + i.instruction.getSize(i.address)); - assert nextInstruction != null; - if (nextInstruction.instruction.opcode == Opcode.MOVE_RESULT_OBJECT) { - nextInstruction.updateRegisterReferenceType( - method.getPrototype().getReturnType().getTypeDescriptor()); - } - return true; - } - case INVOKE_SUPER_QUICK_RANGE: - { - Instruction3rms ins = ((Instruction3rms)i.instruction); - int registerNum = ins.getStartRegister(); - - RegisterType regType = i.registerMap[registerNum]; - - assert regType != RegisterType.NonReference && regType != RegisterType.Conflicted; - - //if the register type is Null, we can't determine the type of the register, and so we can't determine - //what method to call. What will really happen is that when it tries to call the method, it will - //obviously throw a NPE. We can get this same effect by replacing this opcode with a call to - //a method on java.lang.Object. - //We actually just create an Instruction3msn, which doesn't have any method info associated with it, - //and let the caller choose which "default" method to call in this case - if (regType == RegisterType.Null) { - i.fixedInstruction = new UnresolvedNullReference(i.instruction, registerNum); - i.propogateDeadness(); - return true; - } - - if (regType != RegisterType.Reference) { - return false; - } - - String type = i.registerTypes[registerNum]; - if (type == null) { - return false; - } - - MethodIdItem method = deodexerant.lookupVirtualMethod(type, ins.getMethodIndex(), true); - if (method == null) { - throw new RuntimeException("Could not find the super method with vtable index " + - ins.getMethodIndex() + " for class: " + type); - } - - i.fixedInstruction = new Instruction3rmsf(Opcode.INVOKE_SUPER_RANGE, (Instruction3rms)i.instruction, - method); - - insn nextInstruction = i.getInstructionAtAddress(i.address + i.instruction.getSize(i.address)); - assert nextInstruction != null; - if (nextInstruction.instruction.opcode == Opcode.MOVE_RESULT_OBJECT) { - nextInstruction.updateRegisterReferenceType( - method.getPrototype().getReturnType().getTypeDescriptor()); - } - return true; - } - default: - throw new RuntimeException("Unexpected opcode " + i.instruction.opcode); - } - } - - public enum RegisterType { - Unknown, - Null, - NonReference, - Reference, - Conflicted; - - private static RegisterType[][] mergeTable = - { - //Unknown Null Nonreference Reference Conflicted - {Unknown, Null, NonReference, Reference, Conflicted}, //Unknown - {Null, Null, NonReference, Reference, Conflicted}, //Null - {NonReference, NonReference, NonReference, Conflicted, Conflicted}, //NonReference - {Reference, Reference, Conflicted, Reference, Conflicted}, //Referenced - {Conflicted, Conflicted, Conflicted, Conflicted, Conflicted}, //Conflicted - }; - - public static RegisterType mergeRegisterTypes(RegisterType type1, RegisterType type2) { - return mergeTable[type1.ordinal()][type2.ordinal()]; - } - } - - public class insn { - /** - * The CodeItem that this instruction is a part of - */ - public final CodeItem codeItem; - - /** - * The actual instruction - */ - public final Instruction instruction; - - /** - * The code address of the instruction, in 2-byte instruction blocks - */ - public final int address; - - /** - * maps a code address to an insn - */ - public final SparseArray insnsMap; - - /** - * Instructions that execution could pass on to next - */ - public LinkedList successors = new LinkedList(); - - /** - * Instructions that can pass on execution to this one - */ - public LinkedList predecessors = new LinkedList(); - - /** - * If this instruction is in a try block, these are the first instructions for each - * exception handler - */ - public insn[] exceptionHandlers = null; - - /** - * true if this instruction stores a value in a register - */ - public boolean setsRegister = false; - - /** - * true if this instruction sets a wide register. In this case, registerNum is the first of the - * 2 registers - */ - public boolean setsWideRegister = false; - - /** - * If setsRegister is true, this is the instruction that is modified - */ - public int registerNum; - - /** - * If setsRegister is true, this is the register type of register that is modified - */ - public RegisterType registerType; - - /** - * if setsRegister is true, and the register type is a reference, this is the - * reference type of the register, or null if not known yet. - */ - public String registerReferenceType; - - /** - * Stores a "fake" fixed instruction, which is included in the instruction list that deodexerizeCode produces - */ - public Instruction fixedInstruction; - - /** - * This is only used for odexed instructions, and should contain the register num of the object reference - * that the instruction acts on. More specifically, it's only for odexed instructions that require the - * type of the object register in order to look up the correct information. - */ - public int objectRegisterNum = -1; - - /** - * Whether this instruction can be the first instruction to successfully execute. This could be the first - * instruction in the method, or if that instruction is covered by a try block, then the first instruction - * in any of the exception handlers. Or if they are covered by try blocks... you get the idea - */ - public boolean firstInstruction = false; - - /** - * If this instruction has been visited in the course of determining the type of a register - */ - public boolean visited = false; - - /** - * If this code is dead. Note that not all dead code is marked. Only the dead code that comes after an odexed - * instruction can't be resolved because its object register is always null. - */ - public boolean dead = false; - - public final RegisterType[] registerMap; - public final String[] registerTypes; - - public insn(CodeItem codeItem, Instruction instruction, SparseArray insnsMap, int address) { - this.codeItem = codeItem; - this.instruction = instruction; - this.address = address; - this.insnsMap = insnsMap; - - if (instruction.opcode.odexOnly()) { - //we don't need INVOKE_EXECUTE_INLINE or EXECUTE_INLINE_RANGE here, because we don't need to know - //the type of the object register in order to resolve which method is being called - switch (instruction.opcode) { - case IGET_QUICK: - case IGET_WIDE_QUICK: - case IGET_OBJECT_QUICK: - case IPUT_QUICK: - case IPUT_WIDE_QUICK: - case IPUT_OBJECT_QUICK: - objectRegisterNum = ((Instruction22cs)instruction).getRegisterB(); - break; - case INVOKE_VIRTUAL_QUICK: - case INVOKE_SUPER_QUICK: - objectRegisterNum = ((Instruction35ms)instruction).getRegisterD(); - break; - case INVOKE_VIRTUAL_QUICK_RANGE: - case INVOKE_SUPER_QUICK_RANGE: - objectRegisterNum = ((Instruction3rms)instruction).getStartRegister(); - break; - default: - break; - } - } - - registerMap = new RegisterType[codeItem.getRegisterCount()]; - registerTypes = new String[codeItem.getRegisterCount()]; - - for (int i=0; i exceptionTypes = new ArrayList(1); - for (CodeItem.TryItem tryItem: codeItem.getTries()) { - if (tryItem.encodedCatchHandler.getCatchAllHandlerAddress() == this.address) { - //if this is a catch all handler, the only possible type is Ljava/lang/Throwable; - registerReferenceType = "Ljava/lang/Throwable;"; - - //it's possible that Ljava/lang/Throwable; hasn't been interned into the dex file. Since - //we've turned off interning for the current dex file, we will just get a null back. - //This "shouldn't" be a problem, because if the type hasn't been interned, it's safe to - //say that there were no method/field accesses for that type, so we won't need to know - //the specific type of this register. - - break; - } - - for (CodeItem.EncodedTypeAddrPair handler: tryItem.encodedCatchHandler.handlers) { - if (handler.getHandlerAddress() == this.address) { - exceptionTypes.add(handler.exceptionType.getTypeDescriptor()); - } - } - } - if (registerReferenceType == null) { - //optimize for the case when there is only a single exception type - if (exceptionTypes.size() == 1) { - registerReferenceType = exceptionTypes.get(0); - } else { - registerReferenceType = findCommonSuperclass(exceptionTypes); - } - } - break; - } - case CONST_WIDE_16: - case CONST_WIDE_32: - case CONST_WIDE: - case CONST_WIDE_HIGH16: - { - setsRegister = true; - registerNum = ((SingleRegisterInstruction)instruction).getRegisterA(); - registerType = RegisterType.NonReference; - setsWideRegister = true; - break; - } - case CONST_4: - case CONST_16: - case CONST: - case CONST_HIGH16: - { - setsRegister = true; - registerNum = ((SingleRegisterInstruction)instruction).getRegisterA(); - if (((LiteralInstruction)instruction).getLiteral() == 0) { - registerType = RegisterType.Null; - } else { - registerType = RegisterType.NonReference; - } - break; - } - case CONST_STRING: - { - setsRegister = true; - registerNum = ((SingleRegisterInstruction)instruction).getRegisterA(); - registerType = RegisterType.Reference; - registerReferenceType = "Ljava/lang/String;"; - break; - } - case CONST_CLASS: - { - setsRegister = true; - registerNum = ((SingleRegisterInstruction)instruction).getRegisterA(); - registerType = RegisterType.Reference; - registerReferenceType = "Ljava/lang/Class;"; - break; - } - case CHECK_CAST: - case NEW_INSTANCE: - case NEW_ARRAY: - { - setsRegister = true; - registerNum = ((SingleRegisterInstruction)instruction).getRegisterA(); - registerType = RegisterType.Reference; - registerReferenceType = - ((TypeIdItem)((InstructionWithReference)instruction).getReferencedItem()).getTypeDescriptor(); - break; - } - case IGET_OBJECT: - case SGET_OBJECT: - { - setsRegister = true; - registerNum = ((SingleRegisterInstruction)instruction).getRegisterA(); - registerType = RegisterType.Reference; - registerReferenceType = ((FieldIdItem)((InstructionWithReference)instruction).getReferencedItem()) - .getFieldType().getTypeDescriptor(); - break; - } - } - - //if we got here, then we can assume that it's possible for execution to continue on to the next - //instruction. Otherwise, we would have returned from within the switch statement - addSuccessor(getInstructionAtAddress(address + instruction.getSize(address))); - } - - private String findCommonSuperclass(String type1, String type2) { - if (type1 == null) { - return type2; - } - if (type2 == null) { - return type1; - } - - if (type1.equals(type2)) { - return type1; - } - - return deodexerant.lookupCommonSuperclass(type1, type2); - } - - private String findCommonSuperclass(List exceptionTypes) { - assert exceptionTypes.size() > 1; - - String supertype = exceptionTypes.get(0); - - for (int i=1; i vtableMap = new HashMap(); - private final HashMap cachedCommonSuperclassLookup = - new HashMap(); - 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 responseLines = sendMultilineCommand("I"); - - inlineMethods = new InlineMethod[responseLines.size()]; - for (int i=0; i= 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 sendMultilineCommand(String cmd) { - try { - connectIfNeeded(); - - out.println(cmd); - out.flush(); - - ArrayList responseLines = new ArrayList(); - 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 paramList = new LinkedList(); - - for (int i=0; i 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 instanceFields; - private SparseArray 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 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 responseLines = sendMultilineCommand("F " + ClassType); - - instanceFields = new SparseArray(responseLines.size()); - resolvedFields = new SparseArray(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(); - } - } -} diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index 7b42551e..b01c3927 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -31,7 +31,6 @@ package org.jf.baksmali; import org.antlr.stringtemplate.StringTemplate; import org.antlr.stringtemplate.StringTemplateGroup; import org.jf.baksmali.Adaptors.ClassDefinition; -import org.jf.baksmali.Deodex.*; import org.jf.baksmali.Renderers.*; import org.jf.dexlib.Code.Analysis.ClassPath; import org.jf.dexlib.DexFile; @@ -46,11 +45,11 @@ public class baksmali { public static boolean useSequentialLabels = false; public static boolean outputDebugInfo = true; public static boolean addCodeOffsets = false; + public static boolean deodex = false; public static int registerInfo = 0; 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, boolean useLocalsDirective, boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, int registerInfo) @@ -60,17 +59,14 @@ public class baksmali { baksmali.useSequentialLabels = useSequentialLabels; baksmali.outputDebugInfo = outputDebugInfo; baksmali.addCodeOffsets = addCodeOffsets; + baksmali.deodex = deodex; baksmali.registerInfo = registerInfo; baksmali.bootClassPath = bootClassPath; - if (registerInfo != 0) { + if (registerInfo != 0 || deodex) { ClassPath.InitializeClassPath(bootClassPathDir, bootClassPath==null?null:bootClassPath.split(":"), dexFile); } - if (deodexerant != null) { - baksmali.deodexUtil = new DeodexUtil(deodexerant); - } - File outputDirectoryFile = new File(outputDirectory); if (!outputDirectoryFile.exists()) { if (!outputDirectoryFile.mkdirs()) { diff --git a/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java b/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java new file mode 100644 index 00000000..a19a1640 --- /dev/null +++ b/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java @@ -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 : or :"); + 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; + } +} diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java index d54af11b..de524b58 100644 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/baksmali/src/main/java/org/jf/baksmali/main.java @@ -17,7 +17,6 @@ package org.jf.baksmali; import org.apache.commons.cli.*; -import org.jf.baksmali.Deodex.Deodexerant; import org.jf.dexlib.DexFile; import java.io.File; @@ -88,6 +87,7 @@ public class main { boolean useSequentialLabels = false; boolean outputDebugInfo = true; boolean addCodeOffsets = false; + boolean deodex = false; int registerInfo = 0; @@ -95,10 +95,9 @@ public class main { String dumpFileName = null; String outputDexFileName = null; String inputDexFileName = null; - String deodexerantHost = null; String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"; String bootClassPathDir = "."; - int deodexerantPort = 0; + String[] remainingArgs = commandLine.getArgs(); @@ -176,30 +175,14 @@ public class main { break; case 'c': String bcp = commandLine.getOptionValue("c"); - if (bcp.charAt(0) == ':') { + if (bcp != null && bcp.charAt(0) == ':') { bootClassPath = bootClassPath + bcp; } else { bootClassPath = bcp; } break; case 'x': - String deodexerantAddress = commandLine.getOptionValue("x"); - String[] parts = deodexerantAddress.split(":"); - if (parts.length != 2) { - System.err.println("Invalid deodexerant address. Expecting : or :"); - 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); - } + deodex = true; break; case 'N': disassemble = false; @@ -240,16 +223,6 @@ public class main { //Read in and parse the dex file 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 (doDump) { System.err.println("-d cannot be used with on odex file. Ignoring -d"); @@ -257,15 +230,17 @@ public class main { if (write) { 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("won't be able to re-assemble the results unless you use deodexerant, and"); - System.err.println("the -x option for baksmali"); + System.err.println("won't be able to re-assemble the results unless you deodex it with the -x"); + System.err.println("option"); } + } else { + deodex = false; } if (disassemble) { - baksmali.disassembleDexFile(dexFile, deodexerant, outputDirectory, bootClassPathDir, bootClassPath, + baksmali.disassembleDexFile(dexFile, deodex, outputDirectory, bootClassPathDir, bootClassPath, noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets, registerInfo); } @@ -317,7 +292,7 @@ public class main { /** * Prints the version message. */ - private static void version() { + protected static void version() { System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)"); System.out.println("Copyright (C) 2009 Ben Gruver"); System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); @@ -366,15 +341,13 @@ public class main { .create("F"); Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers") - .withDescription("use the v syntax instead of the p syntax for registers mapped to method" + - " parameters") + .withDescription("use the v syntax instead of the p syntax for registers mapped to method " + + "parameters") .create("p"); - Option deodexerantOption = OptionBuilder.withLongOpt("deodexerant") - .withDescription("connect to deodexerant on the specified HOST:PORT, and deodex the input odex" - + " file. This option is ignored if the input file is a dex file instead of an odex file") - .hasArg() - .withArgName("HOST:PORT") + Option deodexerantOption = OptionBuilder.withLongOpt("deodex") + .withDescription("deodex the given odex file. This option is ignored if the input file is not an " + + "odex file.") .create("x"); Option useLocalsOption = OptionBuilder.withLongOpt("use-locals") diff --git a/baksmali/src/main/resources/templates/templates/baksmali.stg b/baksmali/src/main/resources/templates/templates/baksmali.stg index 789f3433..4053d739 100644 --- a/baksmali/src/main/resources/templates/templates/baksmali.stg +++ b/baksmali/src/main/resources/templates/templates/baksmali.stg @@ -188,11 +188,6 @@ Format22cs(Opcode, RegisterA, RegisterB, FieldOffset) ::= , , field@ >> -Format22csf(Opcode, RegisterA, RegisterB, Reference) ::= -<< - , , ->> - Format22s(Opcode, RegisterA, RegisterB, Literal) ::= << , , @@ -248,21 +243,11 @@ Format35s(Opcode, Registers, Reference) ::= {}, >> -Format35sf(Opcode, Registers, Reference) ::= -<< - {}, ->> - Format35ms(Opcode, Registers, MethodIndex) ::= << {}, vtable@ >> -Format35msf(Opcode, Registers, Reference) ::= -<< - {}, ->> - Format3rc(Opcode, StartRegister, LastRegister, Reference) ::= << { .. }, @@ -273,11 +258,6 @@ Format3rms(Opcode, StartRegister, LastRegister, MethodIndex) ::= { .. }, vtable@ >> -Format3rmsf(Opcode, StartRegister, LastRegister, Reference) ::= -<< - { .. }, ->> - Format51l(Opcode, RegisterA, Literal) ::= << , @@ -401,7 +381,6 @@ TypeReference(TypeDescriptor) ::= >> - SimpleEncodedValue(Value) ::= << diff --git a/baksmali/src/test/smali/deodex_test1/main.smali b/baksmali/src/test/smali/deodex_test1/main.smali index 22c18690..8d05f305 100644 --- a/baksmali/src/test/smali/deodex_test1/main.smali +++ b/baksmali/src/test/smali/deodex_test1/main.smali @@ -20,11 +20,7 @@ #and this will always throw an exception. Everything below #here, until the here2: label is dead code, and should be #commented out. This instruction itself should be be replaced - #with a call to Ljava/lang/Object;->hashCode()I, followed - #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) + #with a call to Ljava/lang/Object;->hashCode()I invoke-virtual {v0}, Lrandomclass;->getSuperclass()Lsuperclass; move-result-object v1 diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/AnalyzedInstruction.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/AnalyzedInstruction.java index e6201d7b..7eefcfdf 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/AnalyzedInstruction.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/AnalyzedInstruction.java @@ -6,15 +6,13 @@ import org.jf.dexlib.ItemType; import org.jf.dexlib.MethodIdItem; import org.jf.dexlib.Util.ExceptionWithContext; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; -public class AnalyzedInstruction { +public class AnalyzedInstruction implements Comparable { /** * The actual instruction */ - public final Instruction instruction; + protected Instruction instruction; /** * The index of the instruction, where the first instruction in the method is at index 0, and so on @@ -24,35 +22,59 @@ public class AnalyzedInstruction { /** * Instructions that can pass on execution to this one during normal execution */ - protected final LinkedList predecessors = new LinkedList(); + protected final TreeSet predecessors = new TreeSet(); /** * Instructions that can execution could pass on to next during normal execution */ protected final LinkedList successors = new LinkedList(); + /** + * This contains the register types *before* the instruction has executed + */ + protected final RegisterType[] preRegisterMap; + /** * This contains the register types *after* the instruction has executed */ protected final RegisterType[] postRegisterMap; /** - * This is set to true when this instruction follows an odexed instruction that couldn't be deodexed. In this case - * the unodexable instruction is guaranteed to throw an NPE, so anything following it is dead, up until a non-dead - * code path merges in. And more importantly, the code following the unodexable instruction isn't verifiable in - * some cases, if it depends on the return/field type of the unodexeable instruction. Meaning that if the "dead" - * code was left in, dalvik would reject it because it couldn't verify the register types. In some cases, this - * dead code could be left in without ill-effect, but it's easier to always remove it, which is always valid. Since - * it is dead code, removing it won't have any effect. + * When deodexing, we might need to deodex this instruction multiple times, when we merge in new register + * information. When this happens, we need to restore the original (odexed) instruction, so we can deodex it again + */ + protected final Instruction originalInstruction; + + + /** + * A dead instruction is one that is unreachable because it follows an odexed instruction that can't be deodexed + * because it's object register is always null. In the non-odexed code that the odex was generated from, we would + * have technically considered this code reachable and could verify it, even though the instruction that ended up + * being odexed was always null, because we would assume both "paths" out of the instruction are valid - the one + * where execution proceeds normally to the next instruction, and the one where an exception occurs and execution + * either goes to a catch block, or out of the method. + * + * However, in the odexed case, we can't verify the code following an undeodexable instruction because we lack + * the register information from the undeodexable instruction - because we don't know the actual method or field + * that is being accessed. + * + * The undeodexable instruction is guaranteed to throw an NPE, so the following code is effectivetly unreachable. + * Once we detect an undeodexeable instruction, the following code is marked as dead up until a non-dead execution + * path merges in. Additionally, we remove the predecessors/successors of any dead instruction. For example, if + * there is a dead goto instruction, then we would remove the target instruction as a successor, and we would + * also remove the dead goto instruction as a predecessor to the target. */ protected boolean dead = false; public AnalyzedInstruction(Instruction instruction, int instructionIndex, int registerCount) { this.instruction = instruction; + this.originalInstruction = instruction; this.instructionIndex = instructionIndex; this.postRegisterMap = new RegisterType[registerCount]; + this.preRegisterMap = new RegisterType[registerCount]; RegisterType unknown = RegisterType.getRegisterType(RegisterType.Category.Unknown, null); for (int i=0; i getPredecessors() { - return Collections.unmodifiableList(predecessors); + public SortedSet getPredecessors() { + return Collections.unmodifiableSortedSet(predecessors); } - private boolean checkPredecessorSorted(AnalyzedInstruction predecessor) { - if (predecessors.size() == 0) { - return true; - } - - if (predecessor.getInstructionIndex() <= predecessors.getLast().getInstructionIndex()) { - return false; - } - - return true; + protected boolean addPredecessor(AnalyzedInstruction predecessor) { + return predecessors.add(predecessor); } - protected void addPredecessor(AnalyzedInstruction predecessor) { - assert checkPredecessorSorted(predecessor); - predecessors.add(predecessor); - } - - /** - * @return true if the successor was added or false if it wasn't added because it already existed - */ - protected boolean addSuccessor(AnalyzedInstruction successor) { - for (AnalyzedInstruction instruction: successors) { - if (instruction == successor) { - return false; - } - } + protected void addSuccessor(AnalyzedInstruction successor) { successors.add(successor); - return true; + } + + protected void setDeodexedInstruction(Instruction instruction) { + assert originalInstruction.opcode.odexOnly(); + this.instruction = instruction; + } + + protected void restoreOdexedInstruction() { + assert originalInstruction.opcode.odexOnly(); + instruction = originalInstruction; } public int getSuccessorCount() { @@ -107,6 +117,18 @@ public class AnalyzedInstruction { return Collections.unmodifiableList(successors); } + public Instruction getInstruction() { + return instruction; + } + + public Instruction getOriginalInstruction() { + return originalInstruction; + } + + public boolean isDead() { + return dead; + } + /** * Is this instruction a "beginning instruction". A beginning instruction is defined to be an instruction * that can be the first successfully executed instruction in the method. The first instruction is always a @@ -114,6 +136,9 @@ public class AnalyzedInstruction { * the first instruction of any exception handler for that try block is also a beginning instruction. And likewise, * if any of those instructions can throw an exception and are covered by try blocks, the first instruction of the * corresponding exception handler is a beginning instruction, etc. + * + * To determine this, we simply check if the first predecessor is the fake "StartOfMethod" instruction, which has + * an instruction index of -1. * @return a boolean value indicating whether this instruction is a beginning instruction */ public boolean isBeginningInstruction() { @@ -121,34 +146,50 @@ public class AnalyzedInstruction { return false; } - if (predecessors.getFirst().instructionIndex == -1) { + if (predecessors.first().instructionIndex == -1) { return true; } return false; } /* - * Sets the "post-instruction" register type as indicated. This should only be used to set - * the method parameter types for the "start of method" instruction, or to set the register - * type of the destination register during verification. The change to the register type - * will + * Merges the given register type into the specified pre-instruction register, and also sets the post-instruction + * register type accordingly if it isn't a destination register for this instruction * @param registerNumber Which register to set - * @param registerType The "post-instruction" register type + * @param registerType The register type + * @returns true If the post-instruction register type was changed. This might be false if either the specified + * register is a destination register for this instruction, or if the pre-instruction register type didn't change + * after merging in the given register type */ - protected boolean setPostRegisterType(int registerNumber, RegisterType registerType) { + protected boolean mergeRegister(int registerNumber, RegisterType registerType, BitSet verifiedInstructions) { assert registerNumber >= 0 && registerNumber < postRegisterMap.length; assert registerType != null; - RegisterType oldRegisterType = postRegisterMap[registerNumber]; - if (oldRegisterType == registerType) { + RegisterType oldRegisterType = preRegisterMap[registerNumber]; + RegisterType mergedRegisterType = oldRegisterType.merge(registerType); + + if (mergedRegisterType == oldRegisterType) { return false; } - postRegisterMap[registerNumber] = registerType; - return true; + preRegisterMap[registerNumber] = mergedRegisterType; + verifiedInstructions.clear(instructionIndex); + + if (!setsRegister(registerNumber)) { + postRegisterMap[registerNumber] = mergedRegisterType; + return true; + } + + return false; } - protected RegisterType getMergedRegisterTypeFromPredecessors(int registerNumber) { + /** + * Iterates over the predecessors of this instruction, and merges all the post-instruction register types for the + * given register. Any dead, unreachable, or odexed predecessor is ignored + * @param registerNumber + * @return The register type resulting from merging the post-instruction register types from all predecessors + */ + protected RegisterType mergePreRegisterTypeFromPredecessors(int registerNumber) { RegisterType mergedRegisterType = null; for (AnalyzedInstruction predecessor: predecessors) { RegisterType predecessorRegisterType = predecessor.postRegisterMap[registerNumber]; @@ -158,9 +199,30 @@ public class AnalyzedInstruction { return mergedRegisterType; } + /* + * Sets the "post-instruction" register type as indicated. + * @param registerNumber Which register to set + * @param registerType The "post-instruction" register type + * @returns true if the given register type is different than the existing post-instruction register type + */ + protected boolean setPostRegisterType(int registerNumber, RegisterType registerType) { + assert registerNumber >= 0 && registerNumber < postRegisterMap.length; + assert registerType != null; + + RegisterType oldRegisterType = postRegisterMap[registerNumber]; + if (oldRegisterType == registerType) { + return false; + } + + postRegisterMap[registerNumber] = registerType; + return true; + } + + protected boolean isInvokeInit() { if (instruction == null || - (instruction.opcode != Opcode.INVOKE_DIRECT && instruction.opcode != Opcode.INVOKE_DIRECT_RANGE)) { + (instruction.opcode != Opcode.INVOKE_DIRECT && instruction.opcode != Opcode.INVOKE_DIRECT_RANGE && + instruction.opcode != Opcode.INVOKE_DIRECT_EMPTY)) { return false; } @@ -187,7 +249,6 @@ public class AnalyzedInstruction { } public boolean setsRegister(int registerNumber) { - //When constructing a new object, the register type will be an uninitialized reference after the new-instance //instruction, but becomes an initialized reference once the method is called. So even though invoke //instructions don't normally change any registers, calling an method will change the type of its @@ -207,14 +268,14 @@ public class AnalyzedInstruction { if (registerNumber == destinationRegister) { return true; } - RegisterType preInstructionDestRegisterType = getMergedRegisterTypeFromPredecessors(registerNumber); + RegisterType preInstructionDestRegisterType = getPreInstructionRegisterType(registerNumber); if (preInstructionDestRegisterType.category != RegisterType.Category.UninitRef && preInstructionDestRegisterType.category != RegisterType.Category.UninitThis) { return false; } //check if the uninit ref has been copied to another register - if (getMergedRegisterTypeFromPredecessors(registerNumber) == preInstructionDestRegisterType) { + if (getPreInstructionRegisterType(registerNumber) == preInstructionDestRegisterType) { return true; } return false; @@ -251,14 +312,17 @@ public class AnalyzedInstruction { } public RegisterType getPreInstructionRegisterType(int registerNumber) { - //if the specific register is not a destination register, then the stored post-instruction register type will - //be the same as the pre-instruction regsiter type, so we can use that. - //otherwise, we need to merge the predecessor's post-instruction register types + return preRegisterMap[registerNumber]; + } - if (this.setsRegister(registerNumber)) { - return getMergedRegisterTypeFromPredecessors(registerNumber); + public int compareTo(AnalyzedInstruction analyzedInstruction) { + //TODO: out of curiosity, check the disassembly of this to see if it retrieves the value of analyzedInstruction.instructionIndex for every access. It should, because the field is final. What about if we set the field to non-final? + if (instructionIndex < analyzedInstruction.instructionIndex) { + return -1; + } else if (instructionIndex == analyzedInstruction.instructionIndex) { + return 0; } else { - return postRegisterMap[registerNumber]; + return 1; } } } diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java index 2228c40a..8d8a18be 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java @@ -17,6 +17,24 @@ public class ClassPath { private final HashMap classDefs; protected ClassDef javaLangObjectClassDef; //Ljava/lang/Object; + private final static String[][] inlineMethods = + new String[][] { + { "Lorg/apache/harmony/dalvik/NativeTestTarget;", "emptyInlineMethod()V"}, + { "Ljava/lang/String;", "charAt(I)C"}, + { "Ljava/lang/String;", "compareTo(Ljava/lang/String;)I"}, + { "Ljava/lang/String;", "equals(Ljava/lang/Object;)Z"}, + { "Ljava/lang/String;", "length()I"}, + { "Ljava/lang/Math;", "abs(I)I"}, + { "Ljava/lang/Math;", "abs(J)J"}, + { "Ljava/lang/Math;", "abs(F)F"}, + { "Ljava/lang/Math;", "abs(D)D"}, + { "Ljava/lang/Math;", "min(I)I"}, + { "Ljava/lang/Math;", "max(II)I"}, + { "Ljava/lang/Math;", "sqrt(D)D"}, + { "Ljava/lang/Math;", "cos(D)D"}, + { "Ljava/lang/Math;", "sin(D)D"} + }; + public static void InitializeClassPath(String bootClassPathDir, String[] bootClassPath, DexFile dexFile) { if (theClassPath != null) { throw new ExceptionWithContext("Cannot initialize ClassPath multiple times"); @@ -37,7 +55,9 @@ public class ClassPath { } } - loadDexFile(dexFile); + if (dexFile != null) { + loadDexFile(dexFile); + } for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) { ClassDef classDef = new PrimitiveClassDef(primitiveType); @@ -76,8 +96,6 @@ public class ClassPath { if (classDefItem.getClassType().getTypeDescriptor().equals("Ljava/lang/Object;")) { theClassPath.javaLangObjectClassDef = classDef; } - /*classDef.dumpVtable(); - classDef.dumpFields();*/ } } @@ -87,15 +105,23 @@ public class ClassPath { } } - public static ClassDef getClassDef(String classType) { + public static ClassDef getClassDef(String classType) { + return getClassDef(classType, true); + } + + public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef) { ClassDef classDef = theClassPath.classDefs.get(classType); if (classDef == null) { //if it's an array class, try to create it if (classType.charAt(0) == '[') { return theClassPath.createArrayClassDef(classType); } else { - //TODO: we should output a warning - return theClassPath.createUnresolvedClassDef(classType); + if (createUnresolvedClassDef) { + //TODO: we should output a warning + return theClassPath.createUnresolvedClassDef(classType); + } else { + return null; + } } } return classDef; @@ -105,6 +131,10 @@ public class ClassPath { return getClassDef(classType.getTypeDescriptor()); } + public static ClassDef getClassDef(TypeIdItem classType, boolean creatUnresolvedClassDef) { + return getClassDef(classType.getTypeDescriptor(), creatUnresolvedClassDef); + } + //256 [ characters private static final String arrayPrefix = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + @@ -149,14 +179,14 @@ public class ClassPath { //TODO: do we want to handle primitive types here? I don't think so.. (if not, add assert) - if (!class1.isInterface && class2.isInterface) { + if (class2.isInterface) { if (class1.implementsInterface(class2)) { return class2; } return theClassPath.javaLangObjectClassDef; } - if (!class2.isInterface && class1.isInterface) { + if (class1.isInterface) { if (class2.implementsInterface(class1)) { return class1; } @@ -220,6 +250,15 @@ public class ClassPath { return getArrayClassDefByElementClassAndDimension(theClassPath.javaLangObjectClassDef, dimensions); } + + + public static String[] getInlineMethod(int inlineIndex) { + if (inlineIndex < 0 || inlineIndex >= inlineMethods.length) { + return null; + } + return inlineMethods[inlineIndex]; + } + public static class ArrayClassDef extends ClassDef { private final ClassDef elementClass; private final int arrayDimensions; @@ -382,6 +421,18 @@ public class ClassPath { public final static int PrimitiveClassDef = 1; public final static int UnresolvedClassDef = 2; + + /** + * The following fields are used only during the initial loading of classes, and are set to null afterwards + * TODO: free these + */ + + //This is only the virtual methods that this class declares itself. + private String[] virtualMethods; + //this is a list of all the interfaces that the class implements directory, or any super interfaces of those + //interfaces. It is generated in such a way that it is ordered in the same way as dalvik's ClassObject.iftable, + private LinkedHashMap interfaceTable; + /** * This constructor is used for the ArrayClassDef, PrimitiveClassDef and UnresolvedClassDef subclasses * @param classType the class type @@ -403,6 +454,9 @@ public class ClassPath { instanceFields = superclass.instanceFields; instanceFieldLookup = superclass.instanceFieldLookup; classDepth = 1; //1 off from java.lang.Object + + virtualMethods = null; + interfaceTable = null; } else if (classFlavor == PrimitiveClassDef) { //primitive type assert classType.charAt(0) != '[' && classType.charAt(0) != 'L'; @@ -416,6 +470,9 @@ public class ClassPath { instanceFields = null; instanceFieldLookup = null; classDepth = 0; //TODO: maybe use -1 to indicate not applicable? + + virtualMethods = null; + interfaceTable = null; } else /*if (classFlavor == UnresolvedClassDef)*/ { assert classType.charAt(0) == 'L'; this.classType = classType; @@ -429,6 +486,9 @@ public class ClassPath { instanceFields = superclass.instanceFields; instanceFieldLookup = superclass.instanceFieldLookup; classDepth = 1; //1 off from java.lang.Object + + virtualMethods = null; + interfaceTable = null; } } @@ -446,6 +506,9 @@ public class ClassPath { implementedInterfaces = loadAllImplementedInterfaces(classDefItem); + //TODO: we can probably get away with only creating the interface table for interface types + interfaceTable = loadInterfaceTable(classDefItem); + virtualMethods = loadVirtualMethods(classDefItem); vtable = loadVtable(classDefItem); virtualMethodLookup = new HashMap((int)Math.ceil(vtable.length / .7f), .75f); for (int i=0; i= vtable.length) { + return null; + } + return this.vtable[vtableIndex]; + } + private void swap(byte[] fieldTypes, String[] fields, int position1, int position2) { byte tempType = fieldTypes[position1]; fieldTypes[position1] = fieldTypes[position2]; @@ -586,6 +660,56 @@ public class ClassPath { return implementedInterfaceSet; } + private LinkedHashMap loadInterfaceTable(ClassDefItem classDefItem) { + TypeListItem typeListItem = classDefItem.getInterfaces(); + if (typeListItem == null) { + return null; + } + + LinkedHashMap interfaceTable = new LinkedHashMap(); + + 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 virtualMethodList = new LinkedList(); @@ -607,14 +731,32 @@ public class ClassPath { //iterate over the virtual methods in the current class, and only add them when we don't already have the //method (i.e. if it was implemented by the superclass) - ClassDataItem classDataItem = classDefItem.getClassData(); - if (classDataItem != null) { - EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods(); - if (virtualMethods != null) { - for (EncodedMethod virtualMethod: virtualMethods) { - String methodString = virtualMethod.method.getVirtualMethodString(); - if (tempVirtualMethodLookup.get(methodString) == null) { - virtualMethodList.add(methodString); + if (!this.isInterface) { + ClassDataItem classDataItem = classDefItem.getClassData(); + if (classDataItem != null) { + EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods(); + if (virtualMethods != null) { + for (EncodedMethod virtualMethod: virtualMethods) { + String methodString = virtualMethod.method.getVirtualMethodString(); + if (tempVirtualMethodLookup.get(methodString) == null) { + virtualMethodList.add(methodString); + tempVirtualMethodLookup.put(methodString, methodIndex++); + } + } + } + } + + if (interfaceTable != null) { + for (ClassDef interfaceDef: interfaceTable.values()) { + if (interfaceDef.virtualMethods == null) { + continue; + } + + for (String virtualMethod: interfaceDef.virtualMethods) { + if (tempVirtualMethodLookup.get(virtualMethod) == null) { + virtualMethodList.add(virtualMethod); + tempVirtualMethodLookup.put(virtualMethod, methodIndex++); + } } } } @@ -628,9 +770,30 @@ public class ClassPath { return vtable; } + private int getNextFieldOffset() { + if (instanceFields == null || instanceFields.size() == 0) { + return 8; + } + + int lastItemIndex = instanceFields.size()-1; + int fieldOffset = instanceFields.keyAt(lastItemIndex); + String lastField = instanceFields.valueAt(lastItemIndex); + + int fieldTypeIndex = lastField.indexOf(":") + 1; + + switch (lastField.charAt(fieldTypeIndex)) { + case 'J': + case 'D': + return fieldOffset + 8; + default: + return fieldOffset + 4; + } + } + private SparseArray loadFields(ClassDefItem classDefItem) { //This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to //arrange fields, so that we end up with the same field offsets (which is needed for deodexing). + //See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets() final byte REFERENCE = 0; final byte WIDE = 1; @@ -684,11 +847,24 @@ public class ClassPath { } } + + int startFieldOffset = 8; + if (this.superclass != null) { + startFieldOffset = this.superclass.getNextFieldOffset(); + } + + int fieldIndexMod; + if ((startFieldOffset % 8) == 0) { + fieldIndexMod = 0; + } else { + fieldIndexMod = 1; + } + //next, we need to group all the wide fields after the reference fields. But the wide fields have to be //8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field //is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in. //If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets - if (front < fields.length && (front % 2) != 0) { + if (front < fields.length && (front % 2) != fieldIndexMod) { if (fieldTypes[front] == WIDE) { //we need to swap in a 32-bit field, so the wide fields will be correctly aligned back = fields.length - 1; @@ -725,7 +901,7 @@ public class ClassPath { int superFieldCount = 0; if (superclass != null) { - superclass.instanceFields.size(); + superFieldCount = superclass.instanceFields.size(); } //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets @@ -743,7 +919,7 @@ public class ClassPath { String lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1); assert lastSuperField.indexOf(':') >= 0; - assert lastSuperField.indexOf(':') < superFieldCount-1; //the ':' shouldn't be the last char + assert lastSuperField.indexOf(':') < lastSuperField.length()-1; //the ':' shouldn't be the last char char fieldType = lastSuperField.charAt(lastSuperField.indexOf(':') + 1); if (fieldType == 'J' || fieldType == 'D') { fieldOffset += 8; @@ -819,4 +995,95 @@ public class ClassPath { return classType.compareTo(classDef.classType); } } + + + public static void validateAgainstDeodexerant(String host, int port, int skipClasses) { + Deodexerant deodexerant = new Deodexerant(host, port); + + int count = 0; + + try { + String[] inlineMethods = deodexerant.getInlineMethods(); + (new DeodexUtil(null)).checkInlineMethods(inlineMethods); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, "Error while checking inline methods"); + } + + try { + for (ClassDef classDef: theClassPath.classDefs.values()) { + if (count < skipClasses) { + count++; + continue; + } + if (classDef instanceof UnresolvedClassDef || classDef instanceof ArrayClassDef || + classDef instanceof PrimitiveClassDef) { + continue; + } + + String[] vtable = deodexerant.getVirtualMethods(classDef.classType); + + if (vtable.length != classDef.vtable.length) { + throw new ValidationException(String.format("virtual table size mismatch for class %s", + classDef.classType)); + } + + for (int i=0; i vals) + { + StringBuilder sb = new StringBuilder(); + for (String val: vals) { + sb.append(val); + sb.append('\n'); + } + return sb.toString(); + } } diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java new file mode 100644 index 00000000..f5cc9f33 --- /dev/null +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java @@ -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 paramList = new LinkedList(); + + for (int i=0; i 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%s(%s)%s", classType, methodName, parameters, returnType); + } + } +} diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/Deodexerant.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/Deodexerant.java new file mode 100644 index 00000000..13067d07 --- /dev/null +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/Deodexerant.java @@ -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 responseLines = new ArrayList(); + 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 instructions; private boolean analyzed = false; @@ -25,7 +27,7 @@ public class MethodAnalyzer { //instruction, etc. private AnalyzedInstruction startOfMethod; - public MethodAnalyzer(ClassDataItem.EncodedMethod encodedMethod) { + public MethodAnalyzer(ClassDataItem.EncodedMethod encodedMethod, boolean deodex) { if (encodedMethod == null) { throw new IllegalArgumentException("encodedMethod cannot be null"); } @@ -34,6 +36,12 @@ public class MethodAnalyzer { } this.encodedMethod = encodedMethod; + if (deodex) { + this.deodexUtil = new DeodexUtil(encodedMethod.method.getDexFile()); + } else { + this.deodexUtil = null; + } + //override AnalyzedInstruction and provide custom implementations of some of the methods, so that we don't //have to handle the case this special case of instruction being null, in the main class startOfMethod = new AnalyzedInstruction(null, -1, encodedMethod.codeItem.getRegisterCount()) { @@ -92,7 +100,7 @@ public class MethodAnalyzer { throw new ValidationException("The constructor flag can only be used with an method."); } - setRegisterTypeAndPropagateChanges(startOfMethod, thisRegister, + setPostRegisterTypeAndPropagateChanges(startOfMethod, thisRegister, RegisterType.getRegisterType(RegisterType.Category.UninitThis, ClassPath.getClassDef(methodIdItem.getContainingClass()))); } else { @@ -100,7 +108,7 @@ public class MethodAnalyzer { throw new ValidationException("An method must have the \"constructor\" access flag"); } - setRegisterTypeAndPropagateChanges(startOfMethod, thisRegister, + setPostRegisterTypeAndPropagateChanges(startOfMethod, thisRegister, RegisterType.getRegisterType(RegisterType.Category.Reference, ClassPath.getClassDef(methodIdItem.getContainingClass()))); } @@ -112,13 +120,13 @@ public class MethodAnalyzer { for (int i=0; i=0; i=instructionsToAnalyze.nextSetBit(i+1)) { - instructionsToAnalyze.clear(i); - if (verifiedInstructions.get(i)) { - continue; + BitSet odexedInstructions = new BitSet(verifiedInstructions.size()); + + do { + boolean didSomething = false; + + while (!instructionsToAnalyze.isEmpty()) { + for(int i=instructionsToAnalyze.nextSetBit(0); i>=0; i=instructionsToAnalyze.nextSetBit(i+1)) { + instructionsToAnalyze.clear(i); + if (verifiedInstructions.get(i)) { + continue; + } + AnalyzedInstruction instructionToVerify = instructions.valueAt(i); + try { + if (instructionToVerify.originalInstruction.opcode.odexOnly()) { + instructionToVerify.restoreOdexedInstruction(); + } + + if (!analyzeInstruction(instructionToVerify)) { + odexedInstructions.set(i); + continue; + } else { + didSomething = true; + odexedInstructions.clear(i); + } + } catch (ValidationException ex) { + this.validationException = ex; + int codeAddress = getInstructionAddress(instructionToVerify); + ex.setCodeAddress(codeAddress); + ex.addContext(String.format("opcode: %s", instructionToVerify.instruction.opcode.name)); + ex.addContext(String.format("CodeAddress: %d", codeAddress)); + ex.addContext(String.format("Method: %s", encodedMethod.method.getMethodString())); + break; + } + + verifiedInstructions.set(instructionToVerify.getInstructionIndex()); + + for (AnalyzedInstruction successor: instructionToVerify.successors) { + instructionsToAnalyze.set(successor.getInstructionIndex()); + } } - AnalyzedInstruction instructionToVerify = instructions.valueAt(i); - try { - analyzeInstruction(instructionToVerify); - } catch (ValidationException ex) { - this.validationException = ex; - int codeAddress = getInstructionAddress(instructionToVerify); - ex.setCodeAddress(codeAddress); - ex.addContext(String.format("opcode: %s", instructionToVerify.instruction.opcode.name)); - ex.addContext(String.format("CodeAddress: %d", codeAddress)); - ex.addContext(String.format("Method: %s", encodedMethod.method.getMethodString())); + if (validationException != null) { break; } - - verifiedInstructions.set(instructionToVerify.getInstructionIndex()); - - for (AnalyzedInstruction successor: instructionToVerify.successors) { - instructionsToAnalyze.set(successor.getInstructionIndex()); - } } - if (validationException != null) { + + if (!didSomething) { break; } - } + + if (!odexedInstructions.isEmpty()) { + for (int i=odexedInstructions.nextSetBit(0); i>=0; i=odexedInstructions.nextSetBit(i+1)) { + instructionsToAnalyze.set(i); + } + } + } while (true); for (int i=0; i=0; i=instructionsToProcess.nextSetBit(i+1)) { + AnalyzedInstruction currentInstruction = instructions.valueAt(i); + instructionsToProcess.clear(i); + + if (currentInstruction.dead) { + continue; + } + + boolean isDead = true; + + for (AnalyzedInstruction predecessor: currentInstruction.predecessors) { + if (!predecessor.dead) { + isDead = false; + break; + } + } + + if (isDead) { + currentInstruction.dead = true; + + for (AnalyzedInstruction successor: currentInstruction.successors) { + instructionsToProcess.set(successor.instructionIndex); + } + } + } + } + + analyzedInstruction.dead = false; + } + + private void setPostRegisterTypeAndPropagateChanges(AnalyzedInstruction analyzedInstruction, int registerNumber, RegisterType registerType) { BitSet changedInstructions = new BitSet(instructions.size()); - boolean changed = analyzedInstruction.setPostRegisterType(registerNumber, registerType); - - if (!changed) { + if (!analyzedInstruction.setPostRegisterType(registerNumber, registerType)) { return; } propagateRegisterToSuccessors(analyzedInstruction, registerNumber, changedInstructions); - //using a for loop inside the while loop optimizes for the common case of the successors of an instruction + //Using a for loop inside the while loop optimizes for the common case of the successors of an instruction //occurring after the instruction. Any successors that occur prior to the instruction will be picked up on //the next iteration of the while loop. - //this could also be done recursively, but in large methods it would likely cause very deep recursion, - //which would requires the user to specify a larger stack size. This isn't really a problem, but it is - //slightly annoying. + //This could also be done recursively, but in large methods it would likely cause very deep recursion, + //which requires the user to specify a larger stack size. This isn't really a problem, but it is slightly + //annoying. while (!changedInstructions.isEmpty()) { for (int instructionIndex=changedInstructions.nextSetBit(0); instructionIndex>=0; @@ -278,36 +387,25 @@ public class MethodAnalyzer { if (registerType.category == RegisterType.Category.LongLo) { checkWidePair(registerNumber, analyzedInstruction); - setRegisterTypeAndPropagateChanges(analyzedInstruction, registerNumber+1, + setPostRegisterTypeAndPropagateChanges(analyzedInstruction, registerNumber+1, RegisterType.getRegisterType(RegisterType.Category.LongHi, null)); } else if (registerType.category == RegisterType.Category.DoubleLo) { checkWidePair(registerNumber, analyzedInstruction); - setRegisterTypeAndPropagateChanges(analyzedInstruction, registerNumber+1, + setPostRegisterTypeAndPropagateChanges(analyzedInstruction, registerNumber+1, RegisterType.getRegisterType(RegisterType.Category.DoubleHi, null)); } } private void propagateRegisterToSuccessors(AnalyzedInstruction instruction, int registerNumber, BitSet changedInstructions) { + RegisterType postRegisterType = instruction.getPostInstructionRegisterType(registerNumber); for (AnalyzedInstruction successor: instruction.successors) { - if (!successor.setsRegister(registerNumber)) { - RegisterType registerType = successor.getMergedRegisterTypeFromPredecessors(registerNumber); - - //TODO: GROT? - /*if (registerType.category == RegisterType.Category.UninitRef && instruction.isInvokeInit()) { - continue; - }*/ - - if (successor.setPostRegisterType(registerNumber, registerType)) { - changedInstructions.set(successor.instructionIndex); - verifiedInstructions.clear(successor.instructionIndex); - } + if (successor.mergeRegister(registerNumber, postRegisterType, verifiedInstructions)) { + changedInstructions.set(successor.instructionIndex); } } } - - private void buildInstructionList() { assert encodedMethod != null; assert encodedMethod.codeItem != null; @@ -368,13 +466,18 @@ public class MethodAnalyzer { } } - //finally, populate the successors and predecessors for each instruction + //finally, populate the successors and predecessors for each instruction. We start at the fake "StartOfMethod" + //instruction and follow the execution path. Any unreachable code won't have any predecessors or successors, + //and no reachable code will have an unreachable predessor or successor assert instructions.size() > 0; - addPredecessorSuccessor(startOfMethod, instructions.valueAt(0), exceptionHandlers); - startOfMethod.addSuccessor(instructions.valueAt(0)); + BitSet instructionsToProcess = new BitSet(insns.length); - for (int i=0; i