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