diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index aa9447e1..fb279b55 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -50,7 +50,7 @@ public class baksmali { public static int registerInfo = 0; public static String bootClassPath; - public static void disassembleDexFile(DexFile dexFile, boolean deodex, String outputDirectory, + public static void disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory, String[] classPathDirs, String bootClassPath, boolean noParameterRegisters, boolean useLocalsDirective, boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, int registerInfo, @@ -68,7 +68,8 @@ public class baksmali { if (registerInfo != 0 || deodex || verify) { try { - ClassPath.InitializeClassPath(classPathDirs, bootClassPath==null?null:bootClassPath.split(":"), dexFile); + ClassPath.InitializeClassPath(classPathDirs, bootClassPath==null?null:bootClassPath.split(":"), + dexFilePath, dexFile); } catch (Exception ex) { System.err.println("\n\nError occured while loading boot class path files. Aborting."); ex.printStackTrace(System.err); diff --git a/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java b/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java index 37315c74..d8d20319 100644 --- a/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java +++ b/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java @@ -120,7 +120,8 @@ public class deodexCheck { bootClassPathDirsArray[i] = bootClassPathDirs.get(i); } - ClassPath.InitializeClassPath(bootClassPathDirsArray, bootClassPath==null?null:bootClassPath.split(":"), null); + ClassPath.InitializeClassPath(bootClassPathDirsArray, bootClassPath==null?null:bootClassPath.split(":"), + null, null); ClassPath.validateAgainstDeodexerant(deodexerantHost, deodexerantPort, classStartIndex); } diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java index 4ad5fd44..c5c7bcda 100644 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/baksmali/src/main/java/org/jf/baksmali/main.java @@ -265,9 +265,9 @@ public class main { bootClassPathDirsArray[i] = bootClassPathDirs.get(i); } - baksmali.disassembleDexFile(dexFile, deodex, outputDirectory, bootClassPathDirsArray, bootClassPath, - noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets, - registerInfo, verify); + baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, deodex, outputDirectory, + bootClassPathDirsArray, bootClassPath, noParameterRegisters, useLocalsDirective, + useSequentialLabels, outputDebugInfo, addCodeOffsets, registerInfo, verify); } if ((doDump || write) && !dexFile.isOdex()) { 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 e77b610e..29842c2a 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 @@ -45,20 +45,26 @@ public class ClassPath { private final HashMap classDefs; protected ClassDef javaLangObjectClassDef; //Ljava/lang/Object; - public static void InitializeClassPath(String[] classPathDirs, String[] bootClassPath, DexFile dexFile) { + //This is only used while initialing the class path. It is set to null after initialization has finished. + private LinkedHashMap tempClasses; + + public static void InitializeClassPath(String[] classPathDirs, String[] bootClassPath, String dexFilePath, + DexFile dexFile) { if (theClassPath != null) { throw new ExceptionWithContext("Cannot initialize ClassPath multiple times"); } theClassPath = new ClassPath(); - theClassPath.initClassPath(classPathDirs, bootClassPath, dexFile); + theClassPath.initClassPath(classPathDirs, bootClassPath, dexFilePath, dexFile); } private ClassPath() { classDefs = new HashMap(); } - private void initClassPath(String[] classPathDirs, String[] bootClassPath, DexFile dexFile) { + private void initClassPath(String[] classPathDirs, String[] bootClassPath, String dexFilePath, DexFile dexFile) { + tempClasses = new LinkedHashMap(); + if (bootClassPath != null) { for (String bootClassPathEntry: bootClassPath) { loadBootClassPath(classPathDirs, bootClassPathEntry); @@ -66,13 +72,23 @@ public class ClassPath { } if (dexFile != null) { - loadDexFile(dexFile); + loadDexFile(dexFilePath, dexFile); + } + + + for (String classType: tempClasses.keySet()) { + ClassDef classDef = ClassPath.loadClassDef(classType); + if (classType.equals("Ljava/lang/Object;")) { + this.javaLangObjectClassDef = classDef; + } } for (String primitiveType: new String[]{"Z", "B", "S", "C", "I", "J", "F", "D"}) { ClassDef classDef = new PrimitiveClassDef(primitiveType); classDefs.put(primitiveType, classDef); } + + tempClasses = null; } private void loadBootClassPath(String[] classPathDirs, String bootClassPathEntry) { @@ -116,7 +132,7 @@ public class ClassPath { } try { - loadDexFile(dexFile); + loadDexFile(file.getPath(), dexFile); } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, String.format("Error while loading boot classpath entry %s", bootClassPathEntry)); @@ -126,16 +142,13 @@ public class ClassPath { throw new ExceptionWithContext(String.format("Cannot locate boot class path file %s", bootClassPathEntry)); } - private void loadDexFile(DexFile dexFile) { + private void loadDexFile(String dexFilePath, DexFile dexFile) { for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) { try { //TODO: need to check if the class already exists. (and if so, what to do about it?) - ClassDef classDef = new ClassDef(classDefItem); - classDefs.put(classDef.getClassType(), classDef); + TempClassInfo tempClassInfo = new TempClassInfo(dexFilePath, classDefItem); - if (classDefItem.getClassType().getTypeDescriptor().equals("Ljava/lang/Object;")) { - theClassPath.javaLangObjectClassDef = classDef; - } + tempClasses.put(tempClassInfo.classType, tempClassInfo); } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s", classDefItem.getClassType().getTypeDescriptor())); @@ -153,6 +166,32 @@ public class ClassPath { return getClassDef(classType, true); } + /** + * This method checks if the given class has been loaded yet. If it has, it returns the loaded ClassDef. If not, + * then it looks up the TempClassItem for the given class and (possibly recursively) loads the ClassDef. + * @param classType the class to load + * @return the existing or newly loaded ClassDef object for the given class + */ + private static ClassDef loadClassDef(String classType) { + ClassDef classDef = getClassDef(classType, false); + + if (classDef == null) { + TempClassInfo classInfo = theClassPath.tempClasses.get(classType); + if (classInfo == null) { + throw new ExceptionWithContext(String.format("Could not find class %s", classType)); + } + + try { + classDef = new ClassDef(classInfo); + theClassPath.classDefs.put(classDef.classType, classDef); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, String.format("Error while loading class %s from file %s", + classInfo.classType, classInfo.dexFilePath)); + } + } + return classDef; + } + public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef) { ClassDef classDef = theClassPath.classDefs.get(classType); if (classDef == null) { @@ -527,30 +566,29 @@ public class ClassPath { } } - protected ClassDef(ClassDefItem classDefItem) { - classType = classDefItem.getClassType().getTypeDescriptor(); + protected ClassDef(TempClassInfo classInfo) { + classType = classInfo.classType; + isInterface = classInfo.isInterface; - isInterface = (classDefItem.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0; - - superclass = loadSuperclass(classDefItem); + superclass = loadSuperclass(classInfo); if (superclass == null) { classDepth = 0; } else { classDepth = superclass.classDepth + 1; } - implementedInterfaces = loadAllImplementedInterfaces(classDefItem); + implementedInterfaces = loadAllImplementedInterfaces(classInfo); //TODO: we can probably get away with only creating the interface table for interface types - interfaceTable = loadInterfaceTable(classDefItem); - virtualMethods = loadVirtualMethods(classDefItem); - vtable = loadVtable(classDefItem); + interfaceTable = loadInterfaceTable(classInfo); + virtualMethods = classInfo.virtualMethods; + vtable = loadVtable(classInfo); virtualMethodLookup = new HashMap((int)Math.ceil(vtable.length / .7f), .75f); for (int i=0; i((int)Math.ceil(instanceFields.size() / .7f), .75f); for (int i=0; i loadAllImplementedInterfaces(ClassDefItem classDefItem) { + private TreeSet loadAllImplementedInterfaces(TempClassInfo classInfo) { assert classType != null; assert classType.equals("Ljava/lang/Object;") || superclass != null; - assert classDefItem != null; + assert classInfo != null; TreeSet implementedInterfaceSet = new TreeSet(); @@ -676,10 +713,10 @@ public class ClassPath { } } - TypeListItem interfaces = classDefItem.getInterfaces(); - if (interfaces != null) { - for (TypeIdItem interfaceType: interfaces.getTypes()) { - ClassDef interfaceDef = ClassPath.getClassDef(interfaceType.getTypeDescriptor()); + + if (classInfo.interfaces != null) { + for (String interfaceType: classInfo.interfaces) { + ClassDef interfaceDef = ClassPath.loadClassDef(interfaceType); assert interfaceDef.isInterface(); implementedInterfaceSet.add(interfaceDef); @@ -695,25 +732,23 @@ public class ClassPath { return implementedInterfaceSet; } - private LinkedHashMap loadInterfaceTable(ClassDefItem classDefItem) { - TypeListItem typeListItem = classDefItem.getInterfaces(); - if (typeListItem == null) { + private LinkedHashMap loadInterfaceTable(TempClassInfo classInfo) { + if (classInfo.interfaces == 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())); + for (String interfaceType: classInfo.interfaces) { + if (!interfaceTable.containsKey(interfaceType)) { + ClassDef interfaceDef = ClassPath.loadClassDef(interfaceType); + if (interfaceDef == null) { + throw new ValidationException(String.format("Could not resolve type %s", interfaceType)); } - interfaceTable.put(interfaceType.getTypeDescriptor(), classDef); + interfaceTable.put(interfaceType, interfaceDef); - if (classDef.interfaceTable != null) { - for (ClassDef superInterface: classDef.interfaceTable.values()) { + if (interfaceDef.interfaceTable != null) { + for (ClassDef superInterface: interfaceDef.interfaceTable.values()) { if (!interfaceTable.containsKey(superInterface.classType)) { interfaceTable.put(superInterface.classType, superInterface); } @@ -725,27 +760,7 @@ public class ClassPath { 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(); //use a temp hash table, so that we can construct the final lookup with an appropriate @@ -767,16 +782,11 @@ 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) 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 (classInfo.virtualMethods != null) { + for (String virtualMethod: classInfo.virtualMethods) { + if (tempVirtualMethodLookup.get(virtualMethod) == null) { + virtualMethodList.add(virtualMethod); + tempVirtualMethodLookup.put(virtualMethod, methodIndex++); } } } @@ -825,7 +835,7 @@ public class ClassPath { } } - private SparseArray loadFields(ClassDefItem classDefItem) { + private SparseArray loadFields(TempClassInfo classInfo) { //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() @@ -834,26 +844,23 @@ public class ClassPath { final byte WIDE = 1; final byte OTHER = 2; - ClassDataItem classDataItem = classDefItem.getClassData(); - String[] fields = null; //the "type" for each field in fields. 0=reference,1=wide,2=other byte[] fieldTypes = null; - if (classDataItem != null) { - EncodedField[] encodedFields = classDataItem.getInstanceFields(); - if (encodedFields != null) { - fields = new String[encodedFields.length]; - fieldTypes = new byte[encodedFields.length]; + if (classInfo.instanceFields != null) { + fields = new String[classInfo.instanceFields.length]; + fieldTypes = new byte[fields.length]; - for (int i=0; i= field.length()-1 instead, but that's too easy to mistake for an off-by-one error - if (sepIndex < 0 || sepIndex == field.length()-1 || sepIndex >= field.length()) { - assert false; - throw new ExceptionWithContext("Invalid field format: " + field); - } - switch (field.charAt(sepIndex+1)) { + private byte getFieldType(String fieldType) { + switch (fieldType.charAt(0)) { case '[': case 'L': return 0; //REFERENCE @@ -1031,6 +1031,87 @@ public class ClassPath { } } + /** + * In some cases, classes can reference another class (i.e. a superclass or an interface) that is in a *later* + * boot class path entry. So we load all classes from all boot class path entries before starting to process them + */ + private static class TempClassInfo { + public final String dexFilePath; + public final String classType; + public final boolean isInterface; + public final String superclassType; + public final String[] interfaces; + public final String[] virtualMethods; + public final String[][] instanceFields; + + public TempClassInfo(String dexFilePath, ClassDefItem classDefItem) { + this.dexFilePath = dexFilePath; + + classType = classDefItem.getClassType().getTypeDescriptor(); + + isInterface = (classDefItem.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0; + + TypeIdItem superclassType = classDefItem.getSuperclass(); + if (superclassType == null) { + this.superclassType = null; + } else { + this.superclassType = superclassType.getTypeDescriptor(); + } + + interfaces = loadInterfaces(classDefItem); + + ClassDataItem classDataItem = classDefItem.getClassData(); + if (classDataItem != null) { + virtualMethods = loadVirtualMethods(classDataItem); + instanceFields = loadInstanceFields(classDataItem); + } else { + virtualMethods = null; + instanceFields = null; + } + } + + private String[] loadInterfaces(ClassDefItem classDefItem) { + TypeListItem typeList = classDefItem.getInterfaces(); + if (typeList != null) { + List types = typeList.getTypes(); + if (types != null && types.size() > 0) { + String[] interfaces = new String[types.size()]; + for (int i=0; i 0) { + String[] virtualMethods = new String[encodedMethods.length]; + for (int i=0; i 0) { + String[][] instanceFields = new String[encodedFields.length][2]; + for (int i=0; i