前言
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位. move-object
是为对象赋值.
move-result
指令的作用是将上一个invoke
类型指令的操作结果赋值给目的寄存器,即
move-result vAA
move-result-wide
作用同上,只是赋值的为64位. move-object
同上,只是赋值为对象类型.
返回指令
return-void
表示函数从一个void方法返回.
return
表示函数返回一个32位非对象的值.
return-wide
表示函数返回一个64位非对象的值.
return-object
表示函数返回一个对象类型.
数据定义
const
常用来定义程序中用到是常量、字符串、类等数据. const 、const/4、const/16
给寄存器赋值基本数据类型.即
const/4 v1, 0x2
当const-string
给寄存器赋字符串,即
const-string v0, "\u60a8\u7684\u8bd5"
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 |
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< |
shr-type |
将v1 进行(有符号位)右移v2 位,即v1>>v2 . |
ushr-type |
将v1 进行(无符号位)右移v2 位,即v1>>v2 . |
其中后面的-type
可以是-int、-long、-float、-double
.
至此,Dalvik指令集基本就都介绍完了