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