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