字段操作指令
字段操作指令主要是对实例的字段进行读写操作。其中读操作使用 get 来标记,即 vx=vy.field。写操作使用 put 来标记,即 vy.field=vx。
其中对于 java 中的类来说,主要分为两种字段,普通字段,静态字段。对于普通字段采用操作指令前加 i 来标记,如 iget,iput。对于静态字段采用在操作指令前加 s 来标记,如 sput,sget。
此外,对于不同字段大小的操作会在指令的后面加上后缀来进行区别。如 iget-byte 指令表示读取类型为字节的实例字段的值,iput-short 指令表示设置的实例字段的类型为短整型。
普通字段操作指令有:
iget,iget-wide,iget-object,iget-boolean,iget-byte,iget-char,iget-short,
iput,iput-wide,iput-object,iput-boolean,iput-byte,iput-char,iput-short。
静态字段操作指令有:
sget,sget-wide,sget-object,sget-boolean,sget-byte,sget-char,sget-short,
sput,sput-wide,sput-object,sput-boolean,sput-byte,sput-char,sput-short。
如果我们编写如下代码
int[] arr = new int[2];
int b = arr[0];
arr[1] = b;
其对应的 smali 如下
const/4 v0, 0x2
new-array v1, v0, I
const/4 v0, 0x0
aget-int v2, v1, v0
const/4 v0, 0x1
aput-int v2, v1, v0
如果我们想获得类 com.example.test 的静态 int 类型的字段 staticField,其 smali 如下
sget v0, Lcom/example/Test;->staticField:I
比较指令
比较指令实现了对两个寄存器的值(浮点型或长整型)进行比较的操作。
其格式为 cmp(l/g)-kind vAA, vBB, vCC,其中 vBB 寄存器与 vCC 寄存器是需要比较的两个寄存器或寄存器对,比较的结果放到 vAA 寄存器。
l→less
g→ great
目前的比较指令如下
指令 说明
cmpl-float 比较两个单精度浮点数。如果 vBB 寄存器大于 vCC 寄存器,结果为 - 1,相等则 结果为 0,小于的话结果为 1
cmpg-float 比较两个单精度浮点数。如果 vBB 寄存器大于 vCC 寄存器,则结果为 1,相等则结果为 0,小于的话结果为 - 1
cmpl-double 比较两个双精度浮点数。如果 vBB 寄存器对大于 vCC 寄存器对,则结果为 - 1,相等则结果为 0,小于则结果为 1
cmpg-double 比较两个双精度浮点数。如果 vBB 寄存器对大于 vCC 寄存器对,则结果为 1,相等则结果为 0,小于的话,则结果为 - 1
cmp-long 比较两个长整型数。如果 vBB 寄存器大于 vCC 寄存器,则结果为 1,相等则结果为 0,小则结果为 - 1
跳转指令
跳转指令实现了从当前地址跳转到指定的偏移处的操作。Dalvik 指令集中有三种跳转指令
goto,无条件跳转
switch,分支跳转
if,条件跳转
GOTO 指令
如下
指令 含义
goto +AA 无条件跳转到指定偏移处,偏移量 AA 不能为 0
goto/16 +AAAA 无条件跳转到指定偏移处,偏移量 AAAA 不能为 0
goto/32 +AAAAAAAA 无条件跳转到指定偏移处
IF 指令
if 指令中主要分为两种 if-test 与 if-testz。if-test vA,vB,+CCCC 会比较 vA 与 v,如果比较结果满足就跳转到 CCCC 指定的偏移处(相对当前偏移),偏移量 CCCC 不能为 0。if-test 类型的指令如下:
指令 说明
if-eq vA,vB,target 如果 vA=vB,跳转。
if-ne vA,vB,target 如果 vA!=vB,跳转。
if-lt vA,vB,target 如果 vA<vB,跳转。
if-gt vA,vB,target 如果 vA>vB,跳转。
if-ge vA,vB,target 如果 vA>=vB,跳转。
if-le vA,vB,target 如果 vA<=vB,跳转。
if-testz 类型的指令如下
指令 说明
if-eqz vAA,target 如果 vA=0,跳转。
if-nez vAA,target 如果 vA!=0,跳转。
if-ltz vAA,target 如果 vA<0,跳转。
if-gtz vAA,target 如果 vA>0,跳转。
if-lez vAA,target 如果 vA<=0,跳转。
if-gtz vAA,target 如果 vA>=0,跳转。
举个例子,java 代码如下
int a = 10
if(a > 0)
a = 1;
else
a = 0;
smali 代码如下
const/4 v0, 0xa
if-lez v0, :cond_0 # if 块开始
const/4 v0, 0x1
goto :cond_1 # if 块结束
:cond_0 # else 块开始
const/4 v0, 0x0
:cond_1 # else 块结束
在只有 if 的情况下
int a = 10;
if(a > 0)
a = 1;
smali 代码如下
const/4 v0, 0xa
if-lez v0, :cond_0 # if 块开始
const/4 v0, 0x1
:cond_0 # if 块结束
SWITCH 指令
如下
指令 含义
packed-switch vAA,+BBBBBBBB vAA 寄存器为 switch 分支中需要判断的值,BBBBBBBB 指向一个 packed-switch-payload 格式的偏移表,表中的值是有规律递增的。
sparse-switch vAA,+BBBBBBBB vAA 寄存器为 switch 分支中需要判断的值,BBBBBBBB 指向一个 sparse-switch-payload 格式的偏移表,表中的值是无规律的偏移表,表中的值是无规律的偏移量。
对于第一种递增式的 switch,如下
int a = 10;
switch (a){
case 0:
a = 1;
break;
case 1:
a = 5;
break;
case 2:
a = 10;
break;
case 3:
a = 20;
break;
}
对应的 smali 如下
const/16 v0, 0xa
packed-switch v0, :pswitch_data_0 # switch 开始
:pswitch_0 # case 0
const/4 v0, 0x1
goto :goto_0
:pswitch_1 # case 1
const/4 v0, 0x5
goto :goto_0
:pswitch_2 # case 2
const/16 v0, 0xa
goto :goto_0
:pswitch_3 # case 3
const/16 v0, 0x14
goto :goto_0
:goto_0 # switch 结束
return-void
:pswitch_data_0 # 跳转表开始
.packed-switch 0x0 # 从 0 开始
:pswitch_0
:pswitch_1
:pswitch_2
:pswitch_3
.end packed-switch # 跳转表结束
对于非递增的 switch,代码如下
int a = 10;
switch (a){
case 0:
a = 1;
break;
case 10:
a = 5;
break;
case 20:
a = 10;
break;
case 30:
a = 20;
break;
}
对应的 smali 如下
const/16 v0, 0xa
sparse-switch v0, :sswitch_data_0 # switch 开始
:sswitch_0 # case 0
const/4 v0, 0x1
goto :goto_0
:sswitch_1 # case 10
const/4 v0, 0x5
goto :goto_0
:sswitch_2 # case 20
const/16 v0, 0xa
goto :goto_0
:sswitch_3 # case 15
const/16 v0, 0x14
goto :goto_0
:goto_0 # switch 结束
return-void
.line 55
:sswitch_data_0 # 跳转表开始
.sparse-switch
0x0 -> :sswitch_0
0xa -> :sswitch_1
0x14 -> :sswitch_2
0x1e -> :sswitch_3
.end sparse-switch # 跳转表结束
锁指令
锁指令用于在多线程程序。包含以下两个指令
指令 说明
monitor-enter vAA 为指定的对象获取锁
monitor-exit vAA 释放指定的对象的锁
方法调用指令
方法调用指令实现了调用实例的方法的操作。其基础为 invoke,在其基础上会根据调用方法的类别不同,如虚方法,父类方法等添加后缀,最后会选择性地使用 range 来指定寄存器范围。一般来说会分为两类
invoke-kind {vC, vD, vE, vF, vG},meth@BBBB
invoke-kind/range {vCCCC .. vNNNN},meth@BBBB 两类
总体来说,一般有如下指令
指令 说明
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 调用实例的接口方法
Dalvik 中直接方法是指类的所有实例构造器和private实例方法,对于protected或者public方法都叫做虚方法。
异常指令
利用 throw vAA 指令抛出 vAA 寄存器中指定类型的异常。
TRY CATCH
首先,我们来看一下 try catch,如下
int a = 10;
try {
callSomeMethod();
} catch (Exception e) {
a = 0;
}
callAnotherMethod();
对应的 smali 如下
const/16 v0, 0xa
:try_start_0 # try 块开始
invoke-direct {p0}, Lnet/flygon/myapplication/SubActivity;->callSomeMethod()V
:try_end_0 # try 块结束
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
:goto_0
invoke-direct {p0}, Lnet/flygon/myapplication/SubActivity;->callAnotherMethod()V
return-void
:catch_0 # catch 块开始
move-exception v1
const/4 v0, 0x0
goto :goto_0 # catch 块结束
可以看到,:try_start_0和:try_end_0之间如果存在异常,则会向下寻找.catch(或者.catch-all)语句,符合条件时跳到标签的位置,这里是:catch_0,结束之后会有个goto跳回去。
TRY-FINALLY
java 代码如下
int a = 10;
try {
callSomeMethod();
} finally {
a = 0;
}
callAnotherMethod();
其对应的 smali 代码如下
const/16 v0, 0xa
:try_start_0 # try 块开始
invoke-direct {p0}, Lnet/flygon/myapplication/SubActivity;->callSomeMethod()V
:try_end_0 # try 块结束
.catchall {:try_start_0 .. :try_end_0} :catchall_0
const/4 v0, 0x0 # 复制一份到外面
invoke-direct {p0}, Lnet/flygon/myapplication/SubActivity;->callAnotherMethod()V
return-void
:catchall_0 # finally 块开始
move-exception v1
const/4 v0, 0x0
throw v1 # finally 块结束
可以看出,由于finally中的逻辑无论有没有异常都会执行,所以代码里一共有两部分。
TRY-CATCH-FINALLY
当我们同时使用 catch 与 finally 时,如下
int a = 10;
try {
callSomeMethod();
} catch (Exception e) {
a = 1;
}
finally {
a = 0;
}
callAnotherMethod();
其对应的 smali 代码如下
const/16 v0, 0xa
:try_start_0 # try 块开始
invoke-direct {p0}, Lnet/flygon/myapplication/SubActivity;->callSomeMethod()V
:try_end_0 # try 块结束
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
.catchall {:try_start_0 .. :try_end_0} :catchall_0
const/4 v0, 0x0 # 复制一份到外面
:goto_0
invoke-direct {p0}, Lnet/flygon/myapplication/SubActivity;->callAnotherMethod()V
return-void
:catch_0 # catch 块开始
move-exception v1
const/4 v0, 0x1
const/4 v0, 0x0 # 复制一份到 catch 块里面
goto :goto_0 # catch 块结束
:catchall_0 # finally 块开始
move-exception v2
const/4 v0, 0x0
throw v2 # finally 块结束
返回指令
在 java 中我们会利用 Return 返回方法的执行结果。同样的,在 Davilk 中我们也需要 return 指令来返回方法运行结果。
指令 说明
return-void 什么也不返回
return vAA 返回一个 32 位非对象类型的值
return-wide vAA 返回一个 64 位非对象类型的值
return-object vAA 返回一个对象类型的引用
没有评论:
发表评论