smali语言基础
Dalvik寄存器
Dalivik寄存器都是32位大小,支持所有类型,对于小于或等于32位的类型,使用一个寄存器,对于64位类型(long和double)类型,需要两个相邻寄存器存储。
寄存器命名法
V命名法:局部变量寄存器V0-Vn,参数寄存器Vn-Vn+m
p命名法:参数寄存器P0-Pn,变量寄存器V0-Vn
Dalvik字节码
通用设计
-
栈帧的大小在创建时确定后就固定不变,每一帧由特定数量的寄存器(由相应方法指定)以及执行该方法所需的所有辅助数据构成。
-
当用于位值(例如整数和浮点数)时,寄存器会被视为宽度为 32 位。如果值是 64 位,则使用两个相邻的寄存器。
-
在 Java 或 Android 开发中,我们创建的对象会被存储在堆内存中,而我们在代码中操作的变量并不直接存储对象本身,而是存储一个 “引用”—— 这个引用就像一个 “指针”,告诉虚拟机 “这个对象在堆内存的哪个位置”。
-
在虚拟机底层,null 作为对象引用的 “空值”,其二进制存储通常被表示为全 0 位(即 32 位的 0x00000000 或 64 位的 0x0000000000000000);而整数 0 的二进制表示同样是全 0 位,null 引用和整数 0 的二进制存储完全相同。
-
如果一个方法有 N 个参数,则在该方法的调用帧的最后 N 个寄存器中按顺序传递这些参数。宽参数占用两个寄存器。向实例方法传入一个 this 引用作为其第一个参数。
在调用帧的 “最后 N 个寄存器” 中按顺序传递的原因如下
1.避免与局部变量冲突:方法内部的局部变量会使用帧中靠前的寄存器,而参数放在尾部,两者在寄存器空间上自然分隔,避免了命名或存储位置的冲突。
2.this引用:例如Java代码person.getName()等价于persion.getName(this)“让 person 这个对象自己执行 getName() 方法。在JAVA中是隐式绑定,我们不需要传入this,而在smali中需要我们指名。
- 按位字面数据在指令流中内嵌表示。
字面数据:代码中直接写出的具体值,而非通过变量、表达式等间接引用的值,是 “写死” 在代码里的常量。
内嵌表示:将字面数据嵌入到指令流中,而非是单独存放一个位置进行引用,这样做程序执行速率更快。
- 常规类型的 64 位运算码以 -wide 为后缀。
数据类型
在Smali语法中,常见的数据类型及说明如下表:
| Smali类型标识 | 对应Java数据类型 | 描述 | 示例 |
|---|---|---|---|
V |
void |
表示无返回值类型,主要用于方法声明时表示方法不返回任何结果 | .method public onCreate(Landroid/os/Bundle;)V,这里的V表示该方法没有返回值 |
Z |
boolean |
布尔类型,用于存储true或false |
const/4 v0, 0x1 ,将true(对应1)赋值给寄存器v0,v0存储的是布尔类型数据 |
B |
byte |
字节类型,占1个字节,取值范围是 -128 到 127 | const/16 v1, -0x1,将字节值 -1 赋值给寄存器v1 |
S |
short |
短整型,占2个字节,取值范围是 -32768 到 32767 | const/16 v2, 0x7fff,将短整型值 32767 赋值给寄存器v2 |
C |
char |
字符类型,占2个字节,用于存储Unicode字符 | const/16 v3, 0x61 ,将字符'a'(Unicode编码为0x61)赋值给寄存器v3 |
I |
int |
整型,占4个字节,取值范围是 -2147483648 到 2147483647 | const/32 v4, 0x10,将整型值 16 赋值给寄存器v4 |
J |
long |
长整型,占8个字节,取值范围比int更大 |
const-wide/16 v5, 0x100000000,将长整型值 4294967296 赋值给寄存器v5(这里v5和v6两个寄存器存储该长整型,因为长整型占8字节 ) |
F |
float |
单精度浮点数,占4个字节 | const/high16 v6, 0x41400000,将浮点数 3.0 赋值给寄存器v6 |
D |
double |
双精度浮点数,占8个字节 | const-wide/high16 v7, 0x4000000000000000,将双精度浮点数 2.0 赋值给寄存器v7(v7和v8两个寄存器存储 ) |
L类名; |
引用类型(类、接口等) | 表示对象引用,L开头,以;结尾,中间是类的全限定名,按照/分隔包路径 |
const-string v8, "hello",这里v8存储的是一个String类型(Ljava/lang/String;)的对象引用 |
[数据类型标识] |
数组类型 | 用于表示数组,[表示数组,后面跟数据类型标识。比如[I表示int类型数组,[Ljava/lang/String;表示String类型数组 |
new-array v9, v10, [I] ,创建一个int类型的数组,数组大小由寄存器v10决定,结果存到寄存器v9中 |
变量
.local v6, "num":I
- .local:局部变量
- v6:寄存器
- "num":I:"num" 是变量的名称,I 是类型标识
const/16 v7, 0x2ee2094f //const是常量
把一个32位的常量,加载到v7中。
const-wide/32 v4,0x2332093f //long类型默认使用v4,v5寄存器存储
跳转
if-eqz v0, :cond_67 //if-eqz默认和0进行比较,如果相等跳转到:cond_67
字段
#static fields //static是静态,fields是字段
.field public static final TAG:Ljava/lang/String;="awfhiohaiwfa" //TAG是字段名称,后面的是字段类型和初始化结果。
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
Lpackage/name/ObjectName:是当前字段所在的类,其中"L"是java类类型,"package/name/"是包名,"ObjectName"是类名。
FueldName:是字段名,"Ljava/lang/String":是字段类型。
静态字段格式
# static fields
.field <访问权限> static [修饰关键字]<字段名>:<字段类型>
实例字段格式
# instance fields
.field <访问权限> [修饰关键字] <字段名>:<字段类型>
方法
类型(包名+类名)+方法名(+参数类型)+返回值类型
Lpackage/name/ObjectName;->MethodName (III) Z"Lpackage/name/ObjectName;"
"Lpackage/name/ObjectName;"是当前这个方法所在的类,其中"L"是“java”类型,“package/name/”是包名,“ObjectName”是类名。
“MethodName”是方法名,"(III) Z"是方法的签名信息,由方法参数列表(III)和返回值(Z)构成,(III)表示三个int型参数;Z表示返回类型为boolean。
invoke 系列指令(调用方法)
功能都是 “调用方法”,但后缀区分方法类型 / 参数传递方式:
invoke-virtual:后缀 virtual 表示调用实例的普通方法(非静态、非构造方法),如 invoke-virtual {v0}, Ljava/lang/String;->length()I(调用 String 的 length 方法)。
invoke-static:后缀 static 表示调用静态方法,如 invoke-static {v1, v2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I(调用 Integer 的静态方法 parseInt)。
invoke-direct:后缀 direct 表示调用私有方法或构造方法(
字节码的助记符
后缀
一些运算码具有消除歧义的名称后缀,这些后缀表示运算类型:
常规类型的 32 位运算码未标记。
常规类型的 64 位运算码以 -wide 为后缀。
特定类型的运算码以其类型(或简单缩写)为后缀,这些类型包括:-boolean -byte -char -short -int -long -float -double -object -string -class -void。
| 运算码示例 | 类型后缀 | 功能说明 |
|---|---|---|
const/4 |
隐含 int |
将 4 位(1 字节内)的 int 类型常量存入寄存器(如 const/4 v0, 0x1) |
const-wide/16 |
wide(对应 long/double) |
将 16 位的 long 或 double 常量存入寄存器(占 2 个寄存器,如 const-wide/16 v2, 0x100) |
add-int |
int |
对两个 int 类型数据做加法(如 add-int v0, v1, v2 表示 v0 = v1 + v2) |
sub-long |
long |
对两个 long 类型数据做减法(如 sub-long v4, v2, v0 表示 v4 = v2 - v0,v2/v0 各占 2 个寄存器) |
mul-float |
float |
对两个 float 类型数据做乘法(如 mul-float v1, v3, v5) |
div-double |
double |
对两个 double 类型数据做除法(如 div-double v6, v4, v2) |
neg-boolean |
boolean |
对 boolean 类型数据取反(如 neg-boolean v0, v1 表示 v0 = !v1) |
new-instance |
object |
创建对象实例(隐含操作 object 类型,如 new-instance v0, Ljava/lang/String;) |
const-string |
string |
将字符串常量的引用存入寄存器(如 const-string v1, "hello") |
check-cast |
object |
检查对象类型是否匹配(如 check-cast v0, Ljava/util/List; 验证 v0 是 List 类型对象) |
instance-of |
class |
判断对象是否为某类实例(如 instance-of v1, v0, Ljava/lang/Integer; 检查 v0 是否是 Integer 实例) |
return-void |
void |
无返回值的方法返回(如方法结束时的 return-void) |
iget-byte |
byte |
从对象中读取 byte 类型字段(如 iget-byte v0, v1, Lcom/example/Test;field:B) |
sput-short |
short |
向静态字段写入 short 类型数据(如 sput-short v2, Lcom/example/Config;value:S) |
带斜杠的后缀
带斜杠后缀的运算码主要用于区分功能相同但格式 / 选项不同的指令
const 系列指令(常量赋值)
功能都是 “将常量存入寄存器”,但后缀区分常量的位数 / 存储方式:
const/4:后缀 /4 表示常量是 4 位(0~15),占 1 字节,如 const/4 v0, 0x3(存 3 到 v0)。
const/16:后缀 /16 表示常量是 16 位(0~65535),占 2 字节
const:无数字后缀,表示常量是 32 位(完整 int 范围),占 4 字节
指令表格(全)
| 运算和格式 | 助记符/语法 | 参数 | 说明 |
|---|---|---|---|
| 00 10x | nop | 无 | 空循环。 注意:数据传送伪指令用此运算码标记,运算码单元的高阶字节表示数据的性质。请参阅“packed-switch-payload 格式”“sparse-switch-payload 格式”和“fill-array-data-payload 格式”。 |
| 01 12x | move vA, vB | A: 目标寄存器(4 位) B: 源寄存器(4 位) |
将一个非对象寄存器的内容移到另一个非对象寄存器中。 |
| 02 22x | move/from16 vAA, vBBBB | A: 目标寄存器(8 位) B: 源寄存器(16 位) |
将一个非对象寄存器的内容移到另一个非对象寄存器中。 |
| 03 32x | move/16 vAAAA, vBBBB | A: 目标寄存器(16 位) B: 源寄存器(16 位) |
将一个非对象寄存器的内容移到另一个非对象寄存器中。 |
| 04 12x | move-wide vA, vB | A: 目标寄存器对(4 位) B: 源寄存器对(4 位) |
将一个寄存器对的内容移到另一个寄存器对中。 注意:可以从 vN 移到 vN-1 或 vN+1,需在执行写入前为读取的寄存器对两部分安排实现。 |
| 05 22x | move-wide/from16 vAA, vBBBB | A: 目标寄存器对(8 位) B: 源寄存器对(16 位) |
将一个寄存器对的内容移到另一个寄存器对中。 注意:实现注意事项与 move-wide 相同。 |
| 06 32x | move-wide/16 vAAAA, vBBBB | A: 目标寄存器对(16 位) B: 源寄存器对(16 位) |
将一个寄存器对的内容移到另一个寄存器对中。 注意:实现注意事项与 move-wide 相同。 |
| 07 12x | move-object vA, vB | A: 目标寄存器(4 位) B: 源寄存器(4 位) |
将一个对象传送寄存器的内容移到另一个对象传送寄存器中。 |
| 08 22x | move-object/from16 vAA, vBBBB | A: 目标寄存器(8 位) B: 源寄存器(16 位) |
将一个对象传送寄存器的内容移到另一个对象传送寄存器中。 |
| 09 32x | move-object/16 vAAAA, vBBBB | A: 目标寄存器(16 位) B: 源寄存器(16 位) |
将一个对象传送寄存器的内容移到另一个对象传送寄存器中。 |
| 0a 11x | move-result vAA | A: 目标寄存器(8 位) | 将最新的 invoke-kind 的单字非对象结果移到指定寄存器。该指令必须紧跟在其(单字非对象)结果不被忽略的 invoke-kind 之后,否则无效。 |
| 0b 11x | move-result-wide vAA | A: 目标寄存器对(8 位) | 将最新的 invoke-kind 的双字结果移到指定寄存器对。该指令必须紧跟在其(双字)结果不被忽略的 invoke-kind 之后,否则无效。 |
| 0c 11x | move-result-object vAA | A: 目标寄存器(8 位) | 将最新的 invoke-kind 的对象结果移到指定寄存器。该指令必须紧跟在其(对象)结果不被忽略的 invoke-kind 或 filled-new-array 之后,否则无效。 |
| 0d 11x | move-exception vAA | A: 目标寄存器(8 位) | 将刚刚捕获的异常保存到给定寄存器。该指令必须为捕获的异常不被忽略的任何异常处理程序的第一条指令,且仅作为异常处理程序的第一条指令执行,否则无效。 |
| 0e 10x | return-void | 无 | 从 void 方法返回。 |
| 0f 11x | return vAA | A: 返回值寄存器(8 位) | 从单字宽度(32 位)非对象值返回方法返回。 |
| 10 11x | return-wide vAA | A: 返回值寄存器对(8 位) | 从双字宽度(64 位)值返回方法返回。 |
| 11 11x | return-object vAA | A: 返回值寄存器(8 位) | 从对象返回方法返回。 |
| 12 11n | const/4 vA, #+B | A: 目标寄存器(4 位) B: 有符号整数(4 位) |
将给定的字面量值(符号扩展为 32 位)移到指定寄存器中。 |
| 13 21s | const/16 vAA, #+BBBB | A: 目标寄存器(8 位) B: 有符号整数(16 位) |
将给定的字面量值(符号扩展为 32 位)移到指定寄存器中。 |
| 14 31i | const vAA, #+BBBBBBBB | A: 目标寄存器(8 位) B: 任意 32 位常量 |
将给定的字面量值移到指定寄存器中。 |
| 15 21h | const/high16 vAA, #+BBBB0000 | A: 目标寄存器(8 位) B: 有符号整数(16 位) |
将给定的字面量值(右零扩展为 32 位)移到指定寄存器中。 |
| 16 21s | const-wide/16 vAA, #+BBBB | A: 目标寄存器(8 位) B: 有符号整数(16 位) |
将给定的字面量值(符号扩展为 64 位)移到指定寄存器对中。 |
| 17 31i | const-wide/32 vAA, #+BBBBBBBB | A: 目标寄存器(8 位) B: 有符号整数(32 位) |
将给定的字面量值(符号扩展为 64 位)移到指定寄存器对中。 |
| 18 51l | const-wide vAA, #+BBBBBBBBBBBBBBBB | A: 目标寄存器(8 位) B: 任意双字宽度(64 位)常量 |
将给定的字面量值移到指定寄存器对中。 |
| 19 21h | const-wide/high16 vAA, #+BBBB000000000000 | A: 目标寄存器(8 位) B: 有符号整数(16 位) |
将给定的字面量值(右零扩展为 64 位)移到指定寄存器对中。 |
| 1a 21c | const-string vAA, string@BBBB | A: 目标寄存器(8 位) B: 字符串索引 |
将通过给定索引指定的字符串的引用移到指定寄存器中。 |
| 1b 31c | const-string/jumbo vAA, string@BBBBBBBB | A: 目标寄存器(8 位) B: 字符串索引 |
将通过给定索引指定的字符串的引用移到指定寄存器中。 |
| 1c 21c | const-class vAA, type@BBBB | A: 目标寄存器(8 位) B: 类型索引 |
将通过给定索引指定的类的引用移到指定寄存器中。如果指定的类型是原始类型,则存储对原始类型的退化类的引用。 |
| 1d 11x | monitor-enter vAA | A: 引用传送寄存器(8 位) | 获取指定对象的监视锁。 |
| 1e 11x | monitor-exit vAA | A: 引用传送寄存器(8 位) | 释放指定对象的监视锁。 注意:如果该指令需要抛出异常,则必须像 PC 已超出该指令那样抛出。可理解为指令已成功执行,在该指令执行后但下一条指令执行前抛出异常。此定义使方法可将监视锁清理 catch-all(如 finally)分块用作自身的监视锁清理,以处理可能因 Thread.stop() 既往实现抛出的任意异常,同时维持适当的监视锁安全机制。 |
| 1f 21c | check-cast vAA, type@BBBB | A: 引用传送寄存器(8 位) B: 类型索引(16 位) |
如果给定寄存器中的引用不能转型为指定的类型,则抛出 ClassCastException。 注意:由于 A 必须为引用(非基元值),如果 B 引用基元类型,运行时必然失败(抛出异常)。 |
| 20 22c | instance-of vA, vB, type@CCCC | A: 目标寄存器(4 位) B: 引用传送寄存器(4 位) C: 类型索引(16 位) |
如果指定的引用是给定类型的实例,则为给定目标寄存器赋值 1,否则赋值 0。 注意:由于 B 必须为引用(非基元值),如果 C 引用基元类型,始终赋值 0。 |
| 21 12x | array-length vA, vB | A: 目标寄存器(4 位) B: 数组引用传送寄存器(4 位) |
将指定数组的长度(条目个数)赋值给给定目标寄存器。 |
| 22 21c | new-instance vAA, type@BBBB | A: 目标寄存器(8 位) B: 类型索引 |
根据指定的类型构造新实例,并将对该新实例的引用存储到目标寄存器中。该类型必须引用非数组类。 |
| 23 22c | new-array vA, vB, type@CCCC | A: 目标寄存器(4 位) B: 大小寄存器 C: 类型索引 |
根据指定的类型和大小构造新数组。该类型必须是数组类型。 |
| 24 35c | filled-new-array {vC, vD, vE, vF, vG}, type@BBBB | A: 数组大小和参数字数(4 位) B: 类型索引(16 位) C..G: 参数寄存器(每个 4 位) |
根据给定类型和大小构造数组,并用提供的内容填充。该类型必须是数组类型。数组内容必须是单字类型(不接受 long 或 double 类型数组,但接受引用类型数组)。构造的实例存储为“结果”,方式与方法调用指令存储结果相同,因此必须移到后面紧跟 move-result-object 指令(如需使用)的寄存器。 |
| 25 3rc | filled-new-array/range {vCCCC .. vNNNN}, type@BBBB | A: 数组大小和参数字数(8 位) B: 类型索引(16 位) C: 第一个参数寄存器(16 位) N = A + C - 1 |
根据给定类型和大小构造数组,并用提供的内容填充。说明和限制与 filled-new-array 相同。 |
| 26 31t | fill-array-data vAA, +BBBBBBBB(补充数据见“fill-array-data-payload 格式”) | A: 数组引用(8 位) B: 到表格数据伪指令的有符号“分支”偏移量(32 位) |
用指定的数据填充给定数组。必须引用基元类型的数组,且数据表格的类型必须与数组匹配;数据表格包含的元素个数不得超出数组元素个数。数组可比表格大,此时仅设置初始元素,忽略剩余元素。 |
| 27 11x | throw vAA | A: 异常传送寄存器(8 位) | 抛出指定的异常。 |
| 28 10t | goto +AA | A: 有符号分支偏移量(8 位) | 无条件跳转到指定指令。 注意:分支偏移量不得为 0。(自旋循环可用 goto/32 或在分支前添加 nop 作为目标构造。) |
| 29 20t | goto/16 +AAAA | A: 有符号分支偏移量(16 位) | 无条件跳转到指定指令。 注意:分支偏移量不得为 0。(自旋循环可用 goto/32 或在分支前添加 nop 作为目标构造。) |
| 2a 30t | goto/32 +AAAAAAAA | A: 有符号分支偏移量(32 位) | 无条件跳转到指定指令。 |
| 2b 31t | packed-switch vAA, +BBBBBBBB(补充数据见“packed-switch-payload 格式”) | A: 要测试的寄存器 B: 到表格数据伪指令的有符号“分支”偏移量(32 位) |
通过与特定整数范围内每个值对应的偏移量表,基于给定寄存器中的值跳转到新指令;无匹配项则跳转到下一条指令。 |
| 2c 31t | sparse-switch vAA, +BBBBBBBB(补充数据见“sparse-switch-payload 格式”) | A: 要测试的寄存器 B: 到表格数据伪指令的有符号“分支”偏移量(32 位) |
通过偏移值对的有序表,基于给定寄存器中的值跳转到新指令;无匹配项则跳转到下一条指令。 |
| 2d..31 23x | cmpkind vAA, vBB, vCC 2d: cmpl-float (lt bias) 2e: cmpg-float (gt bias) 2f: cmpl-double (lt bias) 30: cmpg-double (gt bias) 31: cmp-long |
A: 目标寄存器(8 位) B: 第一个源寄存器或寄存器对 C: 第二个源寄存器或寄存器对 |
执行指定的浮点或 long 比较,若 b == c,a 设为 0;b > c,a 设为 1;b < c,a 设为 -1。浮点运算的“bias”表示如何处理 NaN 比较:“gt bias”指令返回 1,“lt bias”指令返回 -1。 例如,建议用 cmpg-float 检查浮点数是否满足 x < y;结果为 -1 表示测试为 true,其他值表示 false(因比较有效但结果不符或值为 NaN)。 |
| 32..37 22t | if-test vA, vB, +CCCC 32: if-eq 33: if-ne 34: if-lt 35: if-ge 36: if-gt 37: if-le |
A: 要测试的第一个寄存器(4 位) B: 要测试的第二个寄存器(4 位) C: 有符号分支偏移量(16 位) |
若两个给定寄存器的值比较结果符合预期,则分支到给定目标寄存器。 注意:分支偏移量不得为 0。(自旋循环可通过围绕后向 goto 分支或在分支前添加 nop 作为目标构造。) |
| 38..3d 21t | if-testz vAA, +BBBB 38: if-eqz 39: if-nez 3a: if-ltz 3b: if-gez 3c: if-gtz 3d: if-lez |
A: 要测试的寄存器(8 位) B: 有符号分支偏移量(16 位) |
若给定寄存器的值与 0 的比较结果符合预期,则分支到给定目标寄存器。 注意:分支偏移量不得为 0。(自旋循环可通过围绕后向 goto 分支或在分支前添加 nop 作为目标构造。) |
| 3e..43 10x | (未使用) | 无 | (未使用) |
| 44..51 23x | arrayop vAA, vBB, vCC 44: aget 45: aget-wide 46: aget-object 47: aget-boolean 48: aget-byte 49: aget-char 4a: aget-short 4b: aput 4c: aput-wide 4d: aput-object 4e: aput-boolean 4f: aput-byte 50: aput-char 51: aput-short |
A: 值寄存器或寄存器对(源或目标,8 位) B: 数组寄存器(8 位) C: 索引寄存器(8 位) |
在给定数组的已标识索引处执行已确定的数组运算,并将结果加载或存储到值寄存器中。 |
| 52..5f 22c | iinstanceop vA, vB, field@CCCC 52: iget 53: iget-wide 54: iget-object 55: iget-boolean 56: iget-byte 57: iget-char 58: iget-short 59: iput 5a: iput-wide 5b: iput-object 5c: iput-boolean 5d: iput-byte 5e: iput-char 5f: iput-short |
A: 值寄存器或寄存器对(源或目标,4 位) B: 对象寄存器(4 位) C: 实例字段引用索引(16 位) |
对已标识的字段执行已确定的对象实例字段运算,并将结果加载或存储到值寄存器中。 注意:这些运算码是静态链接的合理候选项,将字段参数更改为更直接的偏移量。 |
| 60..6d 21c | sstaticop vAA, field@BBBB 60: sget 61: sget-wide 62: sget-object 63: sget-boolean 64: sget-byte 65: sget-char 66: sget-short 67: sput 68: sput-wide 69: sput-object 6a: sput-boolean 6b: sput-byte 6c: sput-char 6d: sput-short |
A: 值寄存器或寄存器对(源或目标,8 位) B: 静态字段引用索引(16 位) |
对已标识的静态字段执行已确定的对象静态字段运算,并将结果加载或存储到值寄存器中。 注意:这些运算码是静态链接的合理候选项,将字段参数更改为更直接的偏移量。 |
| 6e..72 35c | invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB 6e: invoke-virtual 6f: invoke-super 70: invoke-direct 71: invoke-static 72: invoke-interface |
A: 参数字数(4 位) B: 方法引用索引(16 位) C..G: 参数寄存器(每个 4 位) |
调用指定的方法。可使用相应的 move-result* 变体将所得结果(如果有的话)存储为紧跟其后的指令。 invoke-virtual:调用普通虚拟方法(非 static、private 或构造函数)。 invoke-super:当 method_id 引用非接口类方法时,调用最近父类的虚拟方法;Dex 版本 ≥037 时,若引用接口方法,调用接口上未被覆盖的最具体版本(版本 <037 时接口 method_id 无效)。 invoke-direct:调用非 static 直接方法(private 实例方法或构造函数)。 invoke-static:调用 static 方法(视为直接方法)。 invoke-interface:调用接口方法(对象具体类未知)。 注意:这些运算码是静态链接的合理候选项,将方法参数更改为更直接的偏移量。 |
| 73 10x | (未使用) | 无 | (未使用) |
| 74..78 3rc | invoke-kind/range {vCCCC .. vNNNN}, meth@BBBB 74: invoke-virtual/range 75: invoke-super/range 76: invoke-direct/range 77: invoke-static/range 78: invoke-interface/range |
A: 参数字数(8 位) B: 方法引用索引(16 位) C: 第一个参数寄存器(16 位) N = A + C - 1 |
调用指定的方法。详情、注意事项与 invoke-kind 相同。 |
| 79..7a 10x | (未使用) | 无 | (未使用) |
| 7b..8f 12x | unop vA, vB 7b: neg-int 7c: not-int 7d: neg-long 7e: not-long 7f: neg-float 80: neg-double 81: int-to-long 82: int-to-float 83: int-to-double 84: long-to-int 85: long-to-float 86: long-to-double 87: float-to-int 88: float-to-long 89: float-to-double 8a: double-to-int 8b: double-to-long 8c: double-to-float 8d: int-to-byte 8e: int-to-char 8f: int-to-short |
A: 目标寄存器或寄存器对(4 位) B: 源寄存器或寄存器对(4 位) |
对源寄存器执行已确定的一元运算,并将结果存储到目标寄存器中。 |
| 90..af 23x | binop vAA, vBB, vCC 90: add-int 91: sub-int 92: mul-int 93: div-int 94: rem-int 95: and-int 96: or-int 97: xor-int 98: shl-int 99: shr-int 9a: ushr-int 9b: add-long 9c: sub-long 9d: mul-long 9e: div-long 9f: rem-long a0: and-long a1: or-long a2: xor-long a3: shl-long a4: shr-long a5: ushr-long a6: add-float a7: sub-float a8: mul-float a9: div-float aa: rem-float ab: add-double ac: sub-double ad: mul-double ae: div-double af: rem-double |
A: 目标寄存器或寄存器对(8 位) B: 第一个源寄存器或寄存器对(8 位) C: 第二个源寄存器或寄存器对(8 位) |
对两个源寄存器执行已确定的二元运算,并将结果存储到目标寄存器中。 注意:shl-long、shr-long、ushr-long 的第一个源为寄存器对(移位值),第二个源为单个寄存器(移位距离),与其他 long 运算不同。 |
| b0..cf 12x | binop/2addr vA, vB b0: add-int/2addr b1: sub-int/2addr b2: mul-int/2addr b3: div-int/2addr b4: rem-int/2addr b5: and-int/2addr b6: or-int/2addr b7: xor-int/2addr b8: shl-int/2addr b9: shr-int/2addr ba: ushr-int/2addr bb: add-long/2addr bc: sub-long/2addr bd: mul-long/2addr be: div-long/2addr bf: rem-long/2addr c0: and-long/2addr c1: or-long/2addr c2: xor-long/2addr c3: shl-long/2addr c4: shr-long/2addr c5: ushr-long/2addr c6: add-float/2addr c7: sub-float/2addr c8: mul-float/2addr c9: div-float/2addr ca: rem-float/2addr cb: add-double/2addr cc: sub-double/2addr cd: mul-double/2addr ce: div-double/2addr cf: rem-double/2addr |
A: 目标寄存器或寄存器对和第一个源寄存器或寄存器对(4 位) B: 第二个源寄存器或寄存器对(4 位) |
对两个源寄存器执行已确定的二元运算,并将结果存储到第一个源寄存器中。 注意:shl-long/2addr、shr-long/2addr、ushr-long/2addr 的目标/第一个源为寄存器对(移位值),第二个源为单个寄存器(移位距离),与其他 long/2addr 运算不同。 |
| d0..d7 22s | binop/lit16 vA, vB, #+CCCC d0: add-int/lit16 d1: rsub-int (reverse subtract) d2: mul-int/lit16 d3: div-int/lit16 d4: rem-int/lit16 d5: and-int/lit16 d6: or-int/lit16 d7: xor-int/lit16 |
A: 目标寄存器(4 位) B: 源寄存器(4 位) C: 有符号整数常量(16 位) |
对指定的寄存器(第一个参数)和字面量值(第二个参数)执行指定的二元运算,并将结果存储到目标寄存器中。 注意:rsub-int 不含后缀,为该系列主运算码;详见语义说明。 |
| d8..e2 22b | binop/lit8 vAA, vBB, #+CC d8: add-int/lit8 d9: rsub-int/lit8 da: mul-int/lit8 db: div-int/lit8 dc: rem-int/lit8 dd: and-int/lit8 de: or-int/lit8 df: xor-int/lit8 e0: shl-int/lit8 e1: shr-int/lit8 e2: ushr-int/lit8 |
A: 目标寄存器(8 位) B: 源寄存器(8 位) C: 有符号整数常量(8 位) |
对指定的寄存器(第一个参数)和字面量值(第二个参数)执行指定的二元运算,并将结果存储到目标寄存器中。 注意:详见 rsub-int 语义说明。 |
| e3..f9 10x | (未使用) | 无 | (未使用) |
| fa 45cc | invoke-polymorphic {vC, vD, vE, vF, vG}, meth@BBBB, proto@HHHH | A: 参数字数(4 位) B: 方法引用索引(16 位) C: 接收器(4 位) D..G: 参数寄存器(每个 4 位) H: 原型引用索引(16 位) |
调用指定的签名多态方法(如 MethodHandle.invoke)。可使用 move-result* 存储结果。 方法引用必须为签名多态方法,接收器需支持该方法,原型引用说明参数和返回类型。 可能引发异常,详见方法 API 文档。 存在于 Dex 版本 ≥038 中。 |
| fb 4rcc | invoke-polymorphic/range {vCCCC .. vNNNN}, meth@BBBB, proto@HHHH | A: 参数字数(8 位) B: 方法引用索引(16 位) C: 接收器(16 位) H: 原型引用索引(16 位) N = A + C - 1 |
调用指定的方法句柄。详情与 invoke-polymorphic 相同。 存在于 Dex 版本 ≥038 中。 |
| fc 35c | invoke-custom {vC, vD, vE, vF, vG}, call_site@BBBB | A: 参数字数(4 位) B: 调用点引用索引(16 位) C..G: 参数寄存器(每个 4 位) |
解析并调用指定的调用点,分两个阶段: 1. 调用点解析:检查关联的 CallSite 实例,无则调用引导程序链接器方法生成并关联。 2. 调用点调用:调用解析出的 CallSite 目标方法(类似 invoke-polymorphic)。 引导程序异常封装为 BootstrapMethodError;存在于 Dex 版本 ≥038 中。 |
| fd 3rc | invoke-custom/range {vCCCC .. vNNNN}, call_site@BBBB | A: 参数字数(8 位) B: 调用点引用索引(16 位) C: 第一个参数寄存器(16 位) N = A + C - 1 |
解析并调用一个调用点。详情与 invoke-custom 相同。 存在于 Dex 版本 ≥038 中。 |
| fe 21c | const-method-handle vAA, method_handle@BBBB | A: 目标寄存器(8 位) B: 方法句柄索引(16 位) |
将通过给定索引指定的方法句柄的引用移到指定的寄存器中。 存在于 Dex 版本 ≥039 中。 |
| ff 21c | const-method-type vAA, proto@BBBB | A: 目标寄存器(8 位) B: 方法原型引用(16 位) |
将通过给定索引指定的方法原型的引用移到指定的寄存器中。 存在于 Dex 版本 ≥039 中。 |
数学运算指令(全)
以下是提取整理后的运算码语义表格:
| 运算码 | C 语义 | 备注 |
|---|---|---|
| neg-int | int32 a; int32 result = -a; |
一元二进制补码。 |
| not-int | int32 a; int32 result = ~a; |
一元反码。 |
| neg-long | int64 a; int64 result = -a; |
一元二进制补码。 |
| not-long | int64 a; int64 result = ~a; |
一元反码。 |
| neg-float | float a; float result = -a; |
浮点否定。 |
| neg-double | double a; double result = -a; |
浮点否定。 |
| int-to-long | int32 a; int64 result = (int64) a; |
将 int32 符号扩展为 int64。 |
| int-to-float | int32 a; float result = (float) a; |
使用最近舍入,将 int32 转换为 float。这会导致某些值不够精准。 |
| int-to-double | int32 a; double result = (double) a; |
将 int32 转换为 double。 |
| long-to-int | int64 a; int32 result = (int32) a; |
将 int64 截断为 int32。 |
| long-to-float | int64 a; float result = (float) a; |
使用最近舍入,将 int64 转换为 float。这会导致某些值不够精准。 |
| long-to-double | int64 a; double result = (double) a; |
使用最近舍入,将 int64 转换为 double。这会导致某些值不够精准。 |
| float-to-int | float a; int32 result = (int32) a; |
使用向零舍入,将 float 转换为 int32。NaN 和 -0.0(负零)转换为整数 0。无穷数和因所占幅面过大而无法表示的值根据符号转换为 0x7fffffff 或 -0x80000000。 |
| float-to-long | float a; int64 result = (int64) a; |
使用向零舍入,将 float 转换为 int64。适用于 float-to-int 的特殊情况规则也适用于此,但超出范围的值除外,这些值根据符号转换为 0x7fffffffffffffff 或 -0x8000000000000000。 |
| float-to-double | float a; double result = (double) a; |
将 float 转换为 double,值依然精准。 |
| double-to-int | double a; int32 result = (int32) a; |
使用向零舍入,将 double 转换为 int32。适用于 float-to-int 的特殊情况规则也适用于此。 |
| double-to-long | double a; int64 result = (int64) a; |
使用向零舍入,将 double 转换为 int64。适用于 float-to-long 的特殊情况规则也适用于此。 |
| double-to-float | double a; float result = (float) a; |
使用最近舍入,将 double 转换为 float。这会导致某些值不够精准。 |
| int-to-byte | int32 a; int32 result = (a << 24) >> 24; |
将 int32 截断为 int8,对结果进行符号扩展。 |
| int-to-char | int32 a; int32 result = a & 0xffff; |
将 int32 截断为 uint16,无需进行符号扩展。 |
| int-to-short | int32 a; int32 result = (a << 16) >> 16; |
将 int32 截断为 int16,对结果进行符号扩展。 |
| add-int | int32 a, b; int32 result = a + b; |
二进制补码加法。 |
| sub-int | int32 a, b; int32 result = a - b; |
二进制补码减法。 |
| rsub-int | int32 a, b; int32 result = b - a; |
二进制补码反向减法。 |
| mul-int | int32 a, b; int32 result = a * b; |
二进制补码乘法。 |
| div-int | int32 a, b; int32 result = a / b; |
二进制补码除法,向零舍入(即截断为整数)。如果 b == 0,则会抛出 ArithmeticException。 |
| rem-int | int32 a, b; int32 result = a % b; |
二进制补码除后取余数。结果的符号与 a 的符号相同,可更精确地定义为 result == a - (a / b) * b。如果 b == 0,则会抛出 ArithmeticException。 |
| and-int | int32 a, b; int32 result = a & b; |
按位 AND。 |
| or-int | int32 a, b; int32 result = a |
b; |
| xor-int | int32 a, b; int32 result = a ^ b; |
按位 XOR。 |
| shl-int | int32 a, b; int32 result = a << (b & 0x1f); |
按位左移(带掩码参数)。 |
| shr-int | int32 a, b; int32 result = a >> (b & 0x1f); |
按位有符号右移(带掩码参数)。 |
| ushr-int | uint32 a, b; int32 result = a >> (b & 0x1f); |
按位无符号右移(带掩码参数)。 |
| add-long | int64 a, b; int64 result = a + b; |
二进制补码加法。 |
| sub-long | int64 a, b; int64 result = a - b; |
二进制补码减法。 |
| mul-long | int64 a, b; int64 result = a * b; |
二进制补码乘法。 |
| div-long | int64 a, b; int64 result = a / b; |
二进制补码除法,向零舍入(即截断为整数)。如果 b == 0,则会抛出 ArithmeticException。 |
| rem-long | int64 a, b; int64 result = a % b; |
二进制补码除后取余数。结果的符号与 a 的符号相同,可更精确地定义为 result == a - (a / b) * b。如果 b == 0,则会抛出 ArithmeticException。 |
| and-long | int64 a, b; int64 result = a & b; |
按位 AND。 |
| or-long | int64 a, b; int64 result = a |
b; |
| xor-long | int64 a, b; int64 result = a ^ b; |
按位 XOR。 |
| shl-long | int64 a; int32 b; int64 result = a << (b & 0x3f); |
按位左移(带掩码参数)。 |
| shr-long | int64 a; int32 b; int64 result = a >> (b & 0x3f); |
按位有符号右移(带掩码参数)。 |
| ushr-long | uint64 a; int32 b; int64 result = a >> (b & 0x3f); |
按位无符号右移(带掩码参数)。 |
| add-float | float a, b; float result = a + b; |
浮点加法。 |
| sub-float | float a, b; float result = a - b; |
浮点减法。 |
| mul-float | float a, b; float result = a * b; |
浮点乘法。 |
| div-float | float a, b; float result = a / b; |
浮点除法。 |
| rem-float | float a, b; float result = a % b; |
浮点除后取余数。该函数不同于 IEEE 754 取余,定义为 result == a - roundTowardZero(a / b) * b。 |
| add-double | double a, b; double result = a + b; |
浮点加法。 |
| sub-double | double a, b; double result = a - b; |
浮点减法。 |
| mul-double | double a, b; double result = a * b; |
浮点乘法。 |
| div-double | double a, b; double result = a / b; |
浮点除法。 |
| rem-double | double a, b; double result = a % b; |
浮点除后取余数。该函数不同于 IEEE 754 取余,定义为 result == a - roundTowardZero(a / b) * b。 |
packed-switch-payload 格式
switch:对应程序中的 switch 分支语句
payload:指该结构中实际存储的核心数据(如分支目标地址、case 值范围等)
| 名称 | 字段的数据类型或存储格式 | 说明 |
|---|---|---|
| ident | ushort = 0x0100 | 标记当前数据结构是 packed-switch-payload |
| size | ushort | 表示该结构中包含的分支条目数量 |
| first_key | int | 第一位(即最低位)switch case 的值,表示 switch 语句中最小的 case 值 |
| targets | int[] | “目标列表”,存储每个 case 对应的执行地址 |
sparse-switch-payload 格式
| 名称 | 格式 | 说明 |
|---|---|---|
| ident | ushort = 0x0200 | 识别字段,用于唯一标识当前结构是 sparse-switch-payload( |
| size | ushort | 记录键值 - 目标对的总数量 |
| keys | int[] | size 键值列表,从低到高排序,存储 switch 语句中所有的 case 键值 |
| targets | int[] | 分支目标列表字段,存储与每个键值对应的代码跳转地址 |
fill-array-data-payload 格式
| 名称 | 格式 | 说明 |
|---|---|---|
| ident | ushort = 0x0300 | 识别字段,用于标识当前结构是 fill-array-data-payload |
| element_width | ushort | 每个元素的字节数 |
| size | uint | 元素的总数量 |
| 数据 | ubyte[] | 实际的数组元素数据,以字节数组形式存储 |
smali文件格式
一个smali文件就是一个类。
smali文件前三行
.class <访问权限> [修饰关键字] <类名>
.super <父类名>
.source <源文件名>;
方法
方法的开始和结束
.method public static main([Ljava/lang/String;)v
······
.end method
直接方法
# direct methods
.method <访问权限> [修饰关键字] <方法原型>
<.registers> //局部寄存器+参数寄存器个数
<.locals> //局部寄存器个数
[.param] //参数
[.prologue] //代码正式开始
[.line]
<代码体>
.end method
虚方法
虚方法:虚方法是指从父类中继承的方法或者实现的接口方法,它的声明跟直接方法相同。
# virtual methods
.method <访问权限> [修饰关键字] <方法原型>
<.registers>
<.locals>
[.param]
[.prologue]
[.line]
<代码体>
.end method
接口
如果一个类实现了一个接口,那么会在smali文件中用.implements指令表示出。
# interfaces
.implements <接口名>
注解
如果一个类使用了注解,那么Smali会使用.annotation指令。
我们先了解一下元数据的含义:元数据是指描述数据的数据,如一张照片的大小,分辨率就是元数据。
注解就是元数据,让工具或其他代码明白如何去处理其描述的代码。
注解的范围
如果注解的作用范围是类,.annotation指令会直接定义在Smali文件中。
如果注解的作用范围是方法或字段,.annotation会被包含在方法或字段定义中。
# annotations
.annotation [注解属性] <注解类名>
[注解字段 = 值]
.end annotation
参考链接
BV1364y1r7vX?spm_id_from=333.788.player.switch&vd_source=d24c2772c59c9d862438971bcb05f991&p=6

浙公网安备 33010602011771号