diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java index 74063962..32685ddf 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java @@ -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; diff --git a/baksmali/src/main/java/org/jf/baksmali/dump.java b/baksmali/src/main/java/org/jf/baksmali/dump.java index 57a48af8..79405e59 100644 --- a/baksmali/src/main/java/org/jf/baksmali/dump.java +++ b/baksmali/src/main/java/org/jf/baksmali/dump.java @@ -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; diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java index 376611c7..2d6ed8c4 100644 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/baksmali/src/main/java/org/jf/baksmali/main.java @@ -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); } } diff --git a/smali/src/main/java/org/jf/smali/SmaliOptions.java b/smali/src/main/java/org/jf/smali/SmaliOptions.java new file mode 100644 index 00000000..165c3a89 --- /dev/null +++ b/smali/src/main/java/org/jf/smali/SmaliOptions.java @@ -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; +} diff --git a/smali/src/main/java/org/jf/smali/main.java b/smali/src/main/java/org/jf/smali/main.java index 6e0793c1..e5562808 100644 --- a/smali/src/main/java/org/jf/smali/main.java +++ b/smali/src/main/java/org/jf/smali/main.java @@ -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 filesToProcessSet = new LinkedHashSet(); + + 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> tasks = Lists.newArrayList(); + + for (final File file: filesToProcessSet) { + tasks.add(executor.submit(new Callable() { + @Override public Boolean call() throws Exception { + return assembleSmaliFile(file, dexBuilder, options); + } + })); + } + + for (Future 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 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); - } - } - - boolean errors = false; - - final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(Opcodes.forApi(apiLevel, experimental)); - - 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; - final boolean finalExperimental = experimental; - 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, finalExperimental); - } - })); - } - - for (Future 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