smali的数据类型
smali数据类型 | 数据类型 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
V | void |
Z | boolean |
[XXX | array |
Lxxx/yyy | object |
这里解析下最后两项,数组的表示方式是:在基本类型前加上前中括号“[”,例如int数组和float数组分别表示为:[I、[F;对象的表示则以L作为开头,格式是LpackageName/objectName;(注意必须有个分号跟在最后),例如String对象在smali中为:Ljava/lang/String;,其中java/lang对应java.lang包,String就是定义在该包中的一个对象。
内部类:LpackageName/objectNamesubObjectName;。也就是在内部类前加“”符号。
函数的定义
Func-Name (Para-Type1Para-Type2Para-Type3…)Return-Type
注意参数与参数之间没有任何分隔符,同样举几个例子就容易明白了:
1. foo ()V
void foo()。
2. foo (III)Z
boolean foo(int, int, int)。
3. foo (Z[I[ILjava/lang/String;J)Ljava/lang/String;
String foo (boolean, int[], int[], String, long)
4. foo ([Ljava/lang/String)Ljava/lang/String;
String foo (String [])
5. foo(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
String method(int, int[][], int, String, Object[])
寄存器
对于一个使用m个寄存器(m=局部变量寄存器个数l+参数寄存器个数n)的方法而言,局部寄存器使用从v0开始的l个寄存器,而参数寄存器则使用最后的n个寄存器.举个例子说明假设实例方法test(String a,String b)一共使用了5个寄存器:0,1,2,3,4,那么参数寄存器是能使用2,3,4这三个寄存器,如图
寄存器的命名
寄存器有两种不同的命名方法:v字命名法和p字命 名法.这两种命名法仅仅是影响了字节码的可读性.
v字命名法
以小写字母v开头的方式表示方法中使用的局部变量和参数.
对于上面实例方法test(String a,String b)来说,v0,v1为局部变量能够使用的寄存器,v2,v3,v4为参数能够使用的寄存器:
p字命名法
以小写字母p开头的方式表示参数,参数名称从p0开始,依次增大.局部变量能够使用的寄存器仍然是以v开头.
总之不管是P还是V命名法,参数在后,局部变量在前。
指令:
数据定义指令
数据定义指令用于定义代码中使用的常量,类等数据,基础指令是const
指令 | 描述 |
---|---|
const/4 vA,#+B | 将数值符号扩展为32后赋值给寄存器vA |
const-wide/16 vAA,#+BBBB | 将数值符号扩展为64位后赋值个寄存器对vAA |
const-string vAA,string@BBBB | 通过字符串索引高走字符串赋值给寄存器vAA |
const-class vAA,type@BBBB | 通过类型索引获取一个类的引用赋值给寄存器vAA |
数据操作指令
move指令用于数据操作,其表示move destination,source,即数据数据从source寄存器(源寄存器)移动到destionation寄存器(源寄存器),可以理解java中变量间的赋值操作.根据字节码和类型的不同,move指令后会跟上不同的后缀.
指令 | 描述 |
---|---|
move vA,vB | 将vB寄存器的值赋值给vA寄存器,vA和vB寄存器都是4位 |
move/from16 vAA,VBBBB | 将vBBBB寄存器(16位)的值赋值给vAA寄存器(7位),from16表示源寄存器vBBBB是16位的 |
move/16 vAAAA,vBBBB | 将寄存器vBBBB的值赋值给vAAAA寄存器,16表示源寄存器vBBBB和目标寄存器vAAAA都是16位 |
move-object vA,vB | 将vB寄存器中的对象引用赋值给vA寄存器,vA寄存器和vB寄存器都是4位 |
move-result vAA | 将上一个invoke指令(方法调用)操作的单字(32位)非对象结果赋值给vAA寄存器 |
move-result-wide vAA | 将上一个invoke指令操作的双字(64位)非对象结果赋值给vAA寄存器 |
mvoe-result-object vAA | 将上一个invoke指令操作的对象结果赋值给vAA寄存器 |
move-exception vAA | 保存上一个运行时发生的异常到vAA寄存器 |
对象操作指令
与对象实例相关的操作,比如对象创建,对象检查等.
指令 | 描述 |
---|---|
new-instance vAA,type@BBBB | 构造一个指定类型的对象将器引用赋值给vAA寄存器.此处不包含数组对象 |
instance-of vA,vB,type@CCCC | 判断vB寄存器中对象的引用是否是指定类型,如果是,将v1赋值为1,否则赋值为0 |
check-cast vAA,type@BBBB | 将vAA寄存器中对象的引用转成指定类型,成功则将结果赋值给vAA,否则抛出ClassCastException异常. |
数组操作指令
在实例操作指令中我们并没有发现创建对象的指令.Davilk中设置专门的指令用于数组操作.
指令 | 描述 |
---|---|
new-array vA,vB,type@CCCC | 创建指定类型与指定大小(vB寄存器指定)的数组,并将其赋值给vA寄存器 |
fill-array-data vAA,+BBBBBBBB | 用指定的数据填充数组,vAA代表数组的引用(数组的第一个元素的地址) |
数据运算指令
数据运算主要包括两种:算数运算和逻辑运算.
算术运算指令
指令 | 描述 |
---|---|
add-type | 加法指令 |
sub-type | 减法指令 |
mul-type | 乘法指令 |
div-type | 除法指令 |
rem-type | 求 |
逻辑元算指令
指令 | 描述 |
---|---|
and-type | 与运算指令 |
or-type | 或运算指令 |
xor-type | 异或元算指令 |
位移指令
指令 | 描述 |
---|---|
shl-type | 有符号左移指令 |
shr-type | 有符号右移指令 |
ushr-type | 无符号右移指令 |
上面的-type表示操作的寄存器中数据的类型,可以是-int,-float,-long,-double等.
比较指令
比较指令用于比较两个寄存器中值的大小,其基本格式格式是cmp+kind-type vAA,vBB,vCC,type表示比较数据的类型,如-long,-float等;kind则代表操作类型,因此有cmpl,cmpg,cmp三种比较指令.coml是compare less的缩写,cmpg是compare greater的缩写,因此cmpl表示vBB小于vCC中的值这个条件是否成立,是则返回1,否则返回-1,相等返回0;cmpg表示vBB大于vCC中的值这个条件是否成立,是则返回1,否则返回-1,相等返回0.
cmp和cmpg的语意一致,即表示vBB大于vCC寄存器中的值是否成立,成立则返回1,否则返回-1,相等返回0
来具体看看Davilk中的指令:
指令 | 描述 |
---|---|
cmpl-float vAA,vBB,vCC | 比较两个单精度的浮点数.如果vBB寄存器中的值大于vCC寄存器的值,则返回-1到vAA中,相等则返回0,小于返回1 |
cmpg-float vAA,vBB,vCC | 比较两个单精度的浮点数,如果vBB寄存器中的值大于vCC的值,则返回1,相等返回0,小于返回-1 |
cmpl-double vAA,vBB,vCC | 比较两个双精度浮点数,如果vBB寄存器中的值大于vCC的值,则返回-1,相等返回0,小于则返回1 |
cmpg-double vAA,vBB,vCC | 比较双精度浮点数,和cmpl-float的语意一致 |
cmp-double vAA,vBB,vCC | 等价与cmpg-double vAA,vBB,vCC指令 |
字段操作指令
字段操作指令表示对对象字段进行设值和取值操作,就像是你在代码中长些的set和get方法.基本指令是iput-type,iget-type,sput-type,sget-type.type表示数据类型普通字段读写操作
前缀是i的iput-type和iget-type指令用于字段的读写操作.
指令 | 描述 |
---|---|
iget-byte vX,vY,filed_id | 读取vY寄存器中的对象中的filed_id字段值赋值给vX寄存器 |
iput-byte vX,vY,filed_id | 设置vY寄存器中的对象中filed_id字段的值为vX寄存器的值 |
iget-boolean vX,vY,filed_id | |
iput-boolean vX,vY,filed_id | |
iget-long vX,vY,filed_id | |
iput-long vX,vY,filed_id |
静态字段读写操作
前缀是s的sput-type和sget-type指令用于静态字段的读写操作
指令 | 描述 |
---|---|
sget-byte vX,vY,filed_id | |
sput-byte vX,vY,filed_id | |
sget-boolean vX,vY,filed_id | |
sput-boolean vX,vY,filed_id | |
sget-long vX,vY,filed_id | |
sput-long vX,vY,filed_id |
方法调用指令
Davilk中的方法指令和JVM的中指令大部分非常类似.目前共有五条指令集:
指令 | 描述 |
---|---|
invoke-direct{parameters},methodtocall | 调用实例的直接方法,即private修饰的方法.此时需要注意{}中的第一个元素代表的是当前实例对象,即this,后面接下来的才是真正的参数.比如指令invoke-virtual {v3,v1,v4},Test2.method5:(II)V中,v3表示Test2当前实例对象,而v1,v4才是方法参数 |
invoke-static{parameters},methodtocall | 调用实例的静态方法,此时{}中的都是方法参数 |
invoke-super{parameters},methodtocall | 调用父类方法 |
invoke-virtual{parameters},methodtocall | 调用实例的虚方法,即public和protected修饰修饰的方法 |
invoke-interface{parameters},methodtocall | 调用接口方法 |
这五种指令是基本指令,除此之外,你也会遇到invoke-direct/range,invoke-static/range,invoke-super/range,invoke-virtual/range,invoke-interface/range指令,该类型指令和以上指令唯一的区别就是后者可以设置方法参数可以使用的寄存器的范围,在参数多于四个时候使用.
再此强调一遍对于非静态方法而言{}的结构是{当前实例对象,参数1,参数2,…参数n},而对于静态方法而言则是{参数1,参数2,…参数n}
需要注意,如果要获取方法执行有返回值,需要通过上面说道的move-result指令获取执行结果.
方法返回指令
在java中,很多情况下我们需要通过Return返回方法的执行结果,在Davilk中同样提供的return指令来返回运行结果:
指令 | 描述 |
---|---|
return-void | 什么也不返回 |
return vAA | 返回一个32位非对象类型的值 |
return-wide vAA | 返回一个64位非对象类型的值 |
return-object vAA | 反会一个对象类型的引用 |
同步指令
同步一段指令序列通常是由java中的synchronized语句块表示,则JVM中是通过monitorenter和monitorexit的指令来支持synchronized关键字的语义的,而在Davilk中同样提供了两条类似的指令来支持synchronized语义:
指令 | 描述 |
---|---|
monitor-enter vAA | 为指定对象获取锁操作 |
monitor-exit vAA | 为指定对象释放锁操作 |
异常指令
很久以前,VM也是用过jsr和ret指令来实现异常的,但是现在的JVM中已经抛出原先的做法,转而采用异常表来实现异常.而Davilk仍然使用指令来实现:
指令 | 描述 |
---|---|
throw vAA | 抛出vAA寄存器中指定类型的异常 |
跳转指令
跳转指令用于从当前地址条状到指定的偏移处,在if,switch分支中使用的居多.Davilk中提供了goto,packed-switch,if-test指令用于实现跳转操作
指令 | 描述 |
---|---|
goto +AA | 无条件跳转到指定偏移处(AA即偏移量) |
packed-switch vAA,+BBBBBBBB | 分支跳转指令.vAA寄存器中的值是switch分支中需要判断的,BBBBBBBB则是偏移表(packed-switch-payload)中的索引值, |
spare-switch vAA,+BBBBBBBB | 分支跳转指令,和packed-switch类似,只不过BBBBBBBB偏移表(spare-switch-payload)中的索引值 |
if-test vA,vB,+CCCC | 条件跳转指令,用于比较vA和vB寄存器中的值,如果条件满足则跳转到指定偏移处(CCCC即偏移量),test代表比较规则,可以是eq.lt等. |
在条件比较中,if-test中的test表示比较规则.该指令用的非常多,因此我们简单的坐下说明:
指令 | 描述 |
---|---|
if-eq vA,vB,target | vA,vB寄存器中的相等,等价于java中的if(a==b),比如if-eq v3,v10,002c表示如果条件成立,则跳转到current position+002c处.其余的类似 |
if-ne vA,vB,target | 等价与java中的if(a!=b) |
if-lt vA,vB,target | vA寄存器中的值小于vB,等价于if(a>=b) |
if-gt vA,vB,target | 等价于java中的if(a>b) |
if-ge vA,vB,target | 等价于java中的if(a>=b) |
if-le vA,vB,target | 等价于java中的if(a<=b) |
除了以上指令之外,Davilk还提供可一个零值条件指令,该指令用于和0比较,可以理解为将上面指令中的vB寄存器的值固定为0.
指令 | 描述 |
---|---|
if-eqz vAA,target | 等价于java中的if(a==0)或者if(!a) |
if-nez vAA,target | 等价于java中的if(a!=0)或者if(a) |
if-ltz vAA,target | 等价于java中的if(a<0) |
if-gtz vAA,target | 等价于java中的if(a>0) |
if-lez vAA,target | 等价于java中的if(a<=0) |
if-gtz vAA,target | 等价于java中的if(a>=0) |
上面我们说道两张偏移表packed-switch-payload和spare-switch-payload,两者唯一的区别就是表中的值是否有序,后面我们会在下文中进行详细的说明.
数据转换指令
数据类型转换对任何java开发者都是非常熟悉的,用于实现两种不同数据类型的相互转换.其基本指令格式是:unop vA,vB,表示对vB寄存器的中值进行操作,并将结果保存在vA寄存器中.
指令 | 描述 |
---|---|
int-to-long | 整形转为长整型 |
float-to-int | 单精度浮点型转为整形 |
int-to-byte | 整形转为字节类型 |
neg-int | 求补指令,对整数求补 |
not-int | 求反指令,对整数求反 |
结合下表的指令大全:
Opcode (hex) | Opcode name | Explanation | Example |
00 | nop | No operation | 0000 - nop |
01 | move vx,vy | Moves the content of vy into vx. Both registers must be in the first 256 register range. | 0110 - move v0, v1 Moves v1 into v0. |
02 | move/from16 vx,vy | Moves the content of vy into vx. vy may be in the 64k register range while vx is one of the first 256 registers. | 0200 1900 - move/from16 v0, v25 Moves v25 into v0. |
03 | move/16 | ||
04 | move-wide | ||
05 | move-wide/from16 vx,vy | Moves a long/double value from vy to vx. vy may be in the 64k register range while wx is one of the first 256 registers. | 0516 0000 - move-wide/from16 v22, v0 Moves v0 into v22. |
06 | move-wide/16 | ||
07 | move-object vx,vy | Moves the object reference from vy to vx. | 0781 - move-object v1, v8 Moves the object reference in v8 to v1. |
08 | move-object/from16 vx,vy | Moves the object reference from vy to vx, vy can address 64k registers and vx can address 256 registers. | 0801 1500 - move-object/from16 v1, v21 Move the object reference in v21 to v1. |
09 | move-object/16 | ||
0A | move-result vx | Move the result value of the previous method invocation into vx. | 0A00 - move-result v0 Move the return value of a previous method invocation into v0. |
0B | move-result-wide vx | Move the long/double result value of the previous method invocation into vx,vx+1. | 0B02 - move-result-wide v2 Move the long/double result value of the previous method invocation into v2,v3. |
0C | move-result-object vx | Move the result object reference of the previous method invocation into vx. | 0C00 - move-result-object v0 |
0D | move-exception vx | Move the exception object reference thrown during a method invocation into vx. | 0D19 - move-exception v25 |
0E | return-void | Return without a return value | 0E00 - return-void |
0F | return vx | Return with vx return value | 0F00 - return v0 Returns with return value in v0. |
10 | return-wide vx | Return with double/long result in vx,vx+1. | 1000 - return-wide v0 Returns with a double/long value in v0,v1. |
11 | return-object vx | Return with vx object reference value. | 1100 - return-object v0 Returns with object reference value in v0 |
12 | const/4 vx,lit4 | Puts the 4 bit constant into vx | 1221 - const/4 v1, #int2 Moves literal 2 into v1. The destination register is in the lower 4 bit in the second byte, the literal 2 is in the higher 4 bit. |
13 | const/16 vx,lit16 | Puts the 16 bit constant into vx | 1300 0A00 - const/16 v0, #int 10 Puts the literal constant of 10 into v0. |
14 |