blob: 86f43d827d50ba12a16b140c6b71741350d467b7 [file] [log] [blame]
/*
* [The "BSD licence"]
* Copyright (c) 2010 Ben Gruver (JesusFreke)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.google.common.collect.Lists;
import org.apache.commons.cli.*;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.util.ConsoleUtil;
import org.jf.util.SmaliHelpFormatter;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
public class main {
public static final String VERSION;
private static final Options basicOptions;
private static final Options debugOptions;
private static final Options options;
static {
options = new Options();
basicOptions = new Options();
debugOptions = new Options();
buildOptions();
InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
if (templateStream != null) {
Properties properties = new Properties();
String version = "(unknown)";
try {
properties.load(templateStream);
version = properties.getProperty("application.version");
} catch (IOException ex) {
// ignore
}
VERSION = version;
} else {
VERSION = "[unknown version]";
}
}
/**
* This class is uninstantiable.
*/
private main() {
}
/**
* A more programmatic-friendly entry point for baksmali
*
* @param options a baksmaliOptions object with the options to run baksmali with
* @param inputDexFile The DexFile to disassemble
* @return true if disassembly completed with no errors, or false if errors were encountered
*/
public static boolean run(@Nonnull baksmaliOptions options, @Nonnull DexFile inputDexFile) throws IOException {
if (options.bootClassPathEntries.isEmpty() &&
(options.deodex || options.registerInfo != 0 || options.normalizeVirtualMethods)) {
if (inputDexFile instanceof DexBackedOdexFile) {
options.bootClassPathEntries = ((DexBackedOdexFile)inputDexFile).getDependencies();
} else {
options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel,
options.experimental);
}
}
if (options.customInlineDefinitions == null && inputDexFile instanceof DexBackedOdexFile) {
options.inlineResolver =
InlineMethodResolver.createInlineMethodResolver(
((DexBackedOdexFile)inputDexFile).getOdexVersion());
}
boolean errorOccurred = false;
if (options.disassemble) {
errorOccurred = !baksmali.disassembleDexFile(inputDexFile, options);
}
if (options.dump) {
if (!(inputDexFile instanceof DexBackedDexFile)) {
throw new IllegalArgumentException("Annotated hex-dumps require a DexBackedDexFile");
}
dump.dump((DexBackedDexFile)inputDexFile, options.dumpFileName, options.apiLevel);
}
return !errorOccurred;
}
/**
* Run!
*/
public static void main(String[] args) throws IOException {
Locale locale = new Locale("en", "US");
Locale.setDefault(locale);
CommandLineParser parser = new PosixParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args);
} catch (ParseException ex) {
usage();
return;
}
baksmaliOptions options = new baksmaliOptions();
String[] remainingArgs = commandLine.getArgs();
Option[] clOptions = commandLine.getOptions();
for (int i=0; i<clOptions.length; i++) {
Option option = clOptions[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'v':
version();
return;
case '?':
while (++i < clOptions.length) {
if (clOptions[i].getOpt().charAt(0) == '?') {
usage(true);
return;
}
}
usage(false);
return;
case 'o':
options.outputDirectory = commandLine.getOptionValue("o");
break;
case 'p':
options.noParameterRegisters = true;
break;
case 'l':
options.useLocalsDirective = true;
break;
case 's':
options.useSequentialLabels = true;
break;
case 'b':
options.outputDebugInfo = false;
break;
case 'd':
options.bootClassPathDirs.add(option.getValue());
break;
case 'f':
options.addCodeOffsets = true;
break;
case 'r':
String[] values = commandLine.getOptionValues('r');
int registerInfo = 0;
if (values == null || values.length == 0) {
registerInfo = baksmaliOptions.ARGS | baksmaliOptions.DEST;
} else {
for (String value: values) {
if (value.equalsIgnoreCase("ALL")) {
registerInfo |= baksmaliOptions.ALL;
} else if (value.equalsIgnoreCase("ALLPRE")) {
registerInfo |= baksmaliOptions.ALLPRE;
} else if (value.equalsIgnoreCase("ALLPOST")) {
registerInfo |= baksmaliOptions.ALLPOST;
} else if (value.equalsIgnoreCase("ARGS")) {
registerInfo |= baksmaliOptions.ARGS;
} else if (value.equalsIgnoreCase("DEST")) {
registerInfo |= baksmaliOptions.DEST;
} else if (value.equalsIgnoreCase("MERGE")) {
registerInfo |= baksmaliOptions.MERGE;
} else if (value.equalsIgnoreCase("FULLMERGE")) {
registerInfo |= baksmaliOptions.FULLMERGE;
} else {
usage();
return;
}
}
if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) {
registerInfo &= ~baksmaliOptions.MERGE;
}
}
options.registerInfo = registerInfo;
break;
case 'c':
String bcp = commandLine.getOptionValue("c");
if (bcp != null && bcp.charAt(0) == ':') {
options.addExtraClassPath(bcp);
} else {
options.setBootClassPath(bcp);
}
break;
case 'x':
options.deodex = true;
break;
case 'X':
options.experimental = true;
break;
case 'm':
options.noAccessorComments = true;
break;
case 'a':
options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
case 'j':
options.jobs = Integer.parseInt(commandLine.getOptionValue("j"));
break;
case 'i':
String rif = commandLine.getOptionValue("i");
options.setResourceIdFiles(rif);
break;
case 't':
options.useImplicitReferences = true;
break;
case 'e':
options.dexEntry = commandLine.getOptionValue("e");
break;
case 'k':
options.checkPackagePrivateAccess = true;
break;
case 'n':
options.normalizeVirtualMethods = true;
break;
case 'N':
options.disassemble = false;
break;
case 'D':
options.dump = true;
options.dumpFileName = commandLine.getOptionValue("D");
break;
case 'I':
options.ignoreErrors = true;
break;
case 'T':
options.customInlineDefinitions = new File(commandLine.getOptionValue("T"));
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
String inputDexPath = remainingArgs[0];
File dexFileFile = new File(inputDexPath);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexPath);
System.exit(1);
}
//Read in and parse the dex file
DexBackedDexFile dexFile = null;
try {
dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry, options.apiLevel, options.experimental);
} catch (MultipleDexFilesException ex) {
System.err.println(String.format("%s contains multiple dex files. You must specify which one to " +
"disassemble with the -e option", dexFileFile.getName()));
System.err.println("Valid entries include:");
for (OatDexFile oatDexFile: ex.oatFile.getDexFiles()) {
System.err.println(oatDexFile.filename);
}
System.exit(1);
}
if (dexFile.hasOdexOpcodes()) {
if (!options.deodex) {
System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
System.err.println("won't be able to re-assemble the results unless you deodex it with the -x");
System.err.println("option");
options.allowOdex = true;
}
} else {
options.deodex = false;
}
if (options.dump) {
if (options.dumpFileName == null) {
options.dumpFileName = inputDexPath + ".dump";
}
}
try {
if (!run(options, dexFile)) {
System.exit(1);
}
} catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
System.exit(1);
}
}
/**
* Prints the usage message.
*/
private static void usage(boolean printDebugOptions) {
SmaliHelpFormatter formatter = new SmaliHelpFormatter();
int consoleWidth = ConsoleUtil.getConsoleWidth();
if (consoleWidth <= 0) {
consoleWidth = 80;
}
formatter.setWidth(consoleWidth);
formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
"disassembles and/or dumps a dex file", basicOptions, printDebugOptions?debugOptions:null);
}
private static void usage() {
usage(false);
}
/**
* Prints the version message.
*/
protected static void version() {
System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
System.exit(0);
}
@SuppressWarnings("AccessStaticViaInstance")
private static void buildOptions() {
Option versionOption = OptionBuilder.withLongOpt("version")
.withDescription("prints the version then exits")
.create("v");
Option helpOption = OptionBuilder.withLongOpt("help")
.withDescription("prints the help message then exits. Specify twice for debug options")
.create("?");
Option outputDirOption = OptionBuilder.withLongOpt("output")
.withDescription("the directory where the disassembled files will be placed. The default is out")
.hasArg()
.withArgName("DIR")
.create("o");
Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
.withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method " +
"parameters")
.create("p");
Option deodexerantOption = OptionBuilder.withLongOpt("deodex")
.withDescription("deodex the given odex file. This option is ignored if the input file is not an " +
"odex file")
.create("x");
Option experimentalOption = OptionBuilder.withLongOpt("experimental")
.withDescription("enable experimental opcodes to be disassembled, even if they aren't necessarily supported in the Android runtime yet")
.create("X");
Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
.withDescription("output the .locals directive with the number of non-parameter registers, rather" +
" than the .register directive with the total number of register")
.create("l");
Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels")
.withDescription("create label names using a sequential numbering scheme per label type, rather than " +
"using the bytecode address")
.create("s");
Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info")
.withDescription("don't write out debug info (.local, .param, .line, etc.)")
.create("b");
Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
.hasOptionalArgs()
.withArgName("REGISTER_INFO_TYPES")
.withValueSeparator(',')
.withDescription("print the specificed type(s) of register information for each instruction. " +
"\"ARGS,DEST\" is the default if no types are specified.\nValid values are:\nALL: all " +
"pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
"post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
"instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
"pre-instruction register has been merged from more than 1 different post-instruction " +
"register from its predecessors\nFULLMERGE: For each register that would be printed by " +
"MERGE, also show the incoming register types that were merged")
.create("r");
Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
.withDescription("A colon-separated list of bootclasspath jar/oat files to use for analysis. Add an " +
"initial colon to specify that the jars/oats should be appended to the default bootclasspath " +
"instead of replacing it")
.hasOptionalArg()
.withArgName("BOOTCLASSPATH")
.create("c");
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 codeOffsetOption = OptionBuilder.withLongOpt("code-offsets")
.withDescription("add comments to the disassembly containing the code offset for each address")
.create("f");
Option noAccessorCommentsOption = OptionBuilder.withLongOpt("no-accessor-comments")
.withDescription("don't output helper comments for synthetic accessors")
.create("m");
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 jobsOption = OptionBuilder.withLongOpt("jobs")
.withDescription("The number of threads to use. Defaults to the number of cores available, up to a " +
"maximum of 6")
.hasArg()
.withArgName("NUM_THREADS")
.create("j");
Option resourceIdFilesOption = OptionBuilder.withLongOpt("resource-id-files")
.withDescription("the resource ID files to use, for analysis. A colon-separated list of prefix=file " +
"pairs. For example R=res/values/public.xml:" +
"android.R=$ANDROID_HOME/platforms/android-19/data/res/values/public.xml")
.hasArg()
.withArgName("FILES")
.create("i");
Option noImplicitReferencesOption = OptionBuilder.withLongOpt("implicit-references")
.withDescription("Use implicit (type-less) method and field references")
.create("t");
Option checkPackagePrivateAccessOption = OptionBuilder.withLongOpt("check-package-private-access")
.withDescription("When deodexing, use the package-private access check when calculating vtable " +
"indexes. It should only be needed for 4.2.0 odexes. The functionality was reverted for " +
"4.2.1.")
.create("k");
Option normalizeVirtualMethods = OptionBuilder.withLongOpt("normalize-virtual-methods")
.withDescription("Normalize virtual method references to the reference the base method.")
.create("n");
Option dumpOption = OptionBuilder.withLongOpt("dump-to")
.withDescription("dumps the given dex file into a single annotated dump file named FILE" +
" (<dexfile>.dump by default), along with the normal disassembly")
.hasOptionalArg()
.withArgName("FILE")
.create("D");
Option ignoreErrorsOption = OptionBuilder.withLongOpt("ignore-errors")
.withDescription("ignores any non-fatal errors that occur while disassembling/deodexing," +
" ignoring the class if needed, and continuing with the next class. The default" +
" behavior is to stop disassembling and exit once an error is encountered")
.create("I");
Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
.withDescription("suppresses the output of the disassembly")
.create("N");
Option inlineTableOption = OptionBuilder.withLongOpt("inline-table")
.withDescription("specify a file containing a custom inline method table to use for deodexing")
.hasArg()
.withArgName("FILE")
.create("T");
Option dexEntryOption = OptionBuilder.withLongOpt("dex-file")
.withDescription("looks for dex file named DEX_FILE, defaults to classes.dex")
.withArgName("DEX_FILE")
.hasArg()
.create("e");
basicOptions.addOption(versionOption);
basicOptions.addOption(helpOption);
basicOptions.addOption(outputDirOption);
basicOptions.addOption(noParameterRegistersOption);
basicOptions.addOption(deodexerantOption);
basicOptions.addOption(experimentalOption);
basicOptions.addOption(useLocalsOption);
basicOptions.addOption(sequentialLabelsOption);
basicOptions.addOption(noDebugInfoOption);
basicOptions.addOption(registerInfoOption);
basicOptions.addOption(classPathOption);
basicOptions.addOption(classPathDirOption);
basicOptions.addOption(codeOffsetOption);
basicOptions.addOption(noAccessorCommentsOption);
basicOptions.addOption(apiLevelOption);
basicOptions.addOption(jobsOption);
basicOptions.addOption(resourceIdFilesOption);
basicOptions.addOption(noImplicitReferencesOption);
basicOptions.addOption(dexEntryOption);
basicOptions.addOption(checkPackagePrivateAccessOption);
basicOptions.addOption(normalizeVirtualMethods);
debugOptions.addOption(dumpOption);
debugOptions.addOption(ignoreErrorsOption);
debugOptions.addOption(noDisassemblyOption);
debugOptions.addOption(inlineTableOption);
for (Object option: basicOptions.getOptions()) {
options.addOption((Option)option);
}
for (Object option: debugOptions.getOptions()) {
options.addOption((Option)option);
}
}
@Nonnull
private static List<String> getDefaultBootClassPathForApi(int apiLevel, boolean experimental) {
if (apiLevel < 9) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar");
} else if (apiLevel < 12) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel < 14) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/apache-xml.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel < 16) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/filterfw.jar");
} else if (apiLevel < 21) {
// this is correct as of api 17/4.2.2
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel < 26) {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
} else { // api >= 26
// TODO: verify, add new ones?
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
}
}
}