dex(Dalvik 可执行文件) 结构学习
前言
近几个月接触了挺多安卓方面的东西,但本地笔记杂而乱,于是整理一下
背景
1. Java
写一个Helloworld.java文件,javac把它翻译成字节码:Helloworld.class,JVM 启动。当用到 HelloWorld 这个类时,JVM 的 ClassLoader 就会去硬盘上找到那个 .class 文件,读进内存,然后执行
问题:对于app来说,通常有很多个类,如果都按照这个机制来查找的话,硬盘里会有 10000 个分散的 .class 文件,容易造成空间浪费和性能降低(I/O)
2. Android 的 DEX
为了解决上面的问题,出现了DEX (Dalvik Executable) 格式和 ART 虚拟机
采用 常量池去重 的思路,d8 将这10000个.class融合成一个classes.dex文件
Dex 是Android系统中Java字节码的优化格式,相比传统的class文件,Dex具有更高的执行效率和更小的文件体积。

数据类型
| 自定义类型 | 原类型 | 含义 |
|---|---|---|
| s1 | int8_t | 有符号单字节 |
| u1 | uint8_t | 无符号单字节 |
| s2 | int16_t | |
| u2 | uint16_t | |
| s4 | int32_t | |
| u4 | uint32_t | |
| s8 | int64_t | |
| u8 | uint64_t | |
| sleb128 | 无 | 有符号LEB128,可变长度 |
| uleb128 | 无 | 无符号LEB128,可变长度 |
| uleb128p1 | 无 | 等于ULEB128加1,可变长度 |
其中的 u 表示无符号数,u1 就是 8 位无符号数,u4 就是 32 位无符号数。
LEB128是一种可变长度类型,每个 LEB128 由 1~5 个字节组成,每个字节只有 7 个有效位。如果第一个字节的最高位为 1,表示需要继续使用第 2 个字节,如果第二个字节最高位为 1,表示需要继续使用第三个字节,依此类推,直到最后一个字节的最高位为 0,至多 5 个字节。除了 LEB128 以外,还有无符号类型 ULEB128。
结构
只记一些重要的内容,其他参考Android逆向笔记 —— DEX 文件格式解析 - 知乎

Dex文件采用索引+数据的分离设计,所有索引区在前,实际数据在后。这种设计优化了内存访问和加载速度。
-
header : DEX 文件头,记录了一些当前文件的信息以及其他数据结构在文件中的偏移量 -
string_ids : 字符串的偏移量 -
type_ids : 类型信息的偏移量 -
proto_ids : 方法声明的偏移量 -
field_ids : 字段信息的偏移量 -
method_ids : 方法信息(所在类,方法声明以及方法名)的偏移量 -
class_def : 类信息的偏移量 -
data : : 数据区 -
link_data : 静态链接数据区
从 header 到 data 之间都是偏移量数组,并不存储真实数据,所有数据都存在 data 数据区,根据其偏移量区查找。
header
1 | struct DexHeader { |
magic:
1 | 文件标识 dex + 换行符 + DEX 版本 + 0 |
字符串格式为 dex\n035\0,十六进制为 0x6465780A30333500。
checksum 是对去除magic 、checksum 以外的文件部分作 alder32 算法得到的校验值,用于判断 DEX 文件是否被篡改。
signature 是对除去magic 、checksum 、signature 以外的文件部分作 sha1 得到的文件哈希值。
endianTag 用于标记 DEX 文件是大端表示还是小端表示。由于 DEX 文件是运行在 Android 系统中的,所以一般都是小端表示,这个值也是恒定值0x12345678。其余部分分别标记了 DEX 文件中其他各个数据结构的个数和其在数据区的偏移量。根据偏移量我们就可以轻松的获得各个数据结构的内容。下面顺着上面的 DEX 文件结构来认识第一个数据结构
string_ids。
最后贴张图

参考资料:
Dalvik 可执行文件格式 | Android Open Source Project


%20%E7%BB%93%E6%9E%84%E5%AD%A6%E4%B9%A0.jpg)
