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