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