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