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