main.java revision 5b908115c006d791f8198316302453455a650c0c
190d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber/*
2f71323e297a928af368937089d3ed71239786f86Andreas Huber * Copyright (C) 2007 The Android Open Source Project
390d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber *
4f71323e297a928af368937089d3ed71239786f86Andreas Huber * Licensed under the Apache License, Version 2.0 (the "License");
5f71323e297a928af368937089d3ed71239786f86Andreas Huber * you may not use this file except in compliance with the License.
6f71323e297a928af368937089d3ed71239786f86Andreas Huber * You may obtain a copy of the License at
7f71323e297a928af368937089d3ed71239786f86Andreas Huber *
8f71323e297a928af368937089d3ed71239786f86Andreas Huber *      http://www.apache.org/licenses/LICENSE-2.0
990d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber *
1090d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber * Unless required by applicable law or agreed to in writing, software
1190d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber * distributed under the License is distributed on an "AS IS" BASIS,
12b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian * See the License for the specific language governing permissions and
1490d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber * limitations under the License.
1590d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber */
16b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian
17b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanianpackage org.jf.baksmali;
18b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian
19b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanianimport org.apache.commons.cli.*;
20ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport org.jf.baksmali.Deodex.Deodexerant;
21ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport org.jf.dexlib.DexFile;
22ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang
23ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport java.io.File;
24ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport java.io.InputStream;
25ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport java.io.IOException;
2690d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huberimport java.util.Properties;
2790d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber
28ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangpublic class main {
29ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang
30ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang    public static final String VERSION;
3179f15823c34ae1e423108295e416213200bb280fAndreas Huber
3279f15823c34ae1e423108295e416213200bb280fAndreas Huber    private static final Options basicOptions;
3379f15823c34ae1e423108295e416213200bb280fAndreas Huber    private static final Options debugOptions;
34ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang    private static final Options options;
35ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang
36ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang    public static final int ALL = 1;
37ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang    public static final int ALLPRE = 2;
38ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang    public static final int ALLPOST = 4;
39ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang    public static final int ARGS = 8;
4090d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber    public static final int DEST = 16;
4179f15823c34ae1e423108295e416213200bb280fAndreas Huber    public static final int MERGE = 32;
4279f15823c34ae1e423108295e416213200bb280fAndreas Huber    public static final int FULLMERGE = 64;
4390d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber
4490d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber    static {
4590d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        options = new Options();
4690d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        basicOptions = new Options();
4790d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        debugOptions = new Options();
4890d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        buildOptions();
4990d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber
5090d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
5190d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        Properties properties = new Properties();
5290d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        String version = "(unknown)";
5390d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        try {
5479f15823c34ae1e423108295e416213200bb280fAndreas Huber            properties.load(templateStream);
55b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian            version = properties.getProperty("application.version");
56b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian        } catch (IOException ex) {
5790d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber        }
58b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian        VERSION = version;
59b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian    }
60
61    /**
62     * This class is uninstantiable.
63     */
64    private main() {
65    }
66
67    /**
68     * Run!
69     */
70    public static void main(String[] args) {
71        CommandLineParser parser = new PosixParser();
72        CommandLine commandLine;
73
74        try {
75            commandLine = parser.parse(options, args);
76        } catch (ParseException ex) {
77            usage();
78            return;
79        }
80
81        boolean disassemble = true;
82        boolean doDump = false;
83        boolean write = false;
84        boolean sort = false;
85        boolean fixRegisters = false;
86        boolean noParameterRegisters = false;
87        boolean useLocalsDirective = false;
88        boolean useSequentialLabels = false;
89        boolean outputDebugInfo = true;
90
91        int registerInfo = 0;
92
93        String outputDirectory = "out";
94        String dumpFileName = null;
95        String outputDexFileName = null;
96        String inputDexFileName = null;
97        String deodexerantHost = null;
98        String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
99        String bootClassPathDir = ".";
100        int deodexerantPort = 0;
101
102        String[] remainingArgs = commandLine.getArgs();
103
104        Option[] options = commandLine.getOptions();
105
106        for (int i=0; i<options.length; i++) {
107            Option option = options[i];
108            String opt = option.getOpt();
109
110            switch (opt.charAt(0)) {
111                case 'v':
112                    version();
113                    return;
114                case '?':
115                    while (++i < options.length) {
116                        if (options[i].getOpt().charAt(0) == '?') {
117                            usage(true);
118                            return;
119                        }
120                    }
121                    usage(false);
122                    return;
123                case 'o':
124                    outputDirectory = commandLine.getOptionValue("o");
125                    break;
126                case 'p':
127                    noParameterRegisters = true;
128                    break;
129                case 'l':
130                    useLocalsDirective = true;
131                    break;
132                case 's':
133                    useSequentialLabels = true;
134                    break;
135                case 'b':
136                    outputDebugInfo = false;
137                    break;
138                case 'd':
139                    bootClassPathDir = commandLine.getOptionValue("d");
140                    break;
141                case 'r':
142                    String[] values = commandLine.getOptionValues('r');
143
144                    if (values == null || values.length == 0) {
145                        registerInfo = ARGS | DEST | MERGE;
146                    } else {
147                        for (String value: values) {
148                            if (value.equalsIgnoreCase("ALL")) {
149                                registerInfo |= ALL;
150                            } else if (value.equalsIgnoreCase("ALLPRE")) {
151                                registerInfo |= ALLPRE;
152                            } else if (value.equalsIgnoreCase("ALLPOST")) {
153                                registerInfo |= ALLPOST;
154                            } else if (value.equalsIgnoreCase("ARGS")) {
155                                registerInfo |= ARGS;
156                            } else if (value.equalsIgnoreCase("DEST")) {
157                                registerInfo |= DEST;
158                            } else if (value.equalsIgnoreCase("MERGE")) {
159                                registerInfo |= MERGE;
160                            } else if (value.equalsIgnoreCase("FULLMERGE")) {
161                                registerInfo |= FULLMERGE;
162                            } else {
163                                usage();
164                                return;
165                            }
166                        }
167
168                        if ((registerInfo & FULLMERGE) != 0) {
169                            registerInfo &= ~MERGE;
170                        }
171                    }
172                    break;
173                case 'c':
174                    String bcp = commandLine.getOptionValue("c");
175                    if (bcp.charAt(0) == ':') {
176                        bootClassPath = bootClassPath + bcp;
177                    } else {
178                        bootClassPath = bcp;
179                    }
180                    break;
181                case 'x':
182                    String deodexerantAddress = commandLine.getOptionValue("x");
183                    String[] parts = deodexerantAddress.split(":");
184                    if (parts.length != 2) {
185                        System.err.println("Invalid deodexerant address. Expecting :<port> or <host>:<port>");
186                        System.exit(1);
187                    }
188
189                    deodexerantHost = parts[0];
190                    if (deodexerantHost.length() == 0) {
191                        deodexerantHost = "localhost";
192                    }
193                    try {
194                        deodexerantPort = Integer.parseInt(parts[1]);
195                    } catch (NumberFormatException ex) {
196                        System.err.println("Invalid port \"" + deodexerantPort + "\" for deodexerant address");
197                        System.exit(1);
198                    }
199                    break;
200                case 'N':
201                    disassemble = false;
202                    break;
203                case 'D':
204                    doDump = true;
205                    dumpFileName = commandLine.getOptionValue("D", inputDexFileName + ".dump");
206                    break;
207                case 'W':
208                    write = true;
209                    outputDexFileName = commandLine.getOptionValue("W");
210                    break;
211                case 'S':
212                    sort = true;
213                    break;
214                case 'F':
215                    fixRegisters = true;
216                    break;
217                default:
218                    assert false;
219            }
220        }
221
222        if (remainingArgs.length != 1) {
223            usage();
224            return;
225        }
226
227        inputDexFileName = remainingArgs[0];
228
229        try {
230            File dexFileFile = new File(inputDexFileName);
231            if (!dexFileFile.exists()) {
232                System.err.println("Can't find the file " + inputDexFileName);
233                System.exit(1);
234            }
235
236            //Read in and parse the dex file
237            DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false);
238
239            Deodexerant deodexerant = null;
240
241
242            if (deodexerantHost != null) {
243                if (!dexFile.isOdex()) {
244                    System.err.println("-x cannot be used with a normal dex file. Ignoring -x");
245                }
246                deodexerant = new Deodexerant(dexFile, deodexerantHost, deodexerantPort);
247            }
248
249            if (dexFile.isOdex()) {
250                if (doDump) {
251                    System.err.println("-d cannot be used with on odex file. Ignoring -d");
252                }
253                if (write) {
254                    System.err.println("-w cannot be used with an odex file. Ignoring -w");
255                }
256                if (deodexerant == null) {
257                    System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
258                    System.err.println("won't be able to re-assemble the results unless you use deodexerant, and");
259                    System.err.println("the -x option for baksmali");
260                }
261            }
262
263            if (disassemble) {
264                baksmali.disassembleDexFile(dexFile, deodexerant, outputDirectory, bootClassPathDir, bootClassPath,
265                        noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, registerInfo);
266            }
267
268            if ((doDump || write) && !dexFile.isOdex()) {
269                try
270                {
271                    dump.dump(dexFile, dumpFileName, outputDexFileName, sort);
272                }catch (IOException ex) {
273                    System.err.println("Error occured while writing dump file");
274                    ex.printStackTrace();
275                }
276            }
277        } catch (RuntimeException ex) {
278            System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:");
279            ex.printStackTrace();
280            System.exit(1);
281        } catch (Throwable ex) {
282            System.err.println("\n\nUNEXPECTED TOP-LEVEL ERROR:");
283            ex.printStackTrace();
284            System.exit(1);
285        }
286    }
287
288    /**
289     * Prints the usage message.
290     */
291    private static void usage(boolean printDebugOptions) {
292        baksmaliHelpFormatter formatter = new baksmaliHelpFormatter();
293        formatter.setWidth(100);
294
295        formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
296                "disassembles and/or dumps a dex file", basicOptions, "");
297
298        if (printDebugOptions) {
299            System.out.println();
300            System.out.println("Debug Options:");
301
302            StringBuffer sb = new StringBuffer();
303            formatter.renderOptions(sb, debugOptions);
304            System.out.println(sb.toString());
305        }
306    }
307
308    private static void usage() {
309        usage(false);
310    }
311
312    /**
313     * Prints the version message.
314     */
315    private static void version() {
316        System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
317        System.out.println("Copyright (C) 2009 Ben Gruver");
318        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
319        System.exit(0);
320    }
321
322    private static void buildOptions() {
323        Option versionOption = OptionBuilder.withLongOpt("version")
324                .withDescription("prints the version then exits")
325                .create("v");
326
327        Option helpOption = OptionBuilder.withLongOpt("help")
328                .withDescription("prints the help message then exits")
329                .create("?");
330
331        Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
332                .withDescription("suppresses the output of the disassembly")
333                .create("N");
334
335        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
336                .withDescription("dumps the given dex file into a single annotated dump file named FILE" +
337                        " (<dexfile>.dump by default), along with the normal disassembly.")
338                .hasOptionalArg()
339                .withArgName("FILE")
340                .create("D");
341
342        Option writeDexOption = OptionBuilder.withLongOpt("write-dex")
343                .withDescription("additionally rewrites the input dex file to FILE")
344                .hasArg()
345                .withArgName("FILE")
346                .create("W");
347
348        Option outputDirOption = OptionBuilder.withLongOpt("output")
349                .withDescription("the directory where the disassembled files will be placed. The default is out")
350                .hasArg()
351                .withArgName("DIR")
352                .create("o");
353
354        Option sortOption = OptionBuilder.withLongOpt("sort")
355                .withDescription("sort the items in the dex file into a canonical order before dumping/writing")
356                .create("S");
357
358        Option fixSignedRegisterOption = OptionBuilder.withLongOpt("fix-signed-registers")
359                .withDescription("when dumping or rewriting, fix any registers in the debug info that are encoded as" +
360                        " a signed value")
361                .create("F");
362
363        Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
364                .withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method" +
365                        " parameters")
366                .create("p");
367
368        Option deodexerantOption = OptionBuilder.withLongOpt("deodexerant")
369                .withDescription("connect to deodexerant on the specified HOST:PORT, and deodex the input odex"
370                        + " file. This option is ignored if the input file is a dex file instead of an odex file")
371                .hasArg()
372                .withArgName("HOST:PORT")
373                .create("x");
374
375        Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
376                .withDescription("output the .locals directive with the number of non-parameter registers, rather" +
377                        " than the .register directive with the total number of register")
378                .create("l");
379
380        Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels")
381                .withDescription("create label names using a sequential numbering scheme per label type, rather than " +
382                        "using the bytecode address")
383                .create("s");
384
385        Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info")
386                .withDescription("don't write out debug info (.local, .param, .line, etc.)")
387                .create("b");
388
389        Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
390                .hasOptionalArgs()
391                .withArgName("REGISTER_INFO_TYPES")
392                .withValueSeparator(',')
393                .withDescription("print the specificed type(s) of register information for each instruction. " +
394                        "\"ARGS,DEST,MERGE\" is the default if no types are specified.\nValid values are:\nALL: all " +
395                        "pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
396                        "post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
397                        "instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
398                        "pre-instruction register has been merged from more than 1 different post-instruction " +
399                        "register from its predecessors\nFULLMERGE: For each register that would be printed by " +
400                        "MERGE, also show the incoming register types that were merged")
401                .create("r");
402
403        Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
404                .withDescription("the bootclasspath jars to use, for analysis. Defaults to " +
405                        "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar. If you specify a value that " +
406                        "begins with a :, it will be appended to the default bootclasspath")
407                .hasOptionalArg()
408                .withArgName("BOOTCLASSPATH")
409                .create("c");
410
411        Option classPathDirOption = OptionBuilder.withLongOpt("bootclasspath-dir")
412                .withDescription("the base folder to look for the bootclasspath files in. Defaults to the current " +
413                        "directory")
414                .hasArg()
415                .withArgName("DIR")
416                .create("d");
417
418        basicOptions.addOption(versionOption);
419        basicOptions.addOption(helpOption);
420        basicOptions.addOption(outputDirOption);
421        basicOptions.addOption(noParameterRegistersOption);
422        basicOptions.addOption(deodexerantOption);
423        basicOptions.addOption(useLocalsOption);
424        basicOptions.addOption(sequentialLabelsOption);
425        basicOptions.addOption(noDebugInfoOption);
426        basicOptions.addOption(registerInfoOption);
427        basicOptions.addOption(classPathOption);
428        basicOptions.addOption(classPathDirOption);
429
430        debugOptions.addOption(dumpOption);
431        debugOptions.addOption(noDisassemblyOption);
432        debugOptions.addOption(writeDexOption);
433        debugOptions.addOption(sortOption);
434        debugOptions.addOption(fixSignedRegisterOption);
435
436
437        for (Object option: basicOptions.getOptions()) {
438            options.addOption((Option)option);
439        }
440        for (Object option: debugOptions.getOptions()) {
441            options.addOption((Option)option);
442        }
443    }
444}