diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index 0be61b1e..bbd52055 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -34,6 +34,7 @@ import com.google.common.collect.Iterables; import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.CustomInlineMethodResolver; +import org.jf.dexlib2.analysis.InlineMethodResolver; import org.jf.dexlib2.dexbacked.DexBackedOdexFile; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; @@ -91,6 +92,8 @@ public class baksmali { if (inlineTable != null) { options.inlineResolver = new CustomInlineMethodResolver(options.classPath, new File(inlineTable)); + } else if (dexFile instanceof DexBackedOdexFile) { + options.inlineResolver = InlineMethodResolver.createInlineMethodResolver(((DexBackedOdexFile) dexFile).getOdexVersion()); } } catch (Exception ex) { System.err.println("\n\nError occured while loading boot class path files. Aborting."); 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 9fdefe75..a1a93d6c 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 @@ -724,6 +724,14 @@ public class ClassPath { return superclass; } + VirtualMethod[] getVtable() { + return vtable; + } + + SparseArray getInstanceFields() { + return instanceFields; + } + public int getClassDepth() { return classDepth; } @@ -1207,7 +1215,7 @@ public class ClassPath { } } - private static class VirtualMethod { + static class VirtualMethod { public String containingClass; public String method; public boolean isPackagePrivate; diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpFields.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpFields.java new file mode 100644 index 00000000..3f6ccf23 --- /dev/null +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpFields.java @@ -0,0 +1,160 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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 com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import org.apache.commons.cli.*; +import org.jf.dexlib.ClassDefItem; +import org.jf.dexlib.DexFile; +import org.jf.dexlib.Util.SparseArray; +import org.jf.util.ConsoleUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + +public class DumpFields { + private static final Options options; + + static { + options = new Options(); + buildOptions(); + } + + public static void main(String[] args) { + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; + + try { + commandLine = parser.parse(options, args); + } catch (ParseException ex) { + usage(); + return; + } + + String[] remainingArgs = commandLine.getArgs(); + + Option[] parsedOptions = commandLine.getOptions(); + ArrayList bootClassPathDirs = Lists.newArrayList(); + String outFile = "fields.txt"; + + for (int i=0; i fields = classDef.getInstanceFields(); + String className = "Class " + classDef.getClassType() + " : " + fields.size() + " instance fields\n"; + outStream.write(className.getBytes()); + for (int i=0;i"); + } + + private static void buildOptions() { + 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("d"); + + Option outputFileOption = OptionBuilder.withLongOpt("out-file") + .withDescription("output file") + .hasArg() + .withArgName("FILE") + .create("o"); + + options.addOption(classPathDirOption); + options.addOption(outputFileOption); + } +} diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpVtables.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpVtables.java new file mode 100644 index 00000000..5aab9edc --- /dev/null +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpVtables.java @@ -0,0 +1,159 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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 com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import org.apache.commons.cli.*; +import org.jf.dexlib.ClassDefItem; +import org.jf.dexlib.DexFile; +import org.jf.util.ConsoleUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + +public class DumpVtables { + private static final Options options; + + static { + options = new Options(); + buildOptions(); + } + + public static void main(String[] args) { + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; + + try { + commandLine = parser.parse(options, args); + } catch (ParseException ex) { + usage(); + return; + } + + String[] remainingArgs = commandLine.getArgs(); + + Option[] parsedOptions = commandLine.getOptions(); + ArrayList bootClassPathDirs = Lists.newArrayList(); + String outFile = "vtables.txt"; + + for (int i=0; i" + methods[i].method + "\n"; + outStream.write(method.getBytes()); + } + outStream.write("\n".getBytes()); + } + outStream.close(); + } catch (IOException ex) { + System.out.println("IOException thrown when trying to open a dex file or write out vtables: " + ex); + } + + } + + /** + * Prints the usage message. + */ + private static void usage() { + int consoleWidth = ConsoleUtil.getConsoleWidth(); + if (consoleWidth <= 0) { + consoleWidth = 80; + } + + System.out.println("java -cp baksmali.jar org.jf.dexlib.Code.Analysis.DumpVtables -d path/to/jar/files "); + } + + private static void buildOptions() { + 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("d"); + + Option outputFileOption = OptionBuilder.withLongOpt("out-file") + .withDescription("output file") + .hasArg() + .withArgName("FILE") + .create("o"); + + options.addOption(classPathDirOption); + options.addOption(outputFileOption); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java index 6172a5c9..8fcfc8c5 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ArrayProto.java @@ -34,6 +34,7 @@ package org.jf.dexlib2.analysis; import com.google.common.base.Strings; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.immutable.reference.ImmutableFieldReference; import org.jf.dexlib2.util.TypeUtils; import org.jf.util.ExceptionWithContext; @@ -151,12 +152,15 @@ public class ArrayProto implements TypeProto { @Override @Nullable public FieldReference getFieldByOffset(int fieldOffset) { - return classPath.getClass("Ljava/lang/Array;").getFieldByOffset(fieldOffset); + if (fieldOffset==8) { + return new ImmutableFieldReference(getType(), "length", "int"); + } + return null; } @Override @Nullable public MethodReference getMethodByVtableIndex(int vtableIndex) { - return classPath.getClass("Ljava/lang/Array;").getMethodByVtableIndex(vtableIndex); + return classPath.getClass("Ljava/lang/Object;").getMethodByVtableIndex(vtableIndex); } } 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 0775032f..f51dbe96 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java @@ -56,6 +56,7 @@ public class ClassPath { @Nonnull private DexFile[] dexFiles; @Nonnull private HashMap loadedClasses = Maps.newHashMap(); @Nonnull private HashMap availableClasses = Maps.newHashMap(); + @Nonnull private int api; /** * Creates a new ClassPath instance that can load classes from the given dex files @@ -63,19 +64,20 @@ public class ClassPath { * @param classPath An array of DexFile objects. When loading a class, these dex files will be searched in order */ public ClassPath(DexFile... classPath) throws IOException { - this(classPath, true); + this(classPath, true, 15); } /** * Creates a new ClassPath instance that can load classes from the given dex files * * @param classPath An iterable of DexFile objects. When loading a class, these dex files will be searched in order + * @param api API level */ - public ClassPath(Iterable classPath) { - this(Iterables.toArray(classPath, DexFile.class), false); + public ClassPath(Iterable classPath, int api) { + this(Iterables.toArray(classPath, DexFile.class), false, api); } - private ClassPath(@Nonnull DexFile[] classPath, boolean copyArray) { + private ClassPath(@Nonnull DexFile[] classPath, boolean copyArray, int api) { if (copyArray) { dexFiles = new DexFile[classPath.length+1]; System.arraycopy(classPath, 0, dexFiles, 0, classPath.length); @@ -87,6 +89,7 @@ public class ClassPath { unknownClass = new UnknownClassProto(this); loadedClasses.put(unknownClass.getType(), unknownClass); + this.api = api; loadPrimitiveType("Z"); loadPrimitiveType("B"); @@ -156,6 +159,10 @@ public class ClassPath { return unknownClass; } + public int getApi() { + return api; + } + @Nonnull public static ClassPath fromClassPath(Iterable classPathDirs, Iterable classPath, DexFile dexFile, int api) { @@ -165,7 +172,7 @@ public class ClassPath { dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api)); } dexFiles.add(dexFile); - return new ClassPath(dexFiles); + return new ClassPath(dexFiles, api); } private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$"); 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 cb3dca38..e04f0bfc 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -31,20 +31,26 @@ package org.jf.dexlib2.analysis; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; +import com.google.common.collect.Maps; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.analysis.util.TypeProtoUtils; import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Field; +import org.jf.dexlib2.iface.Method; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.util.FieldUtil; import org.jf.util.ExceptionWithContext; +import org.jf.util.SparseArray; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; -import java.util.Set; +import java.util.*; /** * A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields @@ -54,7 +60,9 @@ public class ClassProto implements TypeProto { @Nonnull protected final ClassPath classPath; @Nonnull protected final String type; @Nullable protected ClassDef classDef; - @Nullable protected Set interfaces; + @Nullable protected LinkedHashMap interfaces; + @Nullable protected Method[] vtable; + @Nullable protected SparseArray instanceFields; protected boolean interfacesFullyResolved = true; public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) { @@ -77,6 +85,22 @@ public class ClassProto implements TypeProto { return classDef; } + @Nonnull + Method[] getVtable() { + if (vtable == null) { + vtable = loadVtable(); + } + return vtable; + } + + @Nonnull + SparseArray getInstanceFields() { + if (instanceFields == null) { + instanceFields = loadFields(); + } + return instanceFields; + } + /** * Returns true if this class is an interface. * @@ -89,47 +113,59 @@ public class ClassProto implements TypeProto { return (classDef.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0; } - private void addInterfacesRecursively(@Nonnull ClassDef classDef) { - assert interfaces != null; - for (String iface: classDef.getInterfaces()) { - interfaces.add(iface); - addInterfacesRecursively(iface); - } - } - - private void addInterfacesRecursively(@Nonnull String cls) { - ClassDef classDef; - try { - classDef = classPath.getClassDef(cls); - addInterfacesRecursively(classDef); - } catch (UnresolvedClassException ex) { - interfacesFullyResolved = false; - } - } - @Nonnull - protected Set getInterfaces() { + protected LinkedHashMap getInterfaces() { if (interfaces != null) { return interfaces; } - interfaces = Sets.newHashSet(); + interfaces = Maps.newLinkedHashMap(); try { - ClassDef classDef = getClassDef(); + 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; + } - if (isInterface()) { - interfaces.add(getType()); + 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; + } - while (true) { - addInterfacesRecursively(classDef); + // 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 - String superclass = classDef.getSuperclass(); - if (superclass != null) { - classDef = classPath.getClassDef(superclass); - } else { - break; + 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) { @@ -139,6 +175,15 @@ public class ClassProto implements TypeProto { return interfaces; } + @Nonnull + protected Iterable getDirectInterfaces() { + if (!interfacesFullyResolved) { + throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType()); + } + + return FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull()); + } + /** * Checks if this class implements the given interface. * @@ -150,10 +195,8 @@ public class ClassProto implements TypeProto { */ @Override public boolean implementsInterface(@Nonnull String iface) { - for (String implementIface: getInterfaces()) { - if (implementIface.equals(iface)) { - return true; - } + if (getInterfaces().containsKey(iface)) { + return true; } if (!interfacesFullyResolved) { throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType()); @@ -274,14 +317,306 @@ public class ClassProto implements TypeProto { @Override @Nullable public FieldReference getFieldByOffset(int fieldOffset) { - // TODO: implement this - return null; + if (getInstanceFields().size() == 0) { + return null; + } + return getInstanceFields().get(fieldOffset); } @Override @Nullable public MethodReference getMethodByVtableIndex(int vtableIndex) { - // TODO: implement this - return null; + if (vtableIndex < 0 || vtableIndex >= getVtable().length) { + return null; + } + return getVtable()[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 loadedFields = getInstanceFields(getClassDef()); + Field[] fields = new Field[loadedFields.size()]; + //the "type" for each field in fields. 0=reference,1=wide,2=other + byte[] fieldTypes = new byte[fields.length]; + 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 < 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; + 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 = fields.length - 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 + fields.length; + SparseArray instanceFields = new SparseArray(totalFieldCount); + + int fieldOffset; + + if (superclass != null && superFieldCount > 0) { + for (int i=0; i getInstanceFields(@Nonnull ClassDef classDef) { + ArrayList instanceFields = Lists.newArrayList(); + for (Field field: classDef.getInstanceFields()) { + instanceFields.add(field); + } + return instanceFields; + } + + private byte getFieldType(String fieldType) { + switch (fieldType.charAt(0)) { + case '[': + case 'L': + return 0; //REFERENCE + case 'J': + case 'D': + return 1; //WIDE + default: + return 2; //OTHER + } + } + + private void swap(byte[] fieldTypes, FieldReference[] fields, int position1, int position2) { + byte tempType = fieldTypes[position1]; + fieldTypes[position1] = fieldTypes[position2]; + fieldTypes[position2] = tempType; + + FieldReference tempField = fields[position1]; + fields[position1] = fields[position2]; + fields[position2] = tempField; + } + + private int getNextFieldOffset() { + SparseArray instanceFields = getInstanceFields(); + if (instanceFields.size() == 0) { + return 8; + } + + int lastItemIndex = instanceFields.size()-1; + int fieldOffset = instanceFields.keyAt(lastItemIndex); + FieldReference lastField = instanceFields.valueAt(lastItemIndex); + + switch (lastField.getType().charAt(0)) { + case 'J': + case 'D': + return fieldOffset + 8; + default: + return fieldOffset + 4; + } + } + + //TODO: check the case when we have a package private method that overrides an interface method + @Nonnull + private Method[] loadVtable() { + //TODO: it might be useful to keep track of which class's implementation is used for each virtual method. In other words, associate the implementing class type with each vtable entry + List virtualMethodList = Lists.newLinkedList(); + + //copy the virtual methods from the superclass + String superclassType = getSuperclass(); + if (superclassType != null) { + ClassProto superclass = (ClassProto) classPath.getClass(superclassType); + for (int i=0; i localMethods, @Nonnull List vtable) { + List methods = Lists.newArrayList(localMethods); + Collections.sort(methods); + + for (Method virtualMethod: methods) { + boolean found = false; + for (int i=0; i bootClassPathDirs = Lists.newArrayList(); + String outFile = "fields.txt"; + int apiLevel = 15; + + for (int i=0; i bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"); + ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel); + FileOutputStream outStream = new FileOutputStream(outFile); + + for (ClassDef classDef: dexFile.getClasses()) { + ClassProto classProto = (ClassProto) classPath.getClass(classDef); + SparseArray fields = classProto.getInstanceFields(); + String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n"; + outStream.write(className.getBytes()); + for (int i=0;i"); + } + + private static void buildOptions() { + 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("d"); + + Option outputFileOption = OptionBuilder.withLongOpt("out-file") + .withDescription("output file") + .hasArg() + .withArgName("FILE") + .create("o"); + + Option apiLevelOption = OptionBuilder.withLongOpt("api-level") + .withDescription("The numeric api-level of the file being disassembled. If not " + + "specified, it defaults to 15 (ICS).") + .hasArg() + .withArgName("API_LEVEL") + .create("a"); + + options.addOption(classPathDirOption); + options.addOption(outputFileOption); + options.addOption(apiLevelOption); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java new file mode 100644 index 00000000..e4880a5a --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java @@ -0,0 +1,172 @@ +/* + * Copyright 2013, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.dexlib2.analysis; + +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import org.apache.commons.cli.*; +import org.jf.dexlib2.DexFileFactory; +import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Method; +import org.jf.util.ConsoleUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class DumpVtables { + private static final Options options; + + static { + options = new Options(); + buildOptions(); + } + + public static void main(String[] args) { + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; + + try { + commandLine = parser.parse(options, args); + } catch (ParseException ex) { + usage(); + return; + } + + String[] remainingArgs = commandLine.getArgs(); + + Option[] parsedOptions = commandLine.getOptions(); + ArrayList bootClassPathDirs = Lists.newArrayList(); + String outFile = "vtables.txt"; + int apiLevel = 15; + + for (int i=0; i bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"); + ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel); + FileOutputStream outStream = new FileOutputStream(outFile); + + for (ClassDef classDef: dexFile.getClasses()) { + ClassProto classProto = (ClassProto) classPath.getClass(classDef); + Method[] methods = classProto.getVtable(); + String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.length + " methods\n"; + outStream.write(className.getBytes()); + for (int i=0;i" + methods[i].getName() + "("; + for (CharSequence parameter: methods[i].getParameterTypes()) { + method += parameter; + } + method += ")" + methods[i].getReturnType() + "\n"; + outStream.write(method.getBytes()); + } + outStream.write("\n".getBytes()); + } + outStream.close(); + } catch (IOException ex) { + System.out.println("IOException thrown when trying to open a dex file or write out vtables: " + ex); + } + + } + + /** + * Prints the usage message. + */ + private static void usage() { + int consoleWidth = ConsoleUtil.getConsoleWidth(); + if (consoleWidth <= 0) { + consoleWidth = 80; + } + + System.out.println("java -cp baksmali.jar org.jf.dexlib2.analysis.DumpVtables -d path/to/framework/jar/files "); + } + + private static void buildOptions() { + 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("d"); + + Option outputFileOption = OptionBuilder.withLongOpt("out-file") + .withDescription("output file") + .hasArg() + .withArgName("FILE") + .create("o"); + + Option apiLevelOption = OptionBuilder.withLongOpt("api-level") + .withDescription("The numeric api-level of the file being disassembled. If not " + + "specified, it defaults to 15 (ICS).") + .hasArg() + .withArgName("API_LEVEL") + .create("a"); + + options.addOption(classPathDirOption); + options.addOption(outputFileOption); + options.addOption(apiLevelOption); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java index 921a410f..30cdff30 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java @@ -1545,7 +1545,7 @@ public class MethodAnalyzer { } else { Instruction35ms instruction = (Instruction35ms)analyzedInstruction.instruction; methodIndex = instruction.getVtableIndex(); - objectRegister = instruction.getRegisterD(); + objectRegister = instruction.getRegisterC(); } RegisterType objectRegisterType = getAndCheckSourceRegister(analyzedInstruction, objectRegister, @@ -1590,8 +1590,8 @@ public class MethodAnalyzer { opcode = Opcode.INVOKE_VIRTUAL_RANGE; } - deodexedInstruction = new ImmutableInstruction3rc(opcode, instruction.getRegisterCount(), - instruction.getStartRegister(), resolvedMethod); + deodexedInstruction = new ImmutableInstruction3rc(opcode, instruction.getStartRegister(), + instruction.getRegisterCount(), resolvedMethod); } else { Instruction35ms instruction = (Instruction35ms)analyzedInstruction.instruction; Opcode opcode; diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java index 61561695..1aa9c1eb 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java @@ -33,6 +33,7 @@ package org.jf.dexlib2.dexbacked; import com.google.common.io.ByteStreams; import org.jf.dexlib2.Opcodes; +import org.jf.dexlib2.dexbacked.raw.HeaderItem; import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem; import org.jf.dexlib2.dexbacked.util.VariableSizeList; @@ -121,6 +122,10 @@ public class DexBackedOdexFile extends DexBackedDexFile { } } + public int getOdexVersion() { + return OdexHeaderItem.getVersion(odexBuf); + } + public static class NotAnOdexFile extends RuntimeException { public NotAnOdexFile() { } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java index 4e91736f..c6599bc4 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/OdexHeaderItem.java @@ -51,7 +51,7 @@ public class OdexHeaderItem { public static final int AUX_LENGTH_OFFSET = 28; public static final int FLAGS_OFFSET = 32; - private static int getVersion(byte[] magic) { + public static int getVersion(byte[] magic) { if (magic.length < 8) { return 0; }