diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index fb279b55..6558fb3e 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -38,6 +38,8 @@ import org.jf.dexlib.ClassDefItem; import org.jf.dexlib.StringIdItem; import java.io.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class baksmali { public static boolean noParameterRegisters = false; @@ -51,10 +53,10 @@ public class baksmali { public static String bootClassPath; public static void disassembleDexFile(String dexFilePath, DexFile dexFile, boolean deodex, String outputDirectory, - String[] classPathDirs, String bootClassPath, boolean noParameterRegisters, - boolean useLocalsDirective, boolean useSequentialLabels, - boolean outputDebugInfo, boolean addCodeOffsets, int registerInfo, - boolean verify) + String[] classPathDirs, String bootClassPath, String extraBootClassPath, + boolean noParameterRegisters, boolean useLocalsDirective, + boolean useSequentialLabels, boolean outputDebugInfo, boolean addCodeOffsets, + int registerInfo, boolean verify) { baksmali.noParameterRegisters = noParameterRegisters; baksmali.useLocalsDirective = useLocalsDirective; @@ -68,8 +70,28 @@ public class baksmali { if (registerInfo != 0 || deodex || verify) { try { - ClassPath.InitializeClassPath(classPathDirs, bootClassPath==null?null:bootClassPath.split(":"), - dexFilePath, dexFile); + String[] extraBootClassPathArray = null; + if (extraBootClassPath != null && extraBootClassPath.length() > 0) { + assert extraBootClassPath.charAt(0) == ':'; + extraBootClassPathArray = extraBootClassPath.substring(1).split(":"); + } + + if (dexFile.isOdex() && bootClassPath == null) { + //ext.jar is a special case - it is typically the 2nd jar in the boot class path, but it also + //depends on classes in framework.jar. If the user didn't specify a -c option, we should add + //framework.jar to the boot class path by default, so that it "just works" + if (extraBootClassPathArray == null && isExtJar(dexFilePath)) { + extraBootClassPath = ":framework.jar"; + } + ClassPath.InitializeClassPathFromOdex(classPathDirs, extraBootClassPathArray, dexFilePath, dexFile); + } else { + String[] bootClassPathArray = null; + if (bootClassPath != null) { + bootClassPathArray = bootClassPath.split(":"); + } + ClassPath.InitializeClassPath(classPathDirs, bootClassPathArray, extraBootClassPathArray, + dexFilePath, dexFile); + } } catch (Exception ex) { System.err.println("\n\nError occured while loading boot class path files. Aborting."); ex.printStackTrace(System.err); @@ -182,4 +204,10 @@ public class baksmali { } } } + + private static final Pattern extJarPattern = Pattern.compile("(?:^|\\\\|/)ext.(?:jar|odex)$"); + private static boolean isExtJar(String dexFilePath) { + Matcher m = extJarPattern.matcher(dexFilePath); + return m.find(); + } } diff --git a/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java b/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java index d8d20319..d38670d4 100644 --- a/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java +++ b/baksmali/src/main/java/org/jf/baksmali/deodexCheck.java @@ -120,7 +120,7 @@ public class deodexCheck { bootClassPathDirsArray[i] = bootClassPathDirs.get(i); } - ClassPath.InitializeClassPath(bootClassPathDirsArray, bootClassPath==null?null:bootClassPath.split(":"), + ClassPath.InitializeClassPath(bootClassPathDirsArray, bootClassPath==null?null:bootClassPath.split(":"), null, null, null); ClassPath.validateAgainstDeodexerant(deodexerantHost, deodexerantPort, classStartIndex); diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java index c5c7bcda..b43eb49b 100644 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/baksmali/src/main/java/org/jf/baksmali/main.java @@ -30,6 +30,8 @@ package org.jf.baksmali; import org.apache.commons.cli.*; import org.jf.dexlib.DexFile; +import org.jf.dexlib.OdexDependencies; +import org.jf.dexlib.Util.ExceptionWithContext; import org.jf.util.*; import java.io.File; @@ -111,7 +113,8 @@ public class main { String dumpFileName = null; String outputDexFileName = null; String inputDexFileName = null; - String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"; + String bootClassPath = null; + StringBuffer extraBootClassPathEntries = new StringBuffer(); List bootClassPathDirs = new ArrayList(); bootClassPathDirs.add("."); @@ -193,7 +196,7 @@ public class main { case 'c': String bcp = commandLine.getOptionValue("c"); if (bcp != null && bcp.charAt(0) == ':') { - bootClassPath = bootClassPath + bcp; + extraBootClassPathEntries.append(bcp); } else { bootClassPath = bcp; } @@ -257,6 +260,10 @@ public class main { } } else { deodex = false; + + if (bootClassPath == null) { + bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"; + } } if (disassemble) { @@ -266,8 +273,9 @@ public class main { } baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, deodex, outputDirectory, - bootClassPathDirsArray, bootClassPath, noParameterRegisters, useLocalsDirective, - useSequentialLabels, outputDebugInfo, addCodeOffsets, registerInfo, verify); + bootClassPathDirsArray, bootClassPath, extraBootClassPathEntries.toString(), + noParameterRegisters, useLocalsDirective, useSequentialLabels, outputDebugInfo, addCodeOffsets, + registerInfo, verify); } if ((doDump || write) && !dexFile.isOdex()) { diff --git a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java index 53df5d7c..04490ee8 100644 --- a/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java +++ b/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java @@ -38,6 +38,8 @@ import org.jf.dexlib.Util.SparseArray; import java.io.File; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ClassPath { private static ClassPath theClassPath = null; @@ -48,21 +50,81 @@ public class ClassPath { //This is only used while initialing the class path. It is set to null after initialization has finished. private LinkedHashMap tempClasses; - public static void InitializeClassPath(String[] classPathDirs, String[] bootClassPath, String dexFilePath, - DexFile dexFile) { + + private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$"); + + /** + * Initialize the class path using the dependencies from an odex file + * @param classPathDirs The directories to search for boot class path files + * @param extraBootClassPathEntries any extra entries that should be added after the entries that are read + * from the odex file + * @param dexFilePath The path of the dex file (used for error reporting purposes only) + * @param dexFile The DexFile to load - it must represents an odex file + */ + public static void InitializeClassPathFromOdex(String[] classPathDirs, String[] extraBootClassPathEntries, + String dexFilePath, DexFile dexFile) { + if (!dexFile.isOdex()) { + throw new ExceptionWithContext("Cannot use InitialiazeClassPathFromOdex with a non-odex DexFile"); + } + + if (theClassPath != null) { + throw new ExceptionWithContext("Cannot initialize ClassPath multiple times"); + } + + OdexDependencies odexDependencies = dexFile.getOdexDependencies(); + + String[] bootClassPath = new String[odexDependencies.getDependencyCount()]; + for (int i=0; i(); } - private void initClassPath(String[] classPathDirs, String[] bootClassPath, String dexFilePath, DexFile dexFile) { + private void initClassPath(String[] classPathDirs, String[] bootClassPath, String[] extraBootClassPathEntries, + String dexFilePath, DexFile dexFile) { tempClasses = new LinkedHashMap(); if (bootClassPath != null) { @@ -71,6 +133,12 @@ public class ClassPath { } } + if (extraBootClassPathEntries != null) { + for (String bootClassPathEntry: extraBootClassPathEntries) { + loadBootClassPath(classPathDirs, bootClassPathEntry); + } + } + if (dexFile != null) { loadDexFile(dexFilePath, dexFile); } diff --git a/dexlib/src/main/java/org/jf/dexlib/DexFile.java b/dexlib/src/main/java/org/jf/dexlib/DexFile.java index bf046255..f73f419b 100644 --- a/dexlib/src/main/java/org/jf/dexlib/DexFile.java +++ b/dexlib/src/main/java/org/jf/dexlib/DexFile.java @@ -29,7 +29,6 @@ package org.jf.dexlib; import org.jf.dexlib.Util.*; -import org.jf.dexlib.*; import org.jf.dexlib.Item; import org.jf.dexlib.StringDataItem; @@ -37,13 +36,11 @@ import java.io.*; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.HashMap; import java.util.Arrays; import java.util.Comparator; import java.util.Collections; import java.util.zip.Adler32; import java.util.zip.ZipFile; -import java.util.zip.ZipException; import java.util.zip.ZipEntry; /** @@ -180,6 +177,7 @@ public class DexFile */ private boolean isOdex = false; + private OdexDependencies odexDependencies; private int dataOffset; private int dataSize; @@ -338,7 +336,7 @@ public class DexFile byte[] dexMagic, odexMagic; dexMagic = org.jf.dexlib.HeaderItem.MAGIC; - odexMagic = OdexHeaderItem.MAGIC; + odexMagic = OdexHeader.MAGIC; boolean isDex = true; this.isOdex = true; @@ -354,9 +352,25 @@ public class DexFile if (isOdex) { byte[] odexHeaderBytes = FileUtils.readStream(inputStream, 40); Input odexHeaderIn = new ByteArrayInput(odexHeaderBytes); - OdexHeaderItem odexHeader = new OdexHeaderItem(odexHeaderIn); + OdexHeader odexHeader = new OdexHeader(odexHeaderIn); + + int dependencySkip = odexHeader.depsOffset - odexHeader.dexOffset - odexHeader.dexLength; + if (dependencySkip < 0) { + throw new ExceptionWithContext("Unexpected placement of the odex dependency data"); + } + + if (odexHeader.dexOffset > 40) { + FileUtils.readStream(inputStream, odexHeader.dexOffset - 40); + } in = new ByteArrayInput(FileUtils.readStream(inputStream, odexHeader.dexLength)); + + if (dependencySkip > 0) { + FileUtils.readStream(inputStream, dependencySkip); + } + + odexDependencies = new OdexDependencies( + new ByteArrayInput(FileUtils.readStream(inputStream, odexHeader.depsLength))); } else if (isDex) { in = new ByteArrayInput(FileUtils.readStream(inputStream, (int)fileLength)); } else { @@ -515,6 +529,14 @@ public class DexFile return this.isOdex; } + /** + * @return an OdexDependencies object that contains the dependencies for this odex, or null if this + * DexFile represents a dex file instead of an odex file + */ + public OdexDependencies getOdexDependencies() { + return odexDependencies; + } + /** * Get a boolean value indicating whether items in this dex file should be * written back out "in-place", or whether the normal layout logic should be diff --git a/dexlib/src/main/java/org/jf/dexlib/OdexDependencies.java b/dexlib/src/main/java/org/jf/dexlib/OdexDependencies.java index 0fe6b367..98ae4f78 100644 --- a/dexlib/src/main/java/org/jf/dexlib/OdexDependencies.java +++ b/dexlib/src/main/java/org/jf/dexlib/OdexDependencies.java @@ -25,7 +25,53 @@ * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package org.jf.dexlib; +import org.jf.dexlib.Util.Input; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + public class OdexDependencies { + public final int modificationTime; + public final int crc; + public final int dalvikBuild; + + private final String[] dependencies; + private final byte[][] dependencyChecksums; + + public OdexDependencies (Input in) { + modificationTime = in.readInt(); + crc = in.readInt(); + dalvikBuild = in.readInt(); + + int dependencyCount = in.readInt(); + + dependencies = new String[dependencyCount]; + dependencyChecksums = new byte[dependencyCount][]; + + for (int i=0; i