标签:
参考资料
https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html
https://source.android.com/devices/tech/dalvik/instruction-formats.html
http://www.milk.com/kodebase/dalvik-docs-mirror/docs/dalvik-bytecode.html
dalvik字节码是以一个字(两字节)为单位,必须是一个字的整数倍。所以,你看到dexdump出的字节指令,其长度都必须x2才能得到其字节长度。
不同指令的字节码长度不一样,相同操作类型的指令长度是一样的,根据指令操作类型,就可以确定指令的长度。比如 mov v1, v2,指令长度是1字。
字节码在dex文件中存储时,都是小端的。因此,第一个字节必须是操作类型(操作符),是一个0~255范围内的一个数字,根据操作符,我们可以确定后续有多少个字以及表示什么意思。
每个操作符都有一个格式(format),根据格式,我们可以知道该指令的长度,参数有哪些、每个参数的长度、参数是如何存储的,这些重要的信息。不同的指令可以有相同的格式,因此,dalvik将他们分类,并给每个格式以不同的描述。每种格式被称为Format ID。
下表详细说明了dalvik字节码的格式(来自https://source.android.com/devices/tech/dalvik/instruction-formats.html)。这个表分为4列。其中前3列分别是Format, ID, Syntax。
Mnemonic | Bit Sizes | Meaning |
---|---|---|
b | 8 | 有符号1字节立即数 |
c | 16, 32 | 常量池索引 |
f | 16 | interface相关的常量 |
h | 16 | 16位有符号立即数,表示32/64位中的最高16位 |
i | 32 | 32位有符号立即数,或者32位的浮点数 |
l | 64 | 64位有符号立即数,或者双精度浮点数 |
m | 16 | method的常量索引值 |
n | 4 | 4位有符号立即数 |
s | 16 | 16位有符号立即数 |
t | 8, 16, 32 | 分支目标偏移值 |
x | 0 | 无更多附加数 |
Format | ID | Syntax | Notable Opcodes Covered |
---|---|---|---|
N/A | 00x |
N/A |
针对一些不使用的指令 |
??|op | 10x |
op |
|
B|A|op | 12x |
op vA, vB |
|
11n |
op vA, #+B |
||
AA|op | 11x |
op vAA |
|
10t |
op +AA |
goto | |
??|opAAAA | 20t |
op +AAAA |
goto/16 |
AA|opBBBB | 20bc |
op AA, kind@BBBB |
目前没有该格式的指令 |
AA|opBBBB | 22x |
op vAA, vBBBB |
|
21t |
op vAA, +BBBB |
||
21s |
op vAA, #+BBBB |
||
21h |
op vAA, #+BBBB0000op vAA, #+BBBB000000000000 |
||
21c |
op vAA, type@BBBBop vAA, field@BBBBop vAA, string@BBBB |
check-cast const-class const-string |
|
AA|opCC|BB | 23x |
op vAA, vBB, vCC |
|
22b |
op vAA, vBB, #+CC |
||
B|A|opCCCC | 22t |
op vA, vB, +CCCC |
|
22s |
op vA, vB, #+CCCC |
||
22c |
op vA, vB, type@CCCCop vA, vB, field@CCCC |
instance-of | |
22cs |
op vA, vB, fieldoff@CCCC |
suggested format for statically linked field access instructions of format 22c | |
??|opAAAAloAAAAhi | 30t |
op +AAAAAAAA |
goto/32 |
??|opAAAA BBBB | 32x |
op vAAAA, vBBBB |
|
AA|opBBBBloBBBBhi | 31i |
op vAA, #+BBBBBBBB |
|
31t |
op vAA, +BBBBBBBB |
||
31c |
op vAA, string@BBBBBBBB |
const-string/jumbo | |
A|G|opBBBB F|E|D|C | 35c |
[A=5 ] op {vC, vD, vE, vF, vG}, meth@BBBB[ A=5 ] op {vC, vD, vE, vF, vG}, type@BBBB[ A=4 ] op {vC, vD, vE, vF}, kind @BBBB[ A=3 ] op {vC, vD, vE}, kind @BBBB[ A=2 ] op {vC, vD}, kind @BBBB[ A=1 ] op {vC}, kind @BBBB[ A=0 ] op {}, kind @BBBBThe unusual choice in lettering here reflects a desire to make the count and the reference index have the same label as in format 3rc. |
|
35ms |
[A=5 ] op {vC, vD, vE, vF, vG}, vtaboff@BBBB[ A=4 ] op {vC, vD, vE, vF}, vtaboff@BBBB[ A=3 ] op {vC, vD, vE}, vtaboff@BBBB[ A=2 ] op {vC, vD}, vtaboff@BBBB[ A=1 ] op {vC}, vtaboff@BBBBThe unusual choice in lettering here reflects a desire to make the count and the reference index have the same label as in format 3rms. |
suggested format for statically linked invoke-virtual and invoke-super instructions
of format 35c |
|
35mi |
[A=5 ] op {vC, vD, vE, vF, vG}, inline@BBBB[ A=4 ] op {vC, vD, vE, vF}, inline@BBBB[ A=3 ] op {vC, vD, vE}, inline@BBBB[ A=2 ] op {vC, vD}, inline@BBBB[ A=1 ] op {vC}, inline@BBBBThe unusual choice in lettering here reflects a desire to make the count and the reference index have the same label as in format 3rmi. |
suggested format for inline linked invoke-static and invoke-virtual instructions
of format 35c |
|
AA|opBBBB CCCC | 3rc |
op {vCCCC .. vNNNN}, meth@BBBBop {vCCCC .. vNNNN}, type@BBBBwhere |
|
3rms |
op {vCCCC .. vNNNN}, vtaboff@BBBBwhere |
suggested format for statically linked invoke-virtual and invoke-super instructions
of format3rc |
|
3rmi |
op {vCCCC .. vNNNN}, inline@BBBBwhere |
suggested format for inline linked invoke-static and invoke-virtual instructions
of format 3rc |
|
AA|opBBBBloBBBB BBBB BBBBhi | 51l |
op vAA, #+BBBBBBBBBBBBBBBB |
const-wide |
java的很多操作,都需要类、方法、域和字符串。这些反映在字节码中,都是以字符串的形式存在的。
所有的类名、方法名、域名、域和方法的签名已经常量字符串全部存储在常量池中。常量池是一个字符串池,包含所有字符串。
同时为了访问这些常量字符串,必须通过一些解析表来进行映射。这些解析表包括type表--保存class名字的索引;field表,包括field所属类的解析表索引、field名字的索引、field签名的索引;method表,包括method所属类的索引、method名字的索引、签名的列表;string表保存的则是常量池中的索引。
用下面的图表示的更加清楚:
图中的每一项在Dex文件中都是以表的方式存在的。
这些表对程序的运行至关重要。如果指令中含有“string@XXXX”这种格式的参数时,那么就要从StringId表中取出一个StringId项,然后通过其offset,从常量池中得到对应的字符串;对于"method@XXXX"这种格式,就是从MethodId的表中得到一个MethodId项,从而读取到各项信息。
但是要注意一点,上图描绘的是dex文件中存在的关系,不是运行时的关系。当我们执行一条指令,比如 "const/string vA, string@BBBB", 寄存器vA中保存的不是“BBBB”的值,也不是从常量池中得到的字符串地址,而是一个和该字符串对应的java.lang.String对象;同理,const/class vA, class@BBBB这样的指令,vA保存的是BBBB所描述的class的java.lang.Class对象。但是 invoke指令和iput,iget等field指令,在运行时并不取得对应的java.reflect.Method和java.reflect.Field对象,而是Dalvik内部的Method和Field对象。这些内部对象只能在dalvik内部使用,不会反映在java层的代码中,java代码也无法使用。
之所以出现这些差别,是因为string,class这种对象,是必须能够让java层代码访问的,而field和method对java代码是透明的。
Dalvik为了应对这种差别,都会做一个与这些表大小一样,索引一致的cache表,这个cache表内放置了已经解析好的对象,需要的时候直接从cache中取,不需要这样来回查找了。
当然,这些表内防止的是所有java代码能够访问到的类、field、method和字符串。比如,你要访问一个android.content.Content类及其方法,那么他们的名字就会放在表内。
6e..72 35c |
invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB 6e: invoke-virtual 6f: invoke-super 70: invoke-direct 71: invoke-static 72: invoke-interface |
A: argument word count (4 bits)B: method reference index (16 bits)C..G: argument registers (4 bits each) |
invoke的返回值是放在result寄存器中的。要取得结果需要用move-result vN的方法来取得。
|
74..78 3rc |
invoke-kind/range {vCCCC .. vNNNN}, meth@BBBB 74: invoke-virtual/range 75: invoke-super/range 76: invoke-direct/range 77: invoke-static/range 78: invoke-interface/range |
A: argument word count (8 bits)B: method reference index (16 bits)C: first argument register (16 bits)N = A + C - 1 |
Call the indicated method. See firstinvoke-kind description above for details, caveats, and suggestions. |
在参数传递上,对于非static函数,第一个参数是this对象。
2b 31t |
packed-switch vAA, +BBBBBBBB (with supplemental data as specified below in "packed-switch-payload Format") |
A: register to testB: signed "branch" offset to table data pseudo-instruction (32 bits) |
B所指出的数据就是 switch的payload数据,记录了case分支信息。如果没有对应的case分支,就会走到下条指令来执行。 下条指令是default分支的数据 |
2c 31t |
sparse-switch vAA, +BBBBBBBB (with supplemental data as specified below in "sparse-switch-payload Format") |
A: register to testB: signed "branch" offset to table data pseudo-instruction (32 bits) |
同上 |
Type | packed-switch | spare-switch |
ushort | 0x0100 | 0x0200 |
ushort | size | size |
int first_key int[] targets |
int[] keys int[] targets |
int idx = value_of(vAA) - first_key; if (idx >= size) target_pc = current_pc + PACKED_SWITCH_SIZE; else target_pc = current_pc + targets[idx];注意一点: targets中的数据偏移是以16位(2字节)为偏移单位的。
int idx = find_key(keys, value_of(vAA)); if (idx >= size) target_pc = current_pc + PACKED_SWITCH_SIZE; else target_pc = current_pc + targets[idx];
dalvik有一项优化,即将dex文件转换为odex文件。那么,odex与dex有什么区别呢?在文件结构上,odex文件会创建一个hash表,以类名为key,帮助系统快速找到class,从而快速加载;在字节码上,引入了一些quick指令,这些quick指令,能够加快执行速度。
quick指令是针对iget/iput,以及invoke-virtual的。iget/iput指令总共被分成3种:iget/iput-quick, iget/iput-object-quick, iget/iput-wide-quick。它们主要是类型与长度不同(见上一节),实际原理都是一样的。iget/iput的原理,是将原iget/iput中记录field在解析表中的索引,直接换成了这个field在对象中的偏移。虚拟机只要用this对象+偏移就可以获取到field在object对象中的位置,从而进行读写。
另外一个invoke-virtual-quick和invoke-virtual-quick/range,同样的,它将invoke-virtual指令内包含的method在解析表内的索引,换成了对应的虚函数在对象的虚函数表中的偏移,通过 this->class->vtable + offset的方式取得method指针,然后进行调用。
除了这些外,还有一个execute-inline指令。这个指令其实是将很多非常基础的类的函数换成dalvik内部指针。比如String类的很多函数,如charAt等,换成dalvik内部用c语言编写的 java_lang_String_charAt函数,在运行时,直接调用内部函数,速度会提升很多。execute-inline会将对应的内部函数的索引放在指令里面。
用下面的表表示:
代码数据 |
align pending (如果不是4字节对齐,增加一个0x0000的pending) |
payload数据(switch, fill-array-data指令的数据,如果有) |
align pending (如果不是4字节对齐,增加一个0x0000的pending) |
try-catch数据数组 |
代码数据部分都是可执行的代码。与我们通常看到的代码不同,davlik字节码使用了大量的goto指令,用于实现各种分支和循环。
try-catch数据结构,是放在代码的后面。在dalvik用CodeItem这个结构来保存code数据,其中的insns_size_in_code_units_是以2字节为单位保存从Method代码开始位置到TryItem数组的偏移。
try catch是用TryItem数组保存的,一个TryItem的结构如下
struct TryItem { uint32_t start_addr_; uint16_t insn_count_; uint16_t handler_off_; private: DISALLOW_COPY_AND_ASSIGN(TryItem); };start_addr_是try块开始位置,相对于代码开始位置的偏移
insn_count_记录try块涉及的代码指令数;
handler_off_ 是catch块的数据,这个偏移是相对与代码开始位置。它的数据是经过LEB128编码的,数据可以被解析成一个CatchHandlerItem结构
CatchHandlerItem结构定义如下
struct CatchHandlerItem { uint16_t type_idx_; // type index of the caught exception type uint32_t address_; // handler address } handler_;type_idx_ 是exception type的索引,同样是type解析表的索引;
address_ 是处理代码的入口。
HandleItem可能不止一个,会有多个。
TryItem是一个数组,数组的长度可以0,1,... N个。当异常发出时,得到异常发出位置的dex pc,用这个dex pc去匹配TryItem中start_addr_和insn_count_,看dex pc落在哪个TryItem的区间内。如果找到了TryItem块,就找它下面的CatchHandleItem,去匹配抛出的exception类型,与CatchHandlerItem定义的类型是否一致,如果一致就找到了对应的处理,如果不一致,则继续查找。
如果本函数内已经没有可以处理的Try-catch了,就向上找调用者函数,依次取查找。
当然,我们知道,java的异常,是 try-catch-finally结构,所以,如果有 finally的话,它是最后一个CatchHandleItem了。
一个TryItem的CatchHandlerItem信息结构,可以用下表来描述(数据都是经过LEB128加密的)
剩余handler_catch 个数(int型) |
catch的exception type index |
catch块的addr偏移 |
剩余handler_catch 个数(int型) |
...... |
剩余handler_catch (<0 表示还有一个final块, 0表示没有了) |
finally块的addr偏移(如果<0) |
标签:
原文地址:http://blog.csdn.net/doon/article/details/51193196