Significant rewrite of the command line interface, using apache commons CLI

git-svn-id: https://smali.googlecode.com/svn/trunk@216 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
JesusFreke@JesusFreke.com 2009-06-23 06:19:50 +00:00
parent 44682fe235
commit 2cd9246976
4 changed files with 219 additions and 303 deletions

View File

@ -78,5 +78,10 @@
<artifactId>dexlib</artifactId>
<version>0.91-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>

View File

@ -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.
}

View File

@ -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=<file>]\n" +
" [--dump-to=<file>] [--dump-width=<width>]\n" +
" [--] [<file> | <dir> | -]+\n" +
" Convert a set of smali assembly files into a dex file.\n" +
" <file> - assemble the specified smali file\n" +
" <dir> - 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<File> filesToProcess = new LinkedHashSet<File>();
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<File> 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] [--] [<smali-file>|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 (<dexfile>.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);
}
}

View File

@ -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<File> filesToProcess = new LinkedHashSet<File>();
boolean getFilesFromStdin = false;
String outputFilename = "classes.dex";
String dumpFilename = null;
int dumpWidth = 120;
int i;
for (i=0; i<args.length; i++) {
String arg = args[i];
if (arg.equals("--") || !arg.startsWith("--")) {
break;
}
if (arg.startsWith("--output=")) {
outputFilename = arg.substring(arg.indexOf('=') + 1);
} else if (arg.startsWith("--dump-to=")) {
dumpFilename = arg.substring(arg.indexOf("=") + 1);
} else if (arg.startsWith("--dump-width=")) {
dumpWidth = Integer.parseInt(arg.substring(arg.indexOf("=") + 1));
} else {
System.err.println("unknown option: " + arg);
throw new UsageException();
}
}
for (i=i; i<args.length; i++) {
String arg = args[i];
if (arg.compareTo("-") == 0) {
getFilesFromStdin = true;
} else {
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 (getFilesFromStdin) {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader in = new BufferedReader(isr);
String line = in.readLine();
while (line != null) {
File file = new File(line);
if (!file.exists()) {
throw new RuntimeException("Cannot find file or directory \"" + line + "\"");
}
if (file.isDirectory()) {
getSmaliFilesInDir(file, filesToProcess);
} else {
filesToProcess.add(file);
}
line = in.readLine();
}
}
DexFile dexFile = DexFile.makeBlankDexFile();
boolean errors = false;
for (File file: filesToProcess) {
if (!assembleSmaliFile(file, dexFile)) {
errors = true;
}
}
if (errors) {
System.exit(1);
}
dexFile.place(false);
try
{
ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
if (dumpFilename != null) {
out.enableAnnotations(dumpWidth, 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(outputFilename);
fileOutputStream.write(bytes);
fileOutputStream.close();
} catch (Exception ex)
{
System.out.println(ex.toString());
System.exit(1);
}
}
private static void getSmaliFilesInDir(File dir, Set<File> 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;
}
}