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