您的位置:首页 > 移动开发 > Android开发

ART深入浅出6--了解Dex文件格式(3)

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

本节介绍Dex Code的格式。DexCode是Dex虚拟机的核心。

CodeItem

CodeItem结构的内容

Method的内容都放在CodeItem结构中,它的定义是这样的

art/runtime/dex_file.h:281

// Raw code_item.
struct CodeItem {
uint16_t registers_size_;            // the number of registers used by this code
//   (locals + parameters)
uint16_t ins_size_;                  // the number of words of incoming arguments to the method
//   that this code is for
uint16_t outs_size_;                 // the number of words of outgoing argument space required
//   by this code for method invocation
uint16_t tries_size_;                // the number of try_items for this instance. If non-zero,
//   then these appear as the tries array just after the
//   insns in this instance.
uint32_t debug_info_off_;            // file offset to debug info stream
uint32_t insns_size_in_code_units_;  // size of the insns array, in 2 byte code units
uint16_t insns_[1];                  // actual array of bytecode.

.....
};


这是一个开放结构,最后一个insns_是指令数组的起始地址。 ins_size_是指令的大小,单位是2字节。指令总大小是ins_size_*2.

registers_size_ 是该函数用到的总寄存器总个数。ins_size_是参数的个数。outs_size_是该函数调用子函数的大小

假设registers_siez_的大小是N, ins_size_为M,则参数寄存器序列是 (N-M ~ N - 1) 。

而且 必须outs_size_ <= registers_size_。 因为dex代码只能通过寄存器传递参数,所以,被用作传递参数的寄存器数量也包含在总寄存器中。

一个DexMethod需要的寄存器总大小是registers_size_ * 4。一个Long/Double型为8字节,必须占用两个寄存器。这样两个寄存器就合并成一个Wide寄存器。考虑到效率,Wide寄存器需要从偶数开始。

tries_size_ 是TryItem结构的大小,这个大小是用来记录try-cache/finally信息的。

debug_info_off_ 是调试信息,从文件头开始的。

insns_隐含的信息

insns_其实是由两部分组成:指令和Trycatch信息

dex 指令,长度为 ins_size_ * 2
u2 填充数据,为了对齐,可有可无
TryItem[tries_size_] try信息数据
uled128格式的handler size
handlers 数据数组

TryCatch

TryCatch的数据结构,有TryItem和Handler两种数据。

TryItem

TryItem的定义如下:

art/runtime/dex_file.h:300

struct TryItem {
uint32_t start_addr_;
uint16_t insn_count_;
uint16_t handler_off_;
....
};


start_addr_ 是try 模块开始的dex pc,相对于CodeItem.insns_,单位是2字节。

insn_count_ 是从start_addr_开始的代码try 模块的数量

hander_off_ 是 hander结构的偏移,这个偏移是从codeItem.insns_ + sizeof(TryItem ) * codeItem.tries_size_ 开始的。

handler的结构

handler结构是一个不定长的结构,在TryItem数组之后有一个handler_size的leb128编码的数据,表示handler的结构。

一个handler结构包含多个catch块和finally块。catch块和finally块本质上没有什么区别,只是finally块没有指定Throwable对象。

art里面,通过CatchHandlerIterator对象可以遍历所有的Handler。

一个catch/finally块的结构定义在CatchHandlerIterator中,

art/runtime/dex_file.h:1635 CatchHandlerIterator

struct CatchHandlerItem {
uint16_t type_idx_;  // type index of the caught exception type
uint32_t address_;  // handler address
} handler_;


type_idx_ 是DexFile内的TypeIds数组的索引,表示一个Exception对象。

address_ 是基于CodeItem.insns_的位置,表示catch/finally的代码部分。

当是finally块时,type_idx_值为kNoDexIndex (0xffff)。

以上是解码后的数据,实际上,都是以leb128格式存储的。finally块是放在最后的,而且finally只有address_值,没有type_idx_的值。

多个catch块放在一起,是一个handler对象,结构是(伪代码)

leb128_int remaining_count;
struct {
leb128_uint16 type_idx;
leb128_uint32 address_idx;
}catchs[N];
leb128_uint32 finally_address_idx;


leb128前缀表示该项数据是leb128的编码。

remaining_count表示catch/finally块的个数。如果remaining_count <= 0表示最后一个是finally块。如果 remaining_count > 0表示只有catch块。

remaining_count的绝对值表示catch块的个数。如果remaining_count == 0表示没有catch块,只有finally块

一个handler块对应一个TryItem,共同组成try catch/finally块。

有多个try-catch/finally就有多个handler块组成。

Switch

switch在dex中分为packed switch和sparse switch两种。packed switch是针对 case的值相差只有1的情况。sparse switch针对的是case值之间差不相等的情况。

packed switch

指令的格式是 packed-switch vAA, +BBBBBBBB

BBBBBBBB 表示packed-switch数据的偏移,这是一个32位有符号整数,相对与当前指令的偏移。

packed-switch数据的格式是

名称格式说明
identushort = 0x0100识别伪运算码
sizeushort表格中的条目数
first_keyint第一位(即最低位)switch case 的值
targetsint[]与 size 相对的分支目标的列表。这些目标相对应的是 switch 运算码的地址(而非此表格的地址)。
该结构的总大小 是size * 4 + 4 + 4

targets的值是相对于 代码开始位置。可以直接用作dexPC。

sparse switch

指令的格式是sparse-switch vAA, +BBBBBBBB

+BBBBBBBB的格式同上。

sparse-switch的格式是

名称格式说明
identushort = 0x0200识别伪运算码
sizeushort表格中的条目数
keysint[]size 键值列表,从低到高排序
targetsint[]与 size 相对应的分支目标的列表,每一个目标对应相同索引下的键值。这些目标相对应的是 switch 运算码的地址(而非此表格的地址)。
keys列表是按照从小到达的顺序排列的。

计算方法是,从keys中查找vAA的值对应的索引,用这个索引取targets对应的值。

Table

java代码中有很多数组填充的代码,这些代码在dex中对应的是fill-array-data数据。指令的格式是

fill-array-data vAA, +BBBBBBBB

vAA是一个array数组对象。+BBBBBBBB表示fill-array-data-payload,这个结构是

名称格式说明
identushort = 0x0300识别伪运算码
element_widthushort每个元素的字节数
sizeuint表格中的元素数
dataubyte[]数据值
data数据是可以直接拷贝到array数组内的数组。所以,这种格式只能用在元素为非object对象的array对象中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息