Revamp how classpath loading works

This commit is contained in:
Ben Gruver 2016-04-23 09:18:22 -07:00
parent 8a5a6e3fc5
commit e474301e60
7 changed files with 352 additions and 212 deletions

View File

@ -36,7 +36,6 @@ import org.jf.dexlib2.iface.DexFile;
import org.jf.util.ClassFileNameHandler; import org.jf.util.ClassFileNameHandler;
import org.jf.util.IndentingWriter; import org.jf.util.IndentingWriter;
import javax.annotation.Nonnull;
import java.io.*; import java.io.*;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
@ -155,75 +154,4 @@ public class Baksmali {
} }
return true; return true;
} }
@Nonnull
public static List<String> getDefaultBootClassPath(int apiLevel) {
if (apiLevel < 9) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar");
} else if (apiLevel < 12) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel < 14) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/apache-xml.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel < 16) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/filterfw.jar");
} else if (apiLevel < 21) {
// this is correct as of api 17/4.2.2
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else { // api >= 21
// TODO: verify, add new ones?
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
}
}
} }

View File

@ -35,12 +35,12 @@ import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.beust.jcommander.validators.PositiveInteger; import com.beust.jcommander.validators.PositiveInteger;
import com.google.common.collect.Iterables; import org.jf.dexlib2.DexFileFactory;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile; import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.util.SyntheticAccessorResolver; import org.jf.dexlib2.util.SyntheticAccessorResolver;
import org.jf.util.StringWrapper; import org.jf.util.StringWrapper;
@ -48,6 +48,7 @@ import org.jf.util.jcommander.CommaColonParameterSplitter;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -75,7 +76,7 @@ public class DisassembleCommand extends DexInputCommand {
"bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " + "bootclasspath files that baksmali would otherwise perform. This is analogous to Android's " +
"BOOTCLASSPATH environment variable.", "BOOTCLASSPATH environment variable.",
splitter = CommaColonParameterSplitter.class) splitter = CommaColonParameterSplitter.class)
private List<String> bootClassPath = new ArrayList<String>(); private List<String> bootClassPath = null;
@Parameter(names = {"-c", "--classpath"}, @Parameter(names = {"-c", "--classpath"},
description = "A comma/colon separated list of additional jar/oat files to include in the classpath " + description = "A comma/colon separated list of additional jar/oat files to include in the classpath " +
@ -85,7 +86,8 @@ public class DisassembleCommand extends DexInputCommand {
private List<String> classPath = Lists.newArrayList(); private List<String> classPath = Lists.newArrayList();
@Parameter(names = {"-d", "--classpath-dir"}, @Parameter(names = {"-d", "--classpath-dir"},
description = "baksmali will search these directories in order for any classpath entries.") description = "A directory to search for classpath files. This option can be used multiple times to " +
"specify multiple directories to search. They will be searched in the order they are provided.")
private List<String> classPathDirectories = Lists.newArrayList("."); private List<String> classPathDirectories = Lists.newArrayList(".");
@Parameter(names = {"--code-offsets"}, @Parameter(names = {"--code-offsets"},
@ -170,16 +172,39 @@ public class DisassembleCommand extends DexInputCommand {
return; return;
} }
if (inputList.size() > 1) { String input = inputList.get(0);
System.err.println("Too many files specified"); File dexFileFile = new File(input);
jc.usage(jc.getParsedCommand()); String dexFileEntry = null;
return; if (!dexFileFile.exists()) {
String filename = dexFileFile.getName();
int colonIndex = filename.indexOf(':');
if (colonIndex >= 0) {
dexFileFile = new File(dexFileFile.getParent(), filename.substring(0, colonIndex));
dexFileEntry = filename.substring(colonIndex + 1);
} }
String input = inputList.get(0); if (!dexFileFile.exists()) {
DexBackedDexFile dexFile = loadDexFile(input, apiLevel, experimentalOpcodes); System.err.println("Can't find the file " + input);
if (dexFile == null) { System.exit(1);
return; }
}
//Read in and parse the dex file
DexBackedDexFile dexFile = null;
try {
dexFile = DexFileFactory.loadDexFile(dexFileFile, dexFileEntry, apiLevel, experimentalOpcodes);
} catch (DexFileFactory.MultipleDexFilesException ex) {
System.err.println(String.format("%s contains multiple dex files. You must specify which one to " +
"disassemble with the -e option", dexFileFile.getName()));
System.err.println("Valid entries include:");
for (OatFile.OatDexFile oatDexFile : ex.oatFile.getDexFiles()) {
System.err.println(oatDexFile.filename);
}
System.exit(1);
} catch (IOException ex) {
throw new RuntimeException(ex);
} }
if (showDeodexWarning() && dexFile.hasOdexOpcodes()) { if (showDeodexWarning() && dexFile.hasOdexOpcodes()) {
@ -188,14 +213,6 @@ public class DisassembleCommand extends DexInputCommand {
"re-assemble the results unless you deodex it. See \"baksmali help deodex\""); "re-assemble the results unless you deodex it. See \"baksmali help deodex\"");
} }
if (needsClassPath() && bootClassPath.isEmpty()) {
if (dexFile instanceof DexBackedOdexFile) {
bootClassPath = ((DexBackedOdexFile)dexFile).getDependencies();
} else {
bootClassPath = Baksmali.getDefaultBootClassPath(apiLevel);
}
}
File outputDirectoryFile = new File(outputDir); File outputDirectoryFile = new File(outputDir);
if (!outputDirectoryFile.exists()) { if (!outputDirectoryFile.exists()) {
if (!outputDirectoryFile.mkdirs()) { if (!outputDirectoryFile.mkdirs()) {
@ -227,13 +244,13 @@ public class DisassembleCommand extends DexInputCommand {
if (needsClassPath()) { if (needsClassPath()) {
try { try {
options.classPath = ClassPath.fromClassPath(classPathDirectories, options.classPath = ClassPath.loadClassPath(classPathDirectories,
Iterables.concat(bootClassPath, classPath), dexFile, apiLevel, bootClassPath, classPath, dexFile, apiLevel, shouldCheckPackagePrivateAccess(),
shouldCheckPackagePrivateAccess(), experimentalOpcodes); experimentalOpcodes);
} catch (Exception ex) { } catch (Exception ex) {
System.err.println("\n\nError occurred while loading class path files. Aborting."); System.err.println("\n\nError occurred while loading class path files. Aborting.");
ex.printStackTrace(System.err); ex.printStackTrace(System.err);
return null; System.exit(-1);
} }
} }

View File

@ -34,7 +34,6 @@ package org.jf.baksmali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.ClassProto; import org.jf.dexlib2.analysis.ClassProto;
@ -142,9 +141,8 @@ public class ListFieldOffsetsCommand extends DexInputCommand {
options.apiLevel = apiLevel; options.apiLevel = apiLevel;
try { try {
options.classPath = ClassPath.fromClassPath(classPathDirectories, options.classPath = ClassPath.loadClassPath(classPathDirectories,
Iterables.concat(bootClassPath, classPath), dexFile, apiLevel, bootClassPath, classPath, dexFile, apiLevel, checkPackagePrivateAccess, experimentalOpcodes);
checkPackagePrivateAccess, experimentalOpcodes);
} catch (Exception ex) { } catch (Exception ex) {
System.err.println("Error occurred while loading class path files."); System.err.println("Error occurred while loading class path files.");
ex.printStackTrace(System.err); ex.printStackTrace(System.err);

View File

@ -34,7 +34,6 @@ package org.jf.baksmali;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.jf.dexlib2.analysis.ClassPath; import org.jf.dexlib2.analysis.ClassPath;
import org.jf.dexlib2.analysis.ClassProto; import org.jf.dexlib2.analysis.ClassProto;
@ -164,9 +163,8 @@ public class ListVtablesCommand extends DexInputCommand {
options.apiLevel = apiLevel; options.apiLevel = apiLevel;
try { try {
options.classPath = ClassPath.fromClassPath(classPathDirectories, options.classPath = ClassPath.loadClassPath(classPathDirectories, bootClassPath, classPath, dexFile,
Iterables.concat(bootClassPath, classPath), dexFile, apiLevel, checkPackagePrivateAccess, apiLevel, checkPackagePrivateAccess, experimentalOpcodes);
experimentalOpcodes);
} catch (Exception ex) { } catch (Exception ex) {
System.err.println("Error occurred while loading class path files."); System.err.println("Error occurred while loading class path files.");
ex.printStackTrace(System.err); ex.printStackTrace(System.err);

View File

@ -36,28 +36,29 @@ import com.google.common.base.Suppliers;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList; import com.google.common.collect.*;
import com.google.common.collect.ImmutableSet; import com.google.common.io.Files;
import com.google.common.collect.Lists; import com.google.common.primitives.Ints;
import org.jf.dexlib2.DexFileFactory; import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException; import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
import org.jf.dexlib2.Opcodes; import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.analysis.reflection.ReflectionClassDef; import org.jf.dexlib2.analysis.reflection.ReflectionClassDef;
import org.jf.dexlib2.dexbacked.DexBackedOdexFile;
import org.jf.dexlib2.dexbacked.OatFile;
import org.jf.dexlib2.dexbacked.OatFile.OatDexFile; import org.jf.dexlib2.dexbacked.OatFile.OatDexFile;
import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.ClassDef;
import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.DexFile;
import org.jf.dexlib2.immutable.ImmutableDexFile; import org.jf.dexlib2.immutable.ImmutableDexFile;
import org.jf.util.ExceptionWithContext; import org.jf.util.ExceptionWithContext;
import org.jf.util.PathUtil;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays; import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ClassPath { public class ClassPath {
@Nonnull private final TypeProto unknownClass; @Nonnull private final TypeProto unknownClass;
@ -164,117 +165,181 @@ public class ClassPath {
return checkPackagePrivateAccess; return checkPackagePrivateAccess;
} }
/**
* Creates a ClassPath given a set of user inputs
*
* This performs all the magic in finding the right defaults based on the values provided and what type of dex
* file we have. E.g. choosing the right default bootclasspath if needed, actually locating the files on the
* filesystem, etc.
*
* This is meant to be as forgiving as possible and to generally "do the right thing" based on the given inputs.
*
* @param classPathDirs A list of directories to search for class path entries in. Be sure to include "." to search
* the current working directory, if appropriate.
* @param bootClassPathEntries A list of boot class path entries to load. This can be just the bare filenames,
* relative paths, absolute paths based on the local directory structure, absolute paths
* based on the device directory structure, etc. It can contain paths to
* jar/dex/oat/odex files, or just bare filenames with no extension, etc.
* If non-null and blank, then no entries will be loaded other than dexFile
* If null, it will attempt to use the correct defaults based on the inputs.
* @param extraClassPathEntries Additional class path entries. The same sorts of naming mechanisms as for
* bootClassPathEntries are allowed
* @param checkPackagePrivateAccess Whether checkPackagePrivateAccess is needed, enabled for ONLY early API 17 by
* default
* @param dexFile The dex file that will be analyzed. It can be a dex, odex or oat file.
* @param api The api level of the device that these dex files come from.
* @param experimental Whether to allow experimental opcodes
*
* @return A ClassPath object
*/
@Nonnull @Nonnull
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile, public static ClassPath loadClassPath(@Nonnull Iterable<String> classPathDirs,
int api, boolean experimental) { @Nullable Iterable<String> bootClassPathEntries,
return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17, experimental); @Nonnull Iterable<String> extraClassPathEntries, @Nonnull DexFile dexFile,
int api, boolean experimental, boolean checkPackagePrivateAccess)
throws IOException {
List<ClassProvider> classProviders = Lists.newArrayList();
if (bootClassPathEntries == null) {
bootClassPathEntries = getDefaultDeviceBootClassPath(dexFile, api);
}
if (extraClassPathEntries == null) {
extraClassPathEntries = ImmutableList.of();
}
for (String entry: Iterables.concat(bootClassPathEntries, extraClassPathEntries)) {
List<File> files = Lists.newArrayList();
for (String extension: new String[] { null, ".apk", ".jar", ".odex", ".oat", ".dex" }) {
String searchEntry = entry;
if (Files.getFileExtension(entry).equals(extension)) {
continue;
}
if (extension != null) {
searchEntry = Files.getNameWithoutExtension(entry) + extension;
} }
@Nonnull for (String dir: classPathDirs) {
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile, files.addAll(findFiles(new File(dir), new File(searchEntry).getName(), 100));
int api, boolean checkPackagePrivateAccess, boolean experimental) { }
List<ClassProvider> providers = Lists.newArrayList(); if (files.size() > 0) {
int oatVersion = NOT_ART;
for (String classPathEntry: classPath) {
List<? extends DexFile> classPathDexFiles =
loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
if (oatVersion == NOT_ART) {
for (DexFile classPathDexFile: classPathDexFiles) {
if (classPathDexFile instanceof OatDexFile) {
oatVersion = ((OatDexFile)classPathDexFile).getOatVersion();
break; break;
} }
} }
}
for (DexFile classPathDexFile: classPathDexFiles) { if (files.size() == 0) {
providers.add(new DexClassProvider(classPathDexFile)); throw new FileNotFoundException(String.format("Classpath entry %s could not be found", entry));
}
}
providers.add(new DexClassProvider(dexFile));
return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
} }
@Nonnull File bestMatch = Collections.max(files, new ClassPathEntryComparator(entry));
public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile,
int api, boolean checkPackagePrivateAccess, boolean experimental,
int oatVersion) {
List<ClassProvider> providers = Lists.newArrayList();
for (String classPathEntry: classPath) {
List<? extends DexFile> classPathDexFiles =
loadClassPathEntry(classPathDirs, classPathEntry, api, experimental);
for (DexFile classPathDexFile: classPathDexFiles) {
providers.add(new DexClassProvider(classPathDexFile));
}
}
providers.add(new DexClassProvider(dexFile));
return new ClassPath(providers, checkPackagePrivateAccess, oatVersion);
}
private static final Pattern dalvikCacheOdexPattern = Pattern.compile("@([^@]+)@classes.dex$");
@Nonnull
private static List<? extends DexFile> loadClassPathEntry(@Nonnull Iterable<String> classPathDirs,
@Nonnull String bootClassPathEntry, int api,
boolean experimental) {
File rawEntry = new File(bootClassPathEntry);
// strip off the path - we only care about the filename
String entryName = rawEntry.getName();
// if it's a dalvik-cache entry, grab the name of the jar/apk
if (entryName.endsWith("@classes.dex")) {
Matcher m = dalvikCacheOdexPattern.matcher(entryName);
if (!m.find()) {
throw new ExceptionWithContext(String.format("Cannot parse dependency value %s", bootClassPathEntry));
}
entryName = m.group(1);
}
int extIndex = entryName.lastIndexOf(".");
String baseEntryName;
if (extIndex == -1) {
baseEntryName = entryName;
} else {
baseEntryName = entryName.substring(0, extIndex);
}
for (String classPathDir: classPathDirs) {
String[] extensions;
if (entryName.endsWith(".oat")) {
extensions = new String[] { ".oat" };
} else {
extensions = new String[] { "", ".odex", ".jar", ".apk", ".zip" };
}
for (String ext: extensions) {
File file = new File(classPathDir, baseEntryName + ext);
if (file.exists() && file.isFile()) {
if (!file.canRead()) {
System.err.println(String.format(
"warning: cannot open %s for reading. Will continue looking.", file.getPath()));
} else {
try { try {
return ImmutableList.of(DexFileFactory.loadDexFile(file, api, experimental)); DexFile entryDexFile = DexFileFactory.loadDexFile(bestMatch, api, experimental);
} catch (DexFileNotFound ex) { classProviders.add(new DexClassProvider(entryDexFile));
// ignore and continue
} catch (MultipleDexFilesException ex) { } catch (MultipleDexFilesException ex) {
return ex.oatFile.getDexFiles(); for (DexFile entryDexFile: ex.oatFile.getDexFiles()) {
} catch (Exception ex) { classProviders.add(new DexClassProvider(entryDexFile));
throw ExceptionWithContext.withContext(ex,
"Error while reading boot class path entry \"%s\"", bootClassPathEntry);
} }
} }
} }
int oatVersion = -1;
if (dexFile instanceof OatDexFile) {
oatVersion = ((OatDexFile)dexFile).getOatVersion();
}
classProviders.add(new DexClassProvider(dexFile));
return new ClassPath(classProviders, checkPackagePrivateAccess, oatVersion);
}
private static class ClassPathEntryComparator implements Comparator<File> {
@Nonnull private List<String> reversePathComponents;
public ClassPathEntryComparator(@Nonnull String entry) {
// TODO: will PathUtil.getPathComponents work for unix-style paths while on windows?
this.reversePathComponents = Lists.reverse(PathUtil.getPathComponents(new File(entry)));
}
@Override public int compare(File file1, File file2) {
int comparison = Ints.compare(countMatchingComponents(file1), countMatchingComponents(file2));
if (comparison != 0) {
// the path that matches the entry being searched for wins
return comparison;
}
comparison = Ints.compare(PathUtil.getPathComponents(file1).size(),
PathUtil.getPathComponents(file2).size());
if (comparison != 0) {
// the path "higher up" (with fewer directories) wins
return comparison * -1;
}
// otherwise.. just return the first one alphabetically.
return file1.compareTo(file2);
}
private int countMatchingComponents(File file) {
for (int i=0; i<reversePathComponents.size(); i++) {
if (file == null) {
return i;
}
if (!file.getName().equals(reversePathComponents.get(i))) {
return i;
}
file = file.getParentFile();
}
return reversePathComponents.size();
}
}
/**
* Ye olde recursive file search.
*
* Searches for all files (not directories!) named "name".
*
* It attempts to detect filesystem loops via File.getCanonicalPath, and will not recurse path maxDepth directories.
*/
@Nonnull
private static List<File> findFiles(@Nonnull File dir, @Nonnull String name, int maxDepth) throws IOException {
List<File> files = Lists.newArrayList();
Set<String> visitedPaths = Sets.newHashSet();
if (!dir.exists()) {
throw new IllegalArgumentException(String.format("Directory %s does not exist", dir.getPath()));
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException(String.format("%s is not a directory", dir.getPath()));
}
findFiles(files, visitedPaths, dir, name, maxDepth);
return files;
}
private static void findFiles(@Nonnull List<File> result, @Nonnull Set<String> visitedPaths, @Nonnull File dir,
@Nonnull String name, int maxDepth) throws IOException {
if (maxDepth < 0 || !visitedPaths.add(dir.getCanonicalPath())) {
return;
}
File[] children = dir.listFiles();
if (children == null) {
return;
}
for (File child: children) {
if (child.isDirectory()) {
findFiles(result, visitedPaths, child, name, maxDepth-1);
} else {
if (name.equals(child.getName())) {
try {
DexFileFactory.loadDexFile(child, 15);
} catch (ExceptionWithContext ex) {
if (!(ex instanceof MultipleDexFilesException)) {
// Don't add it to the results if it can't be loaded
continue;
}
}
result.add(child);
}
} }
} }
throw new ExceptionWithContext("Cannot locate boot class path file %s", bootClassPathEntry);
} }
private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize( private final Supplier<OdexedFieldInstructionMapper> fieldInstructionMapperSupplier = Suppliers.memoize(
@ -288,4 +353,133 @@ public class ClassPath {
public OdexedFieldInstructionMapper getFieldInstructionMapper() { public OdexedFieldInstructionMapper getFieldInstructionMapper() {
return fieldInstructionMapperSupplier.get(); return fieldInstructionMapperSupplier.get();
} }
/**
* Returns the default boot class path for the given api. This is boot class path that is used for "stock"
* (i.e nexus) images for the given api level, but may not be correct for devices with heavily modified firmware.
*/
@Nonnull
private static List<String> getDefaultDeviceBootClassPath(DexFile dexFile, int apiLevel) {
if (dexFile instanceof OatFile.OatDexFile) {
if (((OatFile.OatDexFile) dexFile).getOatVersion() >= 74) {
return ((OatFile.OatDexFile) dexFile).getOatFile().getBootClassPath();
} else {
return Lists.newArrayList("boot.oat");
}
}
if (dexFile instanceof DexBackedOdexFile) {
return ((DexBackedOdexFile)dexFile).getDependencies();
}
if (apiLevel <= 8) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar");
} else if (apiLevel <= 11) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel <= 13) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/apache-xml.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/core-junit.jar");
} else if (apiLevel <= 15) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/filterfw.jar");
} else if (apiLevel <= 17) {
// this is correct as of api 17/4.2.2
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel <= 18) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar");
} else if (apiLevel <= 19) {
return Lists.newArrayList(
"/system/framework/core.jar",
"/system/framework/conscrypt.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/framework2.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/services.jar",
"/system/framework/apache-xml.jar",
"/system/framework/webviewchromium.jar");
} else if (apiLevel <= 22) {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/mms-common.jar",
"/system/framework/android.policy.jar",
"/system/framework/apache-xml.jar");
} else /*if (apiLevel <= 23)*/ {
return Lists.newArrayList(
"/system/framework/core-libart.jar",
"/system/framework/conscrypt.jar",
"/system/framework/okhttp.jar",
"/system/framework/core-junit.jar",
"/system/framework/bouncycastle.jar",
"/system/framework/ext.jar",
"/system/framework/framework.jar",
"/system/framework/telephony-common.jar",
"/system/framework/voip-common.jar",
"/system/framework/ims-common.jar",
"/system/framework/apache-xml.jar",
"/system/framework/org.apache.http.legacy.boot.jar");
}
}
} }

View File

@ -51,11 +51,12 @@ import org.jf.dexlib2.immutable.instruction.ImmutableInstruction35mi;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.io.IOException;
import java.util.List; import java.util.List;
public class CustomMethodInlineTableTest { public class CustomMethodInlineTableTest {
@Test @Test
public void testCustomMethodInlineTable_Virtual() { public void testCustomMethodInlineTable_Virtual() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList( List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID)); new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -69,8 +70,9 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef)); DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, ClassPath classPath = ClassPath.loadClassPath(ImmutableList.<String>of(),
15, false); ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, 15, false, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@ -82,7 +84,7 @@ public class CustomMethodInlineTableTest {
} }
@Test @Test
public void testCustomMethodInlineTable_Static() { public void testCustomMethodInlineTable_Static() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList( List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID)); new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -96,8 +98,8 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef)); DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, ClassPath classPath = ClassPath.loadClassPath(ImmutableList.<String>of(),
15, false); ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, 15, false, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
@ -109,7 +111,7 @@ public class CustomMethodInlineTableTest {
} }
@Test @Test
public void testCustomMethodInlineTable_Direct() { public void testCustomMethodInlineTable_Direct() throws IOException {
List<ImmutableInstruction> instructions = Lists.newArrayList( List<ImmutableInstruction> instructions = Lists.newArrayList(
new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0),
new ImmutableInstruction10x(Opcode.RETURN_VOID)); new ImmutableInstruction10x(Opcode.RETURN_VOID));
@ -123,8 +125,8 @@ public class CustomMethodInlineTableTest {
DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef)); DexFile dexFile = new ImmutableDexFile(Opcodes.forApi(19), ImmutableList.of(classDef));
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, ClassPath classPath = ClassPath.loadClassPath(ImmutableList.<String>of(),
15, false); ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, 15, false, false);
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);

