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