main.java revision 4c872e9a7cc5f389449c35f6aea49c8e4ed632d4
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 sort = false; 102 boolean fixStringConst = true; 103 boolean fixGoto = true; 104 boolean verboseErrors = false; 105 boolean oldLexer = false; 106 107 String outputDexFile = "out.dex"; 108 String dumpFileName = null; 109 110 String[] remainingArgs = commandLine.getArgs(); 111 112 Option[] options = commandLine.getOptions(); 113 114 for (int i=0; i<options.length; i++) { 115 Option option = options[i]; 116 String opt = option.getOpt(); 117 118 switch (opt.charAt(0)) { 119 case 'v': 120 version(); 121 return; 122 case '?': 123 while (++i < options.length) { 124 if (options[i].getOpt().charAt(0) == '?') { 125 usage(true); 126 return; 127 } 128 } 129 usage(false); 130 return; 131 case 'o': 132 outputDexFile = commandLine.getOptionValue("o"); 133 break; 134 case 'D': 135 dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump"); 136 break; 137 case 'S': 138 sort = true; 139 break; 140 case 'C': 141 fixStringConst = false; 142 break; 143 case 'G': 144 fixGoto = false; 145 break; 146 case 'V': 147 verboseErrors = true; 148 break; 149 case 'L': 150 oldLexer = true; 151 break; 152 default: 153 assert false; 154 } 155 } 156 157 if (remainingArgs.length == 0) { 158 usage(); 159 return; 160 } 161 162 try { 163 LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>(); 164 165 for (String arg: remainingArgs) { 166 File argFile = new File(arg); 167 168 if (!argFile.exists()) { 169 throw new RuntimeException("Cannot find file or directory \"" + arg + "\""); 170 } 171 172 if (argFile.isDirectory()) { 173 getSmaliFilesInDir(argFile, filesToProcess); 174 } else if (argFile.isFile()) { 175 filesToProcess.add(argFile); 176 } 177 } 178 179 DexFile dexFile = new DexFile(); 180 181 boolean errors = false; 182 183 for (File file: filesToProcess) { 184 if (!assembleSmaliFile(file, dexFile, verboseErrors, oldLexer)) { 185 errors = true; 186 } 187 } 188 189 if (errors) { 190 System.exit(1); 191 } 192 193 194 if (sort) { 195 dexFile.setSortAllItems(true); 196 } 197 198 if (fixStringConst || fixGoto) { 199 fixInstructions(dexFile, fixStringConst, fixGoto); 200 } 201 202 dexFile.place(); 203 204 ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); 205 206 if (dumpFileName != null) { 207 out.enableAnnotations(120, true); 208 } 209 210 dexFile.writeTo(out); 211 212 byte[] bytes = out.toByteArray(); 213 214 DexFile.calcSignature(bytes); 215 DexFile.calcChecksum(bytes); 216 217 if (dumpFileName != null) { 218 out.finishAnnotating(); 219 220 FileWriter fileWriter = new FileWriter(dumpFileName); 221 out.writeAnnotationsTo(fileWriter); 222 fileWriter.close(); 223 } 224 225 FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile); 226 227 fileOutputStream.write(bytes); 228 fileOutputStream.close(); 229 } catch (RuntimeException ex) { 230 System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 231 ex.printStackTrace(); 232 System.exit(2); 233 } catch (Throwable ex) { 234 System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); 235 ex.printStackTrace(); 236 System.exit(3); 237 } 238 } 239 240 private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) { 241 for(File file: dir.listFiles()) { 242 if (file.isDirectory()) { 243 getSmaliFilesInDir(file, smaliFiles); 244 } else if (file.getName().endsWith(".smali")) { 245 smaliFiles.add(file); 246 } 247 } 248 } 249 250 private static void fixInstructions(DexFile dexFile, boolean fixStringConst, boolean fixGoto) { 251 dexFile.place(); 252 253 byte[] newInsns = null; 254 255 for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) { 256 codeItem.fixInstructions(fixStringConst, fixGoto); 257 } 258 } 259 260 private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile, boolean verboseErrors, boolean oldLexer) 261 throws Exception { 262 CommonTokenStream tokens; 263 264 if (oldLexer) { 265 ANTLRFileStream input = new ANTLRFileStream(smaliFile.getAbsolutePath(), "UTF-8"); 266 input.name = smaliFile.getAbsolutePath(); 267 268 smaliLexer lexer = new smaliLexer(input); 269 tokens = new CommonTokenStream(lexer); 270 } else { 271 FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath()); 272 InputStreamReader reader = new InputStreamReader(fis, "UTF-8"); 273 274 smaliFlexLexer lexer = new smaliFlexLexer(reader); 275 276 tokens = new CommonTokenStream(lexer); 277 } 278 279 smaliParser parser = new smaliParser(tokens); 280 parser.setVerboseErrors(verboseErrors); 281 282 smaliParser.smali_file_return result = parser.smali_file(); 283 284 //TODO: check for lexer errors 285 if (parser.getNumberOfSyntaxErrors() > 0 /*|| lexer.getNumberOfSyntaxErrors() > 0*/) { 286 return false; 287 } 288 289 CommonTree t = (CommonTree) result.getTree(); 290 291 CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); 292 treeStream.setTokenStream(tokens); 293 294 smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); 295 296 dexGen.dexFile = dexFile; 297 dexGen.smali_file(); 298 299 if (dexGen.getNumberOfSyntaxErrors() > 0) { 300 return false; 301 } 302 303 return true; 304 } 305 306 307 /** 308 * Prints the usage message. 309 */ 310 private static void usage(boolean printDebugOptions) { 311 smaliHelpFormatter formatter = new smaliHelpFormatter(); 312 formatter.setWidth(ConsoleUtil.getConsoleWidth()); 313 314 formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*", 315 "assembles a set of smali files into a dex file", basicOptions, ""); 316 317 if (printDebugOptions) { 318 System.out.println(); 319 System.out.println("Debug Options:"); 320 321 StringBuffer sb = new StringBuffer(); 322 formatter.renderOptions(sb, debugOptions); 323 System.out.println(sb.toString()); 324 } 325 } 326 327 private static void usage() { 328 usage(false); 329 } 330 331 /** 332 * Prints the version message. 333 */ 334 private static void version() { 335 System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); 336 System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); 337 System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 338 System.exit(0); 339 } 340 341 private static void buildOptions() { 342 Option versionOption = OptionBuilder.withLongOpt("version") 343 .withDescription("prints the version then exits") 344 .create("v"); 345 346 Option helpOption = OptionBuilder.withLongOpt("help") 347 .withDescription("prints the help message then exits. Specify twice for debug options") 348 .create("?"); 349 350 Option outputOption = OptionBuilder.withLongOpt("output") 351 .withDescription("the name of the dex file that will be written. The default is out.dex") 352 .hasArg() 353 .withArgName("FILE") 354 .create("o"); 355 356 Option dumpOption = OptionBuilder.withLongOpt("dump-to") 357 .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)") 358 .hasOptionalArg() 359 .withArgName("FILE") 360 .create("D"); 361 362 Option sortOption = OptionBuilder.withLongOpt("sort") 363 .withDescription("sort the items in the dex file into a canonical order before writing") 364 .create("S"); 365 366 Option noFixStringConstOption = OptionBuilder.withLongOpt("no-fix-string-const") 367 .withDescription("Don't replace string-const instructions with string-const/jumbo where appropriate") 368 .create("C"); 369 370 Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto") 371 .withDescription("Don't replace goto type instructions with a larger version where appropriate") 372 .create("G"); 373 374 Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors") 375 .withDescription("Generate verbose error messages") 376 .create("V"); 377 378 Option oldLexerOption = OptionBuilder.withLongOpt("old-lexer") 379 .withDescription("Use the old lexer") 380 .create("L"); 381 382 383 basicOptions.addOption(versionOption); 384 basicOptions.addOption(helpOption); 385 basicOptions.addOption(outputOption); 386 387 debugOptions.addOption(dumpOption); 388 debugOptions.addOption(sortOption); 389 debugOptions.addOption(noFixStringConstOption); 390 debugOptions.addOption(noFixGotoOption); 391 debugOptions.addOption(verboseErrorsOption); 392 debugOptions.addOption(oldLexerOption); 393 394 for (Object option: basicOptions.getOptions()) { 395 options.addOption((Option)option); 396 } 397 398 for (Object option: debugOptions.getOptions()) { 399 options.addOption((Option)option); 400 } 401 } 402}