main.java revision c616a0dadca9cbffdf4827eeb6c2da064ad9f7b7
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2010 Ben Gruver (JesusFreke)
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29package org.jf.baksmali;
30
31import org.apache.commons.cli.*;
32import org.jf.dexlib.DexFile;
33import org.jf.util.ConsoleUtil;
34
35import java.io.File;
36import java.io.InputStream;
37import java.io.IOException;
38import java.util.ArrayList;
39import java.util.List;
40import java.util.Properties;
41
42public class main {
43
44    public static final String VERSION;
45
46    private static final Options basicOptions;
47    private static final Options debugOptions;
48    private static final Options options;
49
50    public static final int ALL = 1;
51    public static final int ALLPRE = 2;
52    public static final int ALLPOST = 4;
53    public static final int ARGS = 8;
54    public static final int DEST = 16;
55    public static final int MERGE = 32;
56    public static final int FULLMERGE = 64;
57
58    static {
59        options = new Options();
60        basicOptions = new Options();
61        debugOptions = new Options();
62        buildOptions();
63
64        InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
65        Properties properties = new Properties();
66        String version = "(unknown)";
67        try {
68            properties.load(templateStream);
69            version = properties.getProperty("application.version");
70        } catch (IOException ex) {
71        }
72        VERSION = version;
73    }
74
75    /**
76     * This class is uninstantiable.
77     */
78    private main() {
79    }
80
81    /**
82     * Run!
83     */
84    public static void main(String[] args) {
85        CommandLineParser parser = new PosixParser();
86        CommandLine commandLine;
87
88        try {
89            commandLine = parser.parse(options, args);
90        } catch (ParseException ex) {
91            usage();
92            return;
93        }
94
95        boolean disassemble = true;
96        boolean doDump = false;
97        boolean write = false;
98        boolean sort = false;
99        boolean fixRegisters = false;
100        boolean noParameterRegisters = false;
101        boolean useLocalsDirective = false;
102        boolean useSequentialLabels = false;
103        boolean outputDebugInfo = true;
104        boolean addCodeOffsets = false;
105        boolean deodex = false;
106        boolean verify = false;
107
108        int registerInfo = 0;
109
110        String outputDirectory = "out";
111        String dumpFileName = null;
112        String outputDexFileName = null;
113        String inputDexFileName = null;
114        String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
115        List<String> bootClassPathDirs = new ArrayList<String>();
116        bootClassPathDirs.add(".");
117
118
119        String[] remainingArgs = commandLine.getArgs();
120
121        Option[] options = commandLine.getOptions();
122
123        for (int i=0; i<options.length; i++) {
124            Option option = options[i];
125            String opt = option.getOpt();
126
127            switch (opt.charAt(0)) {
128                case 'v':
129                    version();
130                    return;
131                case '?':
132                    while (++i < options.length) {
133                        if (options[i].getOpt().charAt(0) == '?') {
134                            usage(true);
135                            return;
136                        }
137                    }
138                    usage(false);
139                    return;
140                case 'o':
141                    outputDirectory = commandLine.getOptionValue("o");
142                    break;
143                case 'p':
144                    noParameterRegisters = true;
145                    break;
146                case 'l':
147                    useLocalsDirective = true;
148                    break;
149                case 's':
150                    useSequentialLabels = true;
151                    break;
152                case 'b':
153                    outputDebugInfo = false;
154                    break;
155                case 'd':
156                    bootClassPathDirs.add(option.getValue());
157                    break;
158                case 'f':
159                    addCodeOffsets = true;
160                    break;
161                case 'r':
162                    String[] values = commandLine.getOptionValues('r');
163
164                    if (values == null || values.length == 0) {
165                        registerInfo = ARGS | DEST;
166                    } else {
167                        for (String value: values) {
168                            if (value.equalsIgnoreCase("ALL")) {
169                                registerInfo |= ALL;
170                            } else if (value.equalsIgnoreCase("ALLPRE")) {
171                                registerInfo |= ALLPRE;
172                            } else if (value.equalsIgnoreCase("ALLPOST")) {
173                                registerInfo |= ALLPOST;
174                            } else if (value.equalsIgnoreCase("ARGS")) {
175                                registerInfo |= ARGS;
176                            } else if (value.equalsIgnoreCase("DEST")) {
177                                registerInfo |= DEST;
178                            } else if (value.equalsIgnoreCase("MERGE")) {
179                                registerInfo |= MERGE;
180                            } else if (value.equalsIgnoreCase("FULLMERGE")) {
181                                registerInfo |= FULLMERGE;
182                            } else {
183                                usage();
184                                return;
185                            }
186                        }
187
188                        if ((registerInfo & FULLMERGE) != 0) {
189                            registerInfo &= ~MERGE;
190                        }
191                    }
192                    break;
193                case 'c':
194                    String bcp = commandLine.getOptionValue("c");
195                    if (bcp != null && bcp.charAt(0) == ':') {
196                        bootClassPath = bootClassPath + bcp;
197                    } else {
198                        bootClassPath = bcp;
199                    }
200                    break;
201                case 'x':
202                    deodex = true;
203                    break;
204                case 'N':
205                    disassemble = false;
206                    break;
207                case 'D':
208                    doDump = true;
209                    dumpFileName = commandLine.getOptionValue("D", inputDexFileName + ".dump");
210                    break;
211                case 'W':
212                    write = true;
213                    outputDexFileName = commandLine.getOptionValue("W");
214                    break;
215                case 'S':
216                    sort = true;
217                    break;
218                case 'F':
219                    fixRegisters = true;
220                    break;
221                case 'V':
222                    verify = true;
223                    break;
224                default:
225                    assert false;
226            }
227        }
228
229        if (remainingArgs.length != 1) {
230            usage();
231            return;
232        }
233
234        inputDexFileName = remainingArgs[0];
235
236        try {
237            File dexFileFile = new File(inputDexFileName);
238            if (!dexFileFile.exists()) {
239                System.err.println("Can't find the file " + inputDexFileName);
240                System.exit(1);
241            }
242
243            //Read in and parse the dex file
244            DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false);
245
246            if (dexFile.isOdex()) {
247                if (doDump) {
248                    System.err.println("-D cannot be used with on odex file. Ignoring -D");
249                }
250                if (write) {
251                    System.err.println("-W cannot be used with an odex file. Ignoring -W");
252                }
253                if (!deodex) {
254                    System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
255                    System.err.println("won't be able to re-assemble the results unless you deodex it with the -x");
256                    System.err.println("option");
257                }
258            } else {
259                deodex = false;
260            }
261
262            if (disassemble) {
263                String[] bootClassPathDirsArray = new String[bootClassPathDirs.size()];
264                for (int i=0; i<bootClassPathDirsArray.length; i++) {
265                    bootClassPathDirsArray[i] = bootClassPathDirs.get(i);
266                }
267
268                baksmali.disassembleDexFile(dexFile, deodex, outputDirectory, bootClassPathDirsArray, bootClassPath,
269                        noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets,
270                        registerInfo, verify);
271            }
272
273            if ((doDump || write) && !dexFile.isOdex()) {
274                try
275                {
276                    dump.dump(dexFile, dumpFileName, outputDexFileName, sort);
277                }catch (IOException ex) {
278                    System.err.println("Error occured while writing dump file");
279                    ex.printStackTrace();
280                }
281            }
282        } catch (RuntimeException ex) {
283            System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:");
284            ex.printStackTrace();
285            System.exit(1);
286        } catch (Throwable ex) {
287            System.err.println("\n\nUNEXPECTED TOP-LEVEL ERROR:");
288            ex.printStackTrace();
289            System.exit(1);
290        }
291    }
292
293    /**
294     * Prints the usage message.
295     */
296    private static void usage(boolean printDebugOptions) {
297        baksmaliHelpFormatter formatter = new baksmaliHelpFormatter();
298        formatter.setWidth(ConsoleUtil.getConsoleWidth());
299
300        formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
301                "disassembles and/or dumps a dex file", basicOptions, "");
302
303        if (printDebugOptions) {
304            System.out.println();
305            System.out.println("Debug Options:");
306
307            StringBuffer sb = new StringBuffer();
308            formatter.renderOptions(sb, debugOptions);
309            System.out.println(sb.toString());
310        }
311    }
312
313    private static void usage() {
314        usage(false);
315    }
316
317    /**
318     * Prints the version message.
319     */
320    protected static void version() {
321        System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
322        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
323        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
324        System.exit(0);
325    }
326
327    private static void buildOptions() {
328        Option versionOption = OptionBuilder.withLongOpt("version")
329                .withDescription("prints the version then exits")
330                .create("v");
331
332        Option helpOption = OptionBuilder.withLongOpt("help")
333                .withDescription("prints the help message then exits. Specify twice for debug options")
334                .create("?");
335
336        Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
337                .withDescription("suppresses the output of the disassembly")
338                .create("N");
339
340        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
341                .withDescription("dumps the given dex file into a single annotated dump file named FILE" +
342                        " (<dexfile>.dump by default), along with the normal disassembly.")
343                .hasOptionalArg()
344                .withArgName("FILE")
345                .create("D");
346
347        Option writeDexOption = OptionBuilder.withLongOpt("write-dex")
348                .withDescription("additionally rewrites the input dex file to FILE")
349                .hasArg()
350                .withArgName("FILE")
351                .create("W");
352
353        Option outputDirOption = OptionBuilder.withLongOpt("output")
354                .withDescription("the directory where the disassembled files will be placed. The default is out")
355                .hasArg()
356                .withArgName("DIR")
357                .create("o");
358
359        Option sortOption = OptionBuilder.withLongOpt("sort")
360                .withDescription("sort the items in the dex file into a canonical order before dumping/writing")
361                .create("S");
362
363        Option fixSignedRegisterOption = OptionBuilder.withLongOpt("fix-signed-registers")
364                .withDescription("when dumping or rewriting, fix any registers in the debug info that are encoded as" +
365                        " a signed value")
366                .create("F");
367
368        Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
369                .withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method " +
370                        "parameters")
371                .create("p");
372
373        Option deodexerantOption = OptionBuilder.withLongOpt("deodex")
374                .withDescription("deodex the given odex file. This option is ignored if the input file is not an " +
375                        "odex file.")
376                .create("x");
377
378        Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
379                .withDescription("output the .locals directive with the number of non-parameter registers, rather" +
380                        " than the .register directive with the total number of register")
381                .create("l");
382
383        Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels")
384                .withDescription("create label names using a sequential numbering scheme per label type, rather than " +
385                        "using the bytecode address")
386                .create("s");
387
388        Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info")
389                .withDescription("don't write out debug info (.local, .param, .line, etc.)")
390                .create("b");
391
392        Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
393                .hasOptionalArgs()
394                .withArgName("REGISTER_INFO_TYPES")
395                .withValueSeparator(',')
396                .withDescription("print the specificed type(s) of register information for each instruction. " +
397                        "\"ARGS,DEST\" is the default if no types are specified.\nValid values are:\nALL: all " +
398                        "pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
399                        "post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
400                        "instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
401                        "pre-instruction register has been merged from more than 1 different post-instruction " +
402                        "register from its predecessors\nFULLMERGE: For each register that would be printed by " +
403                        "MERGE, also show the incoming register types that were merged")
404                .create("r");
405
406        Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
407                .withDescription("the bootclasspath jars to use, for analysis. Defaults to " +
408                        "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar. If the value begins with a " +
409                        ":, it will be appended to the default bootclasspath instead of replacing it")
410                .hasOptionalArg()
411                .withArgName("BOOTCLASSPATH")
412                .create("c");
413
414        Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
415                .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
416                        "directory")
417                .hasArg()
418                .withArgName("DIR")
419                .create("d");
420
421        Option codeOffsetOption = OptionBuilder.withLongOpt("code-offsets")
422                .withDescription("add comments to the disassembly containing the code offset for each address")
423                .create("f");
424
425        Option verifyDexOption = OptionBuilder.withLongOpt("verify")
426                .withDescription("perform bytecode verification")
427                .create("V");
428
429        basicOptions.addOption(versionOption);
430        basicOptions.addOption(helpOption);
431        basicOptions.addOption(outputDirOption);
432        basicOptions.addOption(noParameterRegistersOption);
433        basicOptions.addOption(deodexerantOption);
434        basicOptions.addOption(useLocalsOption);
435        basicOptions.addOption(sequentialLabelsOption);
436        basicOptions.addOption(noDebugInfoOption);
437        basicOptions.addOption(registerInfoOption);
438        basicOptions.addOption(classPathOption);
439        basicOptions.addOption(classPathDirOption);
440        basicOptions.addOption(codeOffsetOption);
441
442        debugOptions.addOption(dumpOption);
443        debugOptions.addOption(noDisassemblyOption);
444        debugOptions.addOption(writeDexOption);
445        debugOptions.addOption(sortOption);
446        debugOptions.addOption(fixSignedRegisterOption);
447        debugOptions.addOption(verifyDexOption);
448
449
450        for (Object option: basicOptions.getOptions()) {
451            options.addOption((Option)option);
452        }
453        for (Object option: debugOptions.getOptions()) {
454            options.addOption((Option)option);
455        }
456    }
457}