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