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