main.java revision d317a0679d983819f91994b19828e4072918ac4c
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.Code.Opcode; 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.Locale; 45import java.util.Properties; 46import java.util.Set; 47 48/** 49 * Main class for smali. It recognizes enough options to be able to dispatch 50 * to the right "actual" main. 51 */ 52public class main { 53 54 public static final String VERSION; 55 56 private final static Options basicOptions; 57 private final static Options debugOptions; 58 private final static Options options; 59 60 static { 61 basicOptions = new Options(); 62 debugOptions = new Options(); 63 options = new Options(); 64 buildOptions(); 65 66 InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); 67 Properties properties = new Properties(); 68 String version = "(unknown)"; 69 try { 70 properties.load(templateStream); 71 version = properties.getProperty("application.version"); 72 } catch (IOException ex) { 73 } 74 VERSION = version; 75 } 76 77 78 /** 79 * This class is uninstantiable. 80 */ 81 private main() { 82 } 83 84 /** 85 * Run! 86 */ 87 public static void main(String[] args) { 88 Locale locale = new Locale("en", "US"); 89 Locale.setDefault(locale); 90 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 fixJumbo = true; 104 boolean fixGoto = true; 105 boolean verboseErrors = false; 106 boolean printTokens = false; 107 108 boolean apiSet = false; 109 int apiLevel = 14; 110 111 String outputDexFile = "out.dex"; 112 String dumpFileName = null; 113 114 String[] remainingArgs = commandLine.getArgs(); 115 116 Option[] options = commandLine.getOptions(); 117 118 for (int i=0; i<options.length; i++) { 119 Option option = options[i]; 120 String opt = option.getOpt(); 121 122 switch (opt.charAt(0)) { 123 case 'v': 124 version(); 125 return; 126 case '?': 127 while (++i < options.length) { 128 if (options[i].getOpt().charAt(0) == '?') { 129 usage(true); 130 return; 131 } 132 } 133 usage(false); 134 return; 135 case 'o': 136 outputDexFile = commandLine.getOptionValue("o"); 137 break; 138 case 'x': 139 allowOdex = true; 140 break; 141 case 'a': 142 apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); 143 apiSet = true; 144 break; 145 case 'D': 146 dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump"); 147 break; 148 case 'S': 149 sort = true; 150 break; 151 case 'J': 152 fixJumbo = false; 153 break; 154 case 'G': 155 fixGoto = false; 156 break; 157 case 'V': 158 verboseErrors = true; 159 break; 160 case 'T': 161 printTokens = true; 162 break; 163 default: 164 assert false; 165 } 166 } 167 168 if (remainingArgs.length == 0) { 169 usage(); 170 return; 171 } 172 173 try { 174 LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>(); 175 176 for (String arg: remainingArgs) { 177 File argFile = new File(arg); 178 179 if (!argFile.exists()) { 180 throw new RuntimeException("Cannot find file or directory \"" + arg + "\""); 181 } 182 183 if (argFile.isDirectory()) { 184 getSmaliFilesInDir(argFile, filesToProcess); 185 } else if (argFile.isFile()) { 186 filesToProcess.add(argFile); 187 } 188 } 189 190 Opcode.updateMapsForApiLevel(apiLevel); 191 192 DexFile dexFile = new DexFile(); 193 194 if (apiSet && apiLevel >= 14) { 195 dexFile.HeaderItem.setVersion(36); 196 } 197 198 boolean errors = false; 199 200 for (File file: filesToProcess) { 201 if (!assembleSmaliFile(file, dexFile, verboseErrors, printTokens, allowOdex, apiLevel)) { 202 errors = true; 203 } 204 } 205 206 if (errors) { 207 System.exit(1); 208 } 209 210 211 if (sort) { 212 dexFile.setSortAllItems(true); 213 } 214 215 if (fixJumbo || fixGoto) { 216 fixInstructions(dexFile, fixJumbo, fixGoto); 217 } 218 219 dexFile.place(); 220 221 ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); 222 223 if (dumpFileName != null) { 224 out.enableAnnotations(120, true); 225 } 226 227 dexFile.writeTo(out); 228 229 byte[] bytes = out.toByteArray(); 230 231 DexFile.calcSignature(bytes); 232 DexFile.calcChecksum(bytes); 233 234 if (dumpFileName != null) { 235 out.finishAnnotating(); 236 237 FileWriter fileWriter = new FileWriter(dumpFileName); 238 out.writeAnnotationsTo(fileWriter); 239 fileWriter.close(); 240 } 241 242 FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile); 243 244 fileOutputStream.write(bytes); 245 fileOutputStream.close(); 246 } catch (RuntimeException ex) { 247 System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 248 ex.printStackTrace(); 249 System.exit(2); 250 } catch (Throwable ex) { 251 System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); 252 ex.printStackTrace(); 253 System.exit(3); 254 } 255 } 256 257 private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) { 258 for(File file: dir.listFiles()) { 259 if (file.isDirectory()) { 260 getSmaliFilesInDir(file, smaliFiles); 261 } else if (file.getName().endsWith(".smali")) { 262 smaliFiles.add(file); 263 } 264 } 265 } 266 267 private static void fixInstructions(DexFile dexFile, boolean fixJumbo, boolean fixGoto) { 268 dexFile.place(); 269 270 for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) { 271 codeItem.fixInstructions(fixJumbo, fixGoto); 272 } 273 } 274 275 private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile, boolean verboseErrors, 276 boolean printTokens, boolean allowOdex, int apiLevel) 277 throws Exception { 278 CommonTokenStream tokens; 279 280 281 boolean lexerErrors = false; 282 LexerErrorInterface lexer; 283 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 if (printTokens) { 292 tokens.getTokens(); 293 294 for (int i=0; i<tokens.size(); i++) { 295 Token token = tokens.get(i); 296 if (token.getChannel() == smaliParser.HIDDEN) { 297 continue; 298 } 299 300 System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText()); 301 } 302 } 303 304 smaliParser parser = new smaliParser(tokens); 305 parser.setVerboseErrors(verboseErrors); 306 parser.setAllowOdex(allowOdex); 307 parser.setApiLevel(apiLevel); 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 dexGen.setVerboseErrors(verboseErrors); 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 339 int consoleWidth = ConsoleUtil.getConsoleWidth(); 340 if (consoleWidth <= 0) { 341 consoleWidth = 80; 342 } 343 344 formatter.setWidth(consoleWidth); 345 346 formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*", 347 "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null); 348 } 349 350 private static void usage() { 351 usage(false); 352 } 353 354 /** 355 * Prints the version message. 356 */ 357 private static void version() { 358 System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); 359 System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); 360 System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 361 System.exit(0); 362 } 363 364 private static void buildOptions() { 365 Option versionOption = OptionBuilder.withLongOpt("version") 366 .withDescription("prints the version then exits") 367 .create("v"); 368 369 Option helpOption = OptionBuilder.withLongOpt("help") 370 .withDescription("prints the help message then exits. Specify twice for debug options") 371 .create("?"); 372 373 Option outputOption = OptionBuilder.withLongOpt("output") 374 .withDescription("the name of the dex file that will be written. The default is out.dex") 375 .hasArg() 376 .withArgName("FILE") 377 .create("o"); 378 379 Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions") 380 .withDescription("allow odex instructions to be compiled into the dex file. Only a few" + 381 " instructions are supported - the ones that can exist in a dead code path and not" + 382 " cause dalvik to reject the class") 383 .create("x"); 384 385 Option apiLevelOption = OptionBuilder.withLongOpt("api-level") 386 .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " + 387 "specified, it defaults to 14 (ICS).") 388 .hasArg() 389 .withArgName("API_LEVEL") 390 .create("a"); 391 392 Option dumpOption = OptionBuilder.withLongOpt("dump-to") 393 .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)") 394 .hasOptionalArg() 395 .withArgName("FILE") 396 .create("D"); 397 398 Option sortOption = OptionBuilder.withLongOpt("sort") 399 .withDescription("sort the items in the dex file into a canonical order before writing") 400 .create("S"); 401 402 Option noFixJumboOption = OptionBuilder.withLongOpt("no-fix-jumbo") 403 .withDescription("Don't automatically instructions with the /jumbo variant where appropriate") 404 .create("J"); 405 406 Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto") 407 .withDescription("Don't replace goto type instructions with a larger version where appropriate") 408 .create("G"); 409 410 Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors") 411 .withDescription("Generate verbose error messages") 412 .create("V"); 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 basicOptions.addOption(apiLevelOption); 423 424 debugOptions.addOption(dumpOption); 425 debugOptions.addOption(sortOption); 426 debugOptions.addOption(noFixJumboOption); 427 debugOptions.addOption(noFixGotoOption); 428 debugOptions.addOption(verboseErrorsOption); 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}