main.java revision 6786055f3566c4fcafd352329662b6b8b223580c
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.apache.commons.cli.*; 32import org.jf.dexlib.DexFile; 33import org.jf.dexlib.CodeItem; 34import org.jf.dexlib.Util.ByteArrayAnnotatedOutput; 35import org.antlr.runtime.ANTLRInputStream; 36import org.antlr.runtime.CommonTokenStream; 37import org.antlr.runtime.tree.CommonTree; 38import org.antlr.runtime.tree.CommonTreeNodeStream; 39import org.jf.util.*; 40 41import java.io.*; 42import java.util.Set; 43import java.util.LinkedHashSet; 44import java.util.Properties; 45 46/** 47 * Main class for smali. It recognizes enough options to be able to dispatch 48 * to the right "actual" main. 49 */ 50public class main { 51 52 public static final String VERSION; 53 54 private final static Options basicOptions; 55 private final static Options debugOptions; 56 private final static Options options; 57 58 static { 59 basicOptions = new Options(); 60 debugOptions = new Options(); 61 options = new Options(); 62 buildOptions(); 63 64 InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); 65 Properties properties = new Properties(); 66 String version = "(unknown)"; 67 try { 68 properties.load(templateStream); 69 version = properties.getProperty("application.version"); 70 } catch (IOException ex) { 71 } 72 VERSION = version; 73 } 74 75 76 /** 77 * This class is uninstantiable. 78 */ 79 private main() { 80 } 81 82 /** 83 * Run! 84 */ 85 public static void main(String[] args) { 86 CommandLineParser parser = new PosixParser(); 87 CommandLine commandLine; 88 89 try { 90 commandLine = parser.parse(options, args); 91 } catch (ParseException ex) { 92 usage(); 93 return; 94 } 95 96 boolean sort = false; 97 boolean fixStringConst = true; 98 boolean fixGoto = true; 99 100 String outputDexFile = "out.dex"; 101 String dumpFileName = null; 102 103 String[] remainingArgs = commandLine.getArgs(); 104 105 Option[] options = commandLine.getOptions(); 106 107 for (int i=0; i<options.length; i++) { 108 Option option = options[i]; 109 String opt = option.getOpt(); 110 111 switch (opt.charAt(0)) { 112 case 'v': 113 version(); 114 return; 115 case '?': 116 while (++i < options.length) { 117 if (options[i].getOpt().charAt(0) == '?') { 118 usage(true); 119 return; 120 } 121 } 122 usage(false); 123 return; 124 case 'o': 125 outputDexFile = commandLine.getOptionValue("o"); 126 break; 127 case 'D': 128 dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump"); 129 break; 130 case 'S': 131 sort = true; 132 break; 133 case 'C': 134 fixStringConst = false; 135 break; 136 case 'G': 137 fixGoto = false; 138 break; 139 default: 140 assert false; 141 } 142 } 143 144 if (remainingArgs.length == 0) { 145 usage(); 146 return; 147 } 148 149 try { 150 LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>(); 151 152 for (String arg: remainingArgs) { 153 File argFile = new File(arg); 154 155 if (!argFile.exists()) { 156 throw new RuntimeException("Cannot find file or directory \"" + arg + "\""); 157 } 158 159 if (argFile.isDirectory()) { 160 getSmaliFilesInDir(argFile, filesToProcess); 161 } else if (argFile.isFile()) { 162 filesToProcess.add(argFile); 163 } 164 } 165 166 DexFile dexFile = new DexFile(); 167 168 boolean errors = false; 169 170 for (File file: filesToProcess) { 171 if (!assembleSmaliFile(file, dexFile)) { 172 errors = true; 173 } 174 } 175 176 if (errors) { 177 System.exit(1); 178 } 179 180 181 if (sort) { 182 dexFile.setSortAllItems(true); 183 } 184 185 if (fixStringConst || fixGoto) { 186 fixInstructions(dexFile, fixStringConst, fixGoto); 187 } 188 189 dexFile.place(); 190 191 ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); 192 193 if (dumpFileName != null) { 194 out.enableAnnotations(120, true); 195 } 196 197 dexFile.writeTo(out); 198 199 byte[] bytes = out.toByteArray(); 200 201 DexFile.calcSignature(bytes); 202 DexFile.calcChecksum(bytes); 203 204 if (dumpFileName != null) { 205 out.finishAnnotating(); 206 207 FileWriter fileWriter = new FileWriter(dumpFileName); 208 out.writeAnnotationsTo(fileWriter); 209 fileWriter.close(); 210 } 211 212 FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile); 213 214 fileOutputStream.write(bytes); 215 fileOutputStream.close(); 216 } catch (RuntimeException ex) { 217 System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 218 ex.printStackTrace(); 219 System.exit(2); 220 } catch (Throwable ex) { 221 System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); 222 ex.printStackTrace(); 223 System.exit(3); 224 } 225 } 226 227 private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) { 228 for(File file: dir.listFiles()) { 229 if (file.isDirectory()) { 230 getSmaliFilesInDir(file, smaliFiles); 231 } else if (file.getName().endsWith(".smali")) { 232 smaliFiles.add(file); 233 } 234 } 235 } 236 237 private static void fixInstructions(DexFile dexFile, boolean fixStringConst, boolean fixGoto) { 238 dexFile.place(); 239 240 byte[] newInsns = null; 241 242 for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) { 243 codeItem.fixInstructions(fixStringConst, fixGoto); 244 } 245 } 246 247 private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile) 248 throws Exception { 249 ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(smaliFile)); 250 input.name = smaliFile.getAbsolutePath(); 251 252 smaliLexer lexer = new smaliLexer(input); 253 254 CommonTokenStream tokens = new CommonTokenStream(lexer); 255 smaliParser parser = new smaliParser(tokens); 256 257 smaliParser.smali_file_return result = parser.smali_file(); 258 259 if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfLexerErrors() > 0) { 260 return false; 261 } 262 263 CommonTree t = (CommonTree) result.getTree(); 264 265 CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); 266 treeStream.setTokenStream(tokens); 267 268 smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); 269 270 dexGen.dexFile = dexFile; 271 dexGen.smali_file(); 272 273 if (dexGen.getNumberOfSyntaxErrors() > 0) { 274 return false; 275 } 276 277 return true; 278 } 279 280 281 /** 282 * Prints the usage message. 283 */ 284 private static void usage(boolean printDebugOptions) { 285 smaliHelpFormatter formatter = new smaliHelpFormatter(); 286 formatter.setWidth(ConsoleUtil.getConsoleWidth()); 287 288 formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*", 289 "assembles a set of smali files into a dex file", basicOptions, ""); 290 291 if (printDebugOptions) { 292 System.out.println(); 293 System.out.println("Debug Options:"); 294 295 StringBuffer sb = new StringBuffer(); 296 formatter.renderOptions(sb, debugOptions); 297 System.out.println(sb.toString()); 298 } 299 } 300 301 private static void usage() { 302 usage(false); 303 } 304 305 /** 306 * Prints the version message. 307 */ 308 private static void version() { 309 System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); 310 System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); 311 System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 312 System.exit(0); 313 } 314 315 private static void buildOptions() { 316 Option versionOption = OptionBuilder.withLongOpt("version") 317 .withDescription("prints the version then exits") 318 .create("v"); 319 320 Option helpOption = OptionBuilder.withLongOpt("help") 321 .withDescription("prints the help message then exits. Specify twice for debug options") 322 .create("?"); 323 324 Option outputOption = OptionBuilder.withLongOpt("output") 325 .withDescription("the name of the dex file that will be written. The default is out.dex") 326 .hasArg() 327 .withArgName("FILE") 328 .create("o"); 329 330 Option dumpOption = OptionBuilder.withLongOpt("dump-to") 331 .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)") 332 .hasOptionalArg() 333 .withArgName("FILE") 334 .create("D"); 335 336 Option sortOption = OptionBuilder.withLongOpt("sort") 337 .withDescription("sort the items in the dex file into a canonical order before writing") 338 .create("S"); 339 340 Option noFixStringConstOption = OptionBuilder.withLongOpt("no-fix-string-const") 341 .withDescription("Don't replace string-const instructions with string-const/jumbo where appropriate") 342 .create("C"); 343 344 Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto") 345 .withDescription("Don't replace goto type instructions with a larger version where appropriate") 346 .create("G"); 347 348 basicOptions.addOption(versionOption); 349 basicOptions.addOption(helpOption); 350 basicOptions.addOption(outputOption); 351 352 debugOptions.addOption(dumpOption); 353 debugOptions.addOption(sortOption); 354 debugOptions.addOption(noFixStringConstOption); 355 debugOptions.addOption(noFixGotoOption); 356 357 for (Object option: basicOptions.getOptions()) { 358 options.addOption((Option)option); 359 } 360 361 for (Object option: debugOptions.getOptions()) { 362 options.addOption((Option)option); 363 } 364 } 365}