main.java revision dd77ba20f44e0c40a603a28bd0a9bcb0b6f43dfc
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 com.google.common.collect.Lists; 32import org.antlr.runtime.CommonTokenStream; 33import org.antlr.runtime.Token; 34import org.antlr.runtime.TokenSource; 35import org.antlr.runtime.tree.CommonTree; 36import org.antlr.runtime.tree.CommonTreeNodeStream; 37import org.apache.commons.cli.*; 38import org.jf.dexlib2.writer.builder.DexBuilder; 39import org.jf.util.ConsoleUtil; 40import org.jf.util.SmaliHelpFormatter; 41 42import javax.annotation.Nonnull; 43import java.io.*; 44import java.util.*; 45import java.util.concurrent.Callable; 46import java.util.concurrent.ExecutorService; 47import java.util.concurrent.Executors; 48import java.util.concurrent.Future; 49 50/** 51 * Main class for smali. It recognizes enough options to be able to dispatch 52 * to the right "actual" main. 53 */ 54public class main { 55 56 public static final String VERSION; 57 58 private final static Options basicOptions; 59 private final static Options debugOptions; 60 private final static Options options; 61 62 static { 63 basicOptions = new Options(); 64 debugOptions = new Options(); 65 options = new Options(); 66 buildOptions(); 67 68 InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); 69 Properties properties = new Properties(); 70 String version = "(unknown)"; 71 try { 72 properties.load(templateStream); 73 version = properties.getProperty("application.version"); 74 } catch (IOException ex) { 75 // just eat it 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 Locale locale = new Locale("en", "US"); 92 Locale.setDefault(locale); 93 94 CommandLineParser parser = new PosixParser(); 95 CommandLine commandLine; 96 97 try { 98 commandLine = parser.parse(options, args); 99 } catch (ParseException ex) { 100 usage(); 101 return; 102 } 103 104 int jobs = -1; 105 boolean allowOdex = false; 106 boolean verboseErrors = false; 107 boolean printTokens = false; 108 109 int apiLevel = 15; 110 111 String outputDexFile = "out.dex"; 112 113 String[] remainingArgs = commandLine.getArgs(); 114 115 Option[] options = commandLine.getOptions(); 116 117 for (int i=0; i<options.length; i++) { 118 Option option = options[i]; 119 String opt = option.getOpt(); 120 121 switch (opt.charAt(0)) { 122 case 'v': 123 version(); 124 return; 125 case '?': 126 while (++i < options.length) { 127 if (options[i].getOpt().charAt(0) == '?') { 128 usage(true); 129 return; 130 } 131 } 132 usage(false); 133 return; 134 case 'o': 135 outputDexFile = commandLine.getOptionValue("o"); 136 break; 137 case 'x': 138 allowOdex = true; 139 break; 140 case 'a': 141 apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); 142 break; 143 case 'j': 144 jobs = Integer.parseInt(commandLine.getOptionValue("j")); 145 break; 146 case 'V': 147 verboseErrors = true; 148 break; 149 case 'T': 150 printTokens = 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 if (jobs <= 0) { 180 jobs = Runtime.getRuntime().availableProcessors(); 181 if (jobs > 6) { 182 jobs = 6; 183 } 184 } 185 186 boolean errors = false; 187 188 final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(apiLevel); 189 ExecutorService executor = Executors.newFixedThreadPool(jobs); 190 List<Future<Boolean>> tasks = Lists.newArrayList(); 191 192 final boolean finalVerboseErrors = verboseErrors; 193 final boolean finalPrintTokens = printTokens; 194 final boolean finalAllowOdex = allowOdex; 195 final int finalApiLevel = apiLevel; 196 for (final File file: filesToProcess) { 197 tasks.add(executor.submit(new Callable<Boolean>() { 198 @Override public Boolean call() throws Exception { 199 return assembleSmaliFile(file, dexBuilder, finalVerboseErrors, finalPrintTokens, 200 finalAllowOdex, finalApiLevel); 201 } 202 })); 203 } 204 205 for (Future<Boolean> task: tasks) { 206 while(true) { 207 try { 208 if (!task.get()) { 209 errors = true; 210 } 211 } catch (InterruptedException ex) { 212 continue; 213 } 214 break; 215 } 216 } 217 218 executor.shutdown(); 219 220 if (errors) { 221 System.exit(1); 222 } 223 224 dexBuilder.writeTo(outputDexFile); 225 } catch (RuntimeException ex) { 226 System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 227 ex.printStackTrace(); 228 System.exit(2); 229 } catch (Throwable ex) { 230 System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); 231 ex.printStackTrace(); 232 System.exit(3); 233 } 234 } 235 236 private static void getSmaliFilesInDir(@Nonnull File dir, @Nonnull Set<File> smaliFiles) { 237 File[] files = dir.listFiles(); 238 if (files != null) { 239 for(File file: files) { 240 if (file.isDirectory()) { 241 getSmaliFilesInDir(file, smaliFiles); 242 } else if (file.getName().endsWith(".smali")) { 243 smaliFiles.add(file); 244 } 245 } 246 } 247 } 248 249 private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors, 250 boolean printTokens, boolean allowOdex, int apiLevel) 251 throws Exception { 252 CommonTokenStream tokens; 253 254 LexerErrorInterface lexer; 255 256 FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath()); 257 InputStreamReader reader = new InputStreamReader(fis, "UTF-8"); 258 259 lexer = new smaliFlexLexer(reader); 260 ((smaliFlexLexer)lexer).setSourceFile(smaliFile); 261 tokens = new CommonTokenStream((TokenSource)lexer); 262 263 if (printTokens) { 264 tokens.getTokens(); 265 266 for (int i=0; i<tokens.size(); i++) { 267 Token token = tokens.get(i); 268 if (token.getChannel() == smaliParser.HIDDEN) { 269 continue; 270 } 271 272 System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText()); 273 } 274 } 275 276 smaliParser parser = new smaliParser(tokens); 277 parser.setVerboseErrors(verboseErrors); 278 parser.setAllowOdex(allowOdex); 279 parser.setApiLevel(apiLevel); 280 281 smaliParser.smali_file_return result = parser.smali_file(); 282 283 if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) { 284 return false; 285 } 286 287 CommonTree t = result.getTree(); 288 289 CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); 290 treeStream.setTokenStream(tokens); 291 292 smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); 293 dexGen.setVerboseErrors(verboseErrors); 294 dexGen.setDexBuilder(dexBuilder); 295 dexGen.smali_file(); 296 297 return dexGen.getNumberOfSyntaxErrors() == 0; 298 } 299 300 301 /** 302 * Prints the usage message. 303 */ 304 private static void usage(boolean printDebugOptions) { 305 SmaliHelpFormatter formatter = new SmaliHelpFormatter(); 306 307 int consoleWidth = ConsoleUtil.getConsoleWidth(); 308 if (consoleWidth <= 0) { 309 consoleWidth = 80; 310 } 311 312 formatter.setWidth(consoleWidth); 313 314 formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*", 315 "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null); 316 } 317 318 private static void usage() { 319 usage(false); 320 } 321 322 /** 323 * Prints the version message. 324 */ 325 private static void version() { 326 System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); 327 System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); 328 System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 329 System.exit(0); 330 } 331 332 @SuppressWarnings("AccessStaticViaInstance") 333 private static void buildOptions() { 334 Option versionOption = OptionBuilder.withLongOpt("version") 335 .withDescription("prints the version then exits") 336 .create("v"); 337 338 Option helpOption = OptionBuilder.withLongOpt("help") 339 .withDescription("prints the help message then exits. Specify twice for debug options") 340 .create("?"); 341 342 Option outputOption = OptionBuilder.withLongOpt("output") 343 .withDescription("the name of the dex file that will be written. The default is out.dex") 344 .hasArg() 345 .withArgName("FILE") 346 .create("o"); 347 348 Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions") 349 .withDescription("allow odex instructions to be compiled into the dex file. Only a few" + 350 " instructions are supported - the ones that can exist in a dead code path and not" + 351 " cause dalvik to reject the class") 352 .create("x"); 353 354 Option apiLevelOption = OptionBuilder.withLongOpt("api-level") 355 .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " + 356 "specified, it defaults to 15 (ICS).") 357 .hasArg() 358 .withArgName("API_LEVEL") 359 .create("a"); 360 361 Option jobsOption = OptionBuilder.withLongOpt("jobs") 362 .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " + 363 "maximum of 6") 364 .hasArg() 365 .withArgName("NUM_THREADS") 366 .create("j"); 367 368 Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors") 369 .withDescription("Generate verbose error messages") 370 .create("V"); 371 372 Option printTokensOption = OptionBuilder.withLongOpt("print-tokens") 373 .withDescription("Print the name and text of each token") 374 .create("T"); 375 376 basicOptions.addOption(versionOption); 377 basicOptions.addOption(helpOption); 378 basicOptions.addOption(outputOption); 379 basicOptions.addOption(allowOdexOption); 380 basicOptions.addOption(apiLevelOption); 381 basicOptions.addOption(jobsOption); 382 383 debugOptions.addOption(verboseErrorsOption); 384 debugOptions.addOption(printTokensOption); 385 386 for (Object option: basicOptions.getOptions()) { 387 options.addOption((Option)option); 388 } 389 390 for (Object option: debugOptions.getOptions()) { 391 options.addOption((Option)option); 392 } 393 } 394}