From b2ce899471be1c136aa13d502e885585fa59d460 Mon Sep 17 00:00:00 2001 From: Izzat Bahadirov Date: Fri, 19 Apr 2013 00:29:30 -0400 Subject: [PATCH] Getting instance fields by offset and methods by vtable index. - Dump utilities --- .../jf/dexlib/Code/Analysis/ClassPath.java | 10 +- .../jf/dexlib/Code/Analysis/DumpFields.java | 160 ++++++++ .../jf/dexlib/Code/Analysis/DumpVtables.java | 159 ++++++++ .../org/jf/dexlib2/analysis/ArrayProto.java | 8 +- .../org/jf/dexlib2/analysis/ClassProto.java | 370 +++++++++++++++++- .../org/jf/dexlib2/analysis/DumpFields.java | 170 ++++++++ .../org/jf/dexlib2/analysis/DumpVtables.java | 172 ++++++++ .../dexlib2/dexbacked/raw/OdexHeaderItem.java | 2 +- 8 files changed, 1041 insertions(+), 10 deletions(-) create mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpFields.java create mode 100644 dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpVtables.java create mode 100644 dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java create mode 100644 dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java 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/ClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java index cb3dca38..1bc45166 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -33,18 +33,23 @@ package org.jf.dexlib2.analysis; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; 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.dexlib2.util.MethodUtil; 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 @@ -55,6 +60,9 @@ public class ClassProto implements TypeProto { @Nonnull protected final String type; @Nullable protected ClassDef classDef; @Nullable protected Set interfaces; + @Nullable protected LinkedHashMap interfaceTable; + @Nullable protected Method[] vtable; + @Nullable protected SparseArray instanceFields; protected boolean interfacesFullyResolved = true; public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) { @@ -77,6 +85,27 @@ public class ClassProto implements TypeProto { return classDef; } + Method[] getVtable() { + if (vtable == null) { + vtable = loadVtable(); + } + return vtable; + } + + SparseArray getInstanceFields() { + if (instanceFields == null) { + instanceFields = loadFields(); + } + return instanceFields; + } + + LinkedHashMap getInterfaceTable() { + if (interfaceTable == null) { + interfaceTable = loadInterfaceTable(); + } + return interfaceTable; + } + /** * Returns true if this class is an interface. * @@ -274,14 +303,343 @@ public class ClassProto implements TypeProto { @Override @Nullable public FieldReference getFieldByOffset(int fieldOffset) { - // TODO: implement this - return null; + if (getInstanceFields() == null) { + return null; + } + return getInstanceFields().get(fieldOffset); } @Override @Nullable public MethodReference getMethodByVtableIndex(int vtableIndex) { - // TODO: implement this - return null; + if (getVtable() == null + || vtableIndex < 0 + || vtableIndex >= getVtable().length) { + return null; + } + return getVtable()[vtableIndex]; + } + + 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(ClassDef classDef) { + ArrayList instanceFields = Lists.newArrayList(); + for (Field field: classDef.getFields()) { + if (!FieldUtil.isStatic(field)) { + 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 == null || 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 + 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 interfaceTable = loadInterfaceTable(); + if (interfaceTable != null) { + for (ClassDef interfaceDef: interfaceTable.values()) { + addToVtable(getVirtualMethods(interfaceDef), virtualMethodList); + } + } + } + + Method[] vtable = new Method[virtualMethodList.size()]; + for (int i=0; i loadInterfaceTable() { + LinkedHashMap interfaceTable = Maps.newLinkedHashMap(); + + for (String interfaceType: getClassDef().getInterfaces()) { + if (!interfaceTable.containsKey(interfaceType)) { + ClassDef interfaceDef; + try { + interfaceDef = classPath.getClassDef(interfaceType); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, + String.format("Could not find interface %s", interfaceType)); + } + interfaceTable.put(interfaceType, interfaceDef); + + ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType); + for (ClassDef superInterface: interfaceProto.getInterfaceTable().values()) { + if (!interfaceTable.containsKey(superInterface.getType())) { + interfaceTable.put(superInterface.getType(), superInterface); + } + } + } + } + + return interfaceTable; + } + + private LinkedList getVirtualMethods(ClassDef classDef) { + LinkedList virtualMethods = Lists.newLinkedList(); + for (Method method: classDef.getMethods()) { + if (!MethodUtil.isDirect(method)) { + virtualMethods.add(method); + } + } + + return virtualMethods; + } + + private void addToVtable(List localMethods, List vtable) { + for (Method virtualMethod: localMethods) { + 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..9f507c80 --- /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/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/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; }