mirror of
https://github.com/revanced/smali.git
synced 2025-05-09 10:54:29 +02:00
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:
commit
5b69a5f3a5
@ -34,6 +34,7 @@ import com.google.common.collect.Iterables;
|
|||||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||||
import org.jf.dexlib2.analysis.ClassPath;
|
import org.jf.dexlib2.analysis.ClassPath;
|
||||||
import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
|
import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
|
||||||
|
import org.jf.dexlib2.analysis.InlineMethodResolver;
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
|
||||||
import org.jf.dexlib2.iface.ClassDef;
|
import org.jf.dexlib2.iface.ClassDef;
|
||||||
import org.jf.dexlib2.iface.DexFile;
|
import org.jf.dexlib2.iface.DexFile;
|
||||||
@ -91,6 +92,8 @@ public class baksmali {
|
|||||||
|
|
||||||
if (inlineTable != null) {
|
if (inlineTable != null) {
|
||||||
options.inlineResolver = new CustomInlineMethodResolver(options.classPath, new File(inlineTable));
|
options.inlineResolver = new CustomInlineMethodResolver(options.classPath, new File(inlineTable));
|
||||||
|
} else if (dexFile instanceof DexBackedOdexFile) {
|
||||||
|
options.inlineResolver = InlineMethodResolver.createInlineMethodResolver(((DexBackedOdexFile) dexFile).getOdexVersion());
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
System.err.println("\n\nError occured while loading boot class path files. Aborting.");
|
System.err.println("\n\nError occured while loading boot class path files. Aborting.");
|
||||||
|
@ -724,6 +724,14 @@ public class ClassPath {
|
|||||||
return superclass;
|
return superclass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VirtualMethod[] getVtable() {
|
||||||
|
return vtable;
|
||||||
|
}
|
||||||
|
|
||||||
|
SparseArray<FieldDef> getInstanceFields() {
|
||||||
|
return instanceFields;
|
||||||
|
}
|
||||||
|
|
||||||
public int getClassDepth() {
|
public int getClassDepth() {
|
||||||
return classDepth;
|
return classDepth;
|
||||||
}
|
}
|
||||||
@ -1207,7 +1215,7 @@ public class ClassPath {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class VirtualMethod {
|
static class VirtualMethod {
|
||||||
public String containingClass;
|
public String containingClass;
|
||||||
public String method;
|
public String method;
|
||||||
public boolean isPackagePrivate;
|
public boolean isPackagePrivate;
|
||||||
|
160
dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpFields.java
Normal file
160
dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DumpFields.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,7 @@ package org.jf.dexlib2.analysis;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.jf.dexlib2.iface.reference.FieldReference;
|
import org.jf.dexlib2.iface.reference.FieldReference;
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||||
|
import org.jf.dexlib2.immutable.reference.ImmutableFieldReference;
|
||||||
import org.jf.dexlib2.util.TypeUtils;
|
import org.jf.dexlib2.util.TypeUtils;
|
||||||
import org.jf.util.ExceptionWithContext;
|
import org.jf.util.ExceptionWithContext;
|
||||||
|
|
||||||
@ -151,12 +152,15 @@ public class ArrayProto implements TypeProto {
|
|||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public FieldReference getFieldByOffset(int fieldOffset) {
|
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
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodReference getMethodByVtableIndex(int vtableIndex) {
|
public MethodReference getMethodByVtableIndex(int vtableIndex) {
|
||||||
return classPath.getClass("Ljava/lang/Array;").getMethodByVtableIndex(vtableIndex);
|
return classPath.getClass("Ljava/lang/Object;").getMethodByVtableIndex(vtableIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ public class ClassPath {
|
|||||||
@Nonnull private DexFile[] dexFiles;
|
@Nonnull private DexFile[] dexFiles;
|
||||||
@Nonnull private HashMap<String, TypeProto> loadedClasses = Maps.newHashMap();
|
@Nonnull private HashMap<String, TypeProto> loadedClasses = Maps.newHashMap();
|
||||||
@Nonnull private HashMap<String, ClassDef> availableClasses = 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
|
* 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
|
* @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 {
|
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
|
* 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 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) {
|
public ClassPath(Iterable<DexFile> classPath, int api) {
|
||||||
this(Iterables.toArray(classPath, DexFile.class), false);
|
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) {
|
if (copyArray) {
|
||||||
dexFiles = new DexFile[classPath.length+1];
|
dexFiles = new DexFile[classPath.length+1];
|
||||||
System.arraycopy(classPath, 0, dexFiles, 0, classPath.length);
|
System.arraycopy(classPath, 0, dexFiles, 0, classPath.length);
|
||||||
@ -87,6 +89,7 @@ public class ClassPath {
|
|||||||
|
|
||||||
unknownClass = new UnknownClassProto(this);
|
unknownClass = new UnknownClassProto(this);
|
||||||
loadedClasses.put(unknownClass.getType(), unknownClass);
|
loadedClasses.put(unknownClass.getType(), unknownClass);
|
||||||
|
this.api = api;
|
||||||
|
|
||||||
loadPrimitiveType("Z");
|
loadPrimitiveType("Z");
|
||||||
loadPrimitiveType("B");
|
loadPrimitiveType("B");
|
||||||
@ -156,6 +159,10 @@ public class ClassPath {
|
|||||||
return unknownClass;
|
return unknownClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getApi() {
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
|
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
|
||||||
int api) {
|
int api) {
|
||||||
@ -165,7 +172,7 @@ public class ClassPath {
|
|||||||
dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api));
|
dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api));
|
||||||
}
|
}
|
||||||
dexFiles.add(dexFile);
|
dexFiles.add(dexFile);
|
||||||
return new ClassPath(dexFiles);
|
return new ClassPath(dexFiles, api);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
|
private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
|
||||||
|
@ -31,20 +31,26 @@
|
|||||||
|
|
||||||
package org.jf.dexlib2.analysis;
|
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.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
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.AccessFlags;
|
||||||
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
|
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
|
||||||
import org.jf.dexlib2.iface.ClassDef;
|
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.FieldReference;
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||||
|
import org.jf.dexlib2.util.FieldUtil;
|
||||||
import org.jf.util.ExceptionWithContext;
|
import org.jf.util.ExceptionWithContext;
|
||||||
|
import org.jf.util.SparseArray;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class "prototype". This contains things like the interfaces, the superclass, the vtable and the instance fields
|
* 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 ClassPath classPath;
|
||||||
@Nonnull protected final String type;
|
@Nonnull protected final String type;
|
||||||
@Nullable protected ClassDef classDef;
|
@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;
|
protected boolean interfacesFullyResolved = true;
|
||||||
|
|
||||||
public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) {
|
public ClassProto(@Nonnull ClassPath classPath, @Nonnull String type) {
|
||||||
@ -77,6 +85,22 @@ public class ClassProto implements TypeProto {
|
|||||||
return classDef;
|
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.
|
* Returns true if this class is an interface.
|
||||||
*
|
*
|
||||||
@ -89,47 +113,59 @@ public class ClassProto implements TypeProto {
|
|||||||
return (classDef.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0;
|
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
|
@Nonnull
|
||||||
protected Set<String> getInterfaces() {
|
protected LinkedHashMap<String, ClassDef> getInterfaces() {
|
||||||
if (interfaces != null) {
|
if (interfaces != null) {
|
||||||
return interfaces;
|
return interfaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaces = Sets.newHashSet();
|
interfaces = Maps.newLinkedHashMap();
|
||||||
|
|
||||||
try {
|
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()) {
|
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
|
||||||
interfaces.add(getType());
|
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) {
|
// now add self and super class interfaces, required for common super class lookup
|
||||||
addInterfacesRecursively(classDef);
|
// we don't really need ClassDef's for that, so let's just use null
|
||||||
|
|
||||||
String superclass = classDef.getSuperclass();
|
if (isInterface() && !interfaces.containsKey(getType())) {
|
||||||
if (superclass != null) {
|
interfaces.put(getType(), null);
|
||||||
classDef = classPath.getClassDef(superclass);
|
}
|
||||||
} else {
|
|
||||||
break;
|
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) {
|
} catch (UnresolvedClassException ex) {
|
||||||
@ -139,6 +175,15 @@ public class ClassProto implements TypeProto {
|
|||||||
return interfaces;
|
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.
|
* Checks if this class implements the given interface.
|
||||||
*
|
*
|
||||||
@ -150,10 +195,8 @@ public class ClassProto implements TypeProto {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean implementsInterface(@Nonnull String iface) {
|
public boolean implementsInterface(@Nonnull String iface) {
|
||||||
for (String implementIface: getInterfaces()) {
|
if (getInterfaces().containsKey(iface)) {
|
||||||
if (implementIface.equals(iface)) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!interfacesFullyResolved) {
|
if (!interfacesFullyResolved) {
|
||||||
throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
|
throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
|
||||||
@ -274,14 +317,306 @@ public class ClassProto implements TypeProto {
|
|||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public FieldReference getFieldByOffset(int fieldOffset) {
|
public FieldReference getFieldByOffset(int fieldOffset) {
|
||||||
// TODO: implement this
|
if (getInstanceFields().size() == 0) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
return getInstanceFields().get(fieldOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodReference getMethodByVtableIndex(int vtableIndex) {
|
public MethodReference getMethodByVtableIndex(int vtableIndex) {
|
||||||
// TODO: implement this
|
if (vtableIndex < 0 || vtableIndex >= getVtable().length) {
|
||||||
return null;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
170
dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java
Normal file
170
dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
172
dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java
Normal file
172
dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -1545,7 +1545,7 @@ public class MethodAnalyzer {
|
|||||||
} else {
|
} else {
|
||||||
Instruction35ms instruction = (Instruction35ms)analyzedInstruction.instruction;
|
Instruction35ms instruction = (Instruction35ms)analyzedInstruction.instruction;
|
||||||
methodIndex = instruction.getVtableIndex();
|
methodIndex = instruction.getVtableIndex();
|
||||||
objectRegister = instruction.getRegisterD();
|
objectRegister = instruction.getRegisterC();
|
||||||
}
|
}
|
||||||
|
|
||||||
RegisterType objectRegisterType = getAndCheckSourceRegister(analyzedInstruction, objectRegister,
|
RegisterType objectRegisterType = getAndCheckSourceRegister(analyzedInstruction, objectRegister,
|
||||||
@ -1590,8 +1590,8 @@ public class MethodAnalyzer {
|
|||||||
opcode = Opcode.INVOKE_VIRTUAL_RANGE;
|
opcode = Opcode.INVOKE_VIRTUAL_RANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
deodexedInstruction = new ImmutableInstruction3rc(opcode, instruction.getRegisterCount(),
|
deodexedInstruction = new ImmutableInstruction3rc(opcode, instruction.getStartRegister(),
|
||||||
instruction.getStartRegister(), resolvedMethod);
|
instruction.getRegisterCount(), resolvedMethod);
|
||||||
} else {
|
} else {
|
||||||
Instruction35ms instruction = (Instruction35ms)analyzedInstruction.instruction;
|
Instruction35ms instruction = (Instruction35ms)analyzedInstruction.instruction;
|
||||||
Opcode opcode;
|
Opcode opcode;
|
||||||
|
@ -33,6 +33,7 @@ package org.jf.dexlib2.dexbacked;
|
|||||||
|
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import org.jf.dexlib2.Opcodes;
|
import org.jf.dexlib2.Opcodes;
|
||||||
|
import org.jf.dexlib2.dexbacked.raw.HeaderItem;
|
||||||
import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem;
|
import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem;
|
||||||
import org.jf.dexlib2.dexbacked.util.VariableSizeList;
|
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 static class NotAnOdexFile extends RuntimeException {
|
||||||
public NotAnOdexFile() {
|
public NotAnOdexFile() {
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ public class OdexHeaderItem {
|
|||||||
public static final int AUX_LENGTH_OFFSET = 28;
|
public static final int AUX_LENGTH_OFFSET = 28;
|
||||||
public static final int FLAGS_OFFSET = 32;
|
public static final int FLAGS_OFFSET = 32;
|
||||||
|
|
||||||
private static int getVersion(byte[] magic) {
|
public static int getVersion(byte[] magic) {
|
||||||
if (magic.length < 8) {
|
if (magic.length < 8) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user