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