main.java revision 7bb622e0b16201d0a50e4bdb1f133cbd6477a19a
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.Code.Opcode;
33import org.jf.dexlib.DexFile;
34import org.jf.util.ConsoleUtil;
35import org.jf.util.SmaliHelpFormatter;
36
37import java.io.File;
38import java.io.IOException;
39import java.io.InputStream;
40import java.util.ArrayList;
41import java.util.List;
42import java.util.Locale;
43import java.util.Properties;
44
45public class main {
46
47    public static final String VERSION;
48
49    private static final Options basicOptions;
50    private static final Options debugOptions;
51    private static final Options options;
52
53    public static final int ALL = 1;
54    public static final int ALLPRE = 2;
55    public static final int ALLPOST = 4;
56    public static final int ARGS = 8;
57    public static final int DEST = 16;
58    public static final int MERGE = 32;
59    public static final int FULLMERGE = 64;
60
61    static {
62        options = new Options();
63        basicOptions = new Options();
64        debugOptions = new Options();
65        buildOptions();
66
67        InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
68        Properties properties = new Properties();
69        String version = "(unknown)";
70        try {
71            properties.load(templateStream);
72            version = properties.getProperty("application.version");
73        } catch (IOException ex) {
74        }
75        VERSION = version;
76    }
77
78    /**
79     * This class is uninstantiable.
80     */
81    private main() {
82    }
83
84    /**
85     * Run!
86     */
87    public static void main(String[] args) {
88        Locale locale = new Locale("en", "US");
89        Locale.setDefault(locale);
90
91        CommandLineParser parser = new PosixParser();
92        CommandLine commandLine;
93
94        try {
95            commandLine = parser.parse(options, args);
96        } catch (ParseException ex) {
97            usage();
98            return;
99        }
100
101        boolean disassemble = true;
102        boolean doDump = false;
103        boolean write = false;
104        boolean sort = false;
105        boolean fixRegisters = false;
106        boolean noParameterRegisters = false;
107        boolean useLocalsDirective = false;
108        boolean useSequentialLabels = false;
109        boolean outputDebugInfo = true;
110        boolean addCodeOffsets = false;
111        boolean noAccessorComments = false;
112        boolean deodex = false;
113        boolean verify = false;
114        boolean ignoreErrors = false;
115
116        int apiLevel = 14;
117
118        int registerInfo = 0;
119
120        String outputDirectory = "out";
121        String dumpFileName = null;
122        String outputDexFileName = null;
123        String inputDexFileName = null;
124        String bootClassPath = null;
125        StringBuffer extraBootClassPathEntries = new StringBuffer();
126        List<String> bootClassPathDirs = new ArrayList<String>();
127        bootClassPathDirs.add(".");
128        String inlineTable = null;
129
130        String[] remainingArgs = commandLine.getArgs();
131
132        Option[] options = commandLine.getOptions();
133
134        for (int i=0; i<options.length; i++) {
135            Option option = options[i];
136            String opt = option.getOpt();
137
138            switch (opt.charAt(0)) {
139                case 'v':
140                    version();
141                    return;
142                case '?':
143                    while (++i < options.length) {
144                        if (options[i].getOpt().charAt(0) == '?') {
145                            usage(true);
146                            return;
147                        }
148                    }
149                    usage(false);
150                    return;
151                case 'o':
152                    outputDirectory = commandLine.getOptionValue("o");
153                    break;
154                case 'p':
155                    noParameterRegisters = true;
156                    break;
157                case 'l':
158                    useLocalsDirective = true;
159                    break;
160                case 's':
161                    useSequentialLabels = true;
162                    break;
163                case 'b':
164                    outputDebugInfo = false;
165                    break;
166                case 'd':
167                    bootClassPathDirs.add(option.getValue());
168                    break;
169                case 'f':
170                    addCodeOffsets = true;
171                    break;
172                case 'r':
173                    String[] values = commandLine.getOptionValues('r');
174
175                    if (values == null || values.length == 0) {
176                        registerInfo = ARGS | DEST;
177                    } else {
178                        for (String value: values) {
179                            if (value.equalsIgnoreCase("ALL")) {
180                                registerInfo |= ALL;
181                            } else if (value.equalsIgnoreCase("ALLPRE")) {
182                                registerInfo |= ALLPRE;
183                            } else if (value.equalsIgnoreCase("ALLPOST")) {
184                                registerInfo |= ALLPOST;
185                            } else if (value.equalsIgnoreCase("ARGS")) {
186                                registerInfo |= ARGS;
187                            } else if (value.equalsIgnoreCase("DEST")) {
188                                registerInfo |= DEST;
189                            } else if (value.equalsIgnoreCase("MERGE")) {
190                                registerInfo |= MERGE;
191                            } else if (value.equalsIgnoreCase("FULLMERGE")) {
192                                registerInfo |= FULLMERGE;
193                            } else {
194                                usage();
195                                return;
196                            }
197                        }
198
199                        if ((registerInfo & FULLMERGE) != 0) {
200                            registerInfo &= ~MERGE;
201                        }
202                    }
203                    break;
204                case 'c':
205                    String bcp = commandLine.getOptionValue("c");
206                    if (bcp != null && bcp.charAt(0) == ':') {
207                        extraBootClassPathEntries.append(bcp);
208                    } else {
209                        bootClassPath = bcp;
210                    }
211                    break;
212                case 'x':
213                    deodex = true;
214                    break;
215                case 'm':
216                    noAccessorComments = true;
217                    break;
218                case 'a':
219                    apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
220                    break;
221                case 'N':
222                    disassemble = false;
223                    break;
224                case 'D':
225                    doDump = true;
226                    dumpFileName = commandLine.getOptionValue("D", inputDexFileName + ".dump");
227                    break;
228                case 'I':
229                    ignoreErrors = true;
230                    break;
231                case 'W':
232                    write = true;
233                    outputDexFileName = commandLine.getOptionValue("W");
234                    break;
235                case 'S':
236                    sort = true;
237                    break;
238                case 'F':
239                    fixRegisters = true;
240                    break;
241                case 'V':
242                    verify = true;
243                    break;
244                case 'T':
245                    inlineTable = commandLine.getOptionValue("T");
246                    break;
247                default:
248                    assert false;
249            }
250        }
251
252        if (remainingArgs.length != 1) {
253            usage();
254            return;
255        }
256
257        inputDexFileName = remainingArgs[0];
258
259        try {
260            File dexFileFile = new File(inputDexFileName);
261            if (!dexFileFile.exists()) {
262                System.err.println("Can't find the file " + inputDexFileName);
263                System.exit(1);
264            }
265
266            Opcode.updateMapsForApiLevel(apiLevel);
267
268            //Read in and parse the dex file
269            DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false);
270
271            if (dexFile.isOdex()) {
272                if (doDump) {
273                    System.err.println("-D cannot be used with on odex file. Ignoring -D");
274                }
275                if (write) {
276                    System.err.println("-W cannot be used with an odex file. Ignoring -W");
277                }
278                if (!deodex) {
279                    System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
280                    System.err.println("won't be able to re-assemble the results unless you deodex it with the -x");
281                    System.err.println("option");
282                }
283            } else {
284                deodex = false;
285
286                if (bootClassPath == null) {
287                    bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
288                }
289            }
290
291            if (disassemble) {
292                String[] bootClassPathDirsArray = new String[bootClassPathDirs.size()];
293                for (int i=0; i<bootClassPathDirsArray.length; i++) {
294                    bootClassPathDirsArray[i] = bootClassPathDirs.get(i);
295                }
296
297                baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, deodex, outputDirectory,
298                        bootClassPathDirsArray, bootClassPath, extraBootClassPathEntries.toString(),
299                        noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets,
300                        noAccessorComments, registerInfo, verify, ignoreErrors, inlineTable);
301            }
302
303            if ((doDump || write) && !dexFile.isOdex()) {
304                try
305                {
306                    dump.dump(dexFile, dumpFileName, outputDexFileName, sort);
307                }catch (IOException ex) {
308                    System.err.println("Error occured while writing dump file");
309                    ex.printStackTrace();
310                }
311            }
312        } catch (RuntimeException ex) {
313            System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:");
314            ex.printStackTrace();
315            System.exit(1);
316        } catch (Throwable ex) {
317            System.err.println("\n\nUNEXPECTED TOP-LEVEL ERROR:");
318            ex.printStackTrace();
319            System.exit(1);
320        }
321    }
322
323    /**
324     * Prints the usage message.
325     */
326    private static void usage(boolean printDebugOptions) {
327        SmaliHelpFormatter formatter = new SmaliHelpFormatter();
328        int consoleWidth = ConsoleUtil.getConsoleWidth();
329        if (consoleWidth <= 0) {
330            consoleWidth = 80;
331        }
332
333        formatter.setWidth(consoleWidth);
334
335        formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
336                "disassembles and/or dumps a dex file", basicOptions, printDebugOptions?debugOptions:null);
337    }
338
339    private static void usage() {
340        usage(false);
341    }
342
343    /**
344     * Prints the version message.
345     */
346    protected static void version() {
347        System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
348        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
349        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
350        System.exit(0);
351    }
352
353    private static void buildOptions() {
354        Option versionOption = OptionBuilder.withLongOpt("version")
355                .withDescription("prints the version then exits")
356                .create("v");
357
358        Option helpOption = OptionBuilder.withLongOpt("help")
359                .withDescription("prints the help message then exits. Specify twice for debug options")
360                .create("?");
361
362        Option outputDirOption = OptionBuilder.withLongOpt("output")
363                .withDescription("the directory where the disassembled files will be placed. The default is out")
364                .hasArg()
365                .withArgName("DIR")
366                .create("o");
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 noAccessorCommentsOption = OptionBuilder.withLongOpt("no-accessor-comments")
426                .withDescription("don't output helper comments for synthetic accessors")
427                .create("m");
428
429        Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
430                .withDescription("The numeric api-level of the file being disassembled. If not " +
431                        "specified, it defaults to 14 (ICS).")
432                .hasArg()
433                .withArgName("API_LEVEL")
434                .create("a");
435
436        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
437                .withDescription("dumps the given dex file into a single annotated dump file named FILE" +
438                        " (<dexfile>.dump by default), along with the normal disassembly")
439                .hasOptionalArg()
440                .withArgName("FILE")
441                .create("D");
442
443        Option ignoreErrorsOption = OptionBuilder.withLongOpt("ignore-errors")
444                .withDescription("ignores any non-fatal errors that occur while disassembling/deodexing," +
445                        " ignoring the class if needed, and continuing with the next class. The default" +
446                        " behavior is to stop disassembling and exit once an error is encountered")
447                .create("I");
448
449
450        Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
451                .withDescription("suppresses the output of the disassembly")
452                .create("N");
453
454        Option writeDexOption = OptionBuilder.withLongOpt("write-dex")
455                .withDescription("additionally rewrites the input dex file to FILE")
456                .hasArg()
457                .withArgName("FILE")
458                .create("W");
459
460        Option sortOption = OptionBuilder.withLongOpt("sort")
461                .withDescription("sort the items in the dex file into a canonical order before dumping/writing")
462                .create("S");
463
464        Option fixSignedRegisterOption = OptionBuilder.withLongOpt("fix-signed-registers")
465                .withDescription("when dumping or rewriting, fix any registers in the debug info that are encoded as" +
466                        " a signed value")
467                .create("F");
468
469        Option verifyDexOption = OptionBuilder.withLongOpt("verify")
470                .withDescription("perform bytecode verification")
471                .create("V");
472
473        Option inlineTableOption = OptionBuilder.withLongOpt("inline-table")
474                .withDescription("specify a file containing a custom inline method table to use for deodexing")
475                .hasArg()
476                .withArgName("FILE")
477                .create("T");
478
479        basicOptions.addOption(versionOption);
480        basicOptions.addOption(helpOption);
481        basicOptions.addOption(outputDirOption);
482        basicOptions.addOption(noParameterRegistersOption);
483        basicOptions.addOption(deodexerantOption);
484        basicOptions.addOption(useLocalsOption);
485        basicOptions.addOption(sequentialLabelsOption);
486        basicOptions.addOption(noDebugInfoOption);
487        basicOptions.addOption(registerInfoOption);
488        basicOptions.addOption(classPathOption);
489        basicOptions.addOption(classPathDirOption);
490        basicOptions.addOption(codeOffsetOption);
491        basicOptions.addOption(noAccessorCommentsOption);
492        basicOptions.addOption(apiLevelOption);
493
494        debugOptions.addOption(dumpOption);
495        debugOptions.addOption(ignoreErrorsOption);
496        debugOptions.addOption(noDisassemblyOption);
497        debugOptions.addOption(writeDexOption);
498        debugOptions.addOption(sortOption);
499        debugOptions.addOption(fixSignedRegisterOption);
500        debugOptions.addOption(verifyDexOption);
501        debugOptions.addOption(inlineTableOption);
502
503        for (Object option: basicOptions.getOptions()) {
504            options.addOption((Option)option);
505        }
506        for (Object option: debugOptions.getOptions()) {
507            options.addOption((Option)option);
508        }
509    }
510}