View File

@ -28,9 +28,12 @@
package org.jf.util; package org.jf.util;
import com.google.common.collect.Lists;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
public class PathUtil { public class PathUtil {
private PathUtil() { private PathUtil() {
@ -55,8 +58,8 @@ public class PathUtil {
} }
static String getRelativeFileInternal(File canonicalBaseFile, File canonicalFileToRelativize) { static String getRelativeFileInternal(File canonicalBaseFile, File canonicalFileToRelativize) {
ArrayList<String> basePath = getPathComponents(canonicalBaseFile); List<String> basePath = getPathComponents(canonicalBaseFile);
ArrayList<String> pathToRelativize = getPathComponents(canonicalFileToRelativize); List<String> pathToRelativize = getPathComponents(canonicalFileToRelativize);
//if the roots aren't the same (i.e. different drives on a windows machine), we can't construct a relative //if the roots aren't the same (i.e. different drives on a windows machine), we can't construct a relative
//path from one to the other, so just return the canonical file //path from one to the other, so just return the canonical file
@ -105,21 +108,21 @@ public class PathUtil {
return sb.toString(); return sb.toString();
} }
private static ArrayList<String> getPathComponents(File file) { public static List<String> getPathComponents(File file) {
ArrayList<String> path = new ArrayList<String>(); ArrayList<String> path = new ArrayList<String>();
while (file != null) { while (file != null) {
File parentFile = file.getParentFile(); File parentFile = file.getParentFile();
if (parentFile == null) { if (parentFile == null) {
path.add(0, file.getPath()); path.add(file.getPath());
} else { } else {
path.add(0, file.getName()); path.add(file.getName());
} }
file = parentFile; file = parentFile;
} }
return path; return Lists.reverse(path);
} }
} }