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