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 com.google.common.collect.ImmutableList; 32 import com.google.common.collect.Iterables; 33 import com.google.common.collect.Lists; 34 import com.google.common.collect.Ordering; 35 import org.jf.baksmali.Adaptors.ClassDefinition; 36 import org.jf.dexlib2.analysis.ClassPath; 37 import org.jf.dexlib2.analysis.CustomInlineMethodResolver; 38 import org.jf.dexlib2.iface.ClassDef; 39 import org.jf.dexlib2.iface.DexFile; 40 import org.jf.dexlib2.util.SyntheticAccessorResolver; 41 import org.jf.util.ClassFileNameHandler; 42 import org.jf.util.IndentingWriter; 43 import org.xml.sax.Attributes; 44 import org.xml.sax.SAXException; 45 import org.xml.sax.helpers.DefaultHandler; 46 47 import java.io.*; 48 import java.util.List; 49 import java.util.Map.Entry; 50 import java.util.concurrent.*; 51 52 import javax.xml.parsers.SAXParser; 53 import javax.xml.parsers.SAXParserFactory; 54 import javax.xml.parsers.ParserConfigurationException; 55 56 public class baksmali { 57 disassembleDexFile(DexFile dexFile, final baksmaliOptions options)58 public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) { 59 if (options.registerInfo != 0 || options.deodex) { 60 try { 61 Iterable<String> extraClassPathEntries; 62 if (options.extraClassPathEntries != null) { 63 extraClassPathEntries = options.extraClassPathEntries; 64 } else { 65 extraClassPathEntries = ImmutableList.of(); 66 } 67 68 options.classPath = ClassPath.fromClassPath(options.bootClassPathDirs, 69 Iterables.concat(options.bootClassPathEntries, extraClassPathEntries), dexFile, 70 options.apiLevel, options.checkPackagePrivateAccess); 71 72 if (options.customInlineDefinitions != null) { 73 options.inlineResolver = new CustomInlineMethodResolver(options.classPath, 74 options.customInlineDefinitions); 75 } 76 } catch (Exception ex) { 77 System.err.println("\n\nError occurred while loading boot class path files. Aborting."); 78 ex.printStackTrace(System.err); 79 return false; 80 } 81 } 82 83 if (options.resourceIdFileEntries != null) { 84 class PublicHandler extends DefaultHandler { 85 String prefix = null; 86 public PublicHandler(String prefix) { 87 super(); 88 this.prefix = prefix; 89 } 90 91 public void startElement(String uri, String localName, 92 String qName, Attributes attr) throws SAXException { 93 if (qName.equals("public")) { 94 String type = attr.getValue("type"); 95 String name = attr.getValue("name").replace('.', '_'); 96 Integer public_key = Integer.decode(attr.getValue("id")); 97 String public_val = new StringBuffer() 98 .append(prefix) 99 .append(".") 100 .append(type) 101 .append(".") 102 .append(name) 103 .toString(); 104 options.resourceIds.put(public_key, public_val); 105 } 106 } 107 }; 108 109 for (Entry<String,String> entry: options.resourceIdFileEntries.entrySet()) { 110 try { 111 SAXParser saxp = SAXParserFactory.newInstance().newSAXParser(); 112 String prefix = entry.getValue(); 113 saxp.parse(entry.getKey(), new PublicHandler(prefix)); 114 } catch (ParserConfigurationException e) { 115 continue; 116 } catch (SAXException e) { 117 continue; 118 } catch (IOException e) { 119 continue; 120 } 121 } 122 } 123 124 File outputDirectoryFile = new File(options.outputDirectory); 125 if (!outputDirectoryFile.exists()) { 126 if (!outputDirectoryFile.mkdirs()) { 127 System.err.println("Can't create the output directory " + options.outputDirectory); 128 return false; 129 } 130 } 131 132 //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file 133 //name collisions, then we'll use the same name for each class, if the dex file goes through multiple 134 //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames 135 //may still change of course 136 List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses()); 137 138 if (!options.noAccessorComments) { 139 options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs); 140 } 141 142 final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali"); 143 144 ExecutorService executor = Executors.newFixedThreadPool(options.jobs); 145 List<Future<Boolean>> tasks = Lists.newArrayList(); 146 147 for (final ClassDef classDef: classDefs) { 148 tasks.add(executor.submit(new Callable<Boolean>() { 149 @Override public Boolean call() throws Exception { 150 return disassembleClass(classDef, fileNameHandler, options); 151 } 152 })); 153 } 154 155 boolean errorOccurred = false; 156 try { 157 for (Future<Boolean> task: tasks) { 158 while(true) { 159 try { 160 if (!task.get()) { 161 errorOccurred = true; 162 } 163 } catch (InterruptedException ex) { 164 continue; 165 } catch (ExecutionException ex) { 166 throw new RuntimeException(ex); 167 } 168 break; 169 } 170 } 171 } finally { 172 executor.shutdown(); 173 } 174 return !errorOccurred; 175 } 176 disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, baksmaliOptions options)177 private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, 178 baksmaliOptions options) { 179 /** 180 * The path for the disassembly file is based on the package name 181 * The class descriptor will look something like: 182 * Ljava/lang/Object; 183 * Where the there is leading 'L' and a trailing ';', and the parts of the 184 * package name are separated by '/' 185 */ 186 String classDescriptor = classDef.getType(); 187 188 //validate that the descriptor is formatted like we expect 189 if (classDescriptor.charAt(0) != 'L' || 190 classDescriptor.charAt(classDescriptor.length()-1) != ';') { 191 System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class"); 192 return false; 193 } 194 195 File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor); 196 197 //create and initialize the top level string template 198 ClassDefinition classDefinition = new ClassDefinition(options, classDef); 199 200 //write the disassembly 201 Writer writer = null; 202 try 203 { 204 File smaliParent = smaliFile.getParentFile(); 205 if (!smaliParent.exists()) { 206 if (!smaliParent.mkdirs()) { 207 // check again, it's likely it was created in a different thread 208 if (!smaliParent.exists()) { 209 System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class"); 210 return false; 211 } 212 } 213 } 214 215 if (!smaliFile.exists()){ 216 if (!smaliFile.createNewFile()) { 217 System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class"); 218 return false; 219 } 220 } 221 222 BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter( 223 new FileOutputStream(smaliFile), "UTF8")); 224 225 writer = new IndentingWriter(bufWriter); 226 classDefinition.writeTo((IndentingWriter)writer); 227 } catch (Exception ex) { 228 System.err.println("\n\nError occurred while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); 229 ex.printStackTrace(); 230 // noinspection ResultOfMethodCallIgnored 231 smaliFile.delete(); 232 return false; 233 } 234 finally 235 { 236 if (writer != null) { 237 try { 238 writer.close(); 239 } catch (Throwable ex) { 240 System.err.println("\n\nError occurred while closing file " + smaliFile.toString()); 241 ex.printStackTrace(); 242 } 243 } 244 } 245 return true; 246 } 247 } 248