【Android Dalvik虚拟机好学易用系列】之二:Dalvik汇编语言
一 Dalvik指令格式
1.1 位描述
Dalvik汇编代码由Dalvik指令组成,指令语法由指令的位描述与指令格式辨识来决定。位描述的约定如下所示:
- 每16位的字采用空格分隔开来;
- 每个字母表示四位,每个字符顺序从高字节开始,排列到低字节,每四位之间可能使用“|”来表示不同的内容。
- 顺序采用A~Z的单个大写字母作为一个4位操作码,op表示一个8位的操作码;
- “?”表示这字段所有位为0值。
举例,如以下指令:
A|G|op BBBB F|E|D|C
- 两个空格:每个被分开的部分有16位,共有3组;
- A|G|op:高8位有A和G组成,低字节由op操作码组成;
- BBBB:一个16位的偏移值;
- F|E|D|C:4个字节组成,表示寄存器参数。
1.2 指令格式标识
单独使用位标识还无法确定一条指令,必须通过指令格式标识来指定格式编码,它的约定如下所示:
- 指令格式大多由三个字符组成,前两个是数字,最后 一个是字母;
- 第一个数字表示指令由多少个16位的字组成;
- 第二个数字表示指令最多使用寄存器的个数,特殊标记“r”标识使用一定范围内的寄存器。
- 第三个字母是类型码,表示指令用到的额外数据的类型。该类型码的具体含义如下表所示:
1.3 语法约定
- 每条指令从操作码开始,后面紧跟参数,参数个数不定,每个参数采用逗号隔开;
- 每条指令的参数从指令的第一部分开始,op位于低8位,高8位可以是一个8位的参数,也可以是两个4位的参数,还可以为空,如果指令超过16位,则后面部分依次作为参数;
- 如果参数采用“vX”的方式表示,表明它是一个寄存器,如v0、v1等,ARM架构的寄存器名采用“r”开头;
- 如果参数采用“#+X”的方式表示,表明它是一个常量数字;
- 如果参数采用“+X”的方式表示,表明她是一个相对指令的地址偏移;
- 如果参数采用“kind@X”的方式表示,表示它是一个常量池索引值,其中kind表示常量池类型:string(字符串常量池索引)、type(类型常量池索引)、field(字段常量池索引)和meth(方法常量池索引)。
举例,如以下指令
op vAA, string@BBBB
1个寄存器vAA,1个字符串常量池索引。
二 Dalvik指令集
Dalvik指令的语法与助记符有如下特点:
- 参数采用从目标到源的方式;
- 根据字节码大小和类型不同,一些字节码添加了名称后缀以消除歧义,32位常规类型字节码未添加任何后缀,64位类型的字节码添加-wide后缀,特殊类型字节码根据具体的类型添加后缀,它们可以是-boolean、-byte、-char、-short、-int、-long、-float、-double、-object、-string、-class和-void;
- 根据字节码的布局和选项不同,一些字节码添加了字节码后缀以消除歧义,这些后缀通过在字节码主名称后添加“/”来分隔开;
- 在指令集的描述中,宽度值中的每个字母表示宽度为4位。
举例,如以下指令:
move-wide/from16 vAA,vBBBB
- move:基础字节码,标识这是基础操作
- -wide:名称后缀,标识指令操作的数据宽度(64位)
- from:字节码后缀,标识源为一个16位的寄存器引用变量
- vAAA:目的寄存器,它始终在源的前面,取值范围v0~v255
- vBBBB:源寄存器,取值范围v0~v65535
2.1 空操作指令
助记符:nop
指令:对齐代码,无实际操作。
2.2 数据操作指令
助记符:move
指令:move指令会根据字节码的大小和类型不同,后面跟不同的后缀
move destination,source
举例
move/from16 vAA,vBBBB;将vBBBB寄存器的值赋给vAA寄存器,源寄存器和目的寄存器都为16位
move-wide vA,vB;4位寄存器对赋值,源寄存器与目的寄存器都为4位
move-result vAA;将上一个invoke类型指令操作的双字非对象结果赋值给vAA寄存器
move-object vA,vB;为对象赋值,源寄存器与目的寄存器都为4位
move-exception vAA;保存一个运行时发生的异常到vAA寄存器
2.3 返回指令
助记符:return
指令:它有4中类型
return-void;表示函数从一个void方法返回
return-vAA;表示函数返回一个32位非对象类型的值,返回寄存器为6位jicunqivAA
return-wide vAA;表示函数返回一个62位非对象类型的值,返回值为8位的寄存器vAA
return-object vAA;表示函数返回一个对象类型的值,返回值为8位的寄存器vAA
2.4 数据定义指令
助记符:const
指令:数据定义指令用来定义程序中用到的常量、字符串和类等数据。
const/4 vA,#+B;将数值符号扩展为32位后赋值给寄存器vA
const/16 vAA,#+BBBB;将数值符号扩展为32位后赋给寄存器vAA
const/high16 vAA,#+BBBB0000;将数值右边零扩展为32位后赋给寄存器vAA
const-wide/16 vAA,#+BBBB;将数值符号扩展为64位后赋值给寄存器vAA
const-string vAA,string@BBBB;通过字符串索引构造一个字符串并赋值给寄存器vAA
const-class vAA,type@BBBB;通过类型索引获取一个类引用并赋给寄存器vAA
const-class/jumbo vAAAA,type@BBBBBBBB;通过给定类型的索引获取一个类引用并赋给寄存器vAAAA,这条指令占用两个字节,值为0x00ff
2.5 锁指令
助记符:monitor
指令:锁指令多用在多线程程序中对同一对象的操作。
monitor-enter vAA;为指定对象获取锁
monitor-exit vAA;释放指定对象的锁
2.6 实例操作指令
助记符:check-cast、instace-of、new-instance
指令:实例的类型转换、检查和更新。
check-cast vAA,type@BBBB;将寄存器vAA中的对象引用转换成指定的类型,如果失败则抛出ClassCastException,如果类型B指定的是基本类型,对于非基本类型A来说,运行时始终会失败
instance-of vA,vB,type@CCCC;判断vB寄存器中的对象引用是否可以转换成指定的类型,如果可以vA的寄存器赋值为1,否则赋值为0
new-instance vAA,type@BBBB;构造一个指定类型对象的新实例,并将对象引用赋值给vAA寄存器,类型符type指定的类型不能是数组类
2.7 数组操作指令
助记符:array-length、new-array、filed-new-array、fill-array-data、arrayop
指令:取数组长度、新建数组、数组赋值、数组元素取值与赋值。
array-length vA,vB;获取给定vB寄存器中数组的长度并赋值给vA寄存器,数组长度指的是数组的条目个数
new-array vA,vB,type@CCCC;构造指定类型(type@CCCC)与大小(vB)的数组,并将值赋值给vA寄存器
filled-new-array {vC,vD,vE,vF,vG},type@BBBB;构造指定类型(type@BBBB)与大小(vA)的数组并填充数组内容,vA寄存器是隐含使用的,除了指定数组的大小外还指定了参数的个数,vC~vG是使用到的参数寄存器序列
2.8 异常指令
助记符:throw
指令:抛出异常
throw vA;抛出vA寄存器中指定类型的异常
2.9 跳转指令
助记符:goto、packed-switch、sparse-switch、if-test、if-testz
指令:从当前地址跳转到指定的偏移处。
goto+AA;无条件跳转到指定偏移处,偏移量AA不能为0
packed-switch vAA,+BBBBBBBB;分支跳转指令,vAA寄存器为switch分支需要判断的值,BBBBBBBBB指向一个packed-swithc-payload格式的偏移表,表中的值是按规律递增的
sparse-switch vAA,+BBBBBBBB;分支跳转指令,vAA寄存器为switch分支需要判断的值,BBBBBBBBB指向一个sparse-swithc-payload格式的偏移表,表中的值无规律的偏移量
if-test vA,vB,+CCCC;条件跳转指令,比较vA寄存器和vB寄存器的值,如果比较结果满足就跳转到CCCC指定的偏移处,偏移量CCCC不能为0
if-eq vA,vB,+CCCC;if(vA==vB)则跳转
if-ne vA,vB,+CCCC;if(vA!=vB)则跳转
if-lt vA,vB,+CCCC;if(vA<vB)则跳转
if-ge vA,vB,+CCCC;if(vA>=vB)则跳转
if-gt vA,vB,+CCCC;if(vA>vB)则跳转
if-le vA,vB,+CCCC;if(vA<=vB)则跳转
if-testz vA,+CCCC;条件跳转指令,比较vA寄存器和0比较,如果比较结果满足或值为0就跳转到CCCC指定的偏移处,偏移量CCCC不能为0
if-eqz vA,+CCCC;if(!vA)则跳转
if-nez vA,+CCCC;if(vA)则跳转
if-ltz vA,+CCCC;if(vA<0)则跳转
if-gez vA,+CCCC;if(vA>=0)则跳转
if-gtz vA,+CCCC;if(vA>0)则跳转
if-lez vA,+CCCC;if(vA<=0)则跳转
2.10 比较指令
助记符:cmpl-float、cmpg-float、cmpl-double、cmpg-double和cmp-log
指令:比较两个寄存器的值
cmpl-float vAA,vBB,vCC;比较两个单精度浮点数,vBB>vCC,则结果为-1,vBB==vCC,则结果为0,vBB<vCC,则结果为1,最终结果存放到vAA中
cmpg-float vAA,vBB,vCC;比较两个单精度浮点数,vBB>vCC,则结果为1,vBB==vCC,则结果为0,vBB<vCC,则结果为-1,最终结果存放到vAA中
cmpl-double vAA,vBB,vCC;比较两个双精度浮点数,vBB>vCC,则结果为-1,vBB==vCC,则结果为0,vBB<vCC,则结果为1,最终结果存放到vAA中
cmpg-double vAA,vBB,vCC;比较两个双精度浮点数,vBB>vCC,则结果为1,vBB==vCC,则结果为0,vBB<vCC,则结果为-1,最终结果存放到vAA中
cmp-long vAA,vBB,vCC;比较两个长整型数,vBB>vCC,则结果为1,vBB==vCC,则结果为0,vBB<vCC,则结果为-1,最终结果存放到vAA中
2.11 字段操作指令
助记符:iinstaceop、sstaticop
指令:字段操作指令用来对对象实例的字段进入读写操作,字段的类型可以是Java中任何有效的数据类型。
1 普通字段指令
iget、iget-wide、iget-object等
iinstanceop vA,vB,field@CCCCC
2 静态字段指令
sget、sget-wide、sget-object等
`sstaicop vA,vB,field@CCCCC `
2.11 方法调用指令
助记符:invoke
指令:调用实例的方法。
invoke-virtual;调用实例的虚方法
invoke-super;调用实例的父类方法
invoke-direct;调用实例的直接方法
invoke-static;调用实例的静态方法
invoke-interface;调用实例的接口方法
以下方式和上面的写法并无差别,只是后者在设置参数寄存器时使用了range来指定寄存器的范围。
invoke-virtual/range;调用实例的虚方法
invoke-super/range;调用实例的父类方法
invoke-direct/range;调用实例的直接方法
invoke-static/range;调用实例的静态方法
invoke-interface/range;调用实例的接口方法
方法调用指令的放回值需要用“move-result”指令来获取:
invoke-static {}, Landroid/os/Parcel:->obtain()Landroid/Parcel;
move-result-object v0
2.12 数据转换指令
助记符:unop
指令:用于将一种类型的数据转换为另一种数据类型。
neg-int;整数求补
not-int;整数求反
long-to-int;长整型数转换为整型数
2.13 数据运算指令
助记符:add、sub、mul、div、rem、and、or、xor、shl、shr、ushr
指令:算术运算:加、减、乘、除、模、移位。逻辑运算:与、或、非、抑或。
add等操作符后还可以添加后缀2addr、lit16和lit8等后缀
add-type vAA,vBB,vCC;vBB + vCC将结果保存到vAA
sub-type vAA,vBB,vCC;vBB - vCC将结果保存到vAA
mul-type vAA,vBB,vCC;vBB x vCC将结果保存到vAA
div-type vAA,vBB,vCC;vBB / vCC将结果保存到vAA
rem-type vAA,vBB,vCC;vBB % vCC将结果保存到vAA
and-type vAA,vBB,vCC;vBB AND vCC将结果保存到vAA
or-type vAA,vBB,vCC;vBB OR vCC将结果保存到vAA
xor-type vAA,vBB,vCC;vBB XOR vCC将结果保存到vAA
shl-type vAA,vBB,vCC;vBB << vCC将结果保存到vAA
shr-type vAA,vBB,vCC;vBB >> vCC将结果保存到vAA
ushr-type vAA,vBB,vCC;vBB >> vCC将结果保存到vAA
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。