啊啊啊啊啊啊啊,开始吧。
ClassLoader有两种,PathClassLoader和DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
源码就这么点,我也是惊了个呆
不同之处就在于super()函数的第二个参数
上次说到安装阶段已经生成了一个优化后的dex,所以PathClassLoader当中就是null,DexClassLoader则需要自行生成一个优化文件。
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
BaseDexClassLoader的构造函数
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
DexPathList的构造函数
先进行一些验证,然后关键是makeDexElements和splitLibraryPath函数
先看第一个makeDexElements
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ignored) {
/*
* IOException might get thrown "legitimately" by
* the DexFile constructor if the zip file turns
* out to be resource-only (that is, no
* classes.dex file in it). Safe to just ignore
* the exception here, and let dex == null.
*/
}
} else if (file.isDirectory()) {
// We support directories for looking up resources.
// This is only useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else {
System.logW("Unknown file type for: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
注释说的很清楚,产生一个dex或者resource元素的数组
如果文件以.dex、.apk、.jar和.zip结尾,都会调用loadDexFile,区别在于局部变量zip的赋值(具体看代码),如果路径是一个目录,则直接添加到array
前四种情况,都会在调用完loadDexFile后再添加
这里还有个小问题,makeDexElements函数的第一个参数,在调用时为splitDexPath(dexPath)
splitDexPath函数如下
private static ArrayList<File> splitDexPath(String path) {
return splitPaths(path, null, false);
}
splitPaths函数如下
private static ArrayList<File> splitPaths(String path1, String path2,
boolean wantDirectories) {
ArrayList<File> result = new ArrayList<File>();
splitAndAdd(path1, wantDirectories, result);
splitAndAdd(path2, wantDirectories, result);
return result;
}
最终的处理在splitAndAdd函数中
private static void splitAndAdd(String searchPath, boolean directoriesOnly,
ArrayList<File> resultList) {
if (searchPath == null) {
return;
}
for (String path : searchPath.split(":")) {
try {
StructStat sb = Libcore.os.stat(path);
if (!directoriesOnly || S_ISDIR(sb.st_mode)) {
resultList.add(new File(path));
}
} catch (ErrnoException ignored) {
}
}
}
回到主线任务,下面看loadDexFile函数
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
还记得吗,由于PathClassLoader的optimizedDirectory为null,所以这里调用的是new DexFile(file)
自然DexClassLoader调用的就是DexFile.loadDex(file.getPath(), optimizedPath, 0);
先看loadDex
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return new DexFile(sourcePathName, outputPathName, flags);
}
其实也就是DexFile的构造函数
private DexFile(String sourceName, String outputName, int flags) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
首先,如果是DexClassLoader,会先检查父目录的所有者。
无论是哪种ClassLoader,最终都会调用openDexFile
这是一个native函数,位于dalvik\vm\native\dalvik_system_DexFile.cpp
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{ "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I",
Dalvik_dalvik_system_DexFile_openDexFile },
{ "openDexFile", "([B)I",
Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
{ "closeDexFile", "(I)V",
Dalvik_dalvik_system_DexFile_closeDexFile },
{ "defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
Dalvik_dalvik_system_DexFile_defineClass },
{ "getClassNameList", "(I)[Ljava/lang/String;",
Dalvik_dalvik_system_DexFile_getClassNameList },
{ "isDexOptNeeded", "(Ljava/lang/String;)Z",
Dalvik_dalvik_system_DexFile_isDexOptNeeded },
{ NULL, NULL, NULL },
};
根据这个二维字符串数组,找到openDexFile所对应的函数
要注意函数参数,别找错
static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,
JValue* pResult)
{
StringObject* sourceNameObj = (StringObject*) args[0];
StringObject* outputNameObj = (StringObject*) args[1];
DexOrJar* pDexOrJar = NULL;
JarFile* pJarFile;
RawDexFile* pRawDexFile;
char* sourceName;
char* outputName;
if (sourceNameObj == NULL) {
dvmThrowNullPointerException("sourceName == null");
RETURN_VOID();
}
sourceName = dvmCreateCstrFromString(sourceNameObj);
if (outputNameObj != NULL)
outputName = dvmCreateCstrFromString(outputNameObj);
else
outputName = NULL;
/*
* We have to deal with the possibility that somebody might try to
* open one of our bootstrap class DEX files. The set of dependencies
* will be different, and hence the results of optimization might be
* different, which means we'd actually need to have two versions of
* the optimized DEX: one that only knows about part of the boot class
* path, and one that knows about everything in it. The latter might
* optimize field/method accesses based on a class that appeared later
* in the class path.
*
* We can't let the user-defined class loader open it and start using
* the classes, since the optimized form of the code skips some of
* the method and field resolution that we would ordinarily do, and
* we'd have the wrong semantics.
*
* We have to reject attempts to manually open a DEX file from the boot
* class path. The easiest way to do this is by filename, which works
* out because variations in name (e.g. "/system/framework/./ext.jar")
* result in us hitting a different dalvik-cache entry. It's also fine
* if the caller specifies their own output file.
*/
if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) {
ALOGW("Refusing to reopen boot DEX '%s'", sourceName);
dvmThrowIOException(
"Re-opening BOOTCLASSPATH DEX files is not allowed");
free(sourceName);
free(outputName);
RETURN_VOID();
}
/*
* Try to open it directly as a DEX if the name ends with ".dex".
* If that fails (or isn't tried in the first place), try it as a
* Zip with a "classes.dex" inside.
*/
if (hasDexExtension(sourceName)
&& dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
ALOGV("Opening DEX file '%s' (DEX)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
pDexOrJar->pDexMemory = NULL;
} else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
ALOGV("Opening DEX file '%s' (Jar)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
pDexOrJar->pDexMemory = NULL;
} else {
ALOGV("Unable to open DEX file '%s'", sourceName);
dvmThrowIOException("unable to open DEX file");
}
if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
addToDexFileTable(pDexOrJar);
} else {
free(sourceName);
}
free(outputName);
RETURN_PTR(pDexOrJar);
}
dvmRawDexFileOpen函数打开一个原生dex文件,优化,加载。
函数挺长,分段看
int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
RawDexFile** ppRawDexFile, bool isBootstrap)
{
/*
* TODO: This duplicates a lot of code from dvmJarFileOpen() in
* JarFile.c. This should be refactored.
*/
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
int result = -1;
int dexFd = -1;
int optFd = -1;
u4 modTime = 0;
u4 adler32 = 0;
size_t fileSize = 0;
bool newFile = false;
bool locked = false;
dexFd = open(fileName, O_RDONLY);
if (dexFd < 0) goto bail;
/* If we fork/exec into dexopt, don't let it inherit the open fd. */
dvmSetCloseOnExec(dexFd);
声明变量,打开一下文件,再。。。关上
if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {
ALOGE("Error with header for %s", fileName);
goto bail;
}
if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {
ALOGE("Error with stat for %s", fileName);
goto bail;
}
之后的两个函数,第一个验证文件魔数,并且获取adler32校验码
第二个函数调用fstat函数,主要是为了获得上次修改时间,和文件大小
/*
* See if the cached file matches. If so, optFd will become a reference
* to the cached file and will have been seeked to just past the "opt"
* header.
*/
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName, NULL);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
ALOGV("dvmRawDexFileOpen: Checking cache for %s (%s)",
fileName, cachedName);
optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
adler32, isBootstrap, &newFile, /*createIfMissing=*/true);
if (optFd < 0) {
ALOGI("Unable to open or create cache for %s (%s)",
fileName, cachedName);
goto bail;
}
在然后就是核心部分dvmOpenCachedDexFile函数,先往下看,回头再说这个函数
locked = true;
/*
* If optFd points to a new file (because there was no cached
* version, or the cached version was stale), generate the
* optimized DEX. The file descriptor returned is still locked,
* and is positioned just past the optimization header.
*/
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}
if (result) {
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}
if (!result) {
ALOGE("Unable to extract+optimize DEX from '%s'", fileName);
goto bail;
}
endWhen = dvmGetRelativeTimeUsec();
ALOGD("DEX prep '%s': copy in %dms, rewrite %dms",
fileName,
(int) (copyWhen - startWhen) / 1000,
(int) (endWhen - copyWhen) / 1000);
}
/*
* Map the cached version. This immediately rewinds the fd, so it
* doesn't have to be seeked anywhere in particular.
*/
if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {
ALOGI("Unable to map cached %s", fileName);
goto bail;
}
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(optFd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}
ALOGV("Successfully opened '%s'", fileName);
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->cacheFileName = cachedName;
(*ppRawDexFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;
bail:
free(cachedName);
if (dexFd >= 0) {
close(dexFd);
}
if (optFd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(optFd);
close(optFd);
}
return result;
}
比较重要的是dvmDexFileOpenFromFd函数,将dex文件拷贝到内存中
dvmOpenCachedDexFile函数位于/ dalvik / vm / analysis / DexPrepare.cpp
也是很长的一个函数,分段看
int dvmOpenCachedDexFile(const char* fileName, const char* cacheFileName,
u4 modWhen, u4 crc, bool isBootstrap, bool* pNewFile, bool createIfMissing)
{
int fd, cc;
struct stat fdStat, fileStat;
bool readOnly = false;
*pNewFile = false;
retry:
/*
* Try to open the cache file. If we've been asked to,
* create it if it doesn't exist.
*/
fd = createIfMissing ? open(cacheFileName, O_CREAT|O_RDWR, 0644) : -1;
if (fd < 0) {
fd = open(cacheFileName, O_RDONLY, 0);
if (fd < 0) {
if (createIfMissing) {
// TODO: write an equivalent of strerror_r that returns a std::string.
const std::string errnoString(strerror(errno));
if (directoryIsValid(cacheFileName)) {
ALOGE("Can't open dex cache file '%s': %s", cacheFileName, errnoString.c_str());
}
}
return fd;
}
readOnly = true;
} else {
fchmod(fd, 0644);
}
尝试打开cacheFileName文件,否则就创建,根据调用时传递的参数可知,这个已经存在
/*
* Grab an exclusive lock on the cache file. If somebody else is
* working on it, we'll block here until they complete. Because
* we're waiting on an external resource, we go into VMWAIT mode.
*/
ALOGV("DexOpt: locking cache file %s (fd=%d, boot=%d)",
cacheFileName, fd, isBootstrap);
ThreadStatus oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
cc = flock(fd, LOCK_EX | LOCK_NB);
if (cc != 0) {
ALOGD("DexOpt: sleeping on flock(%s)", cacheFileName);
cc = flock(fd, LOCK_EX);
}
dvmChangeStatus(NULL, oldStatus);
if (cc != 0) {
ALOGE("Can't lock dex cache '%s': %d", cacheFileName, cc);
close(fd);
return -1;
}
ALOGV("DexOpt: locked cache file");
这段代码看注释理解,flock函数对fd文件上锁,LOCK_EX代表互斥锁,LOCK_NB代表非阻塞。而在整个上锁期间,如果文件已被上锁,则会一直阻塞,直到flock函数
完成工作。而为了能够完成这一步,需要当前线程环境为VMWAIT,所以在flock函数前后分别用dvmChangeStatus切换当前线程状态(在dalvike启动篇也遇到这个函数,
将当前线程的状态切换)
cc = fstat(fd, &fdStat);
if (cc != 0) {
ALOGE("Can't stat open file '%s'", cacheFileName);
LOGVV("DexOpt: unlocking cache file %s", cacheFileName);
goto close_fail;
}
cc = stat(cacheFileName, &fileStat);
if (cc != 0 ||
fdStat.st_dev != fileStat.st_dev || fdStat.st_ino != fileStat.st_ino)//st_dev文件所在设备id,st_ino结点编号
{
ALOGD("DexOpt: our open cache file is stale; sleeping and retrying");
LOGVV("DexOpt: unlocking cache file %s", cacheFileName);
flock(fd, LOCK_UN);
close(fd);
usleep(250 * 1000); /* if something is hosed, don't peg machine */
goto retry;
}
核实设备id和结点编号,但是为什么要用fstat和stat分别打开同一个文件???暂时不清楚
if (fdStat.st_size == 0) {
if (readOnly) {
ALOGW("DexOpt: file has zero length and isn't writable");
goto close_fail;
}
cc = dexOptCreateEmptyHeader(fd);
if (cc != 0)
goto close_fail;
*pNewFile = true;
ALOGV("DexOpt: successfully initialized new cache file");
} else {
bool expectVerify, expectOpt;
if (gDvm.classVerifyMode == VERIFY_MODE_NONE) {
expectVerify = false;
} else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE) {
expectVerify = !isBootstrap;
} else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/ {
expectVerify = true;
}
if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE) {
expectOpt = false;
} else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED ||
gDvm.dexOptMode == OPTIMIZE_MODE_FULL) {
expectOpt = expectVerify;
} else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/ {
expectOpt = true;
}
ALOGV("checking deps, expecting vfy=%d opt=%d",
expectVerify, expectOpt);
如果文件size为0,并且无法写入(readonly)就报错,若可以写,就产生一个空文件头。如果size不为0,进行设置
注意,PathClassLoader已经在安装阶段生成了odex,所以此处size部位0。相反,DexClassLoader就会进入size为0的分支,*pNewFile会被设置为true
而通过函数声明可以看出,这个pNewFile的值是这个函数的重要输出。
if (!dvmCheckOptHeaderAndDependencies(fd, true, modWhen, crc,
expectVerify, expectOpt))
{
if (readOnly) {
if (createIfMissing) {
ALOGW("Cached DEX '%s' (%s) is stale and not writable",
fileName, cacheFileName);
}
goto close_fail;
}
ALOGD("ODEX file is stale or bad; removing and retrying (%s)",
cacheFileName);
if (ftruncate(fd, 0) != 0) {
ALOGW("Warning: unable to truncate cache file '%s': %s",
cacheFileName, strerror(errno));
/* keep going */
}
if (unlink(cacheFileName) != 0) {
ALOGW("Warning: unable to remove cache file '%s': %d %s",
cacheFileName, errno, strerror(errno));
/* keep going; permission failure should probably be fatal */
}
LOGVV("DexOpt: unlocking cache file %s", cacheFileName);
flock(fd, LOCK_UN);
close(fd);
goto retry;
} else {
ALOGV("DexOpt: good deps in cache file");
}
}
assert(fd >= 0);
return fd;
close_fail:
flock(fd, LOCK_UN);
close(fd);
return -1;
}
pNewFile设置完之后就没什么了。无非是check一下文件头之类的工作。
回到主线,在看一下dvmRawDexFileOpen
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}
if (result) {
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}
if (!result) {
ALOGE("Unable to extract+optimize DEX from '%s'", fileName);
goto bail;
}
endWhen = dvmGetRelativeTimeUsec();
ALOGD("DEX prep '%s': copy in %dms, rewrite %dms",
fileName,
(int) (copyWhen - startWhen) / 1000,
(int) (endWhen - copyWhen) / 1000);
}
这个newFile就是刚才dvmOpenCachedDexFile输出的bool值
当newFile为true时(也就是DexClassLoader),会执行对dex的优化。获得一个空的文件头(dvmOpenCachedDexFile函数中),检查文件偏移量(lseek函数)
将原dex文件拷贝到有一个文件头的空文件当中,最后在dvmOptimizeFile函数执行优化。这个过程我专门回到上一节的优化过程比较下,与extractAndProcessZip
函数的过程大致相同,虽然函数不同,但是过程都是产生一个空文件 ——> 构造文件头 ——> 拷贝 ——> 优化。
dvmOptimizeDexFile函数同样位于/dalvik/vm/analysis/DexPrepare.cpp
bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,
const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
{
const char* lastPart = strrchr(fileName, '/');
if (lastPart != NULL)
lastPart++;
else
lastPart = fileName;
ALOGD("DexOpt: --- BEGIN '%s' (bootstrap=%d) ---", lastPart, isBootstrap);
pid_t pid;
/*
* This could happen if something in our bootclasspath, which we thought
* was all optimized, got rejected.
*/
if (gDvm.optimizing) {
ALOGW("Rejecting recursive optimization attempt on '%s'", fileName);
return false;
}
pid = fork();
if (pid == 0) {
static const int kUseValgrind = 0;
static const char* kDexOptBin = "/bin/dexopt";
static const char* kValgrinder = "/usr/bin/valgrind";
static const int kFixedArgCount = 10;
static const int kValgrindArgCount = 5;
static const int kMaxIntLen = 12; // '-'+10dig+'\0' -OR- 0x+8dig
int bcpSize = dvmGetBootPathSize();
int argc = kFixedArgCount + bcpSize
+ (kValgrindArgCount * kUseValgrind);
const char* argv[argc+1]; // last entry is NULL
char values[argc][kMaxIntLen];
char* execFile;
const char* androidRoot;
int flags;
/* change process groups, so we don't clash with ProcessManager */
setpgid(0, 0);
/* full path to optimizer */
androidRoot = getenv("ANDROID_ROOT");
if (androidRoot == NULL) {
ALOGW("ANDROID_ROOT not set, defaulting to /system");
androidRoot = "/system";
}
execFile = (char*)alloca(strlen(androidRoot) + strlen(kDexOptBin) + 1);
strcpy(execFile, androidRoot);
strcat(execFile, kDexOptBin);
/*
* Create arg vector.
*/
int curArg = 0;
if (kUseValgrind) {
/* probably shouldn't ship the hard-coded path */
argv[curArg++] = (char*)kValgrinder;
argv[curArg++] = "--tool=memcheck";
argv[curArg++] = "--leak-check=yes"; // check for leaks too
argv[curArg++] = "--leak-resolution=med"; // increase from 2 to 4
argv[curArg++] = "--num-callers=16"; // default is 12
assert(curArg == kValgrindArgCount);
}
argv[curArg++] = execFile;
argv[curArg++] = "--dex";
sprintf(values[2], "%d", DALVIK_VM_BUILD);
argv[curArg++] = values[2];
sprintf(values[3], "%d", fd);
argv[curArg++] = values[3];
sprintf(values[4], "%d", (int) dexOffset);
argv[curArg++] = values[4];
sprintf(values[5], "%d", (int) dexLength);
argv[curArg++] = values[5];
argv[curArg++] = (char*)fileName;
sprintf(values[7], "%d", (int) modWhen);
argv[curArg++] = values[7];
sprintf(values[8], "%d", (int) crc);
argv[curArg++] = values[8];
flags = 0;
if (gDvm.dexOptMode != OPTIMIZE_MODE_NONE) {
flags |= DEXOPT_OPT_ENABLED;
if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)
flags |= DEXOPT_OPT_ALL;
}
if (gDvm.classVerifyMode != VERIFY_MODE_NONE) {
flags |= DEXOPT_VERIFY_ENABLED;
if (gDvm.classVerifyMode == VERIFY_MODE_ALL)
flags |= DEXOPT_VERIFY_ALL;
}
if (isBootstrap)
flags |= DEXOPT_IS_BOOTSTRAP;
if (gDvm.generateRegisterMaps)
flags |= DEXOPT_GEN_REGISTER_MAPS;
sprintf(values[9], "%d", flags);
argv[curArg++] = values[9];
assert(((!kUseValgrind && curArg == kFixedArgCount) ||
((kUseValgrind && curArg == kFixedArgCount+kValgrindArgCount))));
ClassPathEntry* cpe;
for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
argv[curArg++] = cpe->fileName;
}
assert(curArg == argc);
argv[curArg] = NULL;
if (kUseValgrind)
execv(kValgrinder, const_cast<char**>(argv));
else
execv(execFile, const_cast<char**>(argv));
ALOGE("execv '%s'%s failed: %s", execFile,
kUseValgrind ? " [valgrind]" : "", strerror(errno));
exit(1);
} else {
ALOGV("DexOpt: waiting for verify+opt, pid=%d", (int) pid);
int status;
pid_t gotPid;
/*
* Wait for the optimization process to finish. We go into VMWAIT
* mode here so GC suspension won't have to wait for us.
*/
ThreadStatus oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
while (true) {
gotPid = waitpid(pid, &status, 0);
if (gotPid == -1 && errno == EINTR) {
ALOGD("waitpid interrupted, retrying");
} else {
break;
}
}
dvmChangeStatus(NULL, oldStatus);
if (gotPid != pid) {
ALOGE("waitpid failed: wanted %d, got %d: %s",
(int) pid, (int) gotPid, strerror(errno));
return false;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
ALOGD("DexOpt: --- END '%s' (success) ---", lastPart);
return true;
} else {
ALOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed",
lastPart, status);
return false;
}
}
}
这个函数的优化很简单,也是执行/bin/dexopt
插播一句,在脱壳时经常使用的dvmDexFileOpenPartial函数的调用过程
从/system/bin/dexopt的main函数开始 ——> 中间函数 ——> extractAndProcessZip ——> dvmContinueOptimization ——> dvmDexFileOpenPartial
回到dvmRawDexFileOpen函数
接下来执行dvmDexFileOpenFromFd函数,位于dalvik\vm\DvmDex.cpp
int dvmDexFileOpenFromFd(int fd, DvmDex** ppDvmDex)
{
DvmDex* pDvmDex;
DexFile* pDexFile;
MemMapping memMap;
int parseFlags = kDexParseDefault;
int result = -1;
if (gDvm.verifyDexChecksum)
parseFlags |= kDexParseVerifyChecksum;
if (lseek(fd, 0, SEEK_SET) < 0) {
ALOGE("lseek rewind failed");
goto bail;
}
if (sysMapFileInShmemWritableReadOnly(fd, &memMap) != 0) {
ALOGE("Unable to map file");
goto bail;
}
pDexFile = dexFileParse((u1*)memMap.addr, memMap.length, parseFlags);
if (pDexFile == NULL) {
ALOGE("DEX parse failed");
sysReleaseShmem(&memMap);
goto bail;
}
pDvmDex = allocateAuxStructures(pDexFile);
if (pDvmDex == NULL) {
dexFileFree(pDexFile);
sysReleaseShmem(&memMap);
goto bail;
}
/* tuck this into the DexFile so it gets released later */
sysCopyMap(&pDvmDex->memMap, &memMap);
pDvmDex->isMappedReadOnly = true;
*ppDvmDex = pDvmDex;
result = 0;
bail:
return result;
}
终于有个不长的函数了
sysMapFileInShmemWritableReadOnly函数将优化后的dex加载进内存,函数位于dalvik\libdex\SysUtil.cpp
int sysMapFileInShmemWritableReadOnly(int fd, MemMapping* pMap)
{
#ifdef HAVE_POSIX_FILEMAP
off_t start;
size_t length;
void* memPtr;
assert(pMap != NULL);
if (getFileStartAndLength(fd, &start, &length) < 0)
return -1;
memPtr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_FILE | MAP_PRIVATE,
fd, start);
if (memPtr == MAP_FAILED) {
ALOGW("mmap(%d, R/W, FILE|PRIVATE, %d, %d) failed: %s", (int) length,
fd, (int) start, strerror(errno));
return -1;
}
if (mprotect(memPtr, length, PROT_READ) < 0) {
/* this fails with EACCESS on FAT filesystems, e.g. /sdcard */
int err = errno;
ALOGV("mprotect(%p, %d, PROT_READ) failed: %s",
memPtr, length, strerror(err));
ALOGD("mprotect(RO) failed (%d), file will remain read-write", err);
}
pMap->baseAddr = pMap->addr = memPtr;
pMap->baseLength = pMap->length = length;
return 0;
#else
return sysFakeMapFile(fd, pMap);
#endif
}
先获得文件头位置和长度,然后用mmap函数映射到内存当中
之后执行dexFileParse,位于dalvik\libdex\DexFile.cpp
又是一个老长的函数,分段看
DexFile* dexFileParse(const u1* data, size_t length, int flags)
{
DexFile* pDexFile = NULL;
const DexHeader* pHeader;
const u1* magic;
int result = -1;
if (length < sizeof(DexHeader)) {
ALOGE("too short to be a valid .dex");
goto bail; /* bad file format */
}
pDexFile = (DexFile*) malloc(sizeof(DexFile));
if (pDexFile == NULL)
goto bail; /* alloc failure */
memset(pDexFile, 0, sizeof(DexFile));
检查文件大小,分配内存
if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {//验证文件魔数
magic = data;
if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {
ALOGE("bad opt version (0x%02x %02x %02x %02x)",
magic[4], magic[5], magic[6], magic[7]);
goto bail;
}
pDexFile->pOptHeader = (const DexOptHeader*) data;//文件头部
ALOGV("Good opt header, DEX offset is %d, flags=0x%02x",
pDexFile->pOptHeader->dexOffset, pDexFile->pOptHeader->flags);
/* parse the optimized dex file tables */
if (!dexParseOptData(data, length, pDexFile))//将优化数据与DexFile文件数据结构中相应的成员进行关联
goto bail;
/* ignore the opt header and appended data from here on out */
data += pDexFile->pOptHeader->dexOffset;
length -= pDexFile->pOptHeader->dexOffset;
if (pDexFile->pOptHeader->dexLength > length) {
ALOGE("File truncated? stored len=%d, rem len=%d",
pDexFile->pOptHeader->dexLength, (int) length);
goto bail;
}
length = pDexFile->pOptHeader->dexLength;
}
dexFileSetupBasicPointers(pDexFile, data);//其他各部分数据与DexFile数据结构建立完整的映射关系
pHeader = pDexFile->pHeader;
if (!dexHasValidMagic(pHeader)) {
goto bail;
}
/*
* Verify the checksum(s). This is reasonably quick, but does require
* touching every byte in the DEX file. The base checksum changes after
* byte-swapping and DEX optimization.
*/
if (flags & kDexParseVerifyChecksum) {
u4 adler = dexComputeChecksum(pHeader);
if (adler != pHeader->checksum) {
ALOGE("ERROR: bad checksum (%08x vs %08x)",
adler, pHeader->checksum);
if (!(flags & kDexParseContinueOnError))
goto bail;
} else {
ALOGV("+++ adler32 checksum (%08x) verified", adler);
}
const DexOptHeader* pOptHeader = pDexFile->pOptHeader;
if (pOptHeader != NULL) {
adler = dexComputeOptChecksum(pOptHeader);
if (adler != pOptHeader->checksum) {
ALOGE("ERROR: bad opt checksum (%08x vs %08x)",
adler, pOptHeader->checksum);
if (!(flags & kDexParseContinueOnError))
goto bail;
} else {
ALOGV("+++ adler32 opt checksum (%08x) verified", adler);
}
}
}
/*
* Verify the SHA-1 digest. (Normally we don't want to do this --
* the digest is used to uniquely identify the original DEX file, and
* can't be computed for verification after the DEX is byte-swapped
* and optimized.)
*/
if (kVerifySignature) {
unsigned char sha1Digest[kSHA1DigestLen];
const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum) +
kSHA1DigestLen;
dexComputeSHA1Digest(data + nonSum, length - nonSum, sha1Digest);
if (memcmp(sha1Digest, pHeader->signature, kSHA1DigestLen) != 0) {
char tmpBuf1[kSHA1DigestOutputLen];
char tmpBuf2[kSHA1DigestOutputLen];
ALOGE("ERROR: bad SHA1 digest (%s vs %s)",
dexSHA1DigestToStr(sha1Digest, tmpBuf1),
dexSHA1DigestToStr(pHeader->signature, tmpBuf2));
if (!(flags & kDexParseContinueOnError))
goto bail;
} else {
ALOGV("+++ sha1 digest verified");
}
}
if (pHeader->fileSize != length) {
ALOGE("ERROR: stored file size (%d) != expected (%d)",
(int) pHeader->fileSize, (int) length);
if (!(flags & kDexParseContinueOnError))
goto bail;
}
if (pHeader->classDefsSize == 0) {
ALOGE("ERROR: DEX file has no classes in it, failing");
goto bail;
}
/*
* Success!
*/
result = 0;
bail:
if (result != 0 && pDexFile != NULL) {
dexFileFree(pDexFile);
pDexFile = NULL;
}
return pDexFile;
}
执行完之后回到dvmDexFileOpenFromFd函数
allocateAuxStructures函数用来生成DvmDex结构
static DvmDex* allocateAuxStructures(DexFile* pDexFile)
{
DvmDex* pDvmDex;
const DexHeader* pHeader;
u4 stringSize, classSize, methodSize, fieldSize;
pHeader = pDexFile->pHeader;
stringSize = pHeader->stringIdsSize * sizeof(struct StringObject*);
classSize = pHeader->typeIdsSize * sizeof(struct ClassObject*);
methodSize = pHeader->methodIdsSize * sizeof(struct Method*);
fieldSize = pHeader->fieldIdsSize * sizeof(struct Field*);
u4 totalSize = sizeof(DvmDex) +
stringSize + classSize + methodSize + fieldSize;
u1 *blob = (u1 *)dvmAllocRegion(totalSize,
PROT_READ | PROT_WRITE, "dalvik-aux-structure");
if ((void *)blob == MAP_FAILED)
return NULL;
pDvmDex = (DvmDex*)blob;
blob += sizeof(DvmDex);
pDvmDex->pDexFile = pDexFile;
pDvmDex->pHeader = pHeader;
pDvmDex->pResStrings = (struct StringObject**)blob;
blob += stringSize;
pDvmDex->pResClasses = (struct ClassObject**)blob;
blob += classSize;
pDvmDex->pResMethods = (struct Method**)blob;
blob += methodSize;
pDvmDex->pResFields = (struct Field**)blob;
ALOGV("+++ DEX %p: allocateAux (%d+%d+%d+%d)*4 = %d bytes",
pDvmDex, stringSize/4, classSize/4, methodSize/4, fieldSize/4,
stringSize + classSize + methodSize + fieldSize);
pDvmDex->pInterfaceCache = dvmAllocAtomicCache(DEX_INTERFACE_CACHE_SIZE);
dvmInitMutex(&pDvmDex->modLock);
return pDvmDex;
}
回到
dvmDexFileOpenFromFd
调用sysCopyMap将memMap关联到pDvmDex
执行完之后,回到dvmRawDexFileOpen
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->cacheFileName = cachedName;
(*ppRawDexFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;
将ppRawDexFile的成员都关联起来
完成
灵魂作画,大概这样
dvmRawDexFileOpen执行完成了
回到开始的Dalvik_dalvik_system_DexFile_openDexFile
if (hasDexExtension(sourceName)
&& dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
ALOGV("Opening DEX file '%s' (DEX)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
pDexOrJar->pDexMemory = NULL;
} else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
ALOGV("Opening DEX file '%s' (Jar)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
pDexOrJar->pDexMemory = NULL;
} else {
ALOGV("Unable to open DEX file '%s'", sourceName);
dvmThrowIOException("unable to open DEX file");
}
if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
addToDexFileTable(pDexOrJar);
} else {
free(sourceName);
}
可以看到另一个分支为dvmJarFileOpen
该函数位于dalvik\vm\JarFile.cpp
int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
/*
* TODO: This function has been duplicated and modified to become
* dvmRawDexFileOpen() in RawDexFile.c. This should be refactored.
*/
ZipArchive archive;
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
bool archiveOpen = false;
bool locked = false;
int fd = -1;
int result = -1;
/* Even if we're not going to look at the archive, we need to
* open it so we can stuff it into ppJarFile.
*/
if (dexZipOpenArchive(fileName, &archive) != 0)
goto bail;
archiveOpen = true;
/* If we fork/exec into dexopt, don't let it inherit the archive's fd.
*/
dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
/* First, look for a ".odex" alongside the jar file. It will
* have the same name/path except for the extension.
*/
fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
if (fd >= 0) {
ALOGV("Using alternate file (odex) for %s ...", fileName);
if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
ALOGE("%s odex has stale dependencies", fileName);
free(cachedName);
cachedName = NULL;
close(fd);
fd = -1;
goto tryArchive;
} else {
ALOGV("%s odex has good dependencies", fileName);
//TODO: make sure that the .odex actually corresponds
// to the classes.dex inside the archive (if present).
// For typical use there will be no classes.dex.
}
} else {
ZipEntry entry;
tryArchive:
/*
* Pre-created .odex absent or stale. Look inside the jar for a
* "classes.dex".
*/
entry = dexZipFindEntry(&archive, kDexInJarName);
if (entry != NULL) {
bool newFile = false;
/*
* We've found the one we want. See if there's an up-to-date copy
* in the cache.
*
* On return, "fd" will be seeked just past the "opt" header.
*
* If a stale .odex file is present and classes.dex exists in
* the archive, this will *not* return an fd pointing to the
* .odex file; the fd will point into dalvik-cache like any
* other jar.
*/
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName,
kDexInJarName);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
ALOGV("dvmJarFileOpen: Checking cache for %s (%s)",
fileName, cachedName);
fd = dvmOpenCachedDexFile(fileName, cachedName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap, &newFile, /*createIfMissing=*/true);
if (fd < 0) {
ALOGI("Unable to open or create cache for %s (%s)",
fileName, cachedName);
goto bail;
}
locked = true;
/*
* If fd points to a new file (because there was no cached version,
* or the cached version was stale), generate the optimized DEX.
* The file descriptor returned is still locked, and is positioned
* just past the optimization header.
*/
if (newFile) {
u8 startWhen, extractWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(fd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
extractWhen = dvmGetRelativeTimeUsec();
}
if (result) {
result = dvmOptimizeDexFile(fd, dexOffset,
dexGetZipEntryUncompLen(&archive, entry),
fileName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap);
}
if (!result) {
ALOGE("Unable to extract+optimize DEX from '%s'",
fileName);
goto bail;
}
endWhen = dvmGetRelativeTimeUsec();
ALOGD("DEX prep '%s': unzip in %dms, rewrite %dms",
fileName,
(int) (extractWhen - startWhen) / 1000,
(int) (endWhen - extractWhen) / 1000);
}
} else {
ALOGI("Zip is good, but no %s inside, and no valid .odex "
"file in the same directory", kDexInJarName);
goto bail;
}
}
/*
* Map the cached version. This immediately rewinds the fd, so it
* doesn't have to be seeked anywhere in particular.
*/
if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
goto bail;
}
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(fd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}
ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);
*ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
(*ppJarFile)->archive = archive;
(*ppJarFile)->cacheFileName = cachedName;
(*ppJarFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;
bail:
/* clean up, closing the open file */
if (archiveOpen && result != 0)
dexZipCloseArchive(&archive);
free(cachedName);
if (fd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(fd);
close(fd);
}
return result;
}
跟
dvmRawDexFileOpen一样,也很多
不过过程大同小异,无非是函数不一样,每一步的目的都是相同的
dvmOpenCachedDexFile、dvmOptimizeDexFile、dvmDexFileOpenFromFd这些关键函数没有变。
简单看一下,就不做详细分析了。
这篇,从上周写到这周,内容超多,还是要时常会看,复习。总结下来,关键步骤也就那几步。