blob: 158ea6ca27e331c13c01437e0d59fa9cd5cddc12 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jf.baksmali;
import org.apache.commons.cli.*;
import org.jf.baksmali.Deodex.Deodexerant;
import org.jf.dexlib.DexFile;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
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;
public static final int ALL = 1;
public static final int ALLPRE = 2;
public static final int ALLPOST = 4;
public static final int ARGS = 8;
public static final int DEST = 16;
public static final int MERGE = 32;
public static final int FULLMERGE = 64;
static {
options = new Options();
basicOptions = new Options();
debugOptions = new Options();
buildOptions();
InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
Properties properties = new Properties();
String version = "(unknown)";
try {
properties.load(templateStream);
version = properties.getProperty("application.version");
} catch (IOException ex) {
}
VERSION = version;
}
/**
* This class is uninstantiable.
*/
private main() {
}
/**
* Run!
*/
public static void main(String[] args) {
CommandLineParser parser = new PosixParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args);
} catch (ParseException ex) {
usage();
return;
}
boolean disassemble = true;
boolean doDump = false;
boolean write = false;
boolean sort = false;
boolean fixRegisters = false;
boolean noParameterRegisters = false;
boolean useLocalsDirective = false;
boolean useSequentialLabels = false;
boolean outputDebugInfo = true;
int registerInfo = 0;
String outputDirectory = "out";
String dumpFileName = null;
String outputDexFileName = null;
String inputDexFileName = null;
String deodexerantHost = null;
String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
String bootClassPathDir = ".";
int deodexerantPort = 0;
String[] remainingArgs = commandLine.getArgs();
Option[] options = commandLine.getOptions();
for (int i=0; i<options.length; i++) {
Option option = options[i];
String opt = option.getOpt();
switch (opt.charAt(0)) {
case 'v':
version();
return;
case '?':
while (++i < options.length) {
if (options[i].getOpt().charAt(0) == '?') {
usage(true);
return;
}
}
usage(false);
return;
case 'o':
outputDirectory = commandLine.getOptionValue("o");
break;
case 'p':
noParameterRegisters = true;
break;
case 'l':
useLocalsDirective = true;
break;
case 's':
useSequentialLabels = true;
break;
case 'b':
outputDebugInfo = false;
break;
case 'd':
bootClassPathDir = commandLine.getOptionValue("d");
break;
case 'r':
String[] values = commandLine.getOptionValues('r');
if (values == null || values.length == 0) {
registerInfo = ARGS | DEST | MERGE;
} else {
for (String value: values) {
if (value.equalsIgnoreCase("ALL")) {
registerInfo |= ALL;
} else if (value.equalsIgnoreCase("ALLPRE")) {
registerInfo |= ALLPRE;
} else if (value.equalsIgnoreCase("ALLPOST")) {
registerInfo |= ALLPOST;
} else if (value.equalsIgnoreCase("ARGS")) {
registerInfo |= ARGS;
} else if (value.equalsIgnoreCase("DEST")) {
registerInfo |= DEST;
} else if (value.equalsIgnoreCase("MERGE")) {
registerInfo |= MERGE;
} else if (value.equalsIgnoreCase("FULLMERGE")) {
registerInfo |= FULLMERGE;
} else {
usage();
return;
}
}
if ((registerInfo & FULLMERGE) != 0) {
registerInfo &= ~MERGE;
}
}
break;
case 'c':
String bcp = commandLine.getOptionValue("c");
if (bcp.charAt(0) == ':') {
bootClassPath = bootClassPath + bcp;
} else {
bootClassPath = bcp;
}
break;
case 'x':
String deodexerantAddress = commandLine.getOptionValue("x");
String[] parts = deodexerantAddress.split(":");
if (parts.length != 2) {
System.err.println("Invalid deodexerant address. Expecting :<port> or <host>:<port>");
System.exit(1);
}
deodexerantHost = parts[0];
if (deodexerantHost.length() == 0) {
deodexerantHost = "localhost";
}
try {
deodexerantPort = Integer.parseInt(parts[1]);
} catch (NumberFormatException ex) {
System.err.println("Invalid port \"" + deodexerantPort + "\" for deodexerant address");
System.exit(1);
}
break;
case 'N':
disassemble = false;
break;
case 'D':
doDump = true;
dumpFileName = commandLine.getOptionValue("D", inputDexFileName + ".dump");
break;
case 'W':
write = true;
outputDexFileName = commandLine.getOptionValue("W");
break;
case 'S':
sort = true;
break;
case 'F':
fixRegisters = true;
break;
default:
assert false;
}
}
if (remainingArgs.length != 1) {
usage();
return;
}
inputDexFileName = remainingArgs[0];
try {
File dexFileFile = new File(inputDexFileName);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.exit(1);
}
//Read in and parse the dex file
DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false);
Deodexerant deodexerant = null;
if (deodexerantHost != null) {
if (!dexFile.isOdex()) {
System.err.println("-x cannot be used with a normal dex file. Ignoring -x");
}
deodexerant = new Deodexerant(dexFile, deodexerantHost, deodexerantPort);
}
if (dexFile.isOdex()) {
if (doDump) {
System.err.println("-d cannot be used with on odex file. Ignoring -d");
}
if (write) {
System.err.println("-w cannot be used with an odex file. Ignoring -w");
}
if (deodexerant == null) {
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 use deodexerant, and");
System.err.println("the -x option for baksmali");
}
}
if (disassemble) {
baksmali.disassembleDexFile(dexFile, deodexerant, outputDirectory, bootClassPathDir, bootClassPath,
noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, registerInfo);
}
if ((doDump || write) && !dexFile.isOdex()) {
try
{
dump.dump(dexFile, dumpFileName, outputDexFileName, sort);
}catch (IOException ex) {
System.err.println("Error occured while writing dump file");
ex.printStackTrace();
}
}
} catch (RuntimeException ex) {
System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:");
ex.printStackTrace();
System.exit(1);
} catch (Throwable ex) {
System.err.println("\n\nUNEXPECTED TOP-LEVEL ERROR:");
ex.printStackTrace();
System.exit(1);
}
}
/**
* Prints the usage message.
*/
private static void usage(boolean printDebugOptions) {
baksmaliHelpFormatter formatter = new baksmaliHelpFormatter();
formatter.setWidth(100);
formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
"disassembles and/or dumps a dex file", basicOptions, "");
if (printDebugOptions) {
System.out.println();
System.out.println("Debug Options:");
StringBuffer sb = new StringBuffer();
formatter.renderOptions(sb, debugOptions);
System.out.println(sb.toString());
}
}
private static void usage() {
usage(false);
}
/**
* Prints the version message.
*/
private static void version() {
System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
System.out.println("Copyright (C) 2009 Ben Gruver");
System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
System.exit(0);
}
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")
.create("?");
Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
.withDescription("suppresses the output of the disassembly")
.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 writeDexOption = OptionBuilder.withLongOpt("write-dex")
.withDescription("additionally rewrites the input dex file to FILE")
.hasArg()
.withArgName("FILE")
.create("W");
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 sortOption = OptionBuilder.withLongOpt("sort")
.withDescription("sort the items in the dex file into a canonical order before dumping/writing")
.create("S");
Option fixSignedRegisterOption = OptionBuilder.withLongOpt("fix-signed-registers")
.withDescription("when dumping or rewriting, fix any registers in the debug info that are encoded as" +
" a signed value")
.create("F");
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("deodexerant")
.withDescription("connect to deodexerant on the specified HOST:PORT, and deodex the input odex"
+ " file. This option is ignored if the input file is a dex file instead of an odex file")
.hasArg()
.withArgName("HOST:PORT")
.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,MERGE\" 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("the bootclasspath jars to use, for analysis. Defaults to " +
"core.jar:ext.jar:framework.jar:android.policy.jar:services.jar. If you specify a value that " +
"begins with a :, it will be appended to the default bootclasspath")
.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");
basicOptions.addOption(versionOption);
basicOptions.addOption(helpOption);
basicOptions.addOption(outputDirOption);
basicOptions.addOption(noParameterRegistersOption);
basicOptions.addOption(deodexerantOption);
basicOptions.addOption(useLocalsOption);
basicOptions.addOption(sequentialLabelsOption);
basicOptions.addOption(noDebugInfoOption);
basicOptions.addOption(registerInfoOption);
basicOptions.addOption(classPathOption);
basicOptions.addOption(classPathDirOption);
debugOptions.addOption(dumpOption);
debugOptions.addOption(noDisassemblyOption);
debugOptions.addOption(writeDexOption);
debugOptions.addOption(sortOption);
debugOptions.addOption(fixSignedRegisterOption);
for (Object option: basicOptions.getOptions()) {
options.addOption((Option)option);
}
for (Object option: debugOptions.getOptions()) {
options.addOption((Option)option);
}
}
}