main.java revision 96b803c8821bac22418e48f976adf0132e3d9b24
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.smali; 30 31import org.antlr.runtime.*; 32import org.antlr.runtime.tree.CommonTree; 33import org.antlr.runtime.tree.CommonTreeNodeStream; 34import org.apache.commons.cli.*; 35import org.jf.dexlib.Code.Opcode; 36import org.jf.dexlib.CodeItem; 37import org.jf.dexlib.DexFile; 38import org.jf.dexlib.Util.ByteArrayAnnotatedOutput; 39import org.jf.util.ConsoleUtil; 40import org.jf.util.smaliHelpFormatter; 41 42import java.io.*; 43import java.nio.CharBuffer; 44import java.nio.MappedByteBuffer; 45import java.nio.channels.FileChannel; 46import java.nio.charset.Charset; 47import java.nio.charset.CharsetDecoder; 48import java.util.LinkedHashSet; 49import java.util.Properties; 50import java.util.Set; 51 52/** 53 * Main class for smali. It recognizes enough options to be able to dispatch 54 * to the right "actual" main. 55 */ 56public class main { 57 58 public static final String VERSION; 59 60 private final static Options basicOptions; 61 private final static Options debugOptions; 62 private final static Options options; 63 64 static { 65 basicOptions = new Options(); 66 debugOptions = new Options(); 67 options = new Options(); 68 buildOptions(); 69 70 InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); 71 Properties properties = new Properties(); 72 String version = "(unknown)"; 73 try { 74 properties.load(templateStream); 75 version = properties.getProperty("application.version"); 76 } catch (IOException ex) { 77 } 78 VERSION = version; 79 } 80 81 82 /** 83 * This class is uninstantiable. 84 */ 85 private main() { 86 } 87 88 /** 89 * Run! 90 */ 91 public static void main(String[] args) { 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 boolean allowOdex = false; 103 boolean sort = false; 104 boolean fixJumbo = true; 105 boolean fixGoto = true; 106 boolean verboseErrors = false; 107 boolean oldLexer = false; 108 boolean printTokens = false; 109 110 boolean apiSet = false; 111 int apiLevel = 14; 112 113 String outputDexFile = "out.dex"; 114 String dumpFileName = null; 115 116 String[] remainingArgs = commandLine.getArgs(); 117 118 Option[] options = commandLine.getOptions(); 119 120 for (int i=0; i<options.length; i++) { 121 Option option = options[i]; 122 String opt = option.getOpt(); 123 124 switch (opt.charAt(0)) { 125 case 'v': 126 version(); 127 return; 128 case '?': 129 while (++i < options.length) { 130 if (options[i].getOpt().charAt(0) == '?') { 131 usage(true); 132 return; 133 } 134 } 135 usage(false); 136 return; 137 case 'o': 138 outputDexFile = commandLine.getOptionValue("o"); 139 break; 140 case 'x': 141 allowOdex = true; 142 break; 143 case 'a': 144 apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); 145 apiSet = true; 146 break; 147 case 'D': 148 dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump"); 149 break; 150 case 'S': 151 sort = true; 152 break; 153 case 'J': 154 fixJumbo = false; 155 break; 156 case 'G': 157 fixGoto = false; 158 break; 159 case 'V': 160 verboseErrors = true; 161 break; 162 case 'L': 163 oldLexer = true; 164 break; 165 case 'T': 166 printTokens = true; 167 break; 168 default: 169 assert false; 170 } 171 } 172 173 if (remainingArgs.length == 0) { 174 usage(); 175 return; 176 } 177 178 try { 179 LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>(); 180 181 for (String arg: remainingArgs) { 182 File argFile = new File(arg); 183 184 if (!argFile.exists()) { 185 throw new RuntimeException("Cannot find file or directory \"" + arg + "\""); 186 } 187 188 if (argFile.isDirectory()) { 189 getSmaliFilesInDir(argFile, filesToProcess); 190 } else if (argFile.isFile()) { 191 filesToProcess.add(argFile); 192 } 193 } 194 195 Opcode.updateMapsForApiLevel(apiLevel); 196 197 DexFile dexFile = new DexFile(); 198 199 if (apiSet && apiLevel >= 14) { 200 dexFile.HeaderItem.setVersion(36); 201 } 202 203 boolean errors = false; 204 205 for (File file: filesToProcess) { 206 if (!assembleSmaliFile(file, dexFile, verboseErrors, oldLexer, printTokens, allowOdex, apiLevel)) { 207 errors = true; 208 } 209 } 210 211 if (errors) { 212 System.exit(1); 213 } 214 215 216 if (sort) { 217 dexFile.setSortAllItems(true); 218 } 219 220 if (fixJumbo || fixGoto) { 221 fixInstructions(dexFile, fixJumbo, fixGoto); 222 } 223 224 dexFile.place(); 225 226 ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); 227 228 if (dumpFileName != null) { 229 out.enableAnnotations(120, true); 230 } 231 232 dexFile.writeTo(out); 233 234 byte[] bytes = out.toByteArray(); 235 236 DexFile.calcSignature(bytes); 237 DexFile.calcChecksum(bytes); 238 239 if (dumpFileName != null) { 240 out.finishAnnotating(); 241 242 FileWriter fileWriter = new FileWriter(dumpFileName); 243 out.writeAnnotationsTo(fileWriter); 244 fileWriter.close(); 245 } 246 247 FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile); 248 249 fileOutputStream.write(bytes); 250 fileOutputStream.close(); 251 } catch (RuntimeException ex) { 252 System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 253 ex.printStackTrace(); 254 System.exit(2); 255 } catch (Throwable ex) { 256 System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); 257 ex.printStackTrace(); 258 System.exit(3); 259 } 260 } 261 262 private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) { 263 for(File file: dir.listFiles()) { 264 if (file.isDirectory()) { 265 getSmaliFilesInDir(file, smaliFiles); 266 } else if (file.getName().endsWith(".smali")) { 267 smaliFiles.add(file); 268 } 269 } 270 } 271 272 private static void fixInstructions(DexFile dexFile, boolean fixJumbo, boolean fixGoto) { 273 dexFile.place(); 274 275 for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) { 276 codeItem.fixInstructions(fixJumbo, fixGoto); 277 } 278 } 279 280 private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile, boolean verboseErrors, boolean oldLexer, 281 boolean printTokens, boolean allowOdex, int apiLevel) 282 throws Exception { 283 CommonTokenStream tokens; 284 285 286 boolean lexerErrors = false; 287 LexerErrorInterface lexer; 288 289 if (oldLexer) { 290 ANTLRFileStream input = new ANTLRFileStream(smaliFile.getAbsolutePath(), "UTF-8"); 291 input.name = smaliFile.getAbsolutePath(); 292 293 lexer = new smaliLexer(input); 294 tokens = new CommonTokenStream((TokenSource)lexer); 295 } else { 296 FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath()); 297 InputStreamReader reader = new InputStreamReader(fis, "UTF-8"); 298 299 lexer = new smaliFlexLexer(reader); 300 ((smaliFlexLexer)lexer).setSourceFile(smaliFile); 301 tokens = new CommonTokenStream((TokenSource)lexer); 302 } 303 304 if (printTokens) { 305 tokens.getTokens(); 306 307 for (int i=0; i<tokens.size(); i++) { 308 Token token = tokens.get(i); 309 if (token.getChannel() == smaliLexer.HIDDEN) { 310 continue; 311 } 312 313 System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText()); 314 } 315 } 316 317 smaliParser parser = new smaliParser(tokens); 318 parser.setVerboseErrors(verboseErrors); 319 parser.setAllowOdex(allowOdex); 320 parser.setApiLevel(apiLevel); 321 322 smaliParser.smali_file_return result = parser.smali_file(); 323 324 if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) { 325 return false; 326 } 327 328 CommonTree t = (CommonTree) result.getTree(); 329 330 CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); 331 treeStream.setTokenStream(tokens); 332 333 smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); 334 335 dexGen.dexFile = dexFile; 336 dexGen.smali_file(); 337 338 if (dexGen.getNumberOfSyntaxErrors() > 0) { 339 return false; 340 } 341 342 return true; 343 } 344 345 346 /** 347 * Prints the usage message. 348 */ 349 private static void usage(boolean printDebugOptions) { 350 smaliHelpFormatter formatter = new smaliHelpFormatter(); 351 formatter.setWidth(ConsoleUtil.getConsoleWidth()); 352 353 formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*", 354 "assembles a set of smali files into a dex file", basicOptions, ""); 355 356 if (printDebugOptions) { 357 System.out.println(); 358 System.out.println("Debug Options:"); 359 360 StringBuffer sb = new StringBuffer(); 361 formatter.renderOptions(sb, debugOptions); 362 System.out.println(sb.toString()); 363 } 364 } 365 366 private static void usage() { 367 usage(false); 368 } 369 370 /** 371 * Prints the version message. 372 */ 373 private static void version() { 374 System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); 375 System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); 376 System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 377 System.exit(0); 378 } 379 380 private static void buildOptions() { 381 Option versionOption = OptionBuilder.withLongOpt("version") 382 .withDescription("prints the version then exits") 383 .create("v"); 384 385 Option helpOption = OptionBuilder.withLongOpt("help") 386 .withDescription("prints the help message then exits. Specify twice for debug options") 387 .create("?"); 388 389 Option outputOption = OptionBuilder.withLongOpt("output") 390 .withDescription("the name of the dex file that will be written. The default is out.dex") 391 .hasArg() 392 .withArgName("FILE") 393 .create("o"); 394 395 Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions") 396 .withDescription("allow odex instructions to be compiled into the dex file. Only a few" + 397 " instructions are supported - the ones that can exist in a dead code path and not" + 398 " cause dalvik to reject the class") 399 .create("x"); 400 401 Option apiLevelOption = OptionBuilder.withLongOpt("api-level") 402 .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " + 403 "specified, it defaults to 14 (ICS).") 404 .hasArg() 405 .withArgName("API_LEVEL") 406 .create("a"); 407 408 Option dumpOption = OptionBuilder.withLongOpt("dump-to") 409 .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)") 410 .hasOptionalArg() 411 .withArgName("FILE") 412 .create("D"); 413 414 Option sortOption = OptionBuilder.withLongOpt("sort") 415 .withDescription("sort the items in the dex file into a canonical order before writing") 416 .create("S"); 417 418 Option noFixJumboOption = OptionBuilder.withLongOpt("no-fix-jumbo") 419 .withDescription("Don't automatically instructions with the /jumbo variant where appropriate") 420 .create("J"); 421 422 Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto") 423 .withDescription("Don't replace goto type instructions with a larger version where appropriate") 424 .create("G"); 425 426 Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors") 427 .withDescription("Generate verbose error messages") 428 .create("V"); 429 430 Option oldLexerOption = OptionBuilder.withLongOpt("old-lexer") 431 .withDescription("Use the old lexer") 432 .create("L"); 433 434 Option printTokensOption = OptionBuilder.withLongOpt("print-tokens") 435 .withDescription("Print the name and text of each token") 436 .create("T"); 437 438 basicOptions.addOption(versionOption); 439 basicOptions.addOption(helpOption); 440 basicOptions.addOption(outputOption); 441 basicOptions.addOption(allowOdexOption); 442 basicOptions.addOption(apiLevelOption); 443 444 debugOptions.addOption(dumpOption); 445 debugOptions.addOption(sortOption); 446 debugOptions.addOption(noFixJumboOption); 447 debugOptions.addOption(noFixGotoOption); 448 debugOptions.addOption(verboseErrorsOption); 449 debugOptions.addOption(oldLexerOption); 450 debugOptions.addOption(printTokensOption); 451 452 for (Object option: basicOptions.getOptions()) { 453 options.addOption((Option)option); 454 } 455 456 for (Object option: debugOptions.getOptions()) { 457 options.addOption((Option)option); 458 } 459 } 460}