大家好,又见面了,我是你们的朋友全栈君。
我们知道,要读取一个类代码,或读取类里的方法代码,都需要打开Dex文件,然后按前面介绍的格式去分析,并且读取出相应的内容,才可以给虚拟机进行解释执行。现在,我们就来学习和分析Dex文件的读取相关的代码。如下:
/*
*Open the specified file read-only. We memory-map the entire thingand
*parse the contents.
*
*This will be called on non-Zip files, especially during VM startup,so
*we don’t want to be too noisy about certain types of failure. (Do
*we want a “quiet” flag?)
*
*On success, we fill out the contents of “pArchive” andreturn 0.
*/
intdexZipOpenArchive(const char* fileName, ZipArchive* pArchive)
{
上面这段代码输入文件名称,输出文件内容对象。
int fd, err;
LOGV(“Opening archive ‘%s’ %pn”,fileName, pArchive);
memset(pArchive, 0, sizeof(ZipArchive));
上面这行代码清空Dex文件对象。
fd = open(fileName, O_RDONLY, 0);
if (fd < 0) {
err = errno ? errno : -1;
LOGV(“Unable to open ‘%s’: %sn”,fileName, strerror(err));
return err;
}
这段代码调用函数open来打开文件,如果不成功就输出出错提示。
return dexZipPrepArchive(fd, fileName,pArchive);
这行代码调用函数dexZipPrepArchive来生成dex文件对象。
}
在上面这个函数里,主要输入两个参数:fileName和pArchive。其中fileName是输入要打开的dex文件名称,当然它是包括文件路径的;pArchive是打开这个文件后用来表达dex文件内容的对象。代码具体过程是先调用函数memset来清空pArchive文件对象,然后调用函数open打开文件,把文件句柄传送给函数dexZipPrepArchive进
行文件分析,并把相应内容设置给文件对象pArchive。
下面来看一下pArchive对象的结构,如下:
typedefstruct ZipArchive {
/* open Zip archive */
int mFd;
这个zip文件的文件句柄,也就是上面调用open函数打开成功后句柄。
/* mapped file */
MemMapping mMap;
这个是把zip文件映射到内存,加快文件读取,提高文件读取效率。
/* number of entries in the Zip archive */
int mNumEntries;
这个是保存zip文件的入口。
/*
* We know how many entries are in the Ziparchive, so we can have a
* fixed-size hash table. We probe oncollisions.
*/
int mHashTableSize;
ZipHashEntry* mHashTable;
这里是通过hash的方法来提高读取文件速度。
}ZipArchive;
ZipArchive结构保存zip文件的句柄、文件内容映射内存地址、zip入口个数和入口地址(使用hash表达)。
这一段代码实现打开Dex文件,由于Dex文件采用zip压缩,所以需要先从zip文件里解压出来,才可以恢复到Dex原始数据。
下面来分析这个函数代码,如下:
intdexZipPrepArchive(int fd, const char* debugFileName, ZipArchive*pArchive)
{
这个函数输入文件句柄、文件名称、压缩文件对象。
MemMapping map;
int err;
map.addr = NULL;
memset(pArchive, 0, sizeof(*pArchive));
pArchive->mFd = fd;
这行代码是保存文件句柄。
if (sysMapFileInShmem(pArchive->mFd,&map) != 0) {
err = -1;
LOGW(“Map of ‘%s’ failedn”,debugFileName);
goto bail;
}
if (map.length < kEOCDLen) {
err = -1;
LOGV(“File ‘%s’ too small to be zip(%zd)n”, debugFileName,map.length);
goto bail;
}
这段代码映射文件数据到内存。
if (!parseZipArchive(pArchive, &map)) {
err = -1;
LOGV(“Parsing ‘%s’ failedn”,debugFileName);
goto bail;
}
这段代码是分析zip文件。
/* success */
err = 0;
sysCopyMap(&pArchive->mMap, &map);
map.addr = NULL;
这段代码拷贝到映射位置。
bail:
if (err != 0)
dexZipCloseArchive(pArchive);
if (map.addr != NULL)
sysReleaseShmem(&map);
return err;
}
函数dexZipPrepArchive的处理,主要就是先保存文件句柄,然后创建文件内存映射,调用parseZipArchive函数来分析zip的所有入口点,并记录到相应的hash表里,最后调用sysCopyMap函数来保存到zip文件对象结构里。
由上面分析可知,dex文件是压缩成zip文件,这样可以减少占用空间。dex文件在系统里是怎么样打开的过程呢?其它经过下面的过程:
1)系统初始化虚拟机时,会初始化原始方法gDvmNativeMethodSet集合。
2)在原始方法集合里有一个函数集合dvm_dalvik_system_DexFile,注册它为Ldalvik/system/DexFile串,当虚拟机调用DexFile相关函数时,就会调用这些函数来处理Dex文件。
3)在处理Dex文件时,会调用函数集合:dvm_dalvik_system_DexFile,这个函数集合里,主要有如下函数:
constDalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{“openDexFile”, “(Ljava/lang/String;Ljava/lang/String;I)I”,
Dalvik_dalvik_system_DexFile_openDexFile},
{“closeDexFile”, “(I)V”,
Dalvik_dalvik_system_DexFile_closeDexFile},
{“defineClass”, “(Ljava/lang/String;Ljava/lang/ClassLoader;ILjava/security/ProtectionDomain;)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方法对应的原始函数是Dalvik_dalvik_system_DexFile_openDexFile,它是打开Dex文件函数。
closeDexFile方法对应的原始函数是Dalvik_dalvik_system_DexFile_closeDexFile,它是关闭已经打开的Dex文件函数。
4)在Dalvik_dalvik_system_DexFile_openDexFile函数里,调用函数dvmJarFileOpen打开JAR或者ZIP压缩的文件。
5)在dvmJarFileOpen函数里,调用dexZipOpenArchive来处理ZIP文件,调用dexZipFindEntry函数读取ZIP解压的文件,调用dvmDexFileOpenFromFd函数读取相应的类数据到内存,并返回给虚拟机。
从上面可知调用函数Dalvik_dalvik_system_DexFile_openDexFile来打开Dex文件,这个函数的源码如下:
staticvoid Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,
JValue* pResult)
{
StringObject* sourceNameObj =(StringObject*) args[0];
这行是输入的Jar或Dex文件名参数。
StringObject* outputNameObj =(StringObject*) args[1];
这行是输出的文件名参数。
int flags = args[2];
这行是处理的标示。
DexOrJar* pDexOrJar = NULL;
JarFile* pJarFile;
RawDexFile* pRawDexFile;
char* sourceName;
char* outputName;
if (sourceNameObj == NULL) {
dvmThrowException(“Ljava/lang/NullPointerException;”,NULL);
RETURN_VOID();
}
这段代码是当输入文件名称为空对象时,就抛出异常。
sourceName =dvmCreateCstrFromString(sourceNameObj);
这行代码调用函数dvmCreateCstrFromString把java字符串转换C字符串,由于Java对象表示的字符串并不能立即就使用到C语言里,所以需要转换才能使用。
if (outputNameObj != NULL)
outputName =dvmCreateCstrFromString(outputNameObj);
else
outputName = NULL;
这段代码是同样把输出字符串转换C字符串。
/*
* We have to deal with the possibility thatsomebody might try to
* open one of our bootstrap class DEXfiles. The set of dependencies
* will be different, and hence the resultsof optimization might be
* different, which means we’d actually needto have two versions of
* the optimized DEX: one that only knowsabout part of the boot class
* path, and one that knows about everythingin it. The latter might
* optimize field/method accesses based on aclass that appeared later
* in the class path.
*
* We can’t let the user-defined classloader open it and start using
* the classes, since the optimized form ofthe code skips some of
* the method and field resolution that wewould ordinarily do, and
* we’d have the wrong semantics.
*
* We have to reject attempts to manuallyopen a DEX file from the boot
* class path. The easiest way to do thisis by filename, which works
* out because variations in name (e.g.”/system/framework/./ext.jar”)
* result in us hitting a differentdalvik-cache entry. It’s also fine
* if the caller specifies their own outputfile.
*/
if(dvmClassPathContains(gDvm.bootClassPath, sourceName)) {
LOGW(“Refusing to reopen boot DEX’%s’n”, sourceName);
dvmThrowException(“Ljava/io/IOException;”,
“Re-opening BOOTCLASSPATH DEXfiles is not allowed”);
free(sourceName);
RETURN_VOID();
}
这段代码是判断用户是否加载系统目录下面的Dex文件,如果加载就要拒绝这样的操作,因为系统启动时已经加载了一份这样的优化代码,没有必要再次加载一次。
/*
* Try to open it directly as a DEX. Ifthat fails, try it as a Zip
* with a “classes.dex” inside.
*/
if (dvmRawDexFileOpen(sourceName,outputName, &pRawDexFile, false) == 0) {
LOGV(“Opening DEX file ‘%s'(DEX)n”, sourceName);
pDexOrJar = (DexOrJar*)malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
这段代码是尝试加载Dex文件,但基本不存在直接加Dex文件的情况,因此在函数dvmRawDexFileOpen还是空函数,没有实际的内容。
}else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false)== 0) {
LOGV(“Opening DEX file ‘%s'(Jar)n”, sourceName);
pDexOrJar = (DexOrJar*)malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
这段代码是加载Jar文件,就是从这里加载Dex文件到缓存里。
}else {
LOGV(“Unable to open DEX file’%s’n”, sourceName);
dvmThrowException(“Ljava/io/IOException;”,”unable to open DEX file”);
}
if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
这行代码保存文件名称到Dex文件对象里。
/* add to hash table */
u4 hash = dvmComputeUtf8Hash(sourceName);
这行代码通过文件名称计算HASH串,加速对文件的查找速度。
void* result;
dvmHashTableLock(gDvm.userDexFiles);
result =dvmHashTableLookup(gDvm.userDexFiles, hash, pDexOrJar,
hashcmpDexOrJar, true);
dvmHashTableUnlock(gDvm.userDexFiles);
这段代码添加HASH表里,以便后面查找使用。
if (result != pDexOrJar) {
LOGE(“Pointer has already beenadded?n”);
dvmAbort();
}
pDexOrJar->okayToFree = true;
}else
free(sourceName);
RETURN_PTR(pDexOrJar);
这行代码返回打开的文件对象。
}
这个函数是通过JAVA调用时输入Dex文件名称,然后加载Dex文件,最后把这个文件名称放到HASH表里,然后返回打开的对象。
在上面的函数里,提到使用dvmJarFileOpen函数找到classes.dex文件,并加载到内存里,然后提供后面的函数使用。现在就来分析这个函数的代码,如下:
intdvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
在这里提供四个参数,第一个参数fileName是输入的Jar的文件名称;第二个参数odexOutputName是进行优化后的Dex输出文件;第三个参数ppJarFile是已经打开并缓存到内存里的文件对象;第四个参数isBootstrap是指示是否系统里Dex文件。
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 thearchive, we need to
* open it so we can stuff it intoppJarFile.
*/
if (dexZipOpenArchive(fileName, &archive)!= 0)
goto bail;
archiveOpen = true;
这段代码是调用前面介绍的函数dexZipOpenArchive来打开Zip文件,并缓存到系统内存里。
/* If we fork/exec into dexopt, don’t letit 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 theextension.
*/
fd = openAlternateSuffix(fileName, “odex”,O_RDONLY, &cachedName);
if (fd >= 0) {
这里优先处理优化的Dex文件。
LOGV(“Using alternate file (odex)for %s …n”, fileName);
if (!dvmCheckOptHeaderAndDependencies(fd,false, 0, 0, true, true)) {
LOGE(“%s odex has staledependenciesn”, fileName);
free(cachedName);
close(fd);
fd = -1;
goto tryArchive;
} else {
LOGV(“%s odex has gooddependenciesn”, fileName);
//TODO: make sure that the .odexactually corresponds
// to the classes.dex inside thearchive (if present).
// For typical use there will beno classes.dex.
}
}else {
ZipEntry entry;
tryArchive:
/*
* Pre-created .odex absent or stale. Look inside the jar for a
* “classes.dex”.
*/
entry = dexZipFindEntry(&archive,kDexInJarName);
这行代码是从压缩包里找到Dex文件,然后打开这个文件。
if (entry != NULL) {
bool newFile = false;
/*
* We’ve found the one we want. Seeif there’s an up-to-date copy
* in the cache.
*
* On return, “fd” will beseeked just past the “opt” header.
*
* If a stale .odex file is presentand classes.dex exists in
* the archive, this will *not*return an fd pointing to the
* .odex file; the fd will point intodalvik-cache like any
* other jar.
*/
if (odexOutputName == NULL) {
cachedName =dexOptGenerateCacheFileName(fileName,
kDexInJarName);
if (cachedName == NULL)
goto bail;
这段代码是把Dex文件进行优化处理,并输出到指定的文件。
} else {
cachedName =strdup(odexOutputName);
}
LOGV(“dvmDexCacheStatus:Checking cache for %s (%s)n”,
fileName, cachedName);
fd = dvmOpenCachedDexFile(fileName,cachedName,
dexGetZipEntryModTime(&archive,entry),
dexGetZipEntryCrc32(&archive,entry),
isBootstrap, &newFile,/*createIfMissing=*/true);
这段代码创建缓存的优化文件。
if (fd < 0) {
LOGI(“Unable to open orcreate cache for %s (%s)n”,
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 isstill locked, and is positioned
* just past the optimization header.
*/
if (newFile) {
u8 startWhen, extractWhen,endWhen;
bool result;
off_t dexOffset, fileLen;
dexOffset = lseek(fd, 0,SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen =dvmGetRelativeTimeUsec();
result =dexZipExtractEntryToFile(&archive, entry, fd);
extractWhen =dvmGetRelativeTimeUsec();
这段代码调用函数dexZipExtractEntryToFile从压缩包里解压文件出来。
}
if (result) {
result =dvmOptimizeDexFile(fd, dexOffset,
dexGetZipEntryUncompLen(&archive,entry),
fileName,
dexGetZipEntryModTime(&archive,entry),
dexGetZipEntryCrc32(&archive,entry),
isBootstrap);
这段代码调用函数dvmOptimizeDexFile对Dex文件进行优化处理。
}
if (!result) {
LOGE(“Unable toextract optimize DEX from ‘%s’n”,
fileName);
goto bail;
}
endWhen =dvmGetRelativeTimeUsec();
LOGD(“DEX prep ‘%s’: unzipin %dms, rewrite %dmsn”,
fileName,
(int) (extractWhen -startWhen) / 1000,
(int) (endWhen – extractWhen)/ 1000);
}
} else {
LOGI(“Zip is good, but no %sinside, and no valid .odex “
“file in the samedirectoryn”, kDexInJarName);
goto bail;
}
}
/*
* Map the cached version. This immediatelyrewinds the fd, so it
* doesn’t have to be seeked anywhere inparticular.
*/
if (dvmDexFileOpenFromFd(fd, &pDvmDex)!= 0) {
LOGI(“Unable to map %s in %sn”,kDexInJarName, fileName);
goto bail;
}
这段代码是调用函数dvmDexFileOpenFromFd来缓存Dex文件,并分析文件的内容。比如标记是否优化的文件,通过签名检查Dex文件是否合法。
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(fd)) {
/* uh oh — this process needs toexit or we’ll wedge the system */
LOGE(“Unable to unlock DEXfilen”);
goto bail;
}
locked = false;
}
这段代码是保存文件到缓存里,标记这个文件句柄已经保存到缓存。
LOGV(“Successfully opened ‘%s’ in’%s’n”, kDexInJarName, fileName);
*ppJarFile = (JarFile*) calloc(1,sizeof(JarFile));
(*ppJarFile)->archive = archive;
(*ppJarFile)->cacheFileName =cachedName;
(*ppJarFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don’t free itbelow
result = 0;
这段代码已经成功打开压缩包,并读取Dex文件到内存缓存,这里只是设置一些相关信息返回前面的函数处理。
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;
}
本函数主要流程是打开压缩文件,然后找到Dex文件,读取出来,并进行分析,然后拷贝到缓存里,再返回给调用函数
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/153520.html原文链接:https://javaforall.cn