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 布尔类型,用于存储truefalse const/4 v0, 0x1 ,将true(对应1)赋值给寄存器v0v0存储的是布尔类型数据
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(这里v5v6两个寄存器存储该长整型,因为长整型占8字节 )
F float 单精度浮点数,占4个字节 const/high16 v6, 0x41400000,将浮点数 3.0 赋值给寄存器v6
D double 双精度浮点数,占8个字节 const-wide/high16 v7, 0x4000000000000000,将双精度浮点数 2.0 赋值给寄存器v7v7v8两个寄存器存储 )
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 表示调用私有方法或构造方法(),如 invoke-direct {v0}, Lcom/example/MyClass;->()V(调用构造方法)

字节码的助记符

后缀

一些运算码具有消除歧义的名称后缀,这些后缀表示运算类型:
常规类型的 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 位的 longdouble 常量存入寄存器(占 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 - v0v2/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; 验证 v0List 类型对象)
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

参考链接

Android开发者

BV1364y1r7vX?spm_id_from=333.788.player.switch&vd_source=d24c2772c59c9d862438971bcb05f991&p=6

posted @ 2025-07-31 23:01  MillionMind  阅读(23)  评论(0)    收藏  举报