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