diff --git a/smali/pom.xml b/smali/pom.xml index 6073a0b3..63fe1ad5 100644 --- a/smali/pom.xml +++ b/smali/pom.xml @@ -78,5 +78,10 @@ dexlib 0.91-SNAPSHOT + + commons-cli + commons-cli + 1.2 + \ No newline at end of file diff --git a/smali/src/main/java/org/jf/smali/UsageException.java b/smali/src/main/java/org/jf/smali/UsageException.java deleted file mode 100644 index 90149c78..00000000 --- a/smali/src/main/java/org/jf/smali/UsageException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jf.smali; - -/** - * Simple exception class used to communicate that the command-line tool - * should print the usage message. - */ -public class UsageException extends RuntimeException { - // This space intentionally left blank. -} diff --git a/smali/src/main/java/org/jf/smali/main.java b/smali/src/main/java/org/jf/smali/main.java index 166803bb..8302d368 100644 --- a/smali/src/main/java/org/jf/smali/main.java +++ b/smali/src/main/java/org/jf/smali/main.java @@ -16,71 +16,158 @@ package org.jf.smali; +import org.apache.commons.cli.*; +import org.jf.dexlib.DexFile; +import org.jf.dexlib.Util.ByteArrayAnnotatedOutput; +import org.antlr.runtime.ANTLRInputStream; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.tree.CommonTree; +import org.antlr.runtime.tree.CommonTreeNodeStream; + +import java.io.File; +import java.io.FileWriter; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.util.Set; +import java.util.LinkedHashSet; + /** * 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 = "0.5b"; + public static final String VERSION = "0.91"; + + private final static Options options; + + static { + options = new Options(); + buildOptions(); + } - private static String USAGE_MESSAGE = - "usage:\n" + - " java -jar smali.jar --dex [--output=]\n" + - " [--dump-to=] [--dump-width=]\n" + - " [--] [ | | -]+\n" + - " Convert a set of smali assembly files into a dex file.\n" + - " - assemble the specified smali file\n" + - " - recusively find and assemble all *.smali files in\n" + - " the specified directory\n"+ - " - - additionally read a list of files and directories\n" + - " from stdin\n" + - " java -jar smali.jar --version\n" + - " Print the version of this tool (" + VERSION + - ").\n" + - " java -jar smali.jar --help\n" + - " Print this message."; - /** * This class is uninstantiable. */ private main() { - // This space intentionally left blank. } /** * Run! */ public static void main(String[] args) { - boolean gotCmd = false; - boolean showUsage = false; + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; try { - for (int i = 0; i < args.length; i++) { - String arg = args[i]; - if (arg.equals("--") || !arg.startsWith("--")) { - gotCmd = false; - showUsage = true; - break; - } + commandLine = parser.parse(options, args); + } catch (ParseException ex) { + usage(); + return; + } - gotCmd = true; - if (arg.equals("--dex")) { - smali.main(without(args, i)); - break; - } else if (arg.equals("--version")) { - version(); - break; - } else if (arg.equals("--help")) { - showUsage = true; - break; - } else { - gotCmd = false; + boolean doDump = false; + boolean sort = false; + + String outputDexFile = "out.dex"; + String dumpFileName = null; + + String[] remainingArgs = commandLine.getArgs(); + + if (commandLine.hasOption("v")) { + version(); + return; + } + + if (commandLine.hasOption("?")) { + usage(); + return; + } + + if (remainingArgs.length == 0) { + usage(); + return; + } + + if (commandLine.hasOption("o")) { + outputDexFile = commandLine.getOptionValue("o"); + } + + if (commandLine.hasOption("d")) { + doDump = true; + dumpFileName = commandLine.getOptionValue("d", outputDexFile + ".dump"); + } + + if (commandLine.hasOption("s")) { + sort = true; + } + + + try { + + LinkedHashSet 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); + } + } + + DexFile dexFile = new DexFile(); + + boolean errors = false; + + for (File file: filesToProcess) { + if (!assembleSmaliFile(file, dexFile)) { + errors = true; } } - } catch (UsageException ex) { - showUsage = true; + + if (errors) { + System.exit(1); + } + + + if (sort) { + dexFile.setSortAllItems(true); + } + + dexFile.place(); + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + + if (dumpFileName != null) { + out.enableAnnotations(120, true); + } + + dexFile.writeTo(out); + + byte[] bytes = out.toByteArray(); + + DexFile.calcSignature(bytes); + DexFile.calcChecksum(bytes); + + if (dumpFileName != null) { + out.finishAnnotating(); + + FileWriter fileWriter = new FileWriter(dumpFileName); + out.writeAnnotationsTo(fileWriter); + fileWriter.close(); + } + + FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile); + + fileOutputStream.write(bytes); + fileOutputStream.close(); } catch (RuntimeException ex) { System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); ex.printStackTrace(); @@ -90,46 +177,104 @@ public class main { ex.printStackTrace(); System.exit(3); } + } - if (!gotCmd) { - System.err.println("error: no command specified"); - showUsage = true; + + private static void getSmaliFilesInDir(File dir, Set smaliFiles) { + for(File file: dir.listFiles()) { + if (file.isDirectory()) { + getSmaliFilesInDir(file, smaliFiles); + } else if (file.getName().endsWith(".smali")) { + smaliFiles.add(file); + } + } + } + + private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile) + throws Exception { + ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(smaliFile)); + input.name = smaliFile.getAbsolutePath(); + + smaliLexer lexer = new smaliLexer(input); + + CommonTokenStream tokens = new CommonTokenStream(lexer); + smaliParser parser = new smaliParser(tokens); + + smaliParser.smali_file_return result = parser.smali_file(); + + if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfLexerErrors() > 0) { + return false; } - if (showUsage) { - usage(); - System.exit(1); + CommonTree t = (CommonTree) result.getTree(); + + CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); + treeStream.setTokenStream(tokens); + + smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); + + dexGen.dexFile = dexFile; + dexGen.smali_file(); + + if (dexGen.getNumberOfSyntaxErrors() > 0) { + return false; } + + dexFile.ClassDefsSection.intern(dexGen.classDefItem); + return true; + } + + + /** + * Prints the usage message. + */ + private static void usage() { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java -jar smali.jar [options] [--] [|folder]*", + "assembles a set of smali files into a dex file, and optionally generats an annotated dump of the output file", options, ""); } /** * Prints the version message. */ private static void version() { - System.err.println("smali version " + VERSION); + System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); + System.out.println("Copyright (C) 2009 Ben Gruver"); + System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); System.exit(0); } - /** - * Prints the usage message. - */ - private static void usage() { - System.err.println(USAGE_MESSAGE); - } + - /** - * Returns a copy of the given args array, but without the indicated - * element. - * - * @param orig non-null; original array - * @param n which element to omit - * @return non-null; new array - */ - private static String[] without(String[] orig, int n) { - int len = orig.length - 1; - String[] newa = new String[len]; - System.arraycopy(orig, 0, newa, 0, n); - System.arraycopy(orig, n + 1, newa, n, len - n); - return newa; + 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") + .create("?"); + + Option dumpOption = OptionBuilder.withLongOpt("dump-to") + .withDescription("additionally writes a dump of written dex file to FILE (.dump by default)") + .hasOptionalArg() + .withArgName("FILE") + .create("d"); + + Option outputOption = OptionBuilder.withLongOpt("output") + .withDescription("the directory where the disassembled files will be placed. The default is out.dex") + .hasArg() + .withArgName("FILE") + .create("o"); + + Option sortOption = OptionBuilder.withLongOpt("sort") + .withDescription("sort the items in the dex file into a canonical order before writing") + .create("s"); + + options.addOption(versionOption); + options.addOption(helpOption); + options.addOption(dumpOption); + options.addOption(outputOption); + options.addOption(sortOption); } } \ No newline at end of file diff --git a/smali/src/main/java/org/jf/smali/smali.java b/smali/src/main/java/org/jf/smali/smali.java deleted file mode 100644 index 1745c73b..00000000 --- a/smali/src/main/java/org/jf/smali/smali.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * [The "BSD licence"] - * Copyright (c) 2009 Ben Gruver - * 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 org.antlr.runtime.ANTLRInputStream; -import org.antlr.runtime.CommonTokenStream; -import org.antlr.runtime.tree.CommonTree; -import org.antlr.runtime.tree.CommonTreeNodeStream; -import org.jf.dexlib.DexFile; -import org.jf.dexlib.AnnotationItem; -import org.jf.dexlib.Util.ByteArrayAnnotatedOutput; - -import java.io.*; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.List; - -public class smali -{ - public static void main(String[] args) throws Exception - { - LinkedHashSet filesToProcess = new LinkedHashSet(); - - boolean getFilesFromStdin = false; - String outputFilename = "classes.dex"; - String dumpFilename = null; - int dumpWidth = 120; - - int i; - - for (i=0; i smaliFiles) { - for(File file: dir.listFiles()) { - if (file.isDirectory()) { - getSmaliFilesInDir(file, smaliFiles); - } else if (file.getName().endsWith(".smali")) { - smaliFiles.add(file); - } - } - } - - private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile) - throws Exception { - ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(smaliFile)); - input.name = smaliFile.getAbsolutePath(); - - smaliLexer lexer = new smaliLexer(input); - - CommonTokenStream tokens = new CommonTokenStream(lexer); - smaliParser parser = new smaliParser(tokens); - - smaliParser.smali_file_return result = parser.smali_file(); - - if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfLexerErrors() > 0) { - return false; - } - - - CommonTree t = (CommonTree) result.getTree(); - - CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); - treeStream.setTokenStream(tokens); - - smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); - - dexGen.dexFile = dexFile; - dexGen.smali_file(); - - if (dexGen.getNumberOfSyntaxErrors() > 0) { - return false; - } - - dexFile.ClassDefsSection.intern(dexFile, dexGen.classDefItem); - return true; - } -} \ No newline at end of file