main.java revision 5b908115c006d791f8198316302453455a650c0c
190d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber/* 2f71323e297a928af368937089d3ed71239786f86Andreas Huber * Copyright (C) 2007 The Android Open Source Project 390d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber * 4f71323e297a928af368937089d3ed71239786f86Andreas Huber * Licensed under the Apache License, Version 2.0 (the "License"); 5f71323e297a928af368937089d3ed71239786f86Andreas Huber * you may not use this file except in compliance with the License. 6f71323e297a928af368937089d3ed71239786f86Andreas Huber * You may obtain a copy of the License at 7f71323e297a928af368937089d3ed71239786f86Andreas Huber * 8f71323e297a928af368937089d3ed71239786f86Andreas Huber * http://www.apache.org/licenses/LICENSE-2.0 990d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber * 1090d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber * Unless required by applicable law or agreed to in writing, software 1190d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber * distributed under the License is distributed on an "AS IS" BASIS, 12b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian * See the License for the specific language governing permissions and 1490d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber * limitations under the License. 1590d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber */ 16b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian 17b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanianpackage org.jf.baksmali; 18b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian 19b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanianimport org.apache.commons.cli.*; 20ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport org.jf.baksmali.Deodex.Deodexerant; 21ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport org.jf.dexlib.DexFile; 22ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang 23ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport java.io.File; 24ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport java.io.InputStream; 25ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangimport java.io.IOException; 2690d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huberimport java.util.Properties; 2790d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber 28ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuangpublic class main { 29ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang 30ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang public static final String VERSION; 3179f15823c34ae1e423108295e416213200bb280fAndreas Huber 3279f15823c34ae1e423108295e416213200bb280fAndreas Huber private static final Options basicOptions; 3379f15823c34ae1e423108295e416213200bb280fAndreas Huber private static final Options debugOptions; 34ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang private static final Options options; 35ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang 36ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang public static final int ALL = 1; 37ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang public static final int ALLPRE = 2; 38ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang public static final int ALLPOST = 4; 39ba164dffc5a6795bce97fae02b51ccf3330e15e4hkuang public static final int ARGS = 8; 4090d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber public static final int DEST = 16; 4179f15823c34ae1e423108295e416213200bb280fAndreas Huber public static final int MERGE = 32; 4279f15823c34ae1e423108295e416213200bb280fAndreas Huber public static final int FULLMERGE = 64; 4390d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber 4490d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber static { 4590d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber options = new Options(); 4690d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber basicOptions = new Options(); 4790d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber debugOptions = new Options(); 4890d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber buildOptions(); 4990d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber 5090d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties"); 5190d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber Properties properties = new Properties(); 5290d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber String version = "(unknown)"; 5390d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber try { 5479f15823c34ae1e423108295e416213200bb280fAndreas Huber properties.load(templateStream); 55b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian version = properties.getProperty("application.version"); 56b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian } catch (IOException ex) { 5790d3ed91ae9228e1c8bab561b6138d4cb8c1e4fdAndreas Huber } 58b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian VERSION = version; 59b08e2e23eec181e9951df33cd704ac294c5407b6Vignesh Venkatasubramanian } 60 61 /** 62 * This class is uninstantiable. 63 */ 64 private main() { 65 } 66 67 /** 68 * Run! 69 */ 70 public static void main(String[] args) { 71 CommandLineParser parser = new PosixParser(); 72 CommandLine commandLine; 73 74 try { 75 commandLine = parser.parse(options, args); 76 } catch (ParseException ex) { 77 usage(); 78 return; 79 } 80 81 boolean disassemble = true; 82 boolean doDump = false; 83 boolean write = false; 84 boolean sort = false; 85 boolean fixRegisters = false; 86 boolean noParameterRegisters = false; 87 boolean useLocalsDirective = false; 88 boolean useSequentialLabels = false; 89 boolean outputDebugInfo = true; 90 91 int registerInfo = 0; 92 93 String outputDirectory = "out"; 94 String dumpFileName = null; 95 String outputDexFileName = null; 96 String inputDexFileName = null; 97 String deodexerantHost = null; 98 String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"; 99 String bootClassPathDir = "."; 100 int deodexerantPort = 0; 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 'r': 142 String[] values = commandLine.getOptionValues('r'); 143 144 if (values == null || values.length == 0) { 145 registerInfo = ARGS | DEST | MERGE; 146 } else { 147 for (String value: values) { 148 if (value.equalsIgnoreCase("ALL")) { 149 registerInfo |= ALL; 150 } else if (value.equalsIgnoreCase("ALLPRE")) { 151 registerInfo |= ALLPRE; 152 } else if (value.equalsIgnoreCase("ALLPOST")) { 153 registerInfo |= ALLPOST; 154 } else if (value.equalsIgnoreCase("ARGS")) { 155 registerInfo |= ARGS; 156 } else if (value.equalsIgnoreCase("DEST")) { 157 registerInfo |= DEST; 158 } else if (value.equalsIgnoreCase("MERGE")) { 159 registerInfo |= MERGE; 160 } else if (value.equalsIgnoreCase("FULLMERGE")) { 161 registerInfo |= FULLMERGE; 162 } else { 163 usage(); 164 return; 165 } 166 } 167 168 if ((registerInfo & FULLMERGE) != 0) { 169 registerInfo &= ~MERGE; 170 } 171 } 172 break; 173 case 'c': 174 String bcp = commandLine.getOptionValue("c"); 175 if (bcp.charAt(0) == ':') { 176 bootClassPath = bootClassPath + bcp; 177 } else { 178 bootClassPath = bcp; 179 } 180 break; 181 case 'x': 182 String deodexerantAddress = commandLine.getOptionValue("x"); 183 String[] parts = deodexerantAddress.split(":"); 184 if (parts.length != 2) { 185 System.err.println("Invalid deodexerant address. Expecting :<port> or <host>:<port>"); 186 System.exit(1); 187 } 188 189 deodexerantHost = parts[0]; 190 if (deodexerantHost.length() == 0) { 191 deodexerantHost = "localhost"; 192 } 193 try { 194 deodexerantPort = Integer.parseInt(parts[1]); 195 } catch (NumberFormatException ex) { 196 System.err.println("Invalid port \"" + deodexerantPort + "\" for deodexerant address"); 197 System.exit(1); 198 } 199 break; 200 case 'N': 201 disassemble = false; 202 break; 203 case 'D': 204 doDump = true; 205 dumpFileName = commandLine.getOptionValue("D", inputDexFileName + ".dump"); 206 break; 207 case 'W': 208 write = true; 209 outputDexFileName = commandLine.getOptionValue("W"); 210 break; 211 case 'S': 212 sort = true; 213 break; 214 case 'F': 215 fixRegisters = true; 216 break; 217 default: 218 assert false; 219 } 220 } 221 222 if (remainingArgs.length != 1) { 223 usage(); 224 return; 225 } 226 227 inputDexFileName = remainingArgs[0]; 228 229 try { 230 File dexFileFile = new File(inputDexFileName); 231 if (!dexFileFile.exists()) { 232 System.err.println("Can't find the file " + inputDexFileName); 233 System.exit(1); 234 } 235 236 //Read in and parse the dex file 237 DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false); 238 239 Deodexerant deodexerant = null; 240 241 242 if (deodexerantHost != null) { 243 if (!dexFile.isOdex()) { 244 System.err.println("-x cannot be used with a normal dex file. Ignoring -x"); 245 } 246 deodexerant = new Deodexerant(dexFile, deodexerantHost, deodexerantPort); 247 } 248 249 if (dexFile.isOdex()) { 250 if (doDump) { 251 System.err.println("-d cannot be used with on odex file. Ignoring -d"); 252 } 253 if (write) { 254 System.err.println("-w cannot be used with an odex file. Ignoring -w"); 255 } 256 if (deodexerant == null) { 257 System.err.println("Warning: You are disassembling an odex file without deodexing it. You"); 258 System.err.println("won't be able to re-assemble the results unless you use deodexerant, and"); 259 System.err.println("the -x option for baksmali"); 260 } 261 } 262 263 if (disassemble) { 264 baksmali.disassembleDexFile(dexFile, deodexerant, outputDirectory, bootClassPathDir, bootClassPath, 265 noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, registerInfo); 266 } 267 268 if ((doDump || write) && !dexFile.isOdex()) { 269 try 270 { 271 dump.dump(dexFile, dumpFileName, outputDexFileName, sort); 272 }catch (IOException ex) { 273 System.err.println("Error occured while writing dump file"); 274 ex.printStackTrace(); 275 } 276 } 277 } catch (RuntimeException ex) { 278 System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 279 ex.printStackTrace(); 280 System.exit(1); 281 } catch (Throwable ex) { 282 System.err.println("\n\nUNEXPECTED TOP-LEVEL ERROR:"); 283 ex.printStackTrace(); 284 System.exit(1); 285 } 286 } 287 288 /** 289 * Prints the usage message. 290 */ 291 private static void usage(boolean printDebugOptions) { 292 baksmaliHelpFormatter formatter = new baksmaliHelpFormatter(); 293 formatter.setWidth(100); 294 295 formatter.printHelp("java -jar baksmali.jar [options] <dex-file>", 296 "disassembles and/or dumps a dex file", basicOptions, ""); 297 298 if (printDebugOptions) { 299 System.out.println(); 300 System.out.println("Debug Options:"); 301 302 StringBuffer sb = new StringBuffer(); 303 formatter.renderOptions(sb, debugOptions); 304 System.out.println(sb.toString()); 305 } 306 } 307 308 private static void usage() { 309 usage(false); 310 } 311 312 /** 313 * Prints the version message. 314 */ 315 private static void version() { 316 System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)"); 317 System.out.println("Copyright (C) 2009 Ben Gruver"); 318 System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 319 System.exit(0); 320 } 321 322 private static void buildOptions() { 323 Option versionOption = OptionBuilder.withLongOpt("version") 324 .withDescription("prints the version then exits") 325 .create("v"); 326 327 Option helpOption = OptionBuilder.withLongOpt("help") 328 .withDescription("prints the help message then exits") 329 .create("?"); 330 331 Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly") 332 .withDescription("suppresses the output of the disassembly") 333 .create("N"); 334 335 Option dumpOption = OptionBuilder.withLongOpt("dump-to") 336 .withDescription("dumps the given dex file into a single annotated dump file named FILE" + 337 " (<dexfile>.dump by default), along with the normal disassembly.") 338 .hasOptionalArg() 339 .withArgName("FILE") 340 .create("D"); 341 342 Option writeDexOption = OptionBuilder.withLongOpt("write-dex") 343 .withDescription("additionally rewrites the input dex file to FILE") 344 .hasArg() 345 .withArgName("FILE") 346 .create("W"); 347 348 Option outputDirOption = OptionBuilder.withLongOpt("output") 349 .withDescription("the directory where the disassembled files will be placed. The default is out") 350 .hasArg() 351 .withArgName("DIR") 352 .create("o"); 353 354 Option sortOption = OptionBuilder.withLongOpt("sort") 355 .withDescription("sort the items in the dex file into a canonical order before dumping/writing") 356 .create("S"); 357 358 Option fixSignedRegisterOption = OptionBuilder.withLongOpt("fix-signed-registers") 359 .withDescription("when dumping or rewriting, fix any registers in the debug info that are encoded as" + 360 " a signed value") 361 .create("F"); 362 363 Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers") 364 .withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method" + 365 " parameters") 366 .create("p"); 367 368 Option deodexerantOption = OptionBuilder.withLongOpt("deodexerant") 369 .withDescription("connect to deodexerant on the specified HOST:PORT, and deodex the input odex" 370 + " file. This option is ignored if the input file is a dex file instead of an odex file") 371 .hasArg() 372 .withArgName("HOST:PORT") 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,MERGE\" 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 you specify a value that " + 406 "begins with a :, it will be appended to the default bootclasspath") 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 basicOptions.addOption(versionOption); 419 basicOptions.addOption(helpOption); 420 basicOptions.addOption(outputDirOption); 421 basicOptions.addOption(noParameterRegistersOption); 422 basicOptions.addOption(deodexerantOption); 423 basicOptions.addOption(useLocalsOption); 424 basicOptions.addOption(sequentialLabelsOption); 425 basicOptions.addOption(noDebugInfoOption); 426 basicOptions.addOption(registerInfoOption); 427 basicOptions.addOption(classPathOption); 428 basicOptions.addOption(classPathDirOption); 429 430 debugOptions.addOption(dumpOption); 431 debugOptions.addOption(noDisassemblyOption); 432 debugOptions.addOption(writeDexOption); 433 debugOptions.addOption(sortOption); 434 debugOptions.addOption(fixSignedRegisterOption); 435 436 437 for (Object option: basicOptions.getOptions()) { 438 options.addOption((Option)option); 439 } 440 for (Object option: debugOptions.getOptions()) { 441 options.addOption((Option)option); 442 } 443 } 444}