baksmali.java revision 8b1508ee58f4918835d8c01483725b508d21be29
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 org.jf.baksmali.Adaptors.ClassDefinition;
32import org.jf.dexlib.ClassDefItem;
33import org.jf.dexlib.Code.Analysis.*;
34import org.jf.dexlib2.iface.ClassDef;
35import org.jf.dexlib2.iface.DexFile;
36import org.jf.util.ClassFileNameHandler;
37import org.jf.util.IndentingWriter;
38
39import java.io.*;
40import java.util.ArrayList;
41import java.util.Collections;
42import java.util.Comparator;
43import java.util.regex.Matcher;
44import java.util.regex.Pattern;
45
46public class baksmali {
47    public static boolean noParameterRegisters = false;
48    public static boolean useLocalsDirective = false;
49    public static boolean useSequentialLabels = false;
50    public static boolean outputDebugInfo = true;
51    public static boolean addCodeOffsets = false;
52    public static boolean noAccessorComments = false;
53    public static boolean deodex = false;
54    public static boolean verify = false;
55    public static InlineMethodResolver inlineResolver = null;
56    public static int registerInfo = 0;
57    public static String bootClassPath;
58
59    public static SyntheticAccessorResolver syntheticAccessorResolver = null;
60
61    public static void disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory,
62                                          String[] classPathDirs, String bootClassPath, String extraBootClassPath,
63                                          boolean noParameterRegisters, boolean useLocalsDirective,
64                                          boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets,
65                                          boolean noAccessorComments, int registerInfo, boolean verify,
66                                          boolean ignoreErrors, String inlineTable, boolean checkPackagePrivateAccess)
67    {
68        baksmali.noParameterRegisters = noParameterRegisters;
69        baksmali.useLocalsDirective = useLocalsDirective;
70        baksmali.useSequentialLabels = useSequentialLabels;
71        baksmali.outputDebugInfo = outputDebugInfo;
72        baksmali.addCodeOffsets = addCodeOffsets;
73        baksmali.noAccessorComments = noAccessorComments;
74        baksmali.deodex = deodex;
75        baksmali.registerInfo = registerInfo;
76        baksmali.bootClassPath = bootClassPath;
77        baksmali.verify = verify;
78
79        //TODO: uncomment
80        /*if (registerInfo != 0 || deodex || verify) {
81            try {
82                String[] extraBootClassPathArray = null;
83                if (extraBootClassPath != null && extraBootClassPath.length() > 0) {
84                    assert extraBootClassPath.charAt(0) == ':';
85                    extraBootClassPathArray = extraBootClassPath.substring(1).split(":");
86                }
87
88                if (dexFile.isOdex() && bootClassPath == null) {
89                    //ext.jar is a special case - it is typically the 2nd jar in the boot class path, but it also
90                    //depends on classes in framework.jar (typically the 3rd jar in the BCP). If the user didn't
91                    //specify a -c option, we should add framework.jar to the boot class path by default, so that it
92                    //"just works"
93                    if (extraBootClassPathArray == null && isExtJar(dexFilePath)) {
94                        extraBootClassPathArray = new String[] {"framework.jar"};
95                    }
96                    ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFilePath, dexFile,
97                            checkPackagePrivateAccess);
98                } else {
99                    String[] bootClassPathArray = null;
100                    if (bootClassPath != null) {
101                        bootClassPathArray = bootClassPath.split(":");
102                    }
103                    ClassPath.InitializeClassPath(classPathDirs, bootClassPathArray, extraBootClassPathArray,
104                            dexFilePath, dexFile, checkPackagePrivateAccess);
105                }
106
107                if (inlineTable != null) {
108                    inlineResolver = new CustomInlineMethodResolver(inlineTable);
109                }
110            } catch (Exception ex) {
111                System.err.println("\n\nError occured while loading boot class path files. Aborting.");
112                ex.printStackTrace(System.err);
113                System.exit(1);
114            }
115        }*/
116
117        File outputDirectoryFile = new File(outputDirectory);
118        if (!outputDirectoryFile.exists()) {
119            if (!outputDirectoryFile.mkdirs()) {
120                System.err.println("Can't create the output directory " + outputDirectory);
121                System.exit(1);
122            }
123        }
124
125        //TODO: uncomment
126        /*if (!noAccessorComments) {
127            syntheticAccessorResolver = new SyntheticAccessorResolver(dexFile);
128        }*/
129
130        //sort the classes, so that if we're on a case-insensitive file system and need to handle classes with file
131        //name collisions, then we'll use the same name for each class, if the dex file goes through multiple
132        //baksmali/smali cycles for some reason. If a class with a colliding name is added or removed, the filenames
133        //may still change of course
134        // TODO: aren't classes already sorted? Why do we need to sort here?
135        /*ArrayList<ClassDefItem> classDefItems = new ArrayList<ClassDefItem>(dexFile.ClassDefsSection.getItems());
136        Collections.sort(classDefItems, new Comparator<ClassDefItem>() {
137            public int compare(ClassDefItem classDefItem1, ClassDefItem classDefItem2) {
138                return classDefItem1.getClassType().getTypeDescriptor().compareTo(classDefItem1.getClassType().getTypeDescriptor());
139            }
140        });*/
141
142        ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
143
144        for (ClassDef classDef: dexFile.getClasses()) {
145            /**
146             * The path for the disassembly file is based on the package name
147             * The class descriptor will look something like:
148             * Ljava/lang/Object;
149             * Where the there is leading 'L' and a trailing ';', and the parts of the
150             * package name are separated by '/'
151             */
152
153            String classDescriptor = classDef.getName();
154
155            //validate that the descriptor is formatted like we expect
156            if (classDescriptor.charAt(0) != 'L' ||
157                classDescriptor.charAt(classDescriptor.length()-1) != ';') {
158                System.err.println("Unrecognized class descriptor - " + classDescriptor + " - skipping class");
159                continue;
160            }
161
162            File smaliFile = fileNameHandler.getUniqueFilenameForClass(classDescriptor);
163
164            //create and initialize the top level string template
165            ClassDefinition classDefinition = new ClassDefinition(classDef);
166
167            //write the disassembly
168            Writer writer = null;
169            try
170            {
171                File smaliParent = smaliFile.getParentFile();
172                if (!smaliParent.exists()) {
173                    if (!smaliParent.mkdirs()) {
174                        System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class");
175                        continue;
176                    }
177                }
178
179                if (!smaliFile.exists()){
180                    if (!smaliFile.createNewFile()) {
181                        System.err.println("Unable to create file " + smaliFile.toString() + " - skipping class");
182                        continue;
183                    }
184                }
185
186                BufferedWriter bufWriter = new BufferedWriter(new OutputStreamWriter(
187                        new FileOutputStream(smaliFile), "UTF8"));
188
189                writer = new IndentingWriter(bufWriter);
190                classDefinition.writeTo((IndentingWriter)writer);
191            } catch (Exception ex) {
192                System.err.println("\n\nError occured while disassembling class " + classDescriptor.replace('/', '.') + " - skipping class");
193                ex.printStackTrace();
194                smaliFile.delete();
195            }
196            finally
197            {
198                if (writer != null) {
199                    try {
200                        writer.close();
201                    } catch (Throwable ex) {
202                        System.err.println("\n\nError occured while closing file " + smaliFile.toString());
203                        ex.printStackTrace();
204                    }
205                }
206            }
207
208            if (!ignoreErrors && classDefinition.hadValidationErrors()) {
209                System.exit(1);
210            }
211        }
212    }
213
214    private static final Pattern extJarPattern = Pattern.compile("(?:^|\\\\|/)ext.(?:jar|odex)$");
215    private static boolean isExtJar(String dexFilePath) {
216        Matcher m = extJarPattern.matcher(dexFilePath);
217        return m.find();
218    }
219}
220