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