Merge remote-tracking branch 'izzy_github/deodex2' into dexlib_redesign

Conflicts:
	dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java
This commit is contained in:
Ben Gruver 2013-05-06 21:13:55 -07:00
commit 5b69a5f3a5
12 changed files with 1077 additions and 54 deletions

View File

@ -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.");

View File

@ -724,6 +724,14 @@ public class ClassPath {
return superclass;
}
VirtualMethod[] getVtable() {
return vtable;
}
SparseArray<FieldDef> 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;

View File

@ -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<String> bootClassPathDirs = Lists.newArrayList();
String outFile = "fields.txt";
for (int i=0; i<parsedOptions.length; i++) {
Option option = parsedOptions[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'd':
bootClassPathDirs.add(option.getValue());
break;
case 'o':
outFile = option.getValue();
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
String inputDexFileName = remainingArgs[0];
File dexFileFile = new File(inputDexFileName);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.exit(1);
}
try {
DexFile dexFile = new DexFile(dexFileFile);
String[] bootClassPaths = new String[bootClassPathDirs.size()];
int j = 0;
for (String bootClassPathDir: bootClassPathDirs) {
bootClassPaths[j++] = bootClassPathDir;
}
ClassPath.InitializeClassPathFromOdex(bootClassPaths, null, inputDexFileName, dexFile, false);
FileOutputStream outStream = new FileOutputStream(outFile);
for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) {
ClassPath.ClassDef classDef = ClassPath.getClassDef(classDefItem.getClassType());
SparseArray<ClassPath.FieldDef> fields = classDef.getInstanceFields();
String className = "Class " + classDef.getClassType() + " : " + fields.size() + " instance fields\n";
outStream.write(className.getBytes());
for (int i=0;i<fields.size();i++) {
String field = fields.keyAt(i) + ":" + fields.valueAt(i).type + " " + fields.valueAt(i).name + "\n";
outStream.write(field.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.DumpFields -d path/to/jar/files <dex-file>");
}
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);
}
}

View File

@ -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<String> bootClassPathDirs = Lists.newArrayList();
String outFile = "vtables.txt";
for (int i=0; i<parsedOptions.length; i++) {
Option option = parsedOptions[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'd':
bootClassPathDirs.add(option.getValue());
break;
case 'o':
outFile = option.getValue();
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
String inputDexFileName = remainingArgs[0];
File dexFileFile = new File(inputDexFileName);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.exit(1);
}
try {
DexFile dexFile = new DexFile(dexFileFile);
String[] bootClassPaths = new String[bootClassPathDirs.size()];
int j = 0;
for (String bootClassPathDir: bootClassPathDirs) {
bootClassPaths[j++] = bootClassPathDir;
}
ClassPath.InitializeClassPathFromOdex(bootClassPaths, null, inputDexFileName, dexFile, false);
FileOutputStream outStream = new FileOutputStream(outFile);
for (ClassDefItem classDefItem: dexFile.ClassDefsSection.getItems()) {
ClassPath.ClassDef classDef = ClassPath.getClassDef(classDefItem.getClassType());
ClassPath.VirtualMethod[] methods = classDef.getVtable();
String className = "Class " + classDef.getClassType() + " extends " + classDef.getSuperclass().getClassType() + " : " + methods.length + " methods\n";
outStream.write(className.getBytes());
for (int i=0;i<methods.length;i++) {
String method = i + ":" + methods[i].containingClass + "->" + 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 <dex-file>");
}
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);
}
}

View File

@ -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);
}
}

View File

