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