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