Add "list field offsets" and "list vtables" commands to baksmali

This is a reimplementation of the "DumpFields" and "DumpVtables" entry
points that were previously in dexlib2
This commit is contained in:
Ben Gruver 2016-04-11 14:15:24 -07:00
parent 75cf7e4c64
commit bccdc809fa
8 changed files with 327 additions and 368 deletions

View File

@ -59,6 +59,8 @@ public class ListCommand implements Command {
subJc.addCommand("types", new ListTypesCommand(subJc), "type", "t"); subJc.addCommand("types", new ListTypesCommand(subJc), "type", "t");
subJc.addCommand("classes", new ListClassesCommand(subJc), "class", "c"); subJc.addCommand("classes", new ListClassesCommand(subJc), "class", "c");
subJc.addCommand("dex", new ListDexCommand(subJc), "d"); subJc.addCommand("dex", new ListDexCommand(subJc), "d");
subJc.addCommand("vtables", new ListVtablesCommand(subJc), "vtable", "v");
subJc.addCommand("fieldoffsets", new ListFieldOffsetsCommand(subJc), "fieldoffset", "fo");
} }
@Override public void run() { @Override public void run() {

View File

@ -0,0 +1,158 @@
/*
* Copyright 2016, 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.ClassProto;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.reference.FieldReference;
import org.jf.util.SparseArray;
import org.jf.util.jcommander.CommaColonParameterSplitter;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Parameters(commandDescription = "Lists the instance field offsets for classes in a dex file.")
public class ListFieldOffsetsCommand extends DexInputCommand {
@Nonnull private final JCommander jc;
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
@Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being loaded.")
private int apiLevel = 15;
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
"colon. E.g. \"something.apk:classes2.dex\"")
private List<String> inputList = Lists.newArrayList();
@Parameter(names = {"-b", "--bootclasspath"},
description = "A comma/colon separated list of the bootclasspath jar/oat files to include in the " +
"classpath when analyzing the dex file. This will override any automatic selection of " +
"bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " +
"BOOTCLASSPATH environment variable.",
splitter = CommaColonParameterSplitter.class)
private List<String> bootClassPath = new ArrayList<String>();
@Parameter(names = {"-c", "--classpath"},
description = "A comma/colon separated list of additional jar/oat files to include in the classpath " +
"when analyzing the dex file. These will be added to the classpath after any bootclasspath " +
"entries.",
splitter = CommaColonParameterSplitter.class)
private List<String> classPath = new ArrayList<String>();
@Parameter(names = {"-d", "--classpath-dir"},
description = "baksmali will search these directories in order for any classpath entries.")
private List<String> classPathDirectories = Lists.newArrayList(".");
@Parameter(names = "--check-package-private-access",
description = "Use the package-private access check when calculating vtable indexes. This should " +
"only be needed for 4.2.0 odexes. It was reverted in 4.2.1.")
private boolean checkPackagePrivateAccess = false;
@Parameter(names = "--experimental",
description = "Enable experimental opcodes to be disassembled, even if they aren't necessarily " +
"supported in the Android runtime yet.")
private boolean experimentalOpcodes = false;
public ListFieldOffsetsCommand(@Nonnull JCommander jc) {
this.jc = jc;
}
@Override public void run() {
if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand());
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand());
return;
}
String input = inputList.get(0);
DexBackedDexFile dexFile = loadDexFile(input, 15, false);
BaksmaliOptions options = getOptions(dexFile);
try {
for (ClassDef classDef: dexFile.getClasses()) {
ClassProto classProto = (ClassProto) options.classPath.getClass(classDef);
SparseArray<FieldReference> fields = classProto.getInstanceFields();
String className = "Class " + classDef.getType() + " : " + fields.size() + " instance fields\n";
System.out.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";
System.out.write(field.getBytes());
}
System.out.write("\n".getBytes());
}
System.out.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Nonnull
private BaksmaliOptions getOptions(DexFile dexFile) {
final BaksmaliOptions options = new BaksmaliOptions();
options.apiLevel = apiLevel;
try {
options.classPath = ClassPath.fromClassPath(classPathDirectories,
Iterables.concat(bootClassPath, classPath), dexFile, apiLevel,
checkPackagePrivateAccess, experimentalOpcodes);
} catch (Exception ex) {
System.err.println("Error occurred while loading class path files.");
ex.printStackTrace(System.err);
System.exit(-1);
}
options.experimentalOpcodes = experimentalOpcodes;
return options;
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright 2016, 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.baksmali;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.ClassProto;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.iface.Method;
import org.jf.util.jcommander.CommaColonParameterSplitter;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Parameters(commandDescription = "Lists the virtual method tables for classes in a dex file.")
public class ListVtablesCommand extends DexInputCommand {
@Nonnull private final JCommander jc;
@Parameter(names = {"-h", "-?", "--help"}, help = true,
description = "Show usage information")
private boolean help;
@Parameter(names = {"-a", "--api"},
description = "The numeric api level of the file being loaded.")
public int apiLevel = 15;
@Parameter(description = "<file> - A dex/apk/oat/odex file. For apk or oat files that contain multiple dex " +
"files, you can specify which dex file to disassemble by appending the name of the dex file with a " +
"colon. E.g. \"something.apk:classes2.dex\"")
private List<String> inputList = Lists.newArrayList();
@Parameter(names = {"-b", "--bootclasspath"},
description = "A comma/colon separated list of the bootclasspath jar/oat files to include in the " +
"classpath when analyzing the dex file. This will override any automatic selection of " +
"bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " +
"BOOTCLASSPATH environment variable.",
splitter = CommaColonParameterSplitter.class)
private List<String> bootClassPath = new ArrayList<String>();
@Parameter(names = {"-c", "--classpath"},
description = "A comma/colon separated list of additional jar/oat files to include in the classpath " +
"when analyzing the dex file. These will be added to the classpath after any bootclasspath " +
"entries.",
splitter = CommaColonParameterSplitter.class)
private List<String> classPath = new ArrayList<String>();
@Parameter(names = {"-d", "--classpath-dir"},
description = "baksmali will search these directories in order for any classpath entries.")
private List<String> classPathDirectories = Lists.newArrayList(".");
@Parameter(names = "--check-package-private-access",
description = "Use the package-private access check when calculating vtable indexes. This should " +
"only be needed for 4.2.0 odexes. It was reverted in 4.2.1.")
private boolean checkPackagePrivateAccess = false;
@Parameter(names = "--experimental",
description = "Enable experimental opcodes to be disassembled, even if they aren't necessarily " +
"supported in the Android runtime yet.")
private boolean experimentalOpcodes = false;
public ListVtablesCommand(@Nonnull JCommander jc) {
this.jc = jc;
}
@Override public void run() {
if (help || inputList == null || inputList.isEmpty()) {
jc.usage(jc.getParsedCommand());
return;
}
if (inputList.size() > 1) {
System.err.println("Too many files specified");
jc.usage(jc.getParsedCommand());
return;
}
String input = inputList.get(0);
DexBackedDexFile dexFile = loadDexFile(input, 15, false);
BaksmaliOptions options = getOptions(dexFile);
if (options == null) {
return;
}
try {
for (ClassDef classDef : dexFile.getClasses()) {
ClassProto classProto = (ClassProto) options.classPath.getClass(classDef);
List<Method> methods = classProto.getVtable();
String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.size() + " methods\n";
System.out.write(className.getBytes());
for (int i = 0; i < methods.size(); i++) {
Method method = methods.get(i);
String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "(";
for (CharSequence parameter : method.getParameterTypes()) {
methodString += parameter;
}
methodString += ")" + method.getReturnType() + "\n";
System.out.write(methodString.getBytes());
}
System.out.write("\n".getBytes());
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
protected BaksmaliOptions getOptions(DexFile dexFile) {
final BaksmaliOptions options = new BaksmaliOptions();
options.apiLevel = apiLevel;
try {
options.classPath = ClassPath.fromClassPath(classPathDirectories,
Iterables.concat(bootClassPath, classPath), dexFile, apiLevel,
checkPackagePrivateAccess, experimentalOpcodes);
} catch (Exception ex) {
System.err.println("Error occurred while loading class path files.");
ex.printStackTrace(System.err);
return null;
}
options.experimentalOpcodes = experimentalOpcodes;
return options;
}
}

View File

@ -104,7 +104,6 @@ subprojects {
antlr_runtime: 'org.antlr:antlr-runtime:3.5.2', antlr_runtime: 'org.antlr:antlr-runtime:3.5.2',
antlr: 'org.antlr:antlr:3.5.2', antlr: 'org.antlr:antlr:3.5.2',
stringtemplate: 'org.antlr:stringtemplate:3.2.1', stringtemplate: 'org.antlr:stringtemplate:3.2.1',
commons_cli: 'commons-cli:commons-cli:1.2',
jflex: 'de.jflex:jflex:1.4.3', jflex: 'de.jflex:jflex:1.4.3',
jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.2', jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.2',
proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1', proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1',

View File

@ -49,7 +49,6 @@ dependencies {
compile project(':util') compile project(':util')
compile depends.findbugs compile depends.findbugs
compile depends.guava compile depends.guava
compile depends.commons_cli
testCompile depends.junit testCompile depends.junit

View File

@ -373,7 +373,7 @@ public class ClassProto implements TypeProto {
return -1; return -1;
} }
@Nonnull SparseArray<FieldReference> getInstanceFields() { @Nonnull public SparseArray<FieldReference> getInstanceFields() {
if (classPath.isArt()) { if (classPath.isArt()) {
return artInstanceFieldsSupplier.get(); return artInstanceFieldsSupplier.get();
} else { } else {
@ -759,7 +759,7 @@ public class ClassProto implements TypeProto {
throw new ExceptionWithContext("Invalid type: %s", type); throw new ExceptionWithContext("Invalid type: %s", type);
} }
@Nonnull List<Method> getVtable() { @Nonnull public List<Method> getVtable() {
return vtableSupplier.get(); return vtableSupplier.get();
} }

View File

@ -1,180 +0,0 @@
/*
* 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;
boolean experimental = false;
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;
case 'X':
experimental = true;
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, experimental);
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, experimental);
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");
Option experimentalOption = OptionBuilder.withLongOpt("experimental")
.withDescription("Enable dumping experimental opcodes, that aren't necessarily " +
"supported by the android runtime yet.")
.create("X");
options.addOption(classPathDirOption);
options.addOption(outputFileOption);
options.addOption(apiLevelOption);
options.addOption(experimentalOption);
}
}

View File

@ -1,184 +0,0 @@
/*
* 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;
boolean experimental = false;
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;
case 'X':
experimental = true;
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, experimental);
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, experimental);
FileOutputStream outStream = new FileOutputStream(outFile);
for (ClassDef classDef: dexFile.getClasses()) {
ClassProto classProto = (ClassProto) classPath.getClass(classDef);
List<Method> methods = classProto.getVtable();
String className = "Class " + classDef.getType() + " extends " + classDef.getSuperclass() + " : " + methods.size() + " methods\n";
outStream.write(className.getBytes());
for (int i=0;i<methods.size();i++) {
Method method = methods.get(i);
String methodString = i + ":" + method.getDefiningClass() + "->" + method.getName() + "(";
for (CharSequence parameter: method.getParameterTypes()) {
methodString += parameter;
}
methodString += ")" + method.getReturnType() + "\n";
outStream.write(methodString.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");
Option experimentalOption = OptionBuilder.withLongOpt("experimental")
.withDescription("Enable dumping experimental opcodes, that aren't necessarily " +
"supported by the android runtime yet.")
.create("X");
options.addOption(classPathDirOption);
options.addOption(outputFileOption);
options.addOption(apiLevelOption);
options.addOption(experimentalOption);
}
}