Add more programmatic-friendly entry points for smali/baksmali

This adds entry points that are more friendly to programmatic usage. E.g.
no calls to System.exit()
This commit is contained in:
Ben Gruver 2016-02-28 12:19:27 -08:00
parent a198b46e20
commit 87d10dac27
5 changed files with 224 additions and 159 deletions

View File

@ -82,6 +82,9 @@ public class baksmaliOptions {
public int registerInfo = 0;
public ClassPath classPath = null;
public int jobs = Runtime.getRuntime().availableProcessors();
public boolean disassemble = true;
public boolean dump = false;
public String dumpFileName = null;
public SyntheticAccessorResolver syntheticAccessorResolver = null;

View File

@ -40,7 +40,7 @@ import java.io.IOException;
import java.io.Writer;
public class dump {
public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel, boolean experimental) throws IOException {
public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel) throws IOException {
if (dumpFileName != null) {
Writer writer = null;

View File

@ -36,6 +36,7 @@ import org.jf.dexlib2.analysis.InlineMethodResolver;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.util.ConsoleUtil;
import org.jf.util.SmaliHelpFormatter;
@ -83,6 +84,45 @@ public class main {
private main() {
}
/**
* A more programmatic-friendly entry point for baksmali
*
* @param options a baksmaliOptions object with the options to run baksmali with
* @param inputDexFile The DexFile to disassemble
* @return true if disassembly completed with no errors, or false if errors were encountered
*/
public static boolean run(@Nonnull baksmaliOptions options, @Nonnull DexFile inputDexFile) throws IOException {
if (options.bootClassPathEntries.isEmpty() &&
(options.deodex || options.registerInfo != 0 || options.normalizeVirtualMethods)) {
if (inputDexFile instanceof DexBackedOdexFile) {
options.bootClassPathEntries = ((DexBackedOdexFile)inputDexFile).getDependencies();
} else {
options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel,
options.experimental);
}
}
if (options.customInlineDefinitions == null && inputDexFile instanceof DexBackedOdexFile) {
options.inlineResolver =
InlineMethodResolver.createInlineMethodResolver(
((DexBackedOdexFile)inputDexFile).getOdexVersion());
}
boolean errorOccurred = false;
if (options.disassemble) {
errorOccurred = !baksmali.disassembleDexFile(inputDexFile, options);
}
if (options.dump) {
if (!(inputDexFile instanceof DexBackedDexFile)) {
throw new IllegalArgumentException("Annotated hex-dumps require a DexBackedDexFile");
}
dump.dump((DexBackedDexFile)inputDexFile, options.dumpFileName, options.apiLevel);
}
return !errorOccurred;
}
/**
* Run!
*/
@ -102,11 +142,6 @@ public class main {
baksmaliOptions options = new baksmaliOptions();
boolean disassemble = true;
boolean doDump = false;
String dumpFileName = null;
boolean setBootClassPath = false;
String[] remainingArgs = commandLine.getArgs();
Option[] clOptions = commandLine.getOptions();
@ -187,7 +222,6 @@ public class main {
if (bcp != null && bcp.charAt(0) == ':') {
options.addExtraClassPath(bcp);
} else {
setBootClassPath = true;
options.setBootClassPath(bcp);
}
break;
@ -223,11 +257,11 @@ public class main {
options.normalizeVirtualMethods = true;
break;
case 'N':
disassemble = false;
options.disassemble = false;
break;
case 'D':
doDump = true;
dumpFileName = commandLine.getOptionValue("D");
options.dump = true;
options.dumpFileName = commandLine.getOptionValue("D");
break;
case 'I':
options.ignoreErrors = true;
@ -245,11 +279,10 @@ public class main {
return;
}
String inputDexFileName = remainingArgs[0];
File dexFileFile = new File(inputDexFileName);
String inputDexPath = remainingArgs[0];
File dexFileFile = new File(inputDexPath);
if (!dexFileFile.exists()) {
System.err.println("Can't find the file " + inputDexFileName);
System.err.println("Can't find the file " + inputDexPath);
System.exit(1);
}
@ -261,6 +294,7 @@ public class main {
System.err.println(String.format("%s contains multiple dex files. You must specify which one to " +
"disassemble with the -e option", dexFileFile.getName()));
System.err.println("Valid entries include:");
for (OatDexFile oatDexFile: ex.oatFile.getDexFiles()) {
System.err.println(oatDexFile.filename);
}
@ -278,34 +312,18 @@ public class main {
options.deodex = false;
}
if (!setBootClassPath && (options.deodex || options.registerInfo != 0 || options.normalizeVirtualMethods)) {
if (dexFile instanceof DexBackedOdexFile) {
options.bootClassPathEntries = ((DexBackedOdexFile)dexFile).getDependencies();
} else {
options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel,
options.experimental);
if (options.dump) {
if (options.dumpFileName == null) {
options.dumpFileName = inputDexPath + ".dump";
}
}
if (options.customInlineDefinitions == null && dexFile instanceof DexBackedOdexFile) {
options.inlineResolver =
InlineMethodResolver.createInlineMethodResolver(
((DexBackedOdexFile)dexFile).getOdexVersion());
}
boolean errorOccurred = false;
if (disassemble) {
errorOccurred = !baksmali.disassembleDexFile(dexFile, options);
}
if (doDump) {
if (dumpFileName == null) {
dumpFileName = commandLine.getOptionValue(inputDexFileName + ".dump");
try {
if (!run(options, dexFile)) {
System.exit(1);
}
dump.dump(dexFile, dumpFileName, options.apiLevel, options.experimental);
}
if (errorOccurred) {
} catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
System.exit(1);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* OWNER OR CONTRIBUTORS 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;
public class SmaliOptions {
public int apiLevel = 15;
public String outputDexFile = "out.dex";
public int jobs = Runtime.getRuntime().availableProcessors();
public boolean allowOdex = false;
public boolean verboseErrors = false;
public boolean printTokens = false;
public boolean experimental = false;
public boolean listMethods = false;
public String methodListFilename = null;
public boolean listFields = false;
public String fieldListFilename = null;
public boolean listTypes = false;
public String typeListFilename = null;
}

View File

@ -46,10 +46,7 @@ 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;
import java.util.concurrent.*;
/**
* Main class for smali. It recognizes enough options to be able to dispatch
@ -92,6 +89,95 @@ public class main {
private main() {
}
/**
* A more programmatic-friendly entry point for smali
*
* @param options a SmaliOptions object with the options to run smali with
* @param input The files/directories to process
* @return true if assembly completed with no errors, or false if errors were encountered
*/
public static boolean run(final SmaliOptions options, String... input) throws IOException {
LinkedHashSet<File> filesToProcessSet = new LinkedHashSet<File>();
for (String fileToProcess: input) {
File argFile = new File(fileToProcess);
if (!argFile.exists()) {
throw new IllegalArgumentException("Cannot find file or directory \"" + fileToProcess + "\"");
}
if (argFile.isDirectory()) {
getSmaliFilesInDir(argFile, filesToProcessSet);
} else if (argFile.isFile()) {
filesToProcessSet.add(argFile);
}
}
boolean errors = false;
final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(
Opcodes.forApi(options.apiLevel, options.experimental));
ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
List<Future<Boolean>> tasks = Lists.newArrayList();
for (final File file: filesToProcessSet) {
tasks.add(executor.submit(new Callable<Boolean>() {
@Override public Boolean call() throws Exception {
return assembleSmaliFile(file, dexBuilder, options);
}
}));
}
for (Future<Boolean> task: tasks) {
while(true) {
try {
try {
if (!task.get()) {
errors = true;
}
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
} catch (InterruptedException ex) {
continue;
}
break;
}
}
executor.shutdown();
if (errors) {
return false;
}
if (options.listMethods) {
if (Strings.isNullOrEmpty(options.methodListFilename)) {
options.methodListFilename = options.outputDexFile + ".methods";
}
writeReferences(dexBuilder.getMethodReferences(), options.methodListFilename);
}
if (options.listFields) {
if (Strings.isNullOrEmpty(options.fieldListFilename)) {
options.fieldListFilename = options.outputDexFile + ".fields";
}
writeReferences(dexBuilder.getFieldReferences(), options.fieldListFilename);
}
if (options.listTypes) {
if (Strings.isNullOrEmpty(options.typeListFilename)) {
options.typeListFilename = options.outputDexFile + ".types";
}
writeReferences(dexBuilder.getTypeReferences(), options.typeListFilename);
}
dexBuilder.writeTo(new FileDataStore(new File(options.outputDexFile)));
return true;
}
/**
* Run!
*/
@ -109,24 +195,7 @@ public class main {
return;
}
int jobs = Runtime.getRuntime().availableProcessors();
boolean allowOdex = false;
boolean verboseErrors = false;
boolean printTokens = false;
boolean experimental = false;
boolean listMethods = false;
String methodListFilename = null;
boolean listFields = false;
String fieldListFilename = null;
boolean listTypes = false;
String typeListFilename = null;
int apiLevel = 15;
String outputDexFile = "out.dex";
SmaliOptions smaliOptions = new SmaliOptions();
String[] remainingArgs = commandLine.getArgs();
@ -150,37 +219,37 @@ public class main {
usage(false);
return;
case 'o':
outputDexFile = commandLine.getOptionValue("o");
smaliOptions.outputDexFile = commandLine.getOptionValue("o");
break;
case 'x':
allowOdex = true;
smaliOptions.allowOdex = true;
break;
case 'X':
experimental = true;
smaliOptions.experimental = true;
break;
case 'a':
apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
smaliOptions.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
break;
case 'j':
jobs = Integer.parseInt(commandLine.getOptionValue("j"));
smaliOptions.jobs = Integer.parseInt(commandLine.getOptionValue("j"));
break;
case 'm':
listMethods = true;
methodListFilename = commandLine.getOptionValue("m");
smaliOptions.listMethods = true;
smaliOptions.methodListFilename = commandLine.getOptionValue("m");
break;
case 'f':
listFields = true;
fieldListFilename = commandLine.getOptionValue("f");
smaliOptions.listFields = true;
smaliOptions.fieldListFilename = commandLine.getOptionValue("f");
break;
case 't':
listTypes = true;
typeListFilename = commandLine.getOptionValue("t");
smaliOptions.listTypes = true;
smaliOptions.typeListFilename = commandLine.getOptionValue("t");
break;
case 'V':
verboseErrors = true;
smaliOptions.verboseErrors = true;
break;
case 'T':
printTokens = true;
smaliOptions.printTokens = true;
break;
default:
assert false;
@ -193,84 +262,9 @@ public class main {
}
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);
}
}
boolean errors = false;
final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(apiLevel, experimental));
ExecutorService executor = Executors.newFixedThreadPool(jobs);
List<Future<Boolean>> tasks = Lists.newArrayList();
final boolean finalVerboseErrors = verboseErrors;
final boolean finalPrintTokens = printTokens;
final boolean finalAllowOdex = allowOdex;
final int finalApiLevel = apiLevel;
final boolean finalExperimental = experimental;
for (final File file: filesToProcess) {
tasks.add(executor.submit(new Callable<Boolean>() {
@Override public Boolean call() throws Exception {
return assembleSmaliFile(file, dexBuilder, finalVerboseErrors, finalPrintTokens,
finalAllowOdex, finalApiLevel, finalExperimental);
}
}));
}
for (Future<Boolean> task: tasks) {
while(true) {
try {
if (!task.get()) {
errors = true;
}
} catch (InterruptedException ex) {
continue;
}
break;
}
}
executor.shutdown();
if (errors) {
if (!run(smaliOptions, remainingArgs)) {
System.exit(1);
}
if (listMethods) {
if (Strings.isNullOrEmpty(methodListFilename)) {
methodListFilename = outputDexFile + ".methods";
}
writeReferences(dexBuilder.getMethodReferences(), methodListFilename);
}
if (listFields) {
if (Strings.isNullOrEmpty(fieldListFilename)) {
fieldListFilename = outputDexFile + ".fields";
}
writeReferences(dexBuilder.getFieldReferences(), fieldListFilename);
}
if (listTypes) {
if (Strings.isNullOrEmpty(typeListFilename)) {
typeListFilename = outputDexFile + ".types";
}
writeReferences(dexBuilder.getTypeReferences(), typeListFilename);
}
dexBuilder.writeTo(new FileDataStore(new File(outputDexFile)));
} catch (RuntimeException ex) {
System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
ex.printStackTrace();
@ -312,9 +306,7 @@ public class main {
}
}
private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors,
boolean printTokens, boolean allowOdex, int apiLevel,
boolean experimental)
private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, SmaliOptions options)
throws Exception {
CommonTokenStream tokens;
@ -327,7 +319,7 @@ public class main {
((smaliFlexLexer)lexer).setSourceFile(smaliFile);
tokens = new CommonTokenStream((TokenSource)lexer);
if (printTokens) {
if (options.printTokens) {
tokens.getTokens();
for (int i=0; i<tokens.size(); i++) {
@ -343,9 +335,9 @@ public class main {
}
smaliParser parser = new smaliParser(tokens);
parser.setVerboseErrors(verboseErrors);
parser.setAllowOdex(allowOdex);
parser.setApiLevel(apiLevel, experimental);
parser.setVerboseErrors(options.verboseErrors);
parser.setAllowOdex(options.allowOdex);
parser.setApiLevel(options.apiLevel, options.experimental);
smaliParser.smali_file_return result = parser.smali_file();
@ -358,14 +350,14 @@ public class main {
CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
treeStream.setTokenStream(tokens);
if (printTokens) {
if (options.printTokens) {
System.out.println(t.toStringTree());
}
smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
dexGen.setApiLevel(apiLevel, experimental);
dexGen.setApiLevel(options.apiLevel, options.experimental);
dexGen.setVerboseErrors(verboseErrors);
dexGen.setVerboseErrors(options.verboseErrors);
dexGen.setDexBuilder(dexBuilder);
dexGen.smali_file();