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