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.Lists; 32 import com.google.common.collect.Ordering; 33 import org.jf.baksmali.Adaptors.ClassDefinition; 34 import org.jf.dexlib2.iface.ClassDef; 35 import org.jf.dexlib2.iface.DexFile; 36 import org.jf.util.ClassFileNameHandler; 37 import org.jf.util.IndentingWriter; 38 39 import javax.annotation.Nullable; 40 import java.io.*; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Set; 44 import java.util.concurrent.*; 45 46 public class Baksmali { disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options)47 public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options) { 48 return disassembleDexFile(dexFile, outputDir, jobs, options, null); 49 } 50 disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options, @Nullable List<String> classes)51 public static boolean disassembleDexFile(DexFile dexFile, File outputDir, int jobs, final BaksmaliOptions options, 52 @Nullable List<String> classes) { 53 54 //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file 55 //name collisions, then we'll use the same name for each class, if the dex file goes through multiple 56 //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames 57 //may still change of course 58 List<? extends ClassDef> classDefs = Ordering.natural().sortedCopy(dexFile.getClasses()); 59 60 final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDir, ".smali"); 61 62 ExecutorService executor = Executors.newFixedThreadPool(jobs); 63 List<Future<Boolean>> tasks = Lists.newArrayList(); 64 65 Set<String> classSet = null; 66 if (classes != null) { 67 classSet = new HashSet<String>(classes); 68 } 69 70 for (final ClassDef classDef: classDefs) { 71 if (classSet != null && !classSet.contains(classDef.getType())) { 72 continue; 73 } 74 tasks.add(executor.submit(new Callable<Boolean>() { 75 @Override public Boolean call() throws Exception { 76 return disassembleClass(classDef, fileNameHandler, options); 77 } 78 })); 79 } 80 81 boolean errorOccurred = false; 82 try { 83 for (Future<Boolean> task: tasks) { 84 while(true) { 85 try { 86 if (!task.get()) { 87 errorOccurred = true; 88 } 89 } catch (InterruptedException ex) { 90 continue; 91 } catch (ExecutionException ex) { 92 throw new RuntimeException(ex); 93 } 94 break; 95 } 96 } 97 } finally { 98 executor.shutdown(); 99 } 100 return !errorOccurred; 101 } 102 disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, BaksmaliOptions options)103 private static boolean disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler, 104 BaksmaliOptions options) { 105 /** 106 * The path for the disassembly file is based on the package name 107 * The class descriptor will look something like: 108 * Ljava/lang/Object; 109 * Where the there is leading 'L' and a trailing ';', and the parts of the 110 * package name are separated by '/' 111 */ 112 String classDescriptor = classDef.getType(); 113 114 //validate that the descriptor is formatted like we expect 115 if (classDescriptor.charAt(0) != 'L' || 116 classDescriptor.charAt(classDescriptor.length()-1) != ';') { 117 System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class"); 118 return false; 119 } 120 121 File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor); 122 123 //create and initialize the top level string template 124 ClassDefinition classDefinition = new ClassDefinition(options, classDef); 125 126 //write the disassembly 127 Writer writer = null; 128 try 129 { 130 File smaliParent = smaliFile.getParentFile(); 131 if (!smaliParent.exists()) { 132 if (!smaliParent.mkdirs()) { 133 // check again, it's likely it was created in a different thread 134 if (!smaliParent.exists()) { 135 System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class"); 136 return false; 137 } 138 } 139 } 140 141 if (!smaliFile.exists()){ 142 if (!smaliFile.createNewFile()) { 143 System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class"); 144 return false; 145 } 146 } 147 148 BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter( 149 new FileOutputStream(smaliFile), "UTF8")); 150 151 writer = new IndentingWriter(bufWriter); 152 classDefinition.writeTo((IndentingWriter)writer); 153 } catch (Exception ex) { 154 System.err.println("\n\nError occurred while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class"); 155 ex.printStackTrace(); 156 // noinspection ResultOfMethodCallIgnored 157 smaliFile.delete(); 158 return false; 159 } 160 finally 161 { 162 if (writer != null) { 163 try { 164 writer.close(); 165 } catch (Throwable ex) { 166 System.err.println("\n\nError occurred while closing file " + smaliFile.toString()); 167 ex.printStackTrace(); 168 } 169 } 170 } 171 return true; 172 } 173 } 174