@ -56,6 +56,7 @@ public class ClassPath {
@Nonnull private DexFile[] dexFiles;
@Nonnull private HashMap<String, TypeProto> loadedClasses = Maps.newHashMap();
@Nonnull private HashMap<String, ClassDef> 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<DexFile> classPath) {
this(Iterables.toArray(classPath, DexFile.class), false);
public ClassPath(Iterable<DexFile> 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<String> classPathDirs, Iterable<String> 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$");

View File

@ -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<String> interfaces;
@Nullable protected LinkedHashMap<String, ClassDef> interfaces;
@Nullable protected Method[] vtable;
@Nullable protected SparseArray<FieldReference> 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<FieldReference> 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<String> getInterfaces() {
protected LinkedHashMap<String, ClassDef> 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<ClassDef> 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<FieldReference> 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<Field> 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<fields.length;i++) {
fields[i] = loadedFields.get(i);
fieldTypes[i] = getFieldType(fields[i].getType());
}
//The first operation is to move all of the reference fields to the front. To do this, find the first
//non-reference field, then find the last reference field, swap them and repeat
int back = fields.length - 1;
int front;
for (front = 0; front<fields.length; front++) {
if (fieldTypes[front] != REFERENCE) {
while (back > 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<fields.length; front++) {
if (fieldTypes[front] != WIDE) {
while (back > 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<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
int fieldOffset;
if (superclass != null && superFieldCount > 0) {
for (int i=0; i<superFieldCount; i++) {
instanceFields.append(superclass.instanceFields.keyAt(i), superclass.instanceFields.valueAt(i));
}
fieldOffset = instanceFields.keyAt(superFieldCount-1);
FieldReference lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1);
char fieldType = lastSuperField.getType().charAt(0);
if (fieldType == 'J' || fieldType == 'D') {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
} else {
//the field values start at 8 bytes into the DataObject dalvik structure
fieldOffset = 8;
}
boolean gotDouble = false;
for (int i=0; i<fields.length; i++) {
FieldReference field = fields[i];
//add padding to align the wide fields, if needed
if (fieldTypes[i] == WIDE && !gotDouble) {
if (!gotDouble) {
if (fieldOffset % 8 != 0) {
assert fieldOffset % 8 == 4;
fieldOffset += 4;
}
gotDouble = true;
}
}
instanceFields.append(fieldOffset, field);
if (fieldTypes[i] == WIDE) {
fieldOffset += 8;
} else {
fieldOffset += 4;
}
}
return instanceFields;
}
@Nonnull
private ArrayList<Field> getInstanceFields(@Nonnull ClassDef classDef) {
ArrayList<Field> 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<FieldReference> 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<Method> 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<superclass.getVtable().length; i++) {
virtualMethodList.add(superclass.getVtable()[i]);
}
}
//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(), virtualMethodList);
for (ClassDef interfaceDef: getDirectInterfaces()) {
addToVtable(interfaceDef.getVirtualMethods(), virtualMethodList);
}
}
Method[] vtable = new Method[virtualMethodList.size()];
for (int i=0; i<virtualMethodList.size(); i++) {
vtable[i] = virtualMethodList.get(i);
}
return vtable;
}
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods, @Nonnull List<Method> vtable) {
List<? extends Method> methods = Lists.newArrayList(localMethods);
Collections.sort(methods);
for (Method virtualMethod: methods) {
boolean found = false;
for (int i=0; i<vtable.size(); i++) {
Method superMethod = vtable.get(i);
if (methodSignaturesMatch(superMethod, virtualMethod)) {
if (classPath.getApi() < 17 || canAccess(superMethod)) {
found = true;
vtable.set(i, virtualMethod);
break;
}
}
}
if (!found) {
vtable.add(virtualMethod);
}
}
}
private boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) {
return (a.getName().equals(b.getName())
&& a.getReturnType().equals(b.getReturnType())
&& a.getParameters().equals(b.getParameters()));
}
private boolean canAccess(@Nonnull Method virtualMethod) {
if (!methodIsPackagePrivate(virtualMethod.getAccessFlags())) {
return true;
}
String otherPackage = getPackage(virtualMethod.getDefiningClass());
String ourPackage = getPackage(getClassDef().getType());
return otherPackage.equals(ourPackage);
}
@Nonnull
private String getPackage(@Nonnull String classType) {
int lastSlash = classType.lastIndexOf('/');
if (lastSlash < 0) {
return "";
}
return classType.substring(1, lastSlash);
}
private static boolean methodIsPackagePrivate(int accessFlags) {
return (accessFlags & (AccessFlags.PRIVATE.getValue() |
AccessFlags.PROTECTED.getValue() |
AccessFlags.PUBLIC.getValue())) == 0;
}
}

View File

@ -0,0 +1,170 @@
/*
* 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.Field;
import org.jf.dexlib2.iface.Method;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.util.ConsoleUtil;
import org.jf.util.SparseArray;
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<String> bootClassPathDirs = Lists.newArrayList();
String outFile = "fields.txt";
int apiLevel = 15;
for (int i=0; i<parsedOptions.length; i++) {
Option option = parsedOptions[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'd':
bootClassPathDirs.add(option.getValue());
break;
case 'o':
outFile = option.getValue();
break;
case 'a':
apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
String inputDexFileName = remainingArgs[0];
File dexFileFile = new File(inputDexFileName);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.exit(1);
}
try {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel);
Iterable<String> 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<FieldReference> fields = classProto.getInstanceFields();
String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n";
outStream.write(className.getBytes());
for (int i=0;i<fields.size();i++) {
String field = fields.keyAt(i) + ":" + fields.valueAt(i).getType() + " " + fields.valueAt(i).getName() + "\n";
outStream.write(field.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.DumpFields -d path/to/framework/jar/files <dex-file>");
}
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);
}
}

View File

@ -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<String> bootClassPathDirs = Lists.newArrayList();
String outFile = "vtables.txt";
int apiLevel = 15;
for (int i=0; i<parsedOptions.length; i++) {
Option option = parsedOptions[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'd':
bootClassPathDirs.add(option.getValue());
break;
case 'o':
outFile = option.getValue();
break;
case 'a':
apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
String inputDexFileName = remainingArgs[0];
File dexFileFile = new File(inputDexFileName);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.exit(1);
}
try {
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel);
Iterable<String> 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.length;i++) {
String method = i + ":" + methods[i].getDefiningClass() + "->" + 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 <dex-file>");
}
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);
}
}

View File

@ -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;

View File

@ -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() {
}

View File

@ -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;
}