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