/* Software Name : AsmDex * Version : 1.0 * * Copyright © 2012 France Télécom * 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. Neither the name of the copyright holders 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.ow2.asmdex.util; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import org.ow2.asmdex.ApplicationReader; import org.ow2.asmdex.ApplicationVisitor; import org.ow2.asmdex.ClassVisitor; import org.ow2.asmdex.Opcodes; /** * An {@link ApplicationVisitor} that prints the AsmDex code that generates the Application * it visits. This Application visitor can be used to quickly write ASM code to generate * some given bytecode: <ul> <li>write the Java source code equivalent to the * bytecode you want to generate;</li> <li>compile it with <tt>javac</tt>;</li> * <li>make a {@link AsmDexifierApplicationVisitor} visit this compiled class (see the * {@link #main main} method);</li> <li>edit the generated source code, if * necessary.</li> </ul> * * NOTE: * <ul> * <li> Having all the dumps in one dump() method provides a code that is quickly superior to the 64k * limit per method. So each class has its own dumpClassName() method, which is called by * the dump() method.</li> * <li> The Opcodes values are obtained through reflection. If refactoring, check out if * OPCODES_FULLY_QUALIFIED_NAME is still valid.</li> * </ul> * * @author Julien Névo, based on the ASM Framework. */ public class AsmDexifierApplicationVisitor extends ApplicationVisitor { final private AsmDexPrinter pr; /** * Fully qualified name of the Opcodes class. */ public static final String OPCODES_FULLY_QUALIFIED_NAME = "org.ow2.asmdex.Opcodes"; /** * How every Opcode names start. */ private static final String OPCODES_START = "INSN_"; /** * Possible package name. */ private String packageName = null; /** * Possible path and filename of the file where to save the output. */ private String filenameOutput = null; /** * How many Opcodes there could be. */ private static final int NB_OPCODES = 256; /** * The print writer to be used to print the Application. */ protected final PrintWriter pw; /** * The Dex filename, without the path and extension. */ private static String dexFileName; /** * Classes to visit, or Null if all must be visited. */ private static String[] classesToVisit; /** * Array where all the opcode names are written. */ private static String[] opcodeNames; /** * List containing the name of the dumpMethods. */ private ArrayList<String> dumpMethods; /** * Used to store the text of the methods to call. We have to store it because we only * know all the method names when everything has been visited. */ private TextComposite methodToCall; /** * Constructs a new {@link AsmDexifierApplicationVisitor} object. * * @param api API level * @param pw the print writer to be used to print the class. */ public AsmDexifierApplicationVisitor(int api, final PrintWriter pw) { super(api); this.pw = pw; pr = new AsmDexPrinter(); dumpMethods = new ArrayList<String>(); methodToCall = new TextComposite(); } /** * Constructs a new {@link AsmDexifierApplicationVisitor} object. * * @param api API level * @param pw the print writer to be used to print the class. * @param packageName name of the package, or Null if none should be written. * @param filenameOutput possible path and filename of the file where to save the output. * If not Null, a Main method will be created. */ public AsmDexifierApplicationVisitor(int api, final PrintWriter pw, final String packageName, final String filenameOutput) { this(api, pw); this.packageName = packageName; this.filenameOutput = filenameOutput; } /** * Prints the ASM source code to generate the given Application to the standard * output. Not to give classes means visiting all the classes of the Dex file. * <p> Usage: ASMifierClassVisitor [-debug] <Dex file to open> * [<fully qualified class names>] * * @param args the command line arguments. * * @throws Exception if the class cannot be found, or if an IO exception * occurs. */ public static void main(final String[] args) throws Exception { int i = 0; int flags = ApplicationReader.SKIP_DEBUG; int nbArgs = args.length; boolean ok = (nbArgs > 0); if (ok && "-debug".equals(args[0])) { i = 1; flags = 0; if (nbArgs < 2) { ok = false; } } if ((!ok) || (!args[i].endsWith(".dex"))) { System.err.println("Prints the AsmDex code to generate the given class."); System.err.println("Usage: ASMifierApplicationVisitor [-debug] " + "<Dex file to open> " + "[<fully qualified class names>]"); return; } // Visits all the Classes or one/several ? classesToVisit = null; if (nbArgs > (i + 1)) { int nbClasses = nbArgs - i - 1; classesToVisit = new String[nbClasses]; int classIndex = 0; for (int j = i + 1; j < nbArgs; j++) { classesToVisit[classIndex++] = args[j]; } } dexFileName = trimDexFileName(args[i]); // Sets up the Opcodes array by using Reflection. opcodeNames = new String[NB_OPCODES]; Field[] fields = Class.forName(OPCODES_FULLY_QUALIFIED_NAME).getFields(); for (Field field : fields) { String fieldName = field.getName(); int goodFlag = Modifier.FINAL | Modifier.STATIC | Modifier.PUBLIC; if (fieldName.startsWith(OPCODES_START)) { if (field.getModifiers() == goodFlag) { int opcode = field.getInt(field); if (opcode < NB_OPCODES) { opcodeNames[opcode] = fieldName; } } } } ApplicationReader ar = new ApplicationReader(Opcodes.ASM4, args[i]); ar.accept(new AsmDexifierApplicationVisitor(Opcodes.ASM4, new PrintWriter(System.out)), classesToVisit, flags); } /** * Trims the given Dex filename of its path and extension. * @param fullFileName name that can contain the path and the extension. * @return name of the dex file, without the path and the extension. */ public static String trimDexFileName(String fullFileName) { String trimmedFileName = fullFileName; int n = trimmedFileName.lastIndexOf(".dex"); if (n != -1) { trimmedFileName = trimmedFileName.substring(0, n); } n = trimmedFileName.lastIndexOf('/'); if (n != -1) { trimmedFileName = trimmedFileName.substring(n + 1); } return trimmedFileName; } /** * Sets the dex filename, which mustn't contain path and extension. It is useful to name * the class generated (prefixed with "Dump"). * @param name the dex filename. */ public static void setDexFileName(String name) { dexFileName = name; } // ------------------------------------------------ // Visitor Methods. // ------------------------------------------------ @Override public void visit() { // Puts the first letter of the filename in upper case. char firstLetter = Character.toUpperCase(dexFileName.charAt(0)); String otherLetters = dexFileName.length() > 1 ? dexFileName.substring(1) : ""; String camelName = firstLetter + otherLetters; if (packageName != null) { pr.addText("package " + packageName + ";\n"); pr.addEOL(); } pr.addText("import java.util.*;\n"); if (filenameOutput != null) { pr.addText("import java.io.*;\n"); } pr.addText("import org.ow2.asmdex.*;\n"); pr.addText("import org.ow2.asmdex.structureCommon.*;\n\n"); pr.addText("public class " + camelName + "Dump implements Opcodes {\n\n"); if (filenameOutput != null) { pr.addText("public static void main(String[] args) throws Exception {\n"); pr.addText(" byte[] b = dump();\n"); pr.addText(" File outputFile;\n"); pr.addText(" if (args.length == 1) {\n"); pr.addText(" outputFile = new File(args[0]);\n"); pr.addText(" FileOutputStream outputStream = new FileOutputStream(outputFile);\n"); pr.addText(" outputStream.write(b);\n"); pr.addText(" outputStream.close();\n"); pr.addText(" }\n"); pr.addText("}\n\n"); } pr.addText("public static byte[] dump() throws Exception {\n\n"); pr.addText("\tApplicationWriter aw = new ApplicationWriter();\n"); pr.addText("\taw.visit();\n"); pr.addEOL(); pr.closeText(); // Prepare the calls to the various dump Methods. pr.addTextToList(methodToCall); pr.addEOL(); pr.addText("\taw.visitEnd();\n\n"); pr.addText("\treturn aw.toByteArray();\n"); pr.addText("}\n\n"); pr.closeText(); } @Override public ClassVisitor visitClass(int access, String name, String[] signature, String superName, String[] interfaces) { String className = getAcceptableMethodName(name); pr.addText("public static void "); pr.addText(className); pr.addText("(ApplicationWriter aw) {\n"); pr.addText("\tClassVisitor cv;\n"); pr.addText("\tFieldVisitor fv;\n"); pr.addText("\tMethodVisitor mv;\n"); pr.addText("\tAnnotationVisitor av0;\n\n"); pr.addText("\tcv = aw.visitClass("); pr.addAccessFlags(access, true); pr.addConstant(name, true); pr.addConstant(signature, true); pr.addConstant(superName, true); pr.addConstant(interfaces, false); pr.addText(");"); pr.addEOL(); pr.closeText(); AsmDexifierClassVisitor cv = new AsmDexifierClassVisitor(api, 1); pr.addTextToList(cv.getTextComponent()); pr.addText("}\n\n"); pr.closeText(); return cv; } @Override public void visitEnd() { pr.addText("}\n\n"); pr.closeText(); for (String methodName : dumpMethods) { StringBuffer sb = new StringBuffer("\t" + methodName); sb.append("(aw);\n"); TextLeaf leaf = new TextLeaf(sb); methodToCall.addComponent(leaf); } // Prints the String Tree. pr.getTextComponent().print(pw); pw.flush(); } // ------------------------------------------------ // Static utility Methods. // ------------------------------------------------ /** * Returns the opcode name from the given opcode, or Null if the opcode isn't found. * @param opcode the opcode. * @return the opcode name from the given opcode, or Null if the opcode isn't found. */ public static String getOpcodeName(int opcode) { String result = null; if ((opcodeNames != null) && (opcode < opcodeNames.length)) { result = opcodeNames[opcode]; } return result; } /** * Returns a correct Method name from the given Class name, which may probably be * fully qualified. If also must not already be in the dumpMethods list, in which * case a number is added to the name. * @param className * @return a correct Class name. */ public String getAcceptableMethodName(String className) { String result = "Method"; // Fail-safe name. if ((className != null) && (!className.equals(""))) { int i = className.lastIndexOf('/'); if (i >= 0) { result = className.substring(i + 1); } else { result = className; } i = result.lastIndexOf(';'); if (i > 0) { result = result.substring(0, i); } } result = "dump" + result; // Checks if this name isn't already in the list. if (dumpMethods.contains(result)) { boolean stillFound = true; int i = 2; String newResult = null; while (stillFound) { newResult = result + i; stillFound = dumpMethods.contains(newResult); i++; } result = newResult; } dumpMethods.add(result); return result; } }