main.java revision dee6ba748e748f1c870cf25f551f3892f867a041
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package org.jf.smali; 18 19import org.apache.commons.cli.*; 20import org.jf.dexlib.DexFile; 21import org.jf.dexlib.Util.ByteArrayAnnotatedOutput; 22import org.jf.dexlib.Util.FileUtils; 23import org.antlr.runtime.ANTLRInputStream; 24import org.antlr.runtime.CommonTokenStream; 25import org.antlr.runtime.TokenRewriteStream; 26import org.antlr.runtime.Lexer; 27import org.antlr.runtime.tree.CommonTree; 28import org.antlr.runtime.tree.CommonTreeNodeStream; 29 30import java.io.*; 31import java.util.Set; 32import java.util.LinkedHashSet; 33import java.util.Properties; 34 35/** 36 * Main class for smali. It recognizes enough options to be able to dispatch 37 * to the right "actual" main. 38 */ 39public class main { 40 41 public static final String VERSION; 42 43 private final static Options options; 44 45 static { 46 options = new Options(); 47 buildOptions(); 48 49 InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); 50 Properties properties = new Properties(); 51 String version = "(unknown)"; 52 try { 53 properties.load(templateStream); 54 version = properties.getProperty("application.version"); 55 } catch (IOException ex) { 56 } 57 VERSION = version; 58 } 59 60 61 /** 62 * This class is uninstantiable. 63 */ 64 private main() { 65 } 66 67 /** 68 * Run! 69 */ 70 public static void main(String[] args) { 71 CommandLineParser parser = new PosixParser(); 72 CommandLine commandLine; 73 74 try { 75 commandLine = parser.parse(options, args); 76 } catch (ParseException ex) { 77 usage(); 78 return; 79 } 80 81 boolean sort = false; 82 boolean rewriteLabels = false; 83 84 String outputDexFile = "out.dex"; 85 String dumpFileName = null; 86 87 String[] remainingArgs = commandLine.getArgs(); 88 89 if (commandLine.hasOption("v")) { 90 version(); 91 return; 92 } 93 94 if (commandLine.hasOption("?")) { 95 usage(); 96 return; 97 } 98 99 if (remainingArgs.length == 0) { 100 usage(); 101 return; 102 } 103 104 if (commandLine.hasOption("r")) { 105 rewriteLabels = true; 106 } 107 108 if (commandLine.hasOption("o")) { 109 if (rewriteLabels) { 110 System.err.println("The --output/-o option is not compatible with the --rewrite-labels/-r option. Ignoring"); 111 } else { 112 outputDexFile = commandLine.getOptionValue("o"); 113 } 114 } 115 116 if (commandLine.hasOption("d")) { 117 if (rewriteLabels) { 118 System.err.println("The --dump/-d option is not compatible with the --rewrite-labels/-r option. Ignoring"); 119 } else { 120 dumpFileName = commandLine.getOptionValue("d", outputDexFile + ".dump"); 121 } 122 } 123 124 if (commandLine.hasOption("s")) { 125 if (rewriteLabels) { 126 System.err.println("The --sort/-s option is not compatible with the --rewrite-labels/-r option. Ignoring"); 127 } else { 128 sort = true; 129 } 130 } 131 132 133 try { 134 135 LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>(); 136 137 for (String arg: remainingArgs) { 138 File argFile = new File(arg); 139 140 if (!argFile.exists()) { 141 throw new RuntimeException("Cannot find file or directory \"" + arg + "\""); 142 } 143 144 if (argFile.isDirectory()) { 145 getSmaliFilesInDir(argFile, filesToProcess); 146 } else if (argFile.isFile()) { 147 filesToProcess.add(argFile); 148 } 149 } 150 151 if (rewriteLabels) { 152 if (doRewriteLabels(filesToProcess)) { 153 System.exit(1); 154 } 155 System.exit(0); 156 } 157 158 DexFile dexFile = new DexFile(); 159 160 boolean errors = false; 161 162 for (File file: filesToProcess) { 163 if (!assembleSmaliFile(file, dexFile)) { 164 errors = true; 165 } 166 } 167 168 if (errors) { 169 System.exit(1); 170 } 171 172 173 if (sort) { 174 dexFile.setSortAllItems(true); 175 } 176 177 dexFile.place(); 178 179 ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); 180 181 if (dumpFileName != null) { 182 out.enableAnnotations(120, true); 183 } 184 185 dexFile.writeTo(out); 186 187 byte[] bytes = out.toByteArray(); 188 189 DexFile.calcSignature(bytes); 190 DexFile.calcChecksum(bytes); 191 192 if (dumpFileName != null) { 193 out.finishAnnotating(); 194 195 FileWriter fileWriter = new FileWriter(dumpFileName); 196 out.writeAnnotationsTo(fileWriter); 197 fileWriter.close(); 198 } 199 200 FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile); 201 202 fileOutputStream.write(bytes); 203 fileOutputStream.close(); 204 } catch (RuntimeException ex) { 205 System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 206 ex.printStackTrace(); 207 System.exit(2); 208 } catch (Throwable ex) { 209 System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); 210 ex.printStackTrace(); 211 System.exit(3); 212 } 213 } 214 215 private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) { 216 for(File file: dir.listFiles()) { 217 if (file.isDirectory()) { 218 getSmaliFilesInDir(file, smaliFiles); 219 } else if (file.getName().endsWith(".smali")) { 220 smaliFiles.add(file); 221 } 222 } 223 } 224 225 private static boolean doRewriteLabels(Set<File> files) 226 throws Exception { 227 boolean errorOccurred = false; 228 229 for (File file: files) { 230 ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(file)); 231 input.name = file.getAbsolutePath(); 232 233 smaliLexer_old lexer = new smaliLexer_old(input); 234 235 if (lexer.getNumberOfLexerErrors() > 0) { 236 errorOccurred = true; 237 continue; 238 } 239 240 TokenRewriteStream tokens = new TokenRewriteStream(lexer); 241 labelConverter parser = new labelConverter(tokens); 242 parser.top(); 243 244 if (parser.getNumberOfSyntaxErrors() > 0) { 245 errorOccurred = true; 246 continue; 247 } 248 249 FileWriter writer = null; 250 try 251 { 252 writer = new FileWriter(file); 253 writer.write(tokens.toString()); 254 } finally { 255 if (writer != null) { 256 writer.close(); 257 } 258 } 259 } 260 261 return !errorOccurred; 262 } 263 264 private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile) 265 throws Exception { 266 ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(smaliFile)); 267 input.name = smaliFile.getAbsolutePath(); 268 269 smaliLexer lexer = new smaliLexer(input); 270 271 CommonTokenStream tokens = new CommonTokenStream(lexer); 272 smaliParser parser = new smaliParser(tokens); 273 274 smaliParser.smali_file_return result = parser.smali_file(); 275 276 if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfLexerErrors() > 0) { 277 return false; 278 } 279 280 CommonTree t = (CommonTree) result.getTree(); 281 282 CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); 283 treeStream.setTokenStream(tokens); 284 285 smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); 286 287 dexGen.dexFile = dexFile; 288 dexGen.smali_file(); 289 290 if (dexGen.getNumberOfSyntaxErrors() > 0) { 291 return false; 292 } 293 294 return true; 295 } 296 297 298 /** 299 * Prints the usage message. 300 */ 301 private static void usage() { 302 HelpFormatter formatter = new HelpFormatter(); 303 formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*", 304 "assembles a set of smali files into a dex file, and optionally generats an annotated dump of the output file", options, ""); 305 } 306 307 /** 308 * Prints the version message. 309 */ 310 private static void version() { 311 System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); 312 System.out.println("Copyright (C) 2009 Ben Gruver"); 313 System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 314 System.exit(0); 315 } 316 317 318 319 private static void buildOptions() { 320 Option versionOption = OptionBuilder.withLongOpt("version") 321 .withDescription("prints the version then exits") 322 .create("v"); 323 324 Option helpOption = OptionBuilder.withLongOpt("help") 325 .withDescription("prints the help message then exits") 326 .create("?"); 327 328 Option dumpOption = OptionBuilder.withLongOpt("dump-to") 329 .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)") 330 .hasOptionalArg() 331 .withArgName("FILE") 332 .create("d"); 333 334 Option outputOption = OptionBuilder.withLongOpt("output") 335 .withDescription("the name of the dex file that will be written. The default is out.dex") 336 .hasArg() 337 .withArgName("FILE") 338 .create("o"); 339 340 Option sortOption = OptionBuilder.withLongOpt("sort") 341 .withDescription("sort the items in the dex file into a canonical order before writing") 342 .create("s"); 343 344 Option rewriteLabelOption = OptionBuilder.withLongOpt("rewrite-labels") 345 .withDescription("rewrite the input smali files using the old label format to the new label format") 346 .create("r"); 347 348 options.addOption(versionOption); 349 options.addOption(helpOption); 350 options.addOption(dumpOption); 351 options.addOption(outputOption); 352 options.addOption(sortOption); 353 options.addOption(rewriteLabelOption); 354 } 355}