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