/* * [The "BSD licence"] * Copyright (c) 2010 Ben Gruver (JesusFreke) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jf.smali; import com.google.common.collect.Lists; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.Token; import org.antlr.runtime.TokenSource; import org.antlr.runtime.tree.CommonTree; import org.antlr.runtime.tree.CommonTreeNodeStream; import org.apache.commons.cli.*; import org.jf.dexlib2.writer.builder.DexBuilder; import org.jf.dexlib2.writer.io.FileDataStore; import org.jf.util.ConsoleUtil; import org.jf.util.SmaliHelpFormatter; import javax.annotation.Nonnull; import java.io.*; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * Main class for smali. It recognizes enough options to be able to dispatch * to the right "actual" main. */ public class main { public static final String VERSION; private final static Options basicOptions; private final static Options debugOptions; private final static Options options; static { basicOptions = new Options(); debugOptions = new Options(); options = new Options(); buildOptions(); InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); if (templateStream != null) { Properties properties = new Properties(); String version = "(unknown)"; try { properties.load(templateStream); version = properties.getProperty("application.version"); } catch (IOException ex) { // just eat it } VERSION = version; } else { VERSION = "[unknown version]"; } } /** * This class is uninstantiable. */ private main() { } /** * Run! */ public static void main(String[] args) { Locale locale = new Locale("en", "US"); Locale.setDefault(locale); CommandLineParser parser = new PosixParser(); CommandLine commandLine; try { commandLine = parser.parse(options, args); } catch (ParseException ex) { usage(); return; } int jobs = -1; boolean allowOdex = false; boolean verboseErrors = false; boolean printTokens = false; int apiLevel = 15; String outputDexFile = "out.dex"; String[] remainingArgs = commandLine.getArgs(); Option[] options = commandLine.getOptions(); for (int i=0; i filesToProcess = new LinkedHashSet(); for (String arg: remainingArgs) { File argFile = new File(arg); if (!argFile.exists()) { throw new RuntimeException("Cannot find file or directory \"" + arg + "\""); } if (argFile.isDirectory()) { getSmaliFilesInDir(argFile, filesToProcess); } else if (argFile.isFile()) { filesToProcess.add(argFile); } } if (jobs <= 0) { jobs = Runtime.getRuntime().availableProcessors(); if (jobs > 6) { jobs = 6; } } boolean errors = false; final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(apiLevel); ExecutorService executor = Executors.newFixedThreadPool(jobs); List> tasks = Lists.newArrayList(); final boolean finalVerboseErrors = verboseErrors; final boolean finalPrintTokens = printTokens; final boolean finalAllowOdex = allowOdex; final int finalApiLevel = apiLevel; for (final File file: filesToProcess) { tasks.add(executor.submit(new Callable() { @Override public Boolean call() throws Exception { return assembleSmaliFile(file, dexBuilder, finalVerboseErrors, finalPrintTokens, finalAllowOdex, finalApiLevel); } })); } for (Future task: tasks) { while(true) { try { if (!task.get()) { errors = true; } } catch (InterruptedException ex) { continue; } break; } } executor.shutdown(); if (errors) { System.exit(1); } dexBuilder.writeTo(new FileDataStore(new File(outputDexFile))); } catch (RuntimeException ex) { System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); ex.printStackTrace(); System.exit(2); } catch (Throwable ex) { System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); ex.printStackTrace(); System.exit(3); } } private static void getSmaliFilesInDir(@Nonnull File dir, @Nonnull Set smaliFiles) { File[] files = dir.listFiles(); if (files != null) { for(File file: files) { if (file.isDirectory()) { getSmaliFilesInDir(file, smaliFiles); } else if (file.getName().endsWith(".smali")) { smaliFiles.add(file); } } } } private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors, boolean printTokens, boolean allowOdex, int apiLevel) throws Exception { CommonTokenStream tokens; LexerErrorInterface lexer; FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath()); InputStreamReader reader = new InputStreamReader(fis, "UTF-8"); lexer = new smaliFlexLexer(reader); ((smaliFlexLexer)lexer).setSourceFile(smaliFile); tokens = new CommonTokenStream((TokenSource)lexer); if (printTokens) { tokens.getTokens(); for (int i=0; i 0 || lexer.getNumberOfSyntaxErrors() > 0) { return false; } CommonTree t = result.getTree(); CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); treeStream.setTokenStream(tokens); smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); dexGen.setVerboseErrors(verboseErrors); dexGen.setDexBuilder(dexBuilder); dexGen.smali_file(); return dexGen.getNumberOfSyntaxErrors() == 0; } /** * Prints the usage message. */ private static void usage(boolean printDebugOptions) { SmaliHelpFormatter formatter = new SmaliHelpFormatter(); int consoleWidth = ConsoleUtil.getConsoleWidth(); if (consoleWidth <= 0) { consoleWidth = 80; } formatter.setWidth(consoleWidth); formatter.printHelp("java -jar smali.jar [options] [--] [|folder]*", "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null); } private static void usage() { usage(false); } /** * Prints the version message. */ private static void version() { System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); System.exit(0); } @SuppressWarnings("AccessStaticViaInstance") private static void buildOptions() { Option versionOption = OptionBuilder.withLongOpt("version") .withDescription("prints the version then exits") .create("v"); Option helpOption = OptionBuilder.withLongOpt("help") .withDescription("prints the help message then exits. Specify twice for debug options") .create("?"); Option outputOption = OptionBuilder.withLongOpt("output") .withDescription("the name of the dex file that will be written. The default is out.dex") .hasArg() .withArgName("FILE") .create("o"); Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions") .withDescription("allow odex instructions to be compiled into the dex file. Only a few" + " instructions are supported - the ones that can exist in a dead code path and not" + " cause dalvik to reject the class") .create("x"); Option apiLevelOption = OptionBuilder.withLongOpt("api-level") .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " + "specified, it defaults to 15 (ICS).") .hasArg() .withArgName("API_LEVEL") .create("a"); Option jobsOption = OptionBuilder.withLongOpt("jobs") .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " + "maximum of 6") .hasArg() .withArgName("NUM_THREADS") .create("j"); Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors") .withDescription("Generate verbose error messages") .create("V"); Option printTokensOption = OptionBuilder.withLongOpt("print-tokens") .withDescription("Print the name and text of each token") .create("T"); basicOptions.addOption(versionOption); basicOptions.addOption(helpOption); basicOptions.addOption(outputOption); basicOptions.addOption(allowOdexOption); basicOptions.addOption(apiLevelOption); basicOptions.addOption(jobsOption); debugOptions.addOption(verboseErrorsOption); debugOptions.addOption(printTokensOption); for (Object option: basicOptions.getOptions()) { options.addOption((Option)option); } for (Object option: debugOptions.getOptions()) { options.addOption((Option)option); } } }