main.java revision bde73b303cb004c2441ec7a001b48bb91301ab8b
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2010 Ben Gruver (JesusFreke)
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29package org.jf.baksmali;
30
31import org.apache.commons.cli.*;
32import org.jf.dexlib.DexFile;
33import org.jf.util.*;
34
35import java.io.File;
36import java.io.InputStream;
37import java.io.IOException;
38import java.util.ArrayList;
39import java.util.List;
40import java.util.Properties;
41
42public class main {
43
44    public static final String VERSION;
45
46    private static final Options basicOptions;
47    private static final Options debugOptions;
48    private static final Options options;
49
50    public static final int ALL = 1;
51    public static final int ALLPRE = 2;
52    public static final int ALLPOST = 4;
53    public static final int ARGS = 8;
54    public static final int DEST = 16;
55    public static final int MERGE = 32;
56    public static final int FULLMERGE = 64;
57
58    static {
59        options = new Options();
60        basicOptions = new Options();
61        debugOptions = new Options();
62        buildOptions();
63
64        InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
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        }
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) {
85        CommandLineParser parser = new PosixParser();
86        CommandLine commandLine;
87
88        try {
89            commandLine = parser.parse(options, args);
90        } catch (ParseException ex) {
91            usage();
92            return;
93        }
94
95        boolean disassemble = true;
96        boolean doDump = false;
97        boolean write = false;
98        boolean sort = false;
99        boolean fixRegisters = false;
100        boolean noParameterRegisters = false;
101        boolean useLocalsDirective = false;
102        boolean useSequentialLabels = false;
103        boolean outputDebugInfo = true;
104        boolean addCodeOffsets = false;
105        boolean deodex = false;
106        boolean verify = false;
107
108        int registerInfo = 0;
109
110        String outputDirectory = "out";
111        String dumpFileName = null;
112        String outputDexFileName = null;
113        String inputDexFileName = null;
114        String bootClassPath = null;
115        StringBuffer extraBootClassPathEntries = new StringBuffer();
116        List<String> bootClassPathDirs = new ArrayList<String>();
117        bootClassPathDirs.add(".");
118
119
120        String[] remainingArgs = commandLine.getArgs();
121
122        Option[] options = commandLine.getOptions();
123
124        for (int i=0; i<options.length; i++) {
125            Option option = options[i];
126            String opt = option.getOpt();
127
128            switch (opt.charAt(0)) {
129                case 'v':
130                    version();
131                    return;
132                case '?':
133                    while (++i < options.length) {
134                        if (options[i].getOpt().charAt(0) == '?') {
135                            usage(true);
136                            return;
137                        }
138                    }
139                    usage(false);
140                    return;
141                case 'o':
142                    outputDirectory = commandLine.getOptionValue("o");
143                    break;
144                case 'p':
145                    noParameterRegisters = true;
146                    break;
147                case 'l':
148                    useLocalsDirective = true;
149                    break;
150                case 's':
151                    useSequentialLabels = true;
152                    break;
153                case 'b':
154                    outputDebugInfo = false;
155                    break;
156                case 'd':
157                    bootClassPathDirs.add(option.getValue());
158                    break;
159                case 'f':
160                    addCodeOffsets = true;
161                    break;
162                case 'r':
163                    String[] values = commandLine.getOptionValues('r');
164
165                    if (values == null || values.length == 0) {
166                        registerInfo = ARGS | DEST;
167                    } else {
168                        for (String value: values) {
169                            if (value.equalsIgnoreCase("ALL")) {
170                                registerInfo |= ALL;
171                            } else if (value.equalsIgnoreCase("ALLPRE")) {
172                                registerInfo |= ALLPRE;
173                            } else if (value.equalsIgnoreCase("ALLPOST")) {
174                                registerInfo |= ALLPOST;
175                            } else if (value.equalsIgnoreCase("ARGS")) {
176                                registerInfo |= ARGS;
177                            } else if (value.equalsIgnoreCase("DEST")) {
178                                registerInfo |= DEST;
179                            } else if (value.equalsIgnoreCase("MERGE")) {
180                                registerInfo |= MERGE;
181                            } else if (value.equalsIgnoreCase("FULLMERGE")) {
182                                registerInfo |= FULLMERGE;
183                            } else {
184                                usage();
185                                return;
186                            }
187                        }
188
189                        if ((registerInfo & FULLMERGE) != 0) {
190                            registerInfo &= ~MERGE;
191                        }
192                    }
193                    break;
194                case 'c':
195                    String bcp = commandLine.getOptionValue("c");
196                    if (bcp != null && bcp.charAt(0) == ':') {
197                        extraBootClassPathEntries.append(bcp);
198                    } else {
199                        bootClassPath = bcp;
200                    }
201                    break;
202                case 'x':
203                    deodex = true;
204                    break;
205                case 'N':
206                    disassemble = false;
207                    break;
208                case 'D':
209                    doDump = true;
210                    dumpFileName = commandLine.getOptionValue("D", inputDexFileName + ".dump");
211                    break;
212                case 'W':
213                    write = true;
214                    outputDexFileName = commandLine.getOptionValue("W");
215                    break;
216                case 'S':
217                    sort = true;
218                    break;
219                case 'F':
220                    fixRegisters = true;
221                    break;
222                case 'V':
223                    verify = true;
224                    break;
225                default:
226                    assert false;
227            }
228        }
229
230        if (remainingArgs.length != 1) {
231            usage();
232            return;
233        }
234
235        inputDexFileName = remainingArgs[0];
236
237        try {
238            File dexFileFile = new File(inputDexFileName);
239            if (!dexFileFile.exists()) {
240                System.err.println("Can't find the file " + inputDexFileName);
241                System.exit(1);
242            }
243
244            //Read in and parse the dex file
245            DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false);
246
247            if (dexFile.isOdex()) {
248                if (doDump) {
249                    System.err.println("-D cannot be used with on odex file. Ignoring -D");
250                }
251                if (write) {
252                    System.err.println("-W cannot be used with an odex file. Ignoring -W");
253                }
254                if (!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                deodex = false;
261
262                if (bootClassPath == null) {
263                    bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
264                }
265            }
266
267            if (disassemble) {
268                String[] bootClassPathDirsArray = new String[bootClassPathDirs.size()];
269                for (int i=0; i<bootClassPathDirsArray.length; i++) {
270                    bootClassPathDirsArray[i] = bootClassPathDirs.get(i);
271                }
272
273                baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, deodex, outputDirectory,
274                        bootClassPathDirsArray, bootClassPath, extraBootClassPathEntries.toString(),
275                        noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets,
276                        registerInfo, verify);
277            }
278
279            if ((doDump || write) && !dexFile.isOdex()) {
280                try
281                {
282                    dump.dump(dexFile, dumpFileName, outputDexFileName, sort);
283                }catch (IOException ex) {
284                    System.err.println("Error occured while writing dump file");
285                    ex.printStackTrace();
286                }
287            }
288        } catch (RuntimeException ex) {
289            System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:");
290            ex.printStackTrace();
291            System.exit(1);
292        } catch (Throwable ex) {
293            System.err.println("\n\nUNEXPECTED TOP-LEVEL ERROR:");
294            ex.printStackTrace();
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        formatter.setWidth(ConsoleUtil.getConsoleWidth());
305
306        formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
307                "disassembles and/or dumps a dex file", basicOptions, "");
308
309        if (printDebugOptions) {
310            System.out.println();
311            System.out.println("Debug Options:");
312
313            StringBuffer sb = new StringBuffer();
314            formatter.renderOptions(sb, debugOptions);
315            System.out.println(sb.toString());
316        }
317    }
318
319    private static void usage() {
320        usage(false);
321    }
322
323    /**
324     * Prints the version message.
325     */
326    protected static void version() {
327        System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
328        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
329        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
330        System.exit(0);
331    }
332
333    private static void buildOptions() {
334        Option versionOption = OptionBuilder.withLongOpt("version")
335                .withDescription("prints the version then exits")
336                .create("v");
337
338        Option helpOption = OptionBuilder.withLongOpt("help")
339                .withDescription("prints the help message then exits. Specify twice for debug options")
340                .create("?");
341
342        Option outputDirOption = OptionBuilder.withLongOpt("output")
343                .withDescription("the directory where the disassembled files will be placed. The default is out")
344                .hasArg()
345                .withArgName("DIR")
346                .create("o");
347
348        Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
349                .withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method " +
350                        "parameters")
351                .create("p");
352
353        Option deodexerantOption = OptionBuilder.withLongOpt("deodex")
354                .withDescription("deodex the given odex file. This option is ignored if the input file is not an " +
355                        "odex file")
356                .create("x");
357
358        Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
359                .withDescription("output the .locals directive with the number of non-parameter registers, rather" +
360                        " than the .register directive with the total number of register")
361                .create("l");
362
363        Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels")
364                .withDescription("create label names using a sequential numbering scheme per label type, rather than " +
365                        "using the bytecode address")
366                .create("s");
367
368        Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info")
369                .withDescription("don't write out debug info (.local, .param, .line, etc.)")
370                .create("b");
371
372        Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
373                .hasOptionalArgs()
374                .withArgName("REGISTER_INFO_TYPES")
375                .withValueSeparator(',')
376                .withDescription("print the specificed type(s) of register information for each instruction. " +
377                        "\"ARGS,DEST\" is the default if no types are specified.\nValid values are:\nALL: all " +
378                        "pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
379                        "post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
380                        "instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
381                        "pre-instruction register has been merged from more than 1 different post-instruction " +
382                        "register from its predecessors\nFULLMERGE: For each register that would be printed by " +
383                        "MERGE, also show the incoming register types that were merged")
384                .create("r");
385
386        Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
387                .withDescription("the bootclasspath jars to use, for analysis. Defaults to " +
388                        "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar. If the value begins with a " +
389                        ":, it will be appended to the default bootclasspath instead of replacing it")
390                .hasOptionalArg()
391                .withArgName("BOOTCLASSPATH")
392                .create("c");
393
394        Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
395                .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
396                        "directory")
397                .hasArg()
398                .withArgName("DIR")
399                .create("d");
400
401        Option codeOffsetOption = OptionBuilder.withLongOpt("code-offsets")
402                .withDescription("add comments to the disassembly containing the code offset for each address")
403                .create("f");
404
405
406
407        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
408                .withDescription("dumps the given dex file into a single annotated dump file named FILE" +
409                        " (<dexfile>.dump by default), along with the normal disassembly")
410                .hasOptionalArg()
411                .withArgName("FILE")
412                .create("D");
413
414        Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
415                .withDescription("suppresses the output of the disassembly")
416                .create("N");
417
418        Option writeDexOption = OptionBuilder.withLongOpt("write-dex")
419                .withDescription("additionally rewrites the input dex file to FILE")
420                .hasArg()
421                .withArgName("FILE")
422                .create("W");
423
424        Option sortOption = OptionBuilder.withLongOpt("sort")
425                .withDescription("sort the items in the dex file into a canonical order before dumping/writing")
426                .create("S");
427
428        Option fixSignedRegisterOption = OptionBuilder.withLongOpt("fix-signed-registers")
429                .withDescription("when dumping or rewriting, fix any registers in the debug info that are encoded as" +
430                        " a signed value")
431                .create("F");
432
433        Option verifyDexOption = OptionBuilder.withLongOpt("verify")
434                .withDescription("perform bytecode verification")
435                .create("V");
436
437        basicOptions.addOption(versionOption);
438        basicOptions.addOption(helpOption);
439        basicOptions.addOption(outputDirOption);
440        basicOptions.addOption(noParameterRegistersOption);
441        basicOptions.addOption(deodexerantOption);
442        basicOptions.addOption(useLocalsOption);
443        basicOptions.addOption(sequentialLabelsOption);
444        basicOptions.addOption(noDebugInfoOption);
445        basicOptions.addOption(registerInfoOption);
446        basicOptions.addOption(classPathOption);
447        basicOptions.addOption(classPathDirOption);
448        basicOptions.addOption(codeOffsetOption);
449
450        debugOptions.addOption(dumpOption);
451        debugOptions.addOption(noDisassemblyOption);
452        debugOptions.addOption(writeDexOption);
453        debugOptions.addOption(sortOption);
454        debugOptions.addOption(fixSignedRegisterOption);
455        debugOptions.addOption(verifyDexOption);
456
457
458        for (Object option: basicOptions.getOptions()) {
459            options.addOption((Option)option);
460        }
461        for (Object option: debugOptions.getOptions()) {
462            options.addOption((Option)option);
463        }
464    }
465}