本文基于Android 7.1,不过因为从BSP拿到的版本略有区别,所以本文提到的源码未必与读者找到的源码完全一致。本文在提供源码片断时,将按照 <源码相对android工程的路径>:<行号> <类名> <函数名> 的方式,如果行号对不上,请参考类名和函数名来找到对应的源码。
可以参考我之前写过的文章:http://blog.youkuaiyun.com/doon/article/details/51691627
理解dex文件的基本格式
理解dex指令特点
要理解dex文件,首先要dex的指令特点。dex被称为是基于寄存器的虚拟机指令。这里的寄存器,不是指物理寄存器,而是指虚拟机虚拟出的寄存器。而且不同于有我们熟悉的编译性语言(如C/C++),dex指令在运行时,只需要寄存器,不需要堆栈。dex指令有如下特点:
- 一个函数拥有一组独立的寄存器 将每个函数的寄存器数量是不固定的,标记为V0~Vn-1,最多可以有65535个
- 一个函数的虚拟寄存器可以划分为三部分:
- 自变量区:保存的是函数内部用到的自变量
- 参数输入区:保存的是输入的参数值
- 输出参数区:被用于保存被调用函数的参数。因为输出参数区只用作参数输出,所以在虚拟机实现的时候,通常把调用函数的输出参数区与被调用函数的输入参数区重叠。
- 假设一个函数有M个参数,那么参数将占据 Vn-m ~ Vn-1,剩余部分将被内部变量占据
- 假如一个函数有自变量a,b,且a,b的作用域不重叠,那么它们可以占据同一个寄存器Vi
如下图:
字符串池
字符串在java和dex中都是非常重要的。不仅在编程中包含大量的常量字符,而且,java的类名、方法名、成员名等都是以字符串的形式保存下来的。
那么,字符串在dex中如何存储呢?了解java class文件结构的朋友一定也听说过,在class文件中有一个字符串池,保存了源码中存在的各种字符串以及各种名称。同样的,在dex文件中,也可以看到这样的字符串池.
dex文件相对于class文件的优越在于,dex文件将多个class集中在一起,消除了它们之间的重复字符串。
dex文件对字符串的引用,是通过stringId来实现的。它的定义是
art/runtime/dex_file.h:163 DexFile
// Raw string_id_item.
struct StringId {
uint32_t string_data_off_; // offset in bytes from the base address
private:
DISALLOW_COPY_AND_ASSIGN(StringId);
};
string_data_off_相对于的base address是去掉了dex header后的地址。
这样就可以直接拿到一个字符串了。
Dex文件中的各种表
当一条指令需要引用一个类、字符串、一个对象/类的方法、一个对象/对象的成员的时候,它必须通过索引来实现。比如,有一条指令:
new-instance v0, type@120
它表示,创建一个类,这个类被标记在typeIds表的120位置处,并将结果放到v0寄存器内。
类似的指令包括invoke-virtual/static/super/interface, iput/iget, sput/sget等指令const-string, const-class等指令,它们都需要访问一个表索引。
在dex文件中,共有下面几种表:
stringIds: 字符串池,这个表内包含的StringId类型的数据,这是字符串池索引,所以访问字符串的地方,只需要给出一个字符串索引即可。
TypeIds: 类型索引数组。包含的是类、枚举、接口的类型。对应的数据结构是TypeId
FieldIds: 所有被引用的域的数组列表。不仅是在本dex文件内定义的class,也包括在外部dex文件中定义,但是被本文件内代码使用的成员类型。这里只是定义了Field的信息;
MethodIds: 同FieldIds,都是定义被应用函数的列表。这里并没有函数的实现代码。
ProtoIds: 定义了所有函数的参数原型列表。这里定义了被应用的函数以及用户定义的函数的参数列表信息
TypeId
它的定义
art/runtime/dex_file.h:171 DexFile
// Raw type_id_item.
struct TypeId {
uint32_t descriptor_idx_; // index into string_ids
private:
DISALLOW_COPY_AND_ASSIGN(TypeId);
};
字符串的偏移是stringIds[descriptor_idx_]。需要指出的是,这里的类名字符串不是我们在java中常用的 java.lang.Object形式,而是java/lang/Object这样的形式。
需要注意的是,descriptor_idx_虽然是32位,但是实际上只有16位可用。因为stringIds最大值是16。把它定义成32位,只是为了对齐。
FieldId
art/runtime/dex_file.h:178
// Raw field_id_item.
struct FieldId {
uint16_t class_idx_; // index into type_ids_ array for defining class
uint16_t type_idx_; // index into type_ids_ array for field type
uint32_t name_idx_; // index into string_ids_ array for field name
private:
DISALLOW_COPY_AND_ASSIGN(FieldId);
};
class_idx指出该field所属的类,type_idx指出field的类型所在的索引,name_idx指出的名字索引。
如果你仔细观察,会发现,FieldId中并没有包含field的访问域(public/protectd/private/protected)和是否是静态、是否是fianl等信息。因为这个FieldId只是告诉代码去那里查询这个field,所以就不包含其他信息了。(其实,只需要class_idx和name_idx就足够了)
// Raw proto_id_item.
struct ProtoId {
uint32_t shorty_idx_; // index into string_ids array for shorty descriptor
uint16_t return_type_idx_; // index into type_ids array for return type
uint16_t pad_; // padding = 0
uint32_t parameters_off_; // file offset to type_list for parameter types
private:
DISALLOW_COPY_AND_ASSIGN(ProtoId);
};
shorty_idx_: 这是参数表的简写
return_type_idx: 指出返回值的索引类型
paramters_off_: 参数列表数据的偏移。
首先要说明的是shroty_idx。
我们知道,java的jni有一套方法签名:
| Java类型 | 签名 |
|---|---|
| boolean | Z |
| byte | B |
| char | C |
| short | S |
| int | I |
| long | L |
| float | F |
| double D | |
| 类 | L全限定名;,比如String, 其签名为Ljava/lang/util/String; |
| 数组 | [类型签名, 比如 [B |
比如 函数Object foo(int[], long, double, String) 就会写成 “([IJDLjava/lang/String;)Ljava/lang/Object;”。
在如果将其中的类型限定符去除,只用一个字母表示参数的类型和返回值,那么就变成这样了: “L[JDL” 。其中第一个字母”L”表示返回值为一个对象,从第二个字母开始,都是参数。这就是短格式。
parameters_off_索引指出了参数类比,他是 TypeList以及TypeList的子格式TypeItem,它们的格式:
art/runtime/dex_file.h:245
// Raw type_item.
struct TypeItem {
uint16_t type_idx_; // index into type_ids section
private:
DISALLOW_COPY_AND_ASSIGN(TypeItem);
};
// Raw type_list.
class TypeList {
public:
....
private:
uint32_t size_; // size of the list, in entries
TypeItem list_[1]; // elements of the list
DISALLOW_COPY_AND_ASSIGN(TypeList);
};
TypeList是一个带头部大小的可变数组,数组的成员是TypeItem,而TypeItem的类型是type_idx_,即typeIds的索引。
这样,typeList就定义了一个参数列表。
好了,dex文件先介绍到这里,下节我们将重点介绍ClassDef的相关内容。

本文详细介绍了Android的Dex文件格式,包括理解dex指令特点、字符串池的存储方式以及Dex文件中的各种表,如TypeIds、FieldIds等。通过对Dex文件结构的解析,帮助读者更好地理解Android应用的编译原理。
1637

被折叠的 条评论
为什么被折叠?



