虚拟机dex文件结构文件分析逻辑及解决办法!
1.dex文件分析
从逻辑上来说,dex文件可以分为三个区域,头文件、索引区和数据区。 索引区的ids后缀是.的缩写。
dex 文件。 除了描述 .dex 文件的文件信息外,还有文件中各个其他区域的索引。
(1) Magic value,这8个字节一般都是常量。 为了使 .dex 文件能够被识别,它必须出现在 .dex 文件的最开头。
(2和。 文件检查代码,使用算法检查出maigc的文件,剩余的所有文件区域用于检查文件错误。 ,使用 SHA-1 算法哈希来删除魔法,以及所有剩余的文件区域。
(3):Dex文件的大小。
其余属性不用于强化。 我不会在这里过多解释它们。
2.odex文件
odex 是优化的 dex 文件的缩写。 存储在/data/-cache目录中。 由于程序的apk文件是zip压缩包格式,虚拟机每次加载时都需要从apk中读取.dex文件,这会消耗大量的CPU时间。 通过odex方法优化后的dex文件已经包含了加载dex所必需的信息。 虚拟机只需要检测并加载所需的依赖库即可执行相应的dex文件,大大缩短了读取dex文件所需的时间。
但是,这个优化过程会根据不同设备上虚拟机的版本和库等因素而有所不同。 在一台设备上优化的 ODEX 文件在复制到另一台设备时可能无法运行。
2.1. odex 文件结构
Odex文件的结构可以理解为dex文件的超集。 其结构如下图所示。 odex文件在dex文件的头部添加一些数据,然后在dex文件的末尾添加dex文件的依赖库和一些辅助数据。
虚拟机将dex文件映射到内存后,其格式在系统源码的//.h文件中定义如下。
该结构中存储的大部分内容是指向其他结构的指针。 前面是odex的头部,后面的部分称为辅助数据段,记录了dex文件优化后添加的一些信息。 但结构体描述的是加载到内存中的数据结构,有的数据不会加载到内存中。 经过分析,odex文件结构定义组织如下。
{
; /*odex文件头*/
; /*dex文件*/
部门; /*依赖库列表*/
; /*类查询结构*/
; /*地图池*/
end;/*结束标记*/
};
2.2. Odex文件结构分析
文件头在.h 文件中定义如下。
{
u1魔法[8]; /*odex版本标识*/
u4; /* dex 文件头偏移 */
u4;/* dex文件总长度*/
u4; /*odex依赖库列表偏移量*/
u4 ;/*依赖库列表总长度*/
u4; /*辅助数据偏移*/
u4; /*辅助数据总长度*/
u4 标志; /*标志*/
u4; /*依赖库和辅助数据的校验和*/
};
3、dex文件的验证与优化 3.1 dex文件加载流程
提供专门用于验证和优化dex文件的工具。 源码位于系统源码的/目录下。 当虚拟机加载dex文件时,通过指定的验证和优化选项调用相应的验证和优化操作。
主程序为.cpp,处理apk/jar/zip文件中.dex的函数为()。 ()首先通过()函数检查目标文件是否有class.dex。 如果没有,它将失败并返回。 如果成功只需调用()函数读取.dex的时间戳和crc校验值即可。 如果这一步没有问题,那么调用-File()函数将.dex释放为缓存文件,然后开始解析通过的验证和优化选项。 验证选项用“v=”表示,优化选项用“o=”表示。 所有准备工作完成后,调用()函数启动一个虚拟机进程。 在这个函数中,优化选项和验证选项被传递到全局结构体gDvm的 和 字段。 此时,所有的初始化工作已经完成,真正的验证和优化工作是从调用ion()函数开始的。
ion()函数的调用链比较长。 首先,从 .cpp 移动到 /vm//.cpp,因为这里有 ion() 函数的实现。 该函数首先对dex文件进行简单的检查,确保传入的目标文件属于dex或odex,然后调用mmap()函数将整个文件映射到内存中,然后设置doOpt的两个布尔值和 doOpt 根据 gDvm 的 和 字段。 然后调用()函数重写dex文件。 这里的重写内容包括字符调整、结构调整、类验证信息和辅助数据。 () 函数调用 () 调整字节顺序,然后调用 l() 创建结构体。 l()函数的实现在系统源代码/vm/.cpp文件中。 该函数调用()函数解析dex文件,()函数读取获取dex文件的头部并根据需要调用()函数或者调用m()函数验证头部和字段dex 或 odex 文件。
然后回到.cpp文件继续看代码。 当验证成功后,l()函数调用s()函数设置结构体辅助数据的相关字段,最终执行后返回到()函数。 () 接下来调用()加载dex文件中的所有类。 如果这一步失败,程序就会退出,不等待后续的优化和验证。 如果没有发生错误,则会调用 sses() 函数来执行实际的验证工作。 ,这个函数会调用ss()函数对具体的类进行优化和验证,而ss()函数会将这些任务进行细分,调用()函数进行验证,然后调用()函数进行优化。
()函数的实现代码位于系统源代码的/vm//.cpp文件中。 该函数调用()函数来验证类的所有直接方法和虚方法。 ()函数的具体工作是首先调用()函数验证方法中指令和数据的正确性,然后调用()函数验证代码流程的正确性。
()函数的实现代码位于系统源代码的/vm//.cpp文件中。 该函数调用()函数来优化类的所有直接方法和虚方法。 优化的主要工作是进行“指令替换”。 替换原则是“优先替换-正确性替换-高性能替换”。 例如,指令 iget-wide 将根据优先级替换为“”形式的 iget-wide-,而不是高性能的 iget-wide-quick。
函数返回后,会再次调用l()来验证odex文件,然后调用aps()函数填充辅助数据区结构。 填充结构体后,会调用()函数重写dex文件的值,然后继续接下来的是()和()。
3.2 dex文件优化加载流程图