前言
Dalvik指令语法详解
该篇文章为本人的学习笔记,如有不对之处,请指教.
附参考链接:smali文件语法参考
类型
字节码类型描述符
| 语法 | 含义 |
|---|---|
V |
void,只用于返回值类型 |
Z |
boolean |
B |
byte |
S |
short |
C |
char |
I |
int |
J |
long |
F |
float |
D |
double |
L |
java类类型 |
[ |
数组类型 |
其中L类型可以表示Java类型中的任何类.
例如
java.lang.String
在smali语法中表示为:
Ljava.lang.String;
注意后面有个分号,L类型最后的分号表示对象名结束.
[类型可以表示所有基本类型的数组. [后面紧跟基本数据类型描述符. 如[I 相当于Java中的int[],即一维数组. [[I相当于Java中的int[][],即二维数组.
三维、四维等等数值以此类推. 注意多维数组的维数最大为255个.
L 与 [ 可以同时使用用来表示对象数组. 如[Ljava.lang.String;就表示这是一个String类型的数组.
方法及字段
方法的表现格式如下
Lpackage/name/ObjectName;->MethodName(III)Z
其中 Lpackage/name/ObjectName;应该理解为该方法所在的类,MethodName为具体方法名,(III)Z 这是方法具体的传参和返回部分,其中括号内的III为方法参数(在这里是表示三个int类型的参数),Z表示方法多维返回值(在这里返回值为boolean类型).
字段的格式和方法很像,只是方法的括号、括号里面的参数及返回值,这些字段都是没有的,后面取而代之的是字段自己的类型.字段格式如下
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
其中Lpackage/name/ObjectName;不用说还是该字段所在的类,FieldName为字段名,Ljava/lang/String;为字段类型.其中字段名与字段类型之间用冒号:隔开.
Dalvik指令
首先咱们来解析一条指令
move-wide/from16 vAA,vBBBB
move为基础字节码,即操作符 . wide为名称后缀,标识操作的数组为64位. from16位字节码的后缀,标识源操作数是一个16位寄存器引用变量. vAA为目的寄存器,他始终在源寄存器的前面.
vBBBB为源寄存器. 若没有wide后缀,默认为32位.
move指令
move 指令的作用是将源寄存器的值赋值给目的寄存器,即
move vA,vB
move-wide作用同上,只是赋值的为64位.</br> move-object是为对象赋值.
move-result 指令的作用是将上一个invoke类型指令的操作结果赋值给目的寄存器,即
move-result vAA
move-result-wide作用同上,只是赋值的为64位. </br> move-object同上,只是赋值为对象类型.
返回指令
return-void表示函数从一个void方法返回.
return 表示函数返回一个32位非对象的值.
return-wide 表示函数返回一个64位非对象的值.
return-object 表示函数返回一个对象类型.
数据定义
const常用来定义程序中用到是常量、字符串、类等数据.</br> const 、const/4、const/16给寄存器赋值基本数据类型.即
const/4 v1, 0x2
当const-string给寄存器赋字符串,即
const-string v0, "\u60a8\u7684\u8bd5"
</br>const-class给寄存器赋值一个类引用.
锁指令
锁指令用于在多线程程序中对同一对象的操作.
monitor-enter v0
为指定的对象获取锁.
monitor-exit v0
释放指定对象的锁.
实例操作指令
- 类型转换指令
check-cast v0,type@BBBB
将v0寄存器转换成指定的类型.
- 检查指令
instance-of v0,v1,type@BBBB
检测v1是否可以转换成指定类型,可以转换v0赋值为1,否则赋值 0.
- 创建指令
new-instance v0,type@BBBB
构造一个指定类型的实例,并把实例对象的引用赋值给v0.类型符 type指定类型不能为数组.
数组操作指令
- 创建数组
new-array v0,v1,type@BBBB
构造指定类型的数组,v1表示数组的大小,并将数组赋值给v0.
filed-new-array {v1,v2,v3},type@BBBB
构造数组的另一种方式,即相当于Java中的
int[] arrays= {1,2,3,4};
- 获取数组长度
array-length v0,v1
获取v1寄存器中的数组长度,并赋值给v0寄存器.
跳转指令
- goto指令
goto +AA
无条件跳转到指定偏移量处,偏移量不能为0.
- switch指令
packed-switch v0,+BBBB
分支跳转,v0寄存器为switch分支中的判断值,+BBBB指向的是packed-switch-payload格式的偏移表,表中的值是有规律的.
sparse-switch v0,+BBBB
作用同上,唯一不同是偏移表中的值是无规律的.
- if指令
if指令格式如下
if-eq(此处可替换) v0,v1,+BBBB
比较两个寄存器的值,符合条件进行跳转.
| 操作符 | 作用 | 对应java语句 |
|---|---|---|
if-eq |
如果v0等于v1则跳转. |
if(v0==v1) |
if-ne |
如果v0不等于v1则跳转. |
if(v0!=v1) |
if-lt |
如果v0小于v1则跳转. |
if(v0<v1) |
if-gt |
如果v0大于v1则跳转. |
if(v0>v1) |
if-le |
如果v0小于等于v1则跳转. |
if(v0<=v1) |
if-ge |
如果v0大于等于v1则跳转. |
if(v0>=v1) |
if-eq(此处可替换) v0,+BBBB
用寄存器中的值和0进行比较,符合跳转跳转.
| 操作符 | 作用 | 对应java语句 |
|---|---|---|
if-eqz |
如果v0等于0则跳转. |
if(v0==0) |
if-nez |
如果v0不等于0则跳转. |
if(v0!=0) |
if-ltz |
如果v0小于0则跳转. |
if(v0<0) |
if-gtz |
如果v0大于0则跳转. |
if(v0>0) |
if-lez |
如果v0小于等于0则跳转. |
if(v0<=0) |
if-gez |
如果v0大于等于0则跳转. |
if(v0>=0) |
比较指令
用于比较两个寄存器的值(浮点型或长整型),比较结果放到v0寄存器中.
格式
cmpl-float(此处可替换) v0,v1,v2
| 操作符 | 作用 |
|---|---|
cmpl-float |
如果v1小于v2则结果为1,相等则结果为0,大于则结果为-1. |
cmpg-float |
如果v1大于v2则结果为1,相等则结果为0,小于则结果为-1. |
cmpl-double |
如果v1小于v2则结果为1,相等则结果为0,大于则结果为-1. |
cmpg-double |
如果v1大于v2则结果为1,相等则结果为0,小于则结果为-1. |
cmp-long |
如果v1大于v2则结果为1,相等则结果为0,小于则结果为-1. |
字段操作指令
字段操作指令分两大类:普通字段和静态字段,普通字段指令的前缀为i,静态字段指令的前缀为s.
字段的读操作指令为get,写操作指令为put,因此普通字段的操作指令为iget,iput.静态字段的操作指令为sget,sput.
指令格式如下
.line 16
iput-object p1, p0, Lcom/view/dialogapplication/PhoneInfo;->context:Landroid/content/Context;
上面是一段iput指令代码,它所对应的java代码如下
this.context = context;
没错,它就会一个简单的赋值context的代码;
由此,可以看出来, p1是要赋值的context,p0是源,而后面的第三个参数
Lcom/view/dialogapplication/PhoneInfo;->context:Landroid/content/Context;
可以看出来是p1的字段名.
此外还有一组以a为前缀的的操作指令,分别为aput和aget,不过它们应该不算在字段的范畴了,应该为数组操作范畴,但因为也是和读写操作有关,所以就写在这里了,具体格式如下
aput-object v2,v1,v0
其具体作用为将v2的值放入到v1数组的v0位置处.所以可以看出,v2为要放入的值,v1代表着存放v2值的数组,而v0则是v2要存放在数组的位置,即v0为index(数组角标).
方法调用指令
方法调用指令赋值调用类实例(也就是对象)的方法,它的基础指令为invoke.指令格式如下
invoke-virtual(名称后缀可替换) {v0,v1},method@BBBB(具体的方法)
其中{v0,v1}大括号中第一位放的是调用方法的对象,之后的为方法中的参数.若没有参数则只需传入调用方法的对象,即{v0}.
| 指令 | 作用 |
|---|---|
invoke-virtual或invoke-virtual/range
|
调用实例的虚方法. |
invoke-super或invoke-super/range
|
调用实例父类的方法. |
invoke-direct或invoke-direct/range
|
调用实例的直接方法. |
invoke-static或invoke-static/range
|
调用实例的静态方法. |
invoke-interface或invoke-interface/range
|
调用实例的接口方法. |
数字转换指令
数据转换指令用于将一种类型的数值转换成另一种类型.格式如下
neg-int(可替换如下) v0,v1
指令中,v1存放需要转换的数据,v0存放转换后的结果.
| 指令 | 作用 |
|---|---|
neg-int |
对整型输求补. |
not-int |
对整型输求反. |
neg-long |
对长整型数求补. |
not-long |
对长整型数求反. |
neg-float |
对单精度浮点型数求补. |
neg-double |
对双精度浮点数求补. |
int-to-long |
将整型数转换为长整型. |
int-to-float |
将整型数转换为单精度浮点型. |
int-to-double |
将整型数转换为双精度浮点型. |
long-to-int |
将长整型数转换位整型. |
long-to-float |
将长整型数转换为单精度浮点型. |
long-to-double |
将长整型数转换为双精度浮点型. |
float-to-int |
将单精度浮点转换为整型. |
float-to-long |
将单精度浮点型转换为长整型. |
float-to-double |
将单精度浮点型转换为双精度浮点型. |
double-to-int |
将双精度浮点型转换为整型. |
double-to-long |
将双精度浮点型转换为长整型. |
double-to-float |
将双精度浮点型转换为单精度浮点型. |
int-to-byte |
将整型转换为字节型. |
int-to-char |
将整型转换为字符串. |
int-to-short |
将整型转换为短整型. |
数据运算指令
数据运算指令分为算术运算指令和逻辑运算指令,即 加、减、乘、除、取模、位移及与、或、非、异或等.
格式如下
add-int(可替换如下) v0,v1,v2
指令中,将v1和v2进行运算,结果存到v0.
| 指令 | 作用 |
|---|---|
add-type |
将v1和v2进行加法运算,即v1+v2. |
sub-type |
将v1和v2进行减法运算,即v1-v2. |
mul-type |
将v1和v2进行乘法运算,即v1*v2. |
div-type |
将v1和v2进行除法运算,即v1/v2. |
rem-type |
将v1和v2进行取模运算,即v1%v2. |
and-type |
将v1和v2进行与运算,即v1 AND v2. |
or-type |
将v1和v2进行或运算,即v1 OR v2. |
xor-type |
将v1和v2进行异或运算,即v1 XOR v2. |
shl-type |
将v1进行(有符号位)左移v2位,即v1<<v2. |
shr-type |
将v1进行(有符号位)右移v2位,即v1>>v2. |
ushr-type |
将v1进行(无符号位)右移v2位,即v1>>v2. |
其中后面的-type可以是-int、-long、-float、-double.
至此,Dalvik指令集基本就都介绍完了