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