| /* |
| * [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 org.apache.commons.cli.*; |
| import org.jf.dexlib2.DexFileFactory; |
| import org.jf.dexlib2.Opcode; |
| import org.jf.dexlib2.dexbacked.DexBackedDexFile; |
| import org.jf.util.ConsoleUtil; |
| import org.jf.util.SmaliHelpFormatter; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| 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; |
| |
| 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) { |
| 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; |
| } |
| |
| 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; |
| boolean addCodeOffsets = false; |
| boolean noAccessorComments = false; |
| boolean deodex = false; |
| boolean verify = false; |
| boolean ignoreErrors = false; |
| boolean checkPackagePrivateAccess = false; |
| |
| int apiLevel = 14; |
| |
| int registerInfo = 0; |
| |
| String outputDirectory = "out"; |
| String dumpFileName = null; |
| String outputDexFileName = null; |
| String inputDexFileName = null; |
| String bootClassPath = null; |
| StringBuffer extraBootClassPathEntries = new StringBuffer(); |
| List<String> bootClassPathDirs = new ArrayList<String>(); |
| bootClassPathDirs.add("."); |
| String inlineTable = null; |
| |
| 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': |
| bootClassPathDirs.add(option.getValue()); |
| break; |
| case 'f': |
| addCodeOffsets = true; |
| break; |
| case 'r': |
| String[] values = commandLine.getOptionValues('r'); |
| |
| if (values == null || values.length == 0) { |
| registerInfo = ARGS | DEST; |
| } 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 != null && bcp.charAt(0) == ':') { |
| extraBootClassPathEntries.append(bcp); |
| } else { |
| bootClassPath = bcp; |
| } |
| break; |
| case 'x': |
| deodex = true; |
| break; |
| case 'm': |
| noAccessorComments = true; |
| break; |
| case 'a': |
| apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); |
| break; |
| case 'N': |
| disassemble = false; |
| break; |
| case 'D': |
| doDump = true; |
| dumpFileName = commandLine.getOptionValue("D", inputDexFileName + ".dump"); |
| break; |
| case 'I': |
| ignoreErrors = true; |
| break; |
| case 'W': |
| write = true; |
| outputDexFileName = commandLine.getOptionValue("W"); |
| break; |
| case 'S': |
| sort = true; |
| break; |
| case 'F': |
| fixRegisters = true; |
| break; |
| case 'V': |
| verify = true; |
| break; |
| case 'T': |
| inlineTable = commandLine.getOptionValue("T"); |
| break; |
| case 'K': |
| checkPackagePrivateAccess = 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); |
| } |
| |
| Opcode.updateMapsForApiLevel(apiLevel); |
| |
| //Read in and parse the dex file |
| //TODO: add "fix registers" functionality? |
| DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile); |
| |
| //TODO: uncomment |
| /*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 (!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"); |
| } |
| } else {*/ |
| deodex = false; |
| |
| if (bootClassPath == null) { |
| bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"; |
| } |
| //} |
| |
| if (disassemble) { |
| String[] bootClassPathDirsArray = new String[bootClassPathDirs.size()]; |
| for (int i=0; i<bootClassPathDirsArray.length; i++) { |
| bootClassPathDirsArray[i] = bootClassPathDirs.get(i); |
| } |
| |
| baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, deodex, outputDirectory, |
| bootClassPathDirsArray, bootClassPath, extraBootClassPathEntries.toString(), |
| noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets, |
| noAccessorComments, registerInfo, verify, ignoreErrors, inlineTable, checkPackagePrivateAccess); |
| } |
| |
| // TODO: implement rewrite + optional sort functionality |
| |
| // TODO: need to check if odex file |
| if (doDump) { |
| try |
| { |
| dump.dump(dexFile, dumpFileName); |
| }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) { |
| 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); |
| } |
| |
| 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 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("the bootclasspath jars to use, for analysis. Defaults to " + |
| "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar. If the value begins with a " + |
| ":, it will 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 14 (ICS).") |
| .hasArg() |
| .withArgName("API_LEVEL") |
| .create("a"); |
| |
| 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 writeDexOption = OptionBuilder.withLongOpt("write-dex") |
| .withDescription("additionally rewrites the input dex file to FILE") |
| .hasArg() |
| .withArgName("FILE") |
| .create("W"); |
| |
| 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 verifyDexOption = OptionBuilder.withLongOpt("verify") |
| .withDescription("perform bytecode verification") |
| .create("V"); |
| |
| 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 checkPackagePrivateAccess = OptionBuilder.withLongOpt("check-package-private-access") |
| .withDescription("When deodexing, use the new virtual table generation logic that " + |
| "prevents overriding an inaccessible package private method. This is a temporary option " + |
| "that will be removed once this new functionality can be tied to a specific api level.") |
| .create("K"); |
| |
| 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); |
| basicOptions.addOption(codeOffsetOption); |
| basicOptions.addOption(noAccessorCommentsOption); |
| basicOptions.addOption(apiLevelOption); |
| |
| debugOptions.addOption(dumpOption); |
| debugOptions.addOption(ignoreErrorsOption); |
| debugOptions.addOption(noDisassemblyOption); |
| debugOptions.addOption(writeDexOption); |
| debugOptions.addOption(sortOption); |
| debugOptions.addOption(fixSignedRegisterOption); |
| debugOptions.addOption(verifyDexOption); |
| debugOptions.addOption(inlineTableOption); |
| debugOptions.addOption(checkPackagePrivateAccess); |
| |
| for (Object option: basicOptions.getOptions()) { |
| options.addOption((Option)option); |
| } |
| for (Object option: debugOptions.getOptions()) { |
| options.addOption((Option)option); |
| } |
| } |
| } |