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