Smali操作指令

各种指令的介绍与操作,包含字段操作指令,比较指令,跳转指令,IF 指令,SWITCH 指令,锁指令,方法调用指令,异常指令和返回指令


字段操作指令

字段操作指令主要是对实例的字段进行读写操作。其中读操作使用 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 返回一个对象类型的引用



发表评论