Add the ability to read the dependency information from an odex file, and use those dependencies as the BOOTCLASSPATH by default for odex files

git-svn-id: https://smali.googlecode.com/svn/trunk@679 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
JesusFreke@JesusFreke.com 2010-04-03 23:01:03 +00:00
parent dfb1b8c6c0
commit 78bde01ad4
6 changed files with 192 additions and 20 deletions

View File

@ -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(":"),
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();
}
}

View File

@ -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);

View File

@ -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<String> bootClassPathDirs = new ArrayList<String>();
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()) {

View File

@ -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<String, TempClassInfo> 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<bootClassPath.length; i++) {
String dependency = odexDependencies.getDependency(i);
if (dependency.endsWith(".odex")) {
int slashIndex = dependency.lastIndexOf("/");
if (slashIndex != -1) {
dependency = dependency.substring(slashIndex+1);
}
} else if (dependency.endsWith("@classes.dex")) {
Matcher m = dalvikCacheOdexPattern.matcher(dependency);
if (!m.find()) {
throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", dependency));
}
dependency = m.group(1);
} else {
throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", dependency));
}
bootClassPath[i] = dependency;
}
theClassPath = new ClassPath();
theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile);
}
/**
* Initialize the class path using the given boot class path entries
* @param classPathDirs The directories to search for boot class path files
* @param bootClassPath A list of the boot class path entries to search for and load
* @param dexFilePath The path of the dex file (used for error reporting purposes only)
* @param dexFile the DexFile to load
*/
public static void InitializeClassPath(String[] classPathDirs, String[] bootClassPath,
String[] extraBootClassPathEntries, String dexFilePath, DexFile dexFile) {
if (theClassPath != null) {
throw new ExceptionWithContext("Cannot initialize ClassPath multiple times");
}
theClassPath = new ClassPath();
theClassPath.initClassPath(classPathDirs, bootClassPath, dexFilePath, dexFile);
theClassPath.initClassPath(classPathDirs, bootClassPath, extraBootClassPathEntries, dexFilePath, dexFile);
}
private ClassPath() {
classDefs = new HashMap<String, ClassDef>();
}
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<String, TempClassInfo>();
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);
}

View File

@ -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

View File

@ -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<dependencyCount; i++) {
int stringLength = in.readInt();
try {
dependencies[i] = new String(in.readBytes(stringLength), 0, stringLength-1, "US-ASCII");
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex);
}
dependencyChecksums[i] = in.readBytes(20);
}
}
public int getDependencyCount() {
return dependencies.length;
}
public String getDependency(int index) {
return dependencies[index];
}
public byte[] getDependencyChecksum(int index) {
return Arrays.copyOf(dependencyChecksums[index], dependencyChecksums.length);
}
}