0x00 前言
在DexClassLoader和PathClassLoader(一)中,我用了几个小例子介绍了一下DexClassLoader的用法,并给出了完整的代码,有兴趣的同学可以跟着玩一下,也可以根据代码进行扩展
在介绍完使用方法后,简单的介绍了下整个加载流程,篇幅问题只是将大概的过程给梳理了一下,留了许多问题,虽然这些问题都没有明说,DexFile结构,DvmDex结构,DexOrJar结构…
那么这一篇开始,就会详细的讲解这其中的各种结构体,各种关键点
0x01 从DexClassLoader的构造函数说起
我们从DexClassLoader的构造函数说起,这里是整个加载过程的入口,也是出口
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
DexClassLoader继承BaseClassLoader,BaseClassLoader的构造函数
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
DexPathList的构造函数
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
//判断BaseClassLoader是否为空
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
//待加载的Dex文件路径是否为空
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
//判断优化后的ODex文件目录是否为空
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
//如果优化后的ODex文件目录不为空,则判断是否可读可写
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
//关键的一个函数
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
}
关键的一个函数调用
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions);
首先看dexElements的定义,这是一个Dex文件的集合
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private final Element[] dexElements;
Element类有一个关键的变量dexFile
/**
* Element of the dex/resource file path
*/
/*package*/ static class Element {
private final File file;
private final boolean isDirectory;
private final File zip;
private final DexFile dexFile;
private ZipFile zipFile;
private boolean initialized;
public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
this.file = file;
this.isDirectory = isDirectory;
this.zip = zip;
this.dexFile = dexFile;
}
//......
}
再看makeDexElements()
方法
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
//定义一个Element ArrayList用于存储返回数据
//因为可能有多个Dex路径,所以这里用了ArrayList
ArrayList<Element> elements = new ArrayList<Element>();
//遍历files,这里在上一篇有详细讲过,传入的Dex文件Path会有多个,用":"隔开
//在调用本函数的时候会先进行待加载Dex文件路径的解析
for (File file : files) {
//定义两个变量,都先置为空,然后根据后缀进行赋值
File zip = null;
DexFile dex = null;
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
try {
//如果是Dex,将加载后返回的DexFile数据赋值给dex
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)) {
//非Dex文件的分支,也就是加载的是Jar,Zip,APK
zip = file;
try {
//本质上还是调用loadDexFile()
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
} else if (file.isDirectory()) {
elements.add(new Element(file, true, null, null));
} else {
System.logW("Unknown file type for: " + file);
}
//将加载后的文件加到Element ArrayList中
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
loadDexFile()
返回DexFile类型的数据
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
//判断是否指定ODex的存储路径
if (optimizedDirectory == null) {
//未指定ODex存储路径则直接new一个DexFile对象返回
return new DexFile(file);
} else {
//如果有指定ODex的存储路径
//先处理ODex的存储路径,最后加上".dex"
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
//调用DexFile.loadDex()
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
处理ODex文件的存储路径后缀,如果有.dex就不处理,如果没有就加上
private static String optimizedPathFor(File path,
File optimizedDirectory) {
String fileName = path.getName();
if (!fileName.endsWith(DEX_SUFFIX)) {
int lastDot = fileName.lastIndexOf(".");
if (lastDot < 0) {
fileName += DEX_SUFFIX;
} else {
StringBuilder sb = new StringBuilder(lastDot + 4);
sb.append(fileName, 0, lastDot);
sb.append(DEX_SUFFIX);
fileName = sb.toString();
}
}
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}
loadDex()
方法,注意和未指定ODex文件存储路径的区别,那个只有一个参数,这里有三个参数
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
//new一个DexFile对象然后返回
return new DexFile(sourcePathName, outputPathName, flags);
}
查看对应的构造函数
private DexFile(String sourceName, String outputName, int flags) throws IOException {
//判断指定的ODex存储路径是否是应用自身的私有文件夹
//如果不是自身的私有文件夹,会报异常
//Google处于安全考虑,在多处的注释里都提过这点
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");
}
mCookie
的定义,一个私有整型变量
private int mCookie;
openDexFile()
方法的前两个参数是待加载的Dex文件路径以及ODex文件的存储路径,前两个参数都是获取一个合法的绝对路径,第三个参数目测暂时没什么用
private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException {
return openDexFileNative(new File(sourceName).getCanonicalPath(),
(outputName == null) ? null : new File(outputName).getCanonicalPath(),
flags);
}
间接调用的是一个native函数
native private static int openDexFileNative(String sourceName, String outputName,
int flags) throws IOException;
native层中的对应关系
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{ "openDexFileNative", "(Ljava/lang/String;Ljava/lang/String;I)I",
Dalvik_dalvik_system_DexFile_openDexFileNative },
{ "openDexFile", "([B)I",
Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
{ "closeDexFile", "(I)V",
Dalvik_dalvik_system_DexFile_closeDexFile },
{ "defineClassNative", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
Dalvik_dalvik_system_DexFile_defineClassNative },
{ "getClassNameList", "(I)[Ljava/lang/String;",
Dalvik_dalvik_system_DexFile_getClassNameList },
{ "isDexOptNeeded", "(Ljava/lang/String;)Z",
Dalvik_dalvik_system_DexFile_isDexOptNeeded },
{ NULL, NULL, NULL },
};
根据第一个Item找到对应的native函数,这里也是正是开始解析Dex文件的函数
static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
JValue* pResult)
{
//前两个参数转换类型
//param1: sourceNameObj: 待加载Dex文件路径
//param2: outputNameObj: ODex文件存储路径
StringObject* sourceNameObj = (StringObject*) args[0];
StringObject* outputNameObj = (StringObject*) args[1];
//关键结构指针,解析Dex得到的数据,以及其它衍生出来的都会存储在这个结构里,用于返回
DexOrJar* pDexOrJar = NULL;
//如果加载的是Jar,Zip,APK文件会使用到这个变量
//DexOrJar结构的一个成员变量
JarFile* pJarFile;
////如果加载的是Dex文件会使用到这个变量
//DexOrJar结构的一个成员变量
RawDexFile* pRawDexFile;
//两个char*类型的指针,用于存储两个路径
char* sourceName;
char* outputName;
//判断待加载Dex文件路径是否为空
//为空则无法加载,抛出异常
if (sourceNameObj == NULL) {
dvmThrowNullPointerException("sourceName == null");
RETURN_VOID();
}
//先将待加载Dex文件的路径转为C的char*类型
sourceName = dvmCreateCstrFromString(sourceNameObj);
//判断ODex文件的存储路径是否为空
//不为空则将路径转为char*类型
//但这里为空并不会抛出异常,而是赋值为NULL
if (outputNameObj != NULL)
outputName = dvmCreateCstrFromString(outputNameObj);
else
outputName = NULL;
//不能加载系统的Dex文件,这些Dex已经加载过了
//还有一个原因,感兴趣的同学可以读一下源码的注释
//注释就在这个位置,一大段
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();
}
//终于走完前面一大堆流程了
//这里开始根据后缀名进行加载
//dvmRawDexFileOpen()加载Dex文件
//dvmJarFileOpen()加载Jar,Zip,APK文件
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");
}
//pDexOrJar就是前面说的一个关键数据结构指针,这个会返回存在Element List里
//添加这个DexOrJar结构到一个Hash Table里,这个Table存的都是Dex文件,还有一些其它数据
if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
addToDexFileTable(pDexOrJar);
} else {
free(sourceName);
}
free(outputName);
RETURN_PTR(pDexOrJar);
}
返回的这个pDexOrJar
指针,我们往上翻,期间会将pDexOrJar
的值赋值给mCookie
,然后会返回Java的DexFile类实例对象,这个实例对象会赋值给makeDexElements()
方法中的dex变量,也就是Element类的DexFile类型变量dexFile
- native private static int openDexFileNative(String sourceName, String outputName, int flags) throws IOException;
- private static int openDexFile(String sourceName, String outputName, int flags) throws IOException;
- private DexFile(String sourceName, String outputName, int flags) throws IOException;
- mCookie = openDexFile(sourceName, outputName, flags);
- mFileName = sourceName;
- static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException;
- private static DexFile loadDexFile(File file, File optimizedDirectory) throws IOException;
- private static Element[] makeDexElements(ArrayList
files, File optimizedDirectory, ArrayList suppressedExceptions)
从这个方法开始,出现了多个结构体
- JarFile
- RawDexFile
- DexOrJar
这三个是直接就以迅雷不及掩耳盗铃之势出现的,其实还有两个很神奇的结构体,必须放最前面强势安利
- DexFile
- DvmDex
首先是DexFile,这个不是Java层的DexFile,这个结构如果详细解析的话蛮复杂的,脱壳的时候得此结构指针者得Dex文件啊,虽然现在可能不能这么说了
/*
* Structure representing a DEX file.
*
* Code should regard DexFile as opaque, using the API calls provided here
* to access specific structures.
*/
struct DexFile {
/* directly-mapped "opt" header */
const DexOptHeader* pOptHeader;
/* pointers to directly-mapped structs and arrays in base DEX */
const DexHeader* pHeader;
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
const DexClassDef* pClassDefs;
const DexLink* pLinkData;
/*
* These are mapped out of the "auxillary" section, and may not be
* included in the file.
*/
//存储类的Hash和偏移,用于快速查找
const DexClassLookup* pClassLookup;
const void* pRegisterMapPool; // RegisterMapClassPool
/* points to start of DEX file data */
//Dex文件的内存首地址
const u1* baseAddr;
/* track memory overhead for auxillary structures */
int overhead;
/* additional app-specific data structures associated with the DEX */
//void* auxData;
};
DvmDex的第一个成员就是DexFile*
指针类型变量pDexFile
,至关重要!!!!!!
/*
* Some additional VM data structures that are associated with the DEX file.
*/
struct DvmDex {
/* pointer to the DexFile we're associated with */
DexFile* pDexFile;
/* clone of pDexFile->pHeader (it's used frequently enough) */
const DexHeader* pHeader;
/* interned strings; parallel to "stringIds" */
struct StringObject** pResStrings;
/* resolved classes; parallel to "typeIds" */
struct ClassObject** pResClasses;
/* resolved methods; parallel to "methodIds" */
struct Method** pResMethods;
/* resolved instance fields; parallel to "fieldIds" */
/* (this holds both InstField and StaticField) */
struct Field** pResFields;
/* interface method lookup cache */
struct AtomicCache* pInterfaceCache;
/* shared memory region with file contents */
bool isMappedReadOnly;
MemMapping memMap;
jobject dex_object;
/* lock ensuring mutual exclusion during updates */
pthread_mutex_t modLock;
};
介绍完这俩结构体,接下来才轮到刚才那仨
依次来分析一下,首先是RawDexFile
,很是简单,就一个pDvmDex
关键点
/*
* Structure representing a "raw" DEX file, in its unswapped unoptimized
* state.
*/
struct RawDexFile {
char* cacheFileName;
DvmDex* pDvmDex;
};
然后是JarFile
,和上面的差不多,多了个ZipArchive
/*
* This represents an open, scanned Jar file. (It's actually for any Zip
* archive that happens to hold a Dex file.)
*/
struct JarFile {
ZipArchive archive;
//MemMapping map;
char* cacheFileName;
DvmDex* pDvmDex;
};
这个重要了,DexOrJar
是作为返回的数据,看起来成员就多了些
/*
* Internal struct for managing DexFile.
*/
struct DexOrJar {
char* fileName;//文件名
bool isDex;//是否是Dex文件
bool okayToFree;
RawDexFile* pRawDexFile;
JarFile* pJarFile;
u1* pDexMemory; // malloc()ed memory, if any
};
接下来,我们来整理一下中间调用到的几个函数
- hasDexExtension(sourceName);
- dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false);
- dvmJarFileOpen(sourceName, outputName, &pJarFile, false);
- addToDexFileTable(pDexOrJar);
hasDexExtension()
方法用于判断是否是Dex文件
static bool hasDexExtension(const char* name) {
size_t len = strlen(name);
return (len >= 5)
&& (name[len - 5] != '/')
&& (strcmp(&name[len - 4], ".dex") == 0);
}
dvmRawDexFileOpen()
方法用于打开Dex以及一系列优化操作,这个才是真正的重点
一共四个参数,注意第三个,第三个原本就是一个指针,现在取指针的指针,是一个二级指针
sourceName
outputName
&pRawDexFile
false
int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
RawDexFile** ppRawDexFile, bool isBootstrap)
{
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;
//设置通过exec()运行时文件描述符会关闭
dvmSetCloseOnExec(dexFd);
//校验Magic Number,并且获取checkcum存储在adler32
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;
}
//对ODex文件存储路径做判断,如果为空,自动生成一个,不为空则使用指定的路径
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);
//获取ODex文件的文件描述符,中间有不少其它操作和判断,包括newFile的赋值
optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
adler32, isBootstrap, &newFile, /*createIfMissing=*/true);
//获取ODex的文件描述符失败则退出
if (optFd < 0) {
ALOGI("Unable to open or create cache for %s (%s)",
fileName, cachedName);
goto bail;
}
locked = true;
//如果是创建的新ODex文件,原先不存在,则进行处理
//因为可能以前加载过,这是第二次或者第n次加载,那么就不需要进行各种优化什么的
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);
//拷贝Dex文件到ODex文件的存储目录
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}
//优化Dex--->ODex
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);
}
//把ODex文件映射到内存中,但不仅仅是做了映射
if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {
ALOGI("Unable to map cached %s", fileName);
goto bail;
}
//释放文件锁
if (locked) {
if (!dvmUnlockCachedDexFile(optFd)) {
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;
}
中间一些很重要的方法
- verifyMagicAndGetAdler32(dexFd, &adler32)
- getModTimeAndSize(dexFd, &modTime, &fileSize)
- dexOptGenerateCacheFileName(fileName, NULL)
- dvmOpenCachedDexFile(fileName, cachedName, modTime, adler32, isBootstrap, &newFile, /createIfMissing=/true)
- copyFileToFile(optFd, dexFd, fileSize)
- dvmOptimizeDexFile(optFd, dexOffset, fileSize, fileName, modTime, adler32, isBootstrap)
- dvmDexFileOpenFromFd(optFd, &pDvmDex)
verifyMagicAndGetAdler32()
方法会校验前12个字节的数据,Magic Number和checksum
static int verifyMagicAndGetAdler32(int fd, u4 *adler32)
{
//读取前12个字节,前八字节时Magic Number,后四个是checksum
u1 headerStart[12];
ssize_t amt = read(fd, headerStart, sizeof(headerStart));
if (amt < 0) {
ALOGE("Unable to read header: %s", strerror(errno));
return -1;
}
if (amt != sizeof(headerStart)) {
ALOGE("Unable to read full header (only got %d bytes)", (int) amt);
return -1;
}
//校验是否是合法Magic Number
if (!dexHasValidMagic((DexHeader*) (void*) headerStart)) {
return -1;
}
//获取后四个字节的数据,存储在adler32中
*adler32 = (u4) headerStart[8]
| (((u4) headerStart[9]) << 8)
| (((u4) headerStart[10]) << 16)
| (((u4) headerStart[11]) << 24);
return 0;
}
其中dexHasValidMagic()
的参数转为DexHeader*类型
bool dexHasValidMagic(const DexHeader* pHeader)
{
//获取前八字节
const u1* magic = pHeader->magic;
//指向第五个字节
const u1* version = &magic[4];
//校验前四字节,DEX_MAGIC的定义如下
///* DEX file magic number */
//#define DEX_MAGIC "dex\n"
if (memcmp(magic, DEX_MAGIC, 4) != 0) {
ALOGE("ERROR: unrecognized magic number (%02x %02x %02x %02x)",
magic[0], magic[1], magic[2], magic[3]);
return false;
}
//校验第五到第八字节,两个校验的字段定义如下
//#define DEX_MAGIC_VERS "036\0"
//#define DEX_MAGIC_VERS_API_13 "035\0"
if ((memcmp(version, DEX_MAGIC_VERS, 4) != 0) &&
(memcmp(version, DEX_MAGIC_VERS_API_13, 4) != 0)) {
/*
* Magic was correct, but this is an unsupported older or
* newer format variant.
*/
ALOGE("ERROR: unsupported dex version (%02x %02x %02x %02x)",
version[0], version[1], version[2], version[3]);
return false;
}
//都校验通过返回True
return true;
}
getModTimeAndSize()
方法获取修改的时间以及文件大小,用到的stat
结构体和fstat()
有兴趣的同学可以深入分析一下,可以结合ls -l
这个命令来理解
static int getModTimeAndSize(int fd, u4* modTime, size_t* size)
{
struct stat buf;
int result = fstat(fd, &buf);
if (result < 0) {
ALOGE("Unable to determine mod time: %s", strerror(errno));
return -1;
}
*modTime = (u4) buf.st_mtime;
*size = (size_t) buf.st_size;
assert((size_t) buf.st_size == buf.st_size);
return 0;
}
stat
结构体大概是这样,具体我就不是很清楚了,但是可以直接通过文件描述符获取这些数据,然后直接获取修改时间和文件大小两个成员
struct stat {
unsigned long long st_dev;
unsigned char __pad0[4];
unsigned long __st_ino;
unsigned int st_mode;
unsigned int st_nlink;
unsigned long st_uid;
unsigned long st_gid;
unsigned long long st_rdev;
unsigned char __pad3[4];
long long st_size;
unsigned long st_blksize;
unsigned long long st_blocks;
unsigned long st_atime;
unsigned long st_atime_nsec;
unsigned long st_mtime;
unsigned long st_mtime_nsec;
unsigned long st_ctime;
unsigned long st_ctime_nsec;
unsigned long long st_ino;
};
当未指定ODex文件的存储路径时,会调用dexOptGenerateCacheFileName()
生成一个存储路径,这应该是PathClassLoader的执行路径
char* dexOptGenerateCacheFileName(const char* fileName, const char* subFileName)
{
char nameBuf[512];
char absoluteFile[sizeof(nameBuf)];
const size_t kBufLen = sizeof(nameBuf) - 1;
const char* dataRoot;
char* cp;
absoluteFile[0] = '\0';
//判断待加载Dex文件路径首字母,这里一定是相对路径
if (fileName[0] != '/') {
//获取当前路径
if (getcwd(absoluteFile, kBufLen) == NULL) {
ALOGE("Can't get CWD while opening jar file");
return NULL;
}
//加上"/"
strncat(absoluteFile, "/", kBufLen);
}
//再将两个路径结合,此时APK在"/data/app"下
//absoluteFile: /data/app/packagaName/
//fileName: xxx/xxx.dex
strncat(absoluteFile, fileName, kBufLen);
//我们讨论的情况它传进来的参数是空,这里不会执行
if (subFileName != NULL) {
strncat(absoluteFile, "/", kBufLen);
strncat(absoluteFile, subFileName, kBufLen);
}
//除首字母"/",把路径其余的"/"全部替换成"@"
cp = absoluteFile + 1;
while (*cp != '\0') {
if (*cp == '/') {
*cp = '@';
}
cp++;
}
dataRoot = getenv("ANDROID_DATA");
if (dataRoot == NULL)
dataRoot = "/data";
//生成前缀"/data/dalvik-cache"
snprintf(nameBuf, kBufLen, "%s/%s", dataRoot, kCacheDirectoryName);
//再次结合
//"/data/dalvik-cache/data@app@packageName.apk@xxx.dex"
//举个例子: /data/dalvik-cache/data@app@com.wnagzihxa1n.dexclassloaderdemo-18.apk@classes.dex
//由于没有指定ODex文件的存储路径,其实这里就是PathClassLoader会执行的分支
//PathClassLoader用于加载这些已安装的APK的classes.dex文件
strncat(nameBuf, absoluteFile, kBufLen);
ALOGV("Cache file for '%s' '%s' is '%s'", fileName, subFileName, nameBuf);
return strdup(nameBuf);
}
那么到这里,前面的准备工作都已经完成,包括各种路径的有效性验证,输出路径的生成等
下面的四个方法留着下一篇慢慢分析
- dvmOpenCachedDexFile(fileName, cachedName, modTime, adler32, isBootstrap, &newFile, /createIfMissing=/true)
- copyFileToFile(optFd, dexFd, fileSize)
- dvmOptimizeDexFile(optFd, dexOffset, fileSize, fileName, modTime, adler32, isBootstrap)
- dvmDexFileOpenFromFd(optFd, &pDvmDex)