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