ART深入浅出4--了解Dex文件格式(1)

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

本文基于Android 7.1,不过因为从BSP拿到的版本略有区别,所以本文提到的源码未必与读者找到的源码完全一致。本文在提供源码片断时,将按照 <源码相对android工程的路径>:<行号> <类名> <函数名> 的方式,如果行号对不上,请参考类名和函数名来找到对应的源码。

可以参考我之前写过的文章:http://blog.youkuaiyun.com/doon/article/details/51691627

理解dex文件的基本格式

理解dex指令特点

要理解dex文件,首先要dex的指令特点。dex被称为是基于寄存器的虚拟机指令。这里的寄存器,不是指物理寄存器,而是指虚拟机虚拟出的寄存器。而且不同于有我们熟悉的编译性语言(如C/C++),dex指令在运行时,只需要寄存器,不需要堆栈。dex指令有如下特点:

  1. 一个函数拥有一组独立的寄存器 将每个函数的寄存器数量是不固定的,标记为V0~Vn-1,最多可以有65535个
  2. 一个函数的虚拟寄存器可以划分为三部分:
    1. 自变量区:保存的是函数内部用到的自变量
    2. 参数输入区:保存的是输入的参数值
    3. 输出参数区:被用于保存被调用函数的参数。因为输出参数区只用作参数输出,所以在虚拟机实现的时候,通常把调用函数的输出参数区与被调用函数的输入参数区重叠
  3. 假设一个函数有M个参数,那么参数将占据 Vn-m ~ Vn-1,剩余部分将被内部变量占据
  4. 假如一个函数有自变量a,b,且a,b的作用域不重叠,那么它们可以占据同一个寄存器Vi

如下图:
Dex寄存器图

字符串池

字符串在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类型签名
booleanZ
byteB
charC
shortS
intI
longL
floatF
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的相关内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值