main.java revision 9cf653f0936511280bd37bb69301f716732dcf4c
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 com.google.common.collect.Lists;
32import org.apache.commons.cli.*;
33import org.jf.dexlib2.DexFileFactory;
34import org.jf.dexlib2.analysis.CustomInlineMethodResolver;
35import org.jf.dexlib2.analysis.InlineMethodResolver;
36import org.jf.dexlib2.dexbacked.DexBackedDexFile;
37import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
38import org.jf.util.ConsoleUtil;
39import org.jf.util.SmaliHelpFormatter;
40
41import javax.annotation.Nonnull;
42import java.io.File;
43import java.io.IOException;
44import java.io.InputStream;
45import java.util.List;
46import java.util.Locale;
47import java.util.Properties;
48
49public class main {
50
51    public static final String VERSION;
52
53    private static final Options basicOptions;
54    private static final Options debugOptions;
55    private static final Options options;
56
57    static {
58        options = new Options();
59        basicOptions = new Options();
60        debugOptions = new Options();
61        buildOptions();
62
63        InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
64        if (templateStream != null) {
65            Properties properties = new Properties();
66            String version = "(unknown)";
67            try {
68                properties.load(templateStream);
69                version = properties.getProperty("application.version");
70            } catch (IOException ex) {
71                // ignore
72            }
73            VERSION = version;
74        } else {
75            VERSION = "[unknown version]";
76        }
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) throws IOException {
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        baksmaliOptions options = new baksmaliOptions();
103
104        boolean disassemble = true;
105        boolean doDump = false;
106        String dumpFileName = null;
107        boolean setBootClassPath = false;
108
109        String[] remainingArgs = commandLine.getArgs();
110        Option[] clOptions = commandLine.getOptions();
111
112        for (int i=0; i<clOptions.length; i++) {
113            Option option = clOptions[i];
114            String opt = option.getOpt();
115
116            switch (opt.charAt(0)) {
117                case 'v':
118                    version();
119                    return;
120                case '?':
121                    while (++i < clOptions.length) {
122                        if (clOptions[i].getOpt().charAt(0) == '?') {
123                            usage(true);
124                            return;
125                        }
126                    }
127                    usage(false);
128                    return;
129                case 'o':
130                    options.outputDirectory = commandLine.getOptionValue("o");
131                    break;
132                case 'p':
133                    options.noParameterRegisters = true;
134                    break;
135                case 'l':
136                    options.useLocalsDirective = true;
137                    break;
138                case 's':
139                    options.useSequentialLabels = true;
140                    break;
141                case 'b':
142                    options.outputDebugInfo = false;
143                    break;
144                case 'd':
145                    options.bootClassPathDirs.add(option.getValue());
146                    break;
147                case 'f':
148                    options.addCodeOffsets = true;
149                    break;
150                case 'r':
151                    String[] values = commandLine.getOptionValues('r');
152                    int registerInfo = 0;
153
154                    if (values == null || values.length == 0) {
155                        registerInfo = baksmaliOptions.ARGS | baksmaliOptions.DEST;
156                    } else {
157                        for (String value: values) {
158                            if (value.equalsIgnoreCase("ALL")) {
159                                registerInfo |= baksmaliOptions.ALL;
160                            } else if (value.equalsIgnoreCase("ALLPRE")) {
161                                registerInfo |= baksmaliOptions.ALLPRE;
162                            } else if (value.equalsIgnoreCase("ALLPOST")) {
163                                registerInfo |= baksmaliOptions.ALLPOST;
164                            } else if (value.equalsIgnoreCase("ARGS")) {
165                                registerInfo |= baksmaliOptions.ARGS;
166                            } else if (value.equalsIgnoreCase("DEST")) {
167                                registerInfo |= baksmaliOptions.DEST;
168                            } else if (value.equalsIgnoreCase("MERGE")) {
169                                registerInfo |= baksmaliOptions.MERGE;
170                            } else if (value.equalsIgnoreCase("FULLMERGE")) {
171                                registerInfo |= baksmaliOptions.FULLMERGE;
172                            } else {
173                                usage();
174                                return;
175                            }
176                        }
177
178                        if ((registerInfo & baksmaliOptions.FULLMERGE) != 0) {
179                            registerInfo &= ~baksmaliOptions.MERGE;
180                        }
181                    }
182                    options.registerInfo = registerInfo;
183                    break;
184                case 'c':
185                    String bcp = commandLine.getOptionValue("c");
186                    if (bcp != null && bcp.charAt(0) == ':') {
187                        options.addExtraClassPath(bcp);
188                    } else {
189                        setBootClassPath = true;
190                        options.setBootClassPath(bcp);
191                    }
192                    break;
193                case 'x':
194                    options.deodex = true;
195                    break;
196                case 'm':
197                    options.noAccessorComments = true;
198                    break;
199                case 'a':
200                    options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
201                    break;
202                case 'j':
203                    options.jobs = Integer.parseInt(commandLine.getOptionValue("j"));
204                    break;
205                case 'N':
206                    disassemble = false;
207                    break;
208                case 'D':
209                    doDump = true;
210                    dumpFileName = commandLine.getOptionValue("D");
211                    break;
212                case 'I':
213                    options.ignoreErrors = true;
214                    break;
215                case 'T':
216                    options.inlineResolver = new CustomInlineMethodResolver(options.classPath, new File(commandLine.getOptionValue("T")));
217                    break;
218                case 'K':
219                    options.checkPackagePrivateAccess = true;
220                    break;
221                case 'R':
222                    String rif = commandLine.getOptionValue("R");
223                    options.setResourceIdFiles(rif);
224                    break;
225                default:
226                    assert false;
227            }
228        }
229
230        if (remainingArgs.length != 1) {
231            usage();
232            return;
233        }
234
235        if (options.jobs <= 0) {
236            options.jobs = Runtime.getRuntime().availableProcessors();
237            if (options.jobs > 6) {
238                options.jobs = 6;
239            }
240        }
241
242        String inputDexFileName = remainingArgs[0];
243
244        File dexFileFile = new File(inputDexFileName);
245        if (!dexFileFile.exists()) {
246            System.err.println("Can't find the file " + inputDexFileName);
247            System.exit(1);
248        }
249
250        //Read in and parse the dex file
251        DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.apiLevel);
252
253        if (dexFile.isOdexFile()) {
254            if (!options.deodex) {
255                System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
256                System.err.println("won't be able to re-assemble the results unless you deodex it with the -x");
257                System.err.println("option");
258            }
259        } else {
260            options.deodex = false;
261        }
262
263        if (!setBootClassPath && (options.deodex || options.registerInfo != 0)) {
264            if (dexFile instanceof DexBackedOdexFile) {
265                options.bootClassPathEntries = ((DexBackedOdexFile)dexFile).getDependencies();
266            } else {
267                options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel);
268            }
269        }
270
271        if (options.inlineResolver == null && dexFile instanceof DexBackedOdexFile) {
272            options.inlineResolver =
273                    InlineMethodResolver.createInlineMethodResolver(((DexBackedOdexFile)dexFile).getOdexVersion());
274        }
275
276        boolean errorOccurred = false;
277        if (disassemble) {
278            errorOccurred = !baksmali.disassembleDexFile(dexFile, options);
279        }
280
281        if (doDump) {
282            if (dumpFileName == null) {
283                dumpFileName = commandLine.getOptionValue(inputDexFileName + ".dump");
284            }
285            dump.dump(dexFile, dumpFileName, options.apiLevel);
286        }
287
288        if (errorOccurred) {
289            System.exit(1);
290        }
291    }
292
293    /**
294     * Prints the usage message.
295     */
296    private static void usage(boolean printDebugOptions) {
297        SmaliHelpFormatter formatter = new SmaliHelpFormatter();
298        int consoleWidth = ConsoleUtil.getConsoleWidth();
299        if (consoleWidth <= 0) {
300            consoleWidth = 80;
301        }
302
303        formatter.setWidth(consoleWidth);
304
305        formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
306                "disassembles and/or dumps a dex file", basicOptions, printDebugOptions?debugOptions:null);
307    }
308
309    private static void usage() {
310        usage(false);
311    }
312
313    /**
314     * Prints the version message.
315     */
316    protected static void version() {
317        System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
318        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
319        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
320        System.exit(0);
321    }
322
323    @SuppressWarnings("AccessStaticViaInstance")
324    private static void buildOptions() {
325        Option versionOption = OptionBuilder.withLongOpt("version")
326                .withDescription("prints the version then exits")
327                .create("v");
328
329        Option helpOption = OptionBuilder.withLongOpt("help")
330                .withDescription("prints the help message then exits. Specify twice for debug options")
331                .create("?");
332
333        Option outputDirOption = OptionBuilder.withLongOpt("output")
334                .withDescription("the directory where the disassembled files will be placed. The default is out")
335                .hasArg()
336                .withArgName("DIR")
337                .create("o");
338
339        Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
340                .withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method " +
341                        "parameters")
342                .create("p");
343
344        Option deodexerantOption = OptionBuilder.withLongOpt("deodex")
345                .withDescription("deodex the given odex file. This option is ignored if the input file is not an " +
346                        "odex file")
347                .create("x");
348
349        Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
350                .withDescription("output the .locals directive with the number of non-parameter registers, rather" +
351                        " than the .register directive with the total number of register")
352                .create("l");
353
354        Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels")
355                .withDescription("create label names using a sequential numbering scheme per label type, rather than " +
356                        "using the bytecode address")
357                .create("s");
358
359        Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info")
360                .withDescription("don't write out debug info (.local, .param, .line, etc.)")
361                .create("b");
362
363        Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
364                .hasOptionalArgs()
365                .withArgName("REGISTER_INFO_TYPES")
366                .withValueSeparator(',')
367                .withDescription("print the specificed type(s) of register information for each instruction. " +
368                        "\"ARGS,DEST\" is the default if no types are specified.\nValid values are:\nALL: all " +
369                        "pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
370                        "post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
371                        "instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
372                        "pre-instruction register has been merged from more than 1 different post-instruction " +
373                        "register from its predecessors\nFULLMERGE: For each register that would be printed by " +
374                        "MERGE, also show the incoming register types that were merged")
375                .create("r");
376
377        Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
378                .withDescription("the bootclasspath jars to use, for analysis. Defaults to " +
379                        "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar. If the value begins with a " +
380                        ":, it will be appended to the default bootclasspath instead of replacing it")
381                .hasOptionalArg()
382                .withArgName("BOOTCLASSPATH")
383                .create("c");
384
385        Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
386                .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
387                        "directory")
388                .hasArg()
389                .withArgName("DIR")
390                .create("d");
391
392        Option codeOffsetOption = OptionBuilder.withLongOpt("code-offsets")
393                .withDescription("add comments to the disassembly containing the code offset for each address")
394                .create("f");
395
396        Option noAccessorCommentsOption = OptionBuilder.withLongOpt("no-accessor-comments")
397                .withDescription("don't output helper comments for synthetic accessors")
398                .create("m");
399
400        Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
401                .withDescription("The numeric api-level of the file being disassembled. If not " +
402                        "specified, it defaults to 15 (ICS).")
403                .hasArg()
404                .withArgName("API_LEVEL")
405                .create("a");
406
407        Option jobsOption = OptionBuilder.withLongOpt("jobs")
408                .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " +
409                        "maximum of 6")
410                .hasArg()
411                .withArgName("NUM_THREADS")
412                .create("j");
413
414        Option resourceIdFilesOption = OptionBuilder.withLongOpt("resource-id-files")
415                .withDescription("the resource ID files to use, for analysis. A colon-separated list of prefix=file " +
416                        "pairs.  For example R=res/values/public.xml:" +
417                        "android.R=$ANDROID_HOME/platforms/android-19/data/res/values/public.xml")
418                .hasArg()
419                .withArgName("FILES")
420                .create("i");
421
422        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
423                .withDescription("dumps the given dex file into a single annotated dump file named FILE" +
424                        " (<dexfile>.dump by default), along with the normal disassembly")
425                .hasOptionalArg()
426                .withArgName("FILE")
427                .create("D");
428
429        Option ignoreErrorsOption = OptionBuilder.withLongOpt("ignore-errors")
430                .withDescription("ignores any non-fatal errors that occur while disassembling/deodexing," +
431                        " ignoring the class if needed, and continuing with the next class. The default" +
432                        " behavior is to stop disassembling and exit once an error is encountered")
433                .create("I");
434
435        Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
436                .withDescription("suppresses the output of the disassembly")
437                .create("N");
438
439        Option inlineTableOption = OptionBuilder.withLongOpt("inline-table")
440                .withDescription("specify a file containing a custom inline method table to use for deodexing")
441                .hasArg()
442                .withArgName("FILE")
443                .create("T");
444
445        Option checkPackagePrivateAccess = OptionBuilder.withLongOpt("check-package-private-access")
446                .withDescription("When deodexing, use the new virtual table generation logic that " +
447                        "prevents overriding an inaccessible package private method. This is a temporary option " +
448                        "that will be removed once this new functionality can be tied to a specific api level.")
449                .create("K");
450
451        basicOptions.addOption(versionOption);
452        basicOptions.addOption(helpOption);
453        basicOptions.addOption(outputDirOption);
454        basicOptions.addOption(noParameterRegistersOption);
455        basicOptions.addOption(deodexerantOption);
456        basicOptions.addOption(useLocalsOption);
457        basicOptions.addOption(sequentialLabelsOption);
458        basicOptions.addOption(noDebugInfoOption);
459        basicOptions.addOption(registerInfoOption);
460        basicOptions.addOption(classPathOption);
461        basicOptions.addOption(classPathDirOption);
462        basicOptions.addOption(codeOffsetOption);
463        basicOptions.addOption(noAccessorCommentsOption);
464        basicOptions.addOption(apiLevelOption);
465        basicOptions.addOption(jobsOption);
466        basicOptions.addOption(resourceIdFilesOption);
467
468        debugOptions.addOption(dumpOption);
469        debugOptions.addOption(ignoreErrorsOption);
470        debugOptions.addOption(noDisassemblyOption);
471        debugOptions.addOption(inlineTableOption);
472        debugOptions.addOption(checkPackagePrivateAccess);
473
474        for (Object option: basicOptions.getOptions()) {
475            options.addOption((Option)option);
476        }
477        for (Object option: debugOptions.getOptions()) {
478            options.addOption((Option)option);
479        }
480    }
481
482    @Nonnull
483    private static List<String> getDefaultBootClassPathForApi(int apiLevel) {
484        if (apiLevel < 9) {
485            return Lists.newArrayList(
486                    "/system/framework/core.jar",
487                    "/system/framework/ext.jar",
488                    "/system/framework/framework.jar",
489                    "/system/framework/android.policy.jar",
490                    "/system/framework/services.jar");
491        } else if (apiLevel < 12) {
492            return Lists.newArrayList(
493                    "/system/framework/core.jar",
494                    "/system/framework/bouncycastle.jar",
495                    "/system/framework/ext.jar",
496                    "/system/framework/framework.jar",
497                    "/system/framework/android.policy.jar",
498                    "/system/framework/services.jar",
499                    "/system/framework/core-junit.jar");
500        } else if (apiLevel < 14) {
501            return Lists.newArrayList(
502                    "/system/framework/core.jar",
503                    "/system/framework/apache-xml.jar",
504                    "/system/framework/bouncycastle.jar",
505                    "/system/framework/ext.jar",
506                    "/system/framework/framework.jar",
507                    "/system/framework/android.policy.jar",
508                    "/system/framework/services.jar",
509                    "/system/framework/core-junit.jar");
510        } else if (apiLevel < 16) {
511            return Lists.newArrayList(
512                    "/system/framework/core.jar",
513                    "/system/framework/core-junit.jar",
514                    "/system/framework/bouncycastle.jar",
515                    "/system/framework/ext.jar",
516                    "/system/framework/framework.jar",
517                    "/system/framework/android.policy.jar",
518                    "/system/framework/services.jar",
519                    "/system/framework/apache-xml.jar",
520                    "/system/framework/filterfw.jar");
521
522        } else {
523            // this is correct as of api 17/4.2.2
524            return Lists.newArrayList(
525                    "/system/framework/core.jar",
526                    "/system/framework/core-junit.jar",
527                    "/system/framework/bouncycastle.jar",
528                    "/system/framework/ext.jar",
529                    "/system/framework/framework.jar",
530                    "/system/framework/telephony-common.jar",
531                    "/system/framework/mms-common.jar",
532                    "/system/framework/android.policy.jar",
533                    "/system/framework/services.jar",
534                    "/system/framework/apache-xml.jar");
535        }
536    }
537}
538