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