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 29 package org.jf.baksmali; 30 31 import org.jf.baksmali.Adaptors.ClassDefinition; 32 import org.jf.dexlib.ClassDefItem; 33 import org.jf.dexlib.Code.Analysis.ClassPath; 34 import org.jf.dexlib.Code.Analysis.SyntheticAccessorResolver; 35 import org.jf.dexlib.DexFile; 36 import org.jf.util.ClassFileNameHandler; 37 import org.jf.util.IndentingWriter; 38 39 import java.io.*; 40 import java.util.ArrayList; 41 import java.util.Collections; 42 import java.util.Comparator; 43 import java.util.regex.Matcher; 44 import java.util.regex.Pattern; 45 46 public class baksmali { 47 public static boolean noParameterRegisters = false; 48 public static boolean useLocalsDirective = false; 49 public static boolean useSequentialLabels = false; 50 public static boolean outputDebugInfo = true; 51 public static boolean addCodeOffsets = false; 52 public static boolean noAccessorComments = false; 53 public static boolean deodex = false; 54 public static boolean verify = false; 55 public static int registerInfo = 0; 56 public static String bootClassPath; 57 58 public static SyntheticAccessorResolver syntheticAccessorResolver = null; 59 disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory, String[] classPathDirs, String bootClassPath, String extraBootClassPath, boolean noParameterRegisters, boolean useLocalsDirective, boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, boolean noAccessorComments, int registerInfo, boolean verify, boolean ignoreErrors)60 public static void disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory, 61 String[] classPathDirs, String bootClassPath, String extraBootClassPath, 62 boolean noParameterRegisters, boolean useLocalsDirective, 63 boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, 64 boolean noAccessorComments, int registerInfo, boolean verify, 65 boolean ignoreErrors) 66 { 67 baksmali.noParameterRegisters = noParameterRegisters; 68 baksmali.useLocalsDirective = useLocalsDirective; 69 baksmali.useSequentialLabels = useSequentialLabels; 70 baksmali.outputDebugInfo = outputDebugInfo; 71 baksmali.addCodeOffsets = addCodeOffsets; 72 baksmali.noAccessorComments = noAccessorComments; 73 baksmali.deodex = deodex; 74 baksmali.registerInfo = registerInfo; 75 baksmali.bootClassPath = bootClassPath; 76 baksmali.verify = verify; 77 78 ClassPath.ClassPathErrorHandler classPathErrorHandler = null; 79 if (ignoreErrors) { 80 classPathErrorHandler = new ClassPath.ClassPathErrorHandler() { 81 public void ClassPathError(String className, Exception ex) { 82 System.err.println(String.format("Skipping %s", className)); 83 ex.printStackTrace(System.err); 84 } 85 }; 86 } 87 88 if (registerInfo != 0 || deodex || verify) { 89 try { 90 String[] extraBootClassPathArray = null; 91 if (extraBootClassPath != null && extraBootClassPath.length() > 0) { 92 assert extraBootClassPath.charAt(0) == ':'; 93 extraBootClassPathArray = extraBootClassPath.substring(1).split(":"); 94 } 95 96 if (dexFile.isOdex() && bootClassPath == null) { 97 //ext.jar is a special case - it is typically the 2nd jar in the boot class path, but it also 98 //depends on classes in framework.jar (typically the 3rd jar in the BCP). If the user didn't 99 //specify a -c option, we should add framework.jar to the boot class path by default, so that it 100 //"just works" 101 if (extraBootClassPathArray == null && isExtJar(dexFilePath)) { 102 extraBootClassPathArray = new String[] {"framework.jar"}; 103 } 104 ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFilePath, dexFile, 105 classPathErrorHandler); 106 } else { 107 String[] bootClassPathArray = null; 108 if (bootClassPath != null) { 109 bootClassPathArray = bootClassPath.split(":"); 110 } 111 ClassPath.InitializeClassPath(classPathDirs, bootClassPathArray, extraBootClassPathArray, 112 dexFilePath, dexFile, classPathErrorHandler); 113 } 114 } catch (Exception ex) { 115 System.err.println("\n\nError occured while loading boot class path files. Aborting."); 116 ex.printStackTrace(System.err); 117 System.exit(1); 118 } 119 } 120 121 File outputDirectoryFile = new File(outputDirectory); 122 if (!outputDirectoryFile.exists()) { 123 if (!outputDirectoryFile.mkdirs()) { 124 System.err.println("Can't create the output directory " + outputDirectory); 125 System.exit(1); 126 } 127 } 128 129 if (!noAccessorComments) { 130 syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile); 131 } 132 133 //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file 134 //name collisions, then we'll use the same name for each class, if the dex file goes through multiple 135 //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames 136 //may still change of course 137 ArrayList<ClassDefItem> classDefItems = new ArrayList<ClassDefItem>(dexFile.ClassDefsSection.getItems()); 138 Collections.sort(classDefItems, new Comparator<ClassDefItem>() { 139 public int compare(ClassDefItem classDefItem1, ClassDefItem classDefItem2) { 140 return classDefItem1.getClassType().getTypeDescriptor().compareTo(classDefItem1.getClassType().getTypeDescriptor()); 141 } 142 }); 143 144 ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali"); 145 146 for (ClassDefItem classDefItem: classDefItems) { 147 /** 148 * The path for the disassembly file is based on the package name 149 * The class descriptor will look something like: 150 * Ljava/lang/Object; 151 * Where the there is leading 'L' and a trailing ';', and the parts of the 152 * package name are separated by '/' 153 */ 154 155 if (registerInfo != 0 || deodex || verify) { 156 //If we are analyzing the bytecode, make sure that this class is loaded into the ClassPath. If it isn't 157 //then there was some error while loading it, and we should skip it 158 ClassPath.ClassDef classDef = ClassPath.getClassDef(classDefItem.getClassType(), false); 159 if (classDef == null || classDef instanceof ClassPath.UnresolvedClassDef) { 160 continue; 161 } 162 } 163 164 String classDescriptor = classDefItem.getClassType().getTypeDescriptor(); 165 166 //validate that the descriptor is formatted like we expect 167 if (classDescriptor.charAt(0) != 'L' || 168 classDescriptor.charAt(classDescriptor.length()-1) != ';') { 169 System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class"); 170 continue; 171 } 172 173 File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor); 174 175 //create and initialize the top level string template 176 ClassDefinition classDefinition = new ClassDefinition(classDefItem); 177 178 //write the disassembly 179 Writer writer = null; 180 try 181 { 182 File smaliParent = smaliFile.getParentFile(); 183 if (!smaliParent.exists()) { 184 if (!smaliParent.mkdirs()) { 185 System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class"); 186 continue; 187 } 188 } 189 190 if (!smaliFile.exists()){ 191 if (!smaliFile.createNewFile()) { 192 System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class"); 193 continue; 194 } 195 } 196 197 BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter( 198 new FileOutputStream(smaliFile), "UTF8")); 199 200 writer = new IndentingWriter(bufWriter); 201 classDefinition.writeTo((IndentingWriter)writer); 202 } catch (Exception ex) { 203 System.err.println("\n\nError occured while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); 204 ex.printStackTrace(); 205 } 206 finally 207 { 208 if (writer != null) { 209 try { 210 writer.close(); 211 } catch (Throwable ex) { 212 System.err.println("\n\nError occured while closing file " + smaliFile.toString()); 213 ex.printStackTrace(); 214 } 215 } 216 } 217 218 if (!ignoreErrors && classDefinition.hadValidationErrors()) { 219 System.exit(1); 220 } 221 } 222 } 223 224 private static final Pattern extJarPattern = Pattern.compile("(?:^|\\\\|/)ext.(?:jar|odex)$"); isExtJar(String dexFilePath)225 private static boolean isExtJar(String dexFilePath) { 226 Matcher m = extJarPattern.matcher(dexFilePath); 227 return m.find(); 228 } 229 } 230