类加载机制
Dalvik虚拟机中,类加载机制的主要功能就是将应用程序中Dalvik操作码以及程序数据提取并加载到虚拟机内部。Dex文件是类加载机制的输入文件,输出是一个名为ClassObject
的数据结构实例对象。
工作流程
类加载机制的主要内容及工作流程主要分三点:
(1) 对Dex文件进行验证并优化。
(2) 对优化后的Dex文件进行解析。
(3) 对指定类进行实际加载。
Dex文件的优化与验证
为了保证原Dex文件的数据安全与优化机制的独立性,优化机制重新创建一个.Odex
文件,主要包括依赖库关系
、寄存器映射关系
以及类的索引关系
。
Odex文件结构分析
Dex文件与Odex文件结构对比:
DexOptHeader数据结构定义:
1 | struct DexOptHeader { |
- 依赖库信息
Dependence结构不会被加载进内存,Android源码中也没有它的明确定义,以下为整理形式。
1 | struct Dependence { |
- 类索引信息
1 | struct DexClassLookup { |
numEntries是通过dexRoundUpPower2()
(用于求比一个数大的最小的2的整数次幂,如数为6,结果为8,降低了哈希冲突率)函数生成。
函数执行流程
PackageManagerService->Installer->installd->do_dexopt->dexopt->run_dexopt->/system/bin/dexopt/system/bin/dexopt
的代码位于dalvik/dexopt/OptMain.cpp文件,其中extractAndProcessZip()
是优化机制的主控程序。
代码清单 dalvik/dexopt/OptMain.cpp: extractAndProcessZip()
1 | /* |
代码清单 dalvik/vm/analysis/DexPrepare.cpp: dvmContinueOptimization()
1 | bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength, |
Dex文件的解析
DexFile数据结构
Dex文件解析的重要目标是为Dex文件生成一个DexFile数据结构。
DexFile结构体定义如下:
1 | struct DexFile { |
Dex文件解析流程
- 对Odex文件进行完整性校验
- 解析Odex文件中的优化数据
- 为Dex文件与相关数据结构建立映射关系
- 为Dex文件进行校验并计算SHA-1
- 保存设置并返回
函数执行流程
Dex文件的解析工作主要由虚拟机源码目录dalvik/vm/RawDexFile.cpp中的dvmRawDexFileOpen()
函数完成,这部分工作中称其为主控函数。
代码清单 dalvik/vm/RawDexFile.cpp: dvmRawDexFileOpen()
1 | int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName, |
dvmRawDexFileOpen()函数完成目标Dex文件优化后,会调用dvmDexFileOpenFromFd()函数完成Dex文件的后续解析工作。
代码清单 dalvik/vm/DvmDex.cpp: dvmDexFileOpenFromFd()
1 | int dvmDexFileOpenFromFd(int fd, DvmDex** ppDvmDex) |
dvmDexFileOpenFromFd()函数首先对已经优化的Dex文件进行正确性检验,随后调用dexFileParse()函数将目标文件进行解析,目标是将Dex文件与DexFile结构体建立关联。
代码清单 dalvik/libdex/DexFile.cpp: dexFileParse()
1 | DexFile* dexFileParse(const u1* data, size_t length, int flags) |
随着dexFileParse函数结束,Dex文件的解析也告一段落,类加载机制接下来的工作就是根据虚拟机的运行需要,从Dex文件中加载指定类,并将其装入虚拟机的运行时环境中。
运行时环境数据加载
ClassObject数据结构
类加载的最终目标就是为目标类生成一个ClassObject的实例对象,并将其存储在运行时环境中,随时被执行模块引用执行。
代码清单 dalvik/vm/oo/Object.h
1 | struct ClassObject : Object { |
类加载整体流程
- 获取描述Dex文件的DexFile结构体对象
- 根据目标类属性选择相应的加载模式
- 在DexFile数据结构中获取目标类数据在Dex文件中的分布信息
- 将类数据信息传给实际加载函数
函数执行流程
dalvik/vm/native/dalvik_system_DexFile.cpp
中的Dalvik_dalvik_System_DexFile_defineClassNative为这一阶段的主控函数。
Dalvik_dalvik_system_DexFile_defineClassNative->dvmGetRawDexFileDex->dvmDefineClass->findClassNoInit完成实际的加载工作。
代码清单 dalvik/vm/oo/Class.cpp: findClassNoInit()
1 | static ClassObject* findClassNoInit(const char* descriptor, Object* loader, |
调用dvmLookupClass函数判断本类是否已经被加载
- 已加载:直接使用,结束函数。
- 未加载:判断能否找到Dex文件,能找到调用
dexFindClass
在指定Dex文件中根据类的描述符查找相关类(用户类);找不到调用searchBootPathForClass
从系统启动基本路径中查找并加载目标类(系统类)。调用loadClassFromDex函数实现加载类达到可运行状态。调用dvmAddClassToHash实现将新加载的类添加到哈希表中方便在此查找。
findClassNoInit函数将调用loadClassFromDex0
函数完成对该类的加载工作,返回值为一个ClassObject
结构体对象。loadClassFromDex0函数源代码如下:
代码清单 dalvik/vm/oo/Class.cpp: loadClassFromDex0()
1 | static ClassObject* loadClassFromDex0(DvmDex* pDvmDex, |
依次完成:1.在内存中为类对象申请存储空间;2.设置字段信息;3.为超类建立索引;4.加载类接口;5.加载类字段;6.加载类方法,并将以上数据封装成一个ClassObject结构体对象并返回。