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