diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index 130b5197..321877af 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -30,6 +30,7 @@ package org.jf.baksmali; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.iface.ClassDef; @@ -40,10 +41,11 @@ import org.jf.util.IndentingWriter; import java.io.*; import java.util.*; +import java.util.concurrent.*; public class baksmali { - public static void disassembleDexFile(DexFile dexFile, baksmaliOptions options) { + public static void disassembleDexFile(DexFile dexFile, final baksmaliOptions options) { if (options.registerInfo != 0 || options.deodex) { try { Iterable extraClassPathEntries; @@ -87,11 +89,34 @@ public class baksmali { options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs); } - ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali"); + final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali"); - for (ClassDef classDef: classDefs) { - disassembleClass(classDef, fileNameHandler, options); + ExecutorService executor = Executors.newFixedThreadPool(options.jobs); + List> tasks = Lists.newArrayList(); + + for (final ClassDef classDef: classDefs) { + tasks.add(executor.submit(new Callable() { + @Override public Void call() throws Exception { + disassembleClass(classDef, fileNameHandler, options); + return null; + } + })); } + + for (Future task: tasks) { + while(true) { + try { + task.get(); + } catch (InterruptedException ex) { + continue; + } catch (ExecutionException ex) { + throw new RuntimeException(ex); + } + break; + } + } + + executor.shutdown(); } private static void disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, @@ -124,8 +149,11 @@ public class baksmali { File smaliParent = smaliFile.getParentFile(); if (!smaliParent.exists()) { if (!smaliParent.mkdirs()) { - System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class"); - return; + // check again, it's likely it was created in a different thread + if (!smaliParent.exists()) { + System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class"); + return; + } } } diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java index 9f0e043d..cb3c563e 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java @@ -68,6 +68,7 @@ public class baksmaliOptions { public InlineMethodResolver inlineResolver = null; public int registerInfo = 0; public ClassPath classPath = null; + public int jobs = -1; public SyntheticAccessorResolver syntheticAccessorResolver = null; diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java index e86b2162..1abcd56c 100644 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/baksmali/src/main/java/org/jf/baksmali/main.java @@ -193,6 +193,9 @@ public class main { case 'a': options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); break; + case 'j': + options.jobs = Integer.parseInt(commandLine.getOptionValue("j")); + break; case 'N': disassemble = false; break; @@ -219,6 +222,13 @@ public class main { return; } + if (options.jobs <= 0) { + options.jobs = Runtime.getRuntime().availableProcessors(); + if (options.jobs > 6) { + options.jobs = 6; + } + } + String inputDexFileName = remainingArgs[0]; File dexFileFile = new File(inputDexFileName); @@ -375,6 +385,13 @@ public class main { .withArgName("API_LEVEL") .create("a"); + Option jobsOption = OptionBuilder.withLongOpt("jobs") + .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " + + "maximum of 6") + .hasArg() + .withArgName("NUM_THREADS") + .create("j"); + Option dumpOption = OptionBuilder.withLongOpt("dump-to") .withDescription("dumps the given dex file into a single annotated dump file named FILE" + " (.dump by default), along with the normal disassembly") @@ -418,6 +435,7 @@ public class main { basicOptions.addOption(codeOffsetOption); basicOptions.addOption(noAccessorCommentsOption); basicOptions.addOption(apiLevelOption); + basicOptions.addOption(jobsOption); debugOptions.addOption(dumpOption); debugOptions.addOption(ignoreErrorsOption); diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java index f51dbe96..7cd72b0c 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java @@ -31,6 +31,9 @@ package org.jf.dexlib2.analysis; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -53,10 +56,8 @@ import java.util.regex.Pattern; public class ClassPath { @Nonnull private final TypeProto unknownClass; - @Nonnull private DexFile[] dexFiles; - @Nonnull private HashMap loadedClasses = Maps.newHashMap(); @Nonnull private HashMap availableClasses = Maps.newHashMap(); - @Nonnull private int api; + private int api; /** * Creates a new ClassPath instance that can load classes from the given dex files @@ -78,6 +79,7 @@ public class ClassPath { } private ClassPath(@Nonnull DexFile[] classPath, boolean copyArray, int api) { + DexFile[] dexFiles; if (copyArray) { dexFiles = new DexFile[classPath.length+1]; System.arraycopy(classPath, 0, dexFiles, 0, classPath.length); @@ -128,23 +130,21 @@ public class ClassPath { @Nonnull public TypeProto getClass(CharSequence type) { - String typeString = type.toString(); - TypeProto typeProto = loadedClasses.get(typeString); - if (typeProto != null) { - return typeProto; - } - - if (type.charAt(0) == '[') { - typeProto = new ArrayProto(this, typeString); - } else { - typeProto = new ClassProto(this, typeString); - } - // All primitive types are preloaded into loadedClasses, so we don't need to check for that here - - loadedClasses.put(typeString, typeProto); - return typeProto; + return loadedClasses.getUnchecked(type.toString()); } + private final CacheLoader classLoader = new CacheLoader() { + @Override public TypeProto load(String type) throws Exception { + if (type.charAt(0) == '[') { + return new ArrayProto(ClassPath.this, type); + } else { + return new ClassProto(ClassPath.this, type); + } + } + }; + + @Nonnull private LoadingCache loadedClasses = CacheBuilder.newBuilder().build(classLoader); + @Nonnull public ClassDef getClassDef(String type) { ClassDef ret = availableClasses.get(type); diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java index 06e59977..39953c42 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -32,7 +32,12 @@ package org.jf.dexlib2.analysis; import com.google.common.base.Predicates; -import com.google.common.collect.*; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.analysis.util.TypeProtoUtils; import org.jf.dexlib2.iface.ClassDef; @@ -57,10 +62,7 @@ import java.util.List; public class ClassProto implements TypeProto { @Nonnull protected final ClassPath classPath; @Nonnull protected final String type; - @Nullable protected ClassDef classDef; - @Nullable protected LinkedHashMap interfaces; - @Nullable protected List vtable; - @Nullable protected SparseArray instanceFields; + protected boolean vtableFullyResolved = true; protected boolean interfacesFullyResolved = true; @@ -78,27 +80,15 @@ public class ClassProto implements TypeProto { @Nonnull public ClassDef getClassDef() { - if (classDef == null) { - classDef = classPath.getClassDef(type); - } - return classDef; + return classDefSupplier.get(); } - @Nonnull - List getVtable() { - if (vtable == null) { - loadVtable(); - } - return vtable; - } - @Nonnull - SparseArray getInstanceFields() { - if (instanceFields == null) { - instanceFields = loadFields(); + @Nonnull private final Supplier classDefSupplier = Suppliers.memoize(new Supplier() { + @Override public ClassDef get() { + return classPath.getClassDef(type); } - return instanceFields; - } + }); /** * Returns true if this class is an interface. @@ -128,81 +118,88 @@ public class ClassProto implements TypeProto { */ @Nonnull protected LinkedHashMap getInterfaces() { - if (interfaces != null) { - return interfaces; - } - - interfaces = Maps.newLinkedHashMap(); - - try { - for (String interfaceType: getClassDef().getInterfaces()) { - if (!interfaces.containsKey(interfaceType)) { - ClassDef interfaceDef; - try { - interfaceDef = classPath.getClassDef(interfaceType); - interfaces.put(interfaceType, interfaceDef); - } catch (UnresolvedClassException ex) { - interfaces.put(interfaceType, null); - interfacesFullyResolved = false; - } - - ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType); - for (String superInterface: interfaceProto.getInterfaces().keySet()) { - if (!interfaces.containsKey(superInterface)) { - interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface)); - } - } - if (!interfaceProto.interfacesFullyResolved) { - interfacesFullyResolved = false; - } - } - } - } catch (UnresolvedClassException ex) { - interfacesFullyResolved = false; - } - - // now add self and super class interfaces, required for common super class lookup - // we don't really need ClassDef's for that, so let's just use null - - if (isInterface() && !interfaces.containsKey(getType())) { - interfaces.put(getType(), null); - } - - try { - String superclass = getSuperclass(); - if (superclass != null) { - ClassProto superclassProto = (ClassProto) classPath.getClass(superclass); - for (String superclassInterface: superclassProto.getInterfaces().keySet()) { - if (!interfaces.containsKey(superclassInterface)) { - interfaces.put(superclassInterface, null); - } - } - if (!superclassProto.interfacesFullyResolved) { - interfacesFullyResolved = false; - } - } - } catch (UnresolvedClassException ex) { - interfacesFullyResolved = false; - } - - return interfaces; + return interfacesSupplier.get(); } + @Nonnull + private final Supplier> interfacesSupplier = + Suppliers.memoize(new Supplier>() { + @Override public LinkedHashMap get() { + LinkedHashMap interfaces = Maps.newLinkedHashMap(); + + try { + for (String interfaceType: getClassDef().getInterfaces()) { + if (!interfaces.containsKey(interfaceType)) { + ClassDef interfaceDef; + try { + interfaceDef = classPath.getClassDef(interfaceType); + interfaces.put(interfaceType, interfaceDef); + } catch (UnresolvedClassException ex) { + interfaces.put(interfaceType, null); + interfacesFullyResolved = false; + } + + ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType); + for (String superInterface: interfaceProto.getInterfaces().keySet()) { + if (!interfaces.containsKey(superInterface)) { + interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface)); + } + } + if (!interfaceProto.interfacesFullyResolved) { + interfacesFullyResolved = false; + } + } + } + } catch (UnresolvedClassException ex) { + interfacesFullyResolved = false; + } + + // now add self and super class interfaces, required for common super class lookup + // we don't really need ClassDef's for that, so let's just use null + + if (isInterface() && !interfaces.containsKey(getType())) { + interfaces.put(getType(), null); + } + + try { + String superclass = getSuperclass(); + if (superclass != null) { + ClassProto superclassProto = (ClassProto) classPath.getClass(superclass); + for (String superclassInterface: superclassProto.getInterfaces().keySet()) { + if (!interfaces.containsKey(superclassInterface)) { + interfaces.put(superclassInterface, null); + } + } + if (!superclassProto.interfacesFullyResolved) { + interfacesFullyResolved = false; + } + } + } catch (UnresolvedClassException ex) { + interfacesFullyResolved = false; + } + + return interfaces; + } + }); + /** * Gets the interfaces directly implemented by this class, or the interfaces they transitively implement. * * This does not include any interfaces that are only implemented by a superclass * * @return An iterables of ClassDefs representing the directly or transitively implemented interfaces - * @throws UnresolvedClassException if any interface could not be resolved + * @throws UnresolvedClassException if interfaces could not be fully resolved */ @Nonnull protected Iterable getDirectInterfaces() { + Iterable directInterfaces = + FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull()); + if (!interfacesFullyResolved) { throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType()); } - return FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull()); + return directInterfaces; } /** @@ -213,6 +210,8 @@ public class ClassProto implements TypeProto { * * @param iface The interface to check for * @return true if this class implements the given interface, otherwise false + * @throws UnresolvedClassException if the interfaces for this class could not be fully resolved, and the interface + * is not one of the interfaces that were successfully resolved */ @Override public boolean implementsInterface(@Nonnull String iface) { @@ -354,184 +353,193 @@ public class ClassProto implements TypeProto { return vtable.get(vtableIndex); } - @Nonnull - private SparseArray loadFields() { - //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; - final byte OTHER = 2; - - ArrayList fields = getSortedInstanceFields(getClassDef()); - final int fieldCount = fields.size(); - //the "type" for each field in fields. 0=reference,1=wide,2=other - byte[] fieldTypes = new byte[fields.size()]; - for (int i=0; i front) { - if (fieldTypes[back] == REFERENCE) { - swap(fieldTypes, fields, front, back--); - break; - } - back--; - } - } - - if (fieldTypes[front] != REFERENCE) { - break; - } - } - - int startFieldOffset = 8; - String superclassType = getSuperclass(); - ClassProto superclass = null; - if (superclassType != null) { - superclass = (ClassProto) classPath.getClass(superclassType); - if (superclass != null) { - startFieldOffset = 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 < fieldCount && (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 = fieldCount - 1; - while (back > front) { - if (fieldTypes[back] == OTHER) { - swap(fieldTypes, fields, front++, back); - break; - } - back--; - } - } else { - //there's already a 32-bit field here that we can use - front++; - } - } - - //do the swap thing for wide fields - back = fieldCount - 1; - for (; front front) { - if (fieldTypes[back] == WIDE) { - swap(fieldTypes, fields, front, back--); - break; - } - back--; - } - } - - if (fieldTypes[front] != WIDE) { - break; - } - } - - int superFieldCount = 0; - if (superclass != null) { - superFieldCount = superclass.instanceFields.size(); - } - - //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets - int totalFieldCount = superFieldCount + fieldCount; - SparseArray instanceFields = new SparseArray(totalFieldCount); - - int fieldOffset; - - if (superclass != null && superFieldCount > 0) { - for (int i=0; i getInstanceFields() { + return instanceFieldsSupplier.get(); } - @Nonnull - private static ArrayList getSortedInstanceFields(@Nonnull ClassDef classDef) { - ArrayList fields = Lists.newArrayList(classDef.getInstanceFields()); - Collections.sort(fields); - return fields; - } + @Nonnull private final Supplier> instanceFieldsSupplier = + Suppliers.memoize(new Supplier>() { + @Override public SparseArray get() { + //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() - private byte getFieldType(@Nonnull FieldReference field) { - switch (field.getType().charAt(0)) { - case '[': - case 'L': - return 0; //REFERENCE - case 'J': - case 'D': - return 1; //WIDE - default: - return 2; //OTHER - } - } + final byte REFERENCE = 0; + final byte WIDE = 1; + final byte OTHER = 2; - private void swap(byte[] fieldTypes, List fields, int position1, int position2) { - byte tempType = fieldTypes[position1]; - fieldTypes[position1] = fieldTypes[position2]; - fieldTypes[position2] = tempType; + ArrayList fields = getSortedInstanceFields(getClassDef()); + final int fieldCount = fields.size(); + //the "type" for each field in fields. 0=reference,1=wide,2=other + byte[] fieldTypes = new byte[fields.size()]; + for (int i=0; i front) { + if (fieldTypes[back] == REFERENCE) { + swap(fieldTypes, fields, front, back--); + break; + } + back--; + } + } + + if (fieldTypes[front] != REFERENCE) { + break; + } + } + + int startFieldOffset = 8; + String superclassType = getSuperclass(); + ClassProto superclass = null; + if (superclassType != null) { + superclass = (ClassProto) classPath.getClass(superclassType); + if (superclass != null) { + startFieldOffset = 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 < fieldCount && (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 = fieldCount - 1; + while (back > front) { + if (fieldTypes[back] == OTHER) { + swap(fieldTypes, fields, front++, back); + break; + } + back--; + } + } else { + //there's already a 32-bit field here that we can use + front++; + } + } + + //do the swap thing for wide fields + back = fieldCount - 1; + for (; front front) { + if (fieldTypes[back] == WIDE) { + swap(fieldTypes, fields, front, back--); + break; + } + back--; + } + } + + if (fieldTypes[front] != WIDE) { + break; + } + } + + SparseArray superFields; + if (superclass != null) { + superFields = superclass.getInstanceFields(); + } else { + superFields = new SparseArray(); + } + int superFieldCount = superFields.size(); + + //now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets + int totalFieldCount = superFieldCount + fieldCount; + SparseArray instanceFields = new SparseArray(totalFieldCount); + + int fieldOffset; + + if (superclass != null && superFieldCount > 0) { + for (int i=0; i getSortedInstanceFields(@Nonnull ClassDef classDef) { + ArrayList fields = Lists.newArrayList(classDef.getInstanceFields()); + Collections.sort(fields); + return fields; + } + + private byte getFieldType(@Nonnull FieldReference field) { + switch (field.getType().charAt(0)) { + case '[': + case 'L': + return 0; //REFERENCE + case 'J': + case 'D': + return 1; //WIDE + default: + return 2; //OTHER + } + } + + private void swap(byte[] fieldTypes, List fields, int position1, int position2) { + byte tempType = fieldTypes[position1]; + fieldTypes[position1] = fieldTypes[position2]; + fieldTypes[position2] = tempType; + + Field tempField = fields.set(position1, fields.get(position2)); + fields.set(position2, tempField); + } + }); private int getNextFieldOffset() { SparseArray instanceFields = getInstanceFields(); @@ -552,93 +560,100 @@ public class ClassProto implements TypeProto { } } - //TODO: check the case when we have a package private method that overrides an interface method - private void loadVtable() { - vtable = Lists.newArrayList(); - - //copy the virtual methods from the superclass - String superclassType; - try { - superclassType = getSuperclass(); - } catch (UnresolvedClassException ex) { - vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable()); - vtableFullyResolved = false; - return; - } - - if (superclassType != null) { - ClassProto superclass = (ClassProto) classPath.getClass(superclassType); - vtable.addAll(superclass.getVtable()); - - // if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this - // class should start, so we just propagate what we can from the parent and hope for the best. - if (!superclass.interfacesFullyResolved) { - vtableFullyResolved = false; - return; - } - } - - //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 (!isInterface()) { - addToVtable(getClassDef().getVirtualMethods(), vtable, true); - - for (ClassDef interfaceDef: getDirectInterfaces()) { - addToVtable(interfaceDef.getVirtualMethods(), vtable, false); - } - } + @Nonnull List getVtable() { + return vtableSupplier.get(); } - private void addToVtable(@Nonnull Iterable localMethods, - @Nonnull List vtable, boolean replaceExisting) { - List methods = Lists.newArrayList(localMethods); - Collections.sort(methods); + //TODO: check the case when we have a package private method that overrides an interface method + @Nonnull private final Supplier> vtableSupplier = Suppliers.memoize(new Supplier>() { + @Override public List get() { + List vtable = Lists.newArrayList(); - outer: for (Method virtualMethod: methods) { - for (int i=0; i localMethods, + @Nonnull List vtable, boolean replaceExisting) { + List methods = Lists.newArrayList(localMethods); + Collections.sort(methods); - @Nonnull - private static String getPackage(@Nonnull String classType) { - int lastSlash = classType.lastIndexOf('/'); - if (lastSlash < 0) { - return ""; + outer: for (Method virtualMethod: methods) { + for (int i=0; i