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