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