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