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