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