baksmali.java revision a6e5671a627284347484db96f40a29a45e4e4ed1
1/* 2 * [The "BSD licence"] 3 * Copyright (c) 2010 Ben Gruver (JesusFreke) 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29package org.jf.baksmali; 30 31import org.jf.baksmali.Adaptors.ClassDefinition; 32import org.jf.dexlib.ClassDefItem; 33import org.jf.dexlib.Code.Analysis.ClassPath; 34import org.jf.dexlib.DexFile; 35 36import java.io.*; 37import java.util.ArrayList; 38import java.util.Collections; 39import java.util.Comparator; 40import java.util.regex.Matcher; 41import java.util.regex.Pattern; 42 43public class baksmali { 44 public static boolean noParameterRegisters = false; 45 public static boolean useLocalsDirective = false; 46 public static boolean useSequentialLabels = false; 47 public static boolean outputDebugInfo = true; 48 public static boolean addCodeOffsets = false; 49 public static boolean deodex = false; 50 public static boolean verify = false; 51 public static int registerInfo = 0; 52 public static String bootClassPath; 53 54 public static void disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory, 55 String[] classPathDirs, String bootClassPath, String extraBootClassPath, 56 boolean noParameterRegisters, boolean useLocalsDirective, 57 boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, 58 int registerInfo, boolean verify, boolean ignoreErrors) 59 { 60 baksmali.noParameterRegisters = noParameterRegisters; 61 baksmali.useLocalsDirective = useLocalsDirective; 62 baksmali.useSequentialLabels = useSequentialLabels; 63 baksmali.outputDebugInfo = outputDebugInfo; 64 baksmali.addCodeOffsets = addCodeOffsets; 65 baksmali.deodex = deodex; 66 baksmali.registerInfo = registerInfo; 67 baksmali.bootClassPath = bootClassPath; 68 baksmali.verify = verify; 69 70 ClassPath.ClassPathErrorHandler classPathErrorHandler = null; 71 if (ignoreErrors) { 72 classPathErrorHandler = new ClassPath.ClassPathErrorHandler() { 73 public void ClassPathError(String className, Exception ex) { 74 System.err.println(String.format("Skipping %s", className)); 75 ex.printStackTrace(System.err); 76 } 77 }; 78 } 79 80 if (registerInfo != 0 || deodex || verify) { 81 try { 82 String[] extraBootClassPathArray = null; 83 if (extraBootClassPath != null && extraBootClassPath.length() > 0) { 84 assert extraBootClassPath.charAt(0) == ':'; 85 extraBootClassPathArray = extraBootClassPath.substring(1).split(":"); 86 } 87 88 if (dexFile.isOdex() && bootClassPath == null) { 89 //ext.jar is a special case - it is typically the 2nd jar in the boot class path, but it also 90 //depends on classes in framework.jar (typically the 3rd jar in the BCP). If the user didn't 91 //specify a -c option, we should add framework.jar to the boot class path by default, so that it 92 //"just works" 93 if (extraBootClassPathArray == null && isExtJar(dexFilePath)) { 94 extraBootClassPathArray = new String[] {"framework.jar"}; 95 } 96 ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFilePath, dexFile, 97 classPathErrorHandler); 98 } else { 99 String[] bootClassPathArray = null; 100 if (bootClassPath != null) { 101 bootClassPathArray = bootClassPath.split(":"); 102 } 103 ClassPath.InitializeClassPath(classPathDirs, bootClassPathArray, extraBootClassPathArray, 104 dexFilePath, dexFile, classPathErrorHandler); 105 } 106 } catch (Exception ex) { 107 System.err.println("\n\nError occured while loading boot class path files. Aborting."); 108 ex.printStackTrace(System.err); 109 System.exit(1); 110 } 111 } 112 113 File outputDirectoryFile = new File(outputDirectory); 114 if (!outputDirectoryFile.exists()) { 115 if (!outputDirectoryFile.mkdirs()) { 116 System.err.println("Can't create the output directory " + outputDirectory); 117 System.exit(1); 118 } 119 } 120 121 //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file 122 //name collisions, then we'll use the same name for each class, if the dex file goes through multiple 123 //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames 124 //may still change of course 125 ArrayList<ClassDefItem> classDefItems = new ArrayList<ClassDefItem>(dexFile.ClassDefsSection.getItems()); 126 Collections.sort(classDefItems, new Comparator<ClassDefItem>() { 127 public int compare(ClassDefItem classDefItem1, ClassDefItem classDefItem2) { 128 return classDefItem1.getClassType().getTypeDescriptor().compareTo(classDefItem1.getClassType().getTypeDescriptor()); 129 } 130 }); 131 132 fileNameHandler fileNameHandler = new fileNameHandler(outputDirectoryFile); 133 134 for (ClassDefItem classDefItem: classDefItems) { 135 /** 136 * The path for the disassembly file is based on the package name 137 * The class descriptor will look something like: 138 * Ljava/lang/Object; 139 * Where the there is leading 'L' and a trailing ';', and the parts of the 140 * package name are separated by '/' 141 */ 142 143 if (registerInfo != 0 || deodex || verify) { 144 //If we are analyzing the bytecode, make sure that this class is loaded into the ClassPath. If it isn't 145 //then there was some error while loading it, and we should skip it 146 ClassPath.ClassDef classDef = ClassPath.getClassDef(classDefItem.getClassType(), false); 147 if (classDef == null || classDef instanceof ClassPath.UnresolvedClassDef) { 148 continue; 149 } 150 } 151 152 String classDescriptor = classDefItem.getClassType().getTypeDescriptor(); 153 154 //validate that the descriptor is formatted like we expect 155 if (classDescriptor.charAt(0) != 'L' || 156 classDescriptor.charAt(classDescriptor.length()-1) != ';') { 157 System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class"); 158 continue; 159 } 160 161 File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor); 162 163 //create and initialize the top level string template 164 ClassDefinition classDefinition = new ClassDefinition(classDefItem); 165 166 //write the disassembly 167 Writer writer = null; 168 try 169 { 170 File smaliParent = smaliFile.getParentFile(); 171 if (!smaliParent.exists()) { 172 if (!smaliParent.mkdirs()) { 173 System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class"); 174 continue; 175 } 176 } 177 178 if (!smaliFile.exists()){ 179 if (!smaliFile.createNewFile()) { 180 System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class"); 181 continue; 182 } 183 } 184 185 BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter( 186 new FileOutputStream(smaliFile), "UTF8")); 187 188 writer = new IndentingWriter(bufWriter); 189 classDefinition.writeTo((IndentingWriter)writer); 190 } catch (Exception ex) { 191 System.err.println("\n\nError occured while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); 192 ex.printStackTrace(); 193 } 194 finally 195 { 196 if (writer != null) { 197 try { 198 writer.close(); 199 } catch (Throwable ex) { 200 System.err.println("\n\nError occured while closing file " + smaliFile.toString()); 201 ex.printStackTrace(); 202 } 203 } 204 } 205 206 if (!ignoreErrors && classDefinition.hadValidationErrors()) { 207 System.exit(1); 208 } 209 } 210 } 211 212 private static final Pattern extJarPattern = Pattern.compile("(?:^|\\\\|/)ext.(?:jar|odex)$"); 213 private static boolean isExtJar(String dexFilePath) { 214 Matcher m = extJarPattern.matcher(dexFilePath); 215 return m.find(); 216 } 217} 218