# Smali / Dalvik 指令与语法完整参考 # (在原始分类基础上补全,涵盖官方 opcode 全集、payload # 伪指令、smali 文件结构、ODEX 快速指令等) # 一、数据操作与常量 nop # 空指令,不执行任何操作 # ---- move 族 ---- move vA, vB # vA = vB(4 位寄存器字段) move/from16 vAA, vBBBB # 源寄存器 16 位 move/16 vAAAA, vBBBB # 两个寄存器字段均扩展到 16 位 move-wide vA, vB # 64 位移动,占用 vA/vA+1 与 vB/vB+1 move-wide/from16 vAA, vBBBB move-wide/16 vAAAA, vBBBB move-object vA, vB # 对象引用移动 move-object/from16 vAA, vBBBB move-object/16 vAAAA, vBBBB # ---- move-result 族 ---- move-result vAA # 取上一个 invoke 的 32 位非对象结果 move-result-wide vAA # 取 64 位结果(vAA/vAA+1) move-result-object vAA # 取对象结果 move-exception vAA # 将捕获的异常对象存入 vAA # ---- const 族 ---- const/4 vA, #+B # 4 位符号扩展常量(-8 ~ +7) const/16 vAA, #+BBBB # 16 位符号扩展常量 const vAA, #+BBBBBBBB # 32 位常量 const/high16 vAA, #+BBBB0000 # 16 位字面值放入高 16 位,低 16 位补 0 const-wide/16 vAA, #+BBBB # 16 位符号扩展为 64 位 const-wide/32 vAA, #+BBBBBBBB # 32 位符号扩展为 64 位 const-wide vAA, #+BBBBBBBBBBBBBBBB # 完整 64 位常量 const-wide/high16 vAA, #+BBBB000000000000 # 16 位字面值放入 64 位高段 # ---- 引用类常量 ---- const-string vAA, "string" # 字符串常量引用 const-string/jumbo vAA, "string" # 更宽索引的字符串常量 const-class vAA, type@BBBB # 类对象引用 const-class/jumbo vAAAA, type@BBBBBBBB const-method-handle vAA, method_handle@BBBB # 方法句柄常量(DEX ≥ 038) const-method-type vAA, proto@BBBB # 方法类型常量(DEX ≥ 038) # 注意:64 位数据始终占用相邻寄存器对 vA / vA+1。 # 二、方法调用 invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBB # 普通实例方法 invoke-super {vC, vD, vE, vF, vG}, meth@BBBB # 父类方法 invoke-direct {vC, vD, vE, vF, vG}, meth@BBBB # 私有方法 / 构造函数 invoke-static {vC, vD, vE, vF, vG}, meth@BBBB # 静态方法 invoke-interface {vC, vD, vE, vF, vG}, meth@BBBB # 接口方法 invoke-virtual/range {vCCCC .. vNNNN}, meth@BBBB invoke-super/range {vCCCC .. vNNNN}, meth@BBBB invoke-direct/range {vCCCC .. vNNNN}, meth@BBBB invoke-static/range {vCCCC .. vNNNN}, meth@BBBB invoke-interface/range {vCCCC .. vNNNN}, meth@BBBB invoke-polymorphic {vC, vD, vE, vF, vG}, meth@BBBB, proto@HHHH invoke-polymorphic/range {vCCCC .. vNNNN}, meth@BBBB, proto@HHHH invoke-custom {vC, vD, vE, vF, vG}, call_site@BBBB # 对应 JVM invokedynamic invoke-custom/range {vCCCC .. vNNNN}, call_site@BBBB # 说明:普通 invoke 最多携带 5 个寄存器字(long/double 各算 2 字)。 # invoke-polymorphic / invoke-custom 出现于 DEX 版本 ≥ 038。 # 三、对象与类型 new-instance vAA, type@BBBB # 创建实例,不调用构造函数 new-instance/jumbo vAAAA, type@BBBBBBBB new-array vA, vB, type@CCCC # vB 为长度 new-array/jumbo vAAAA, vBBBB, type@CCCCCCCC array-length vA, vB # vA = vB 数组长度 filled-new-array {vC, vD, vE, vF, vG}, type@BBBB # 创建并填充数组 filled-new-array/range {vCCCC .. vNNNN}, type@BBBB check-cast vAA, type@BBBB # 类型转换,失败抛 ClassCastException check-cast/jumbo vAAAA, type@BBBBBBBB instance-of vA, vB, type@CCCC # vA = (vB instanceof type) ? 1 : 0 instance-of/jumbo vAAAA, vBBBB, type@CCCCCCCC # 说明:filled-new-array 不直接产生结果寄存器,需紧跟 move-result-object 取新数组引用。 # 四、实例字段读写 iget vA, vB, field@CCCC # 读 32 位实例字段 iget-object vA, vB, field@CCCC # 读对象引用实例字段 iget-wide vA, vB, field@CCCC # 读 64 位实例字段(vA/vA+1) iget-boolean vA, vB, field@CCCC # 窄类型变体 iget-byte vA, vB, field@CCCC iget-char vA, vB, field@CCCC iget-short vA, vB, field@CCCC iput vA, vB, field@CCCC # 写 32 位实例字段 iput-object vA, vB, field@CCCC iput-wide vA, vB, field@CCCC iput-boolean vA, vB, field@CCCC iput-byte vA, vB, field@CCCC iput-char vA, vB, field@CCCC iput-short vA, vB, field@CCCC # 说明:vB 为对象引用;vA 为值/目标寄存器。 # 窄类型变体在部分 ODEX / 工具链产物中出现。 # 五、静态字段读写 sget vA, field@BBBB # 读 32 位静态字段 sget-object vA, field@BBBB sget-wide vA, field@BBBB sget-boolean vA, field@BBBB sget-byte vA, field@BBBB sget-char vA, field@BBBB sget-short vA, field@BBBB sput vA, field@BBBB # 写 32 位静态字段 sput-object vA, field@BBBB sput-wide vA, field@BBBB sput-boolean vA, field@BBBB sput-byte vA, field@BBBB sput-char vA, field@BBBB sput-short vA, field@BBBB # 六、数组操作 aget vA, vB, vC # vA = vB[vC](32 位) aget-object vA, vB, vC aget-wide vA, vB, vC # 64 位元素(vA/vA+1) aget-boolean vA, vB, vC aget-byte vA, vB, vC aget-char vA, vB, vC aget-short vA, vB, vC aput vA, vB, vC # vB[vC] = vA(32 位) aput-object vA, vB, vC aput-wide vA, vB, vC aput-boolean vA, vB, vC aput-byte vA, vB, vC aput-char vA, vB, vC aput-short vA, vB, vC fill-array-data vAA, +BBBBBBBB # 用静态数据填充基本类型数组 # 七、跳转与分支 goto :label # 无条件跳转(8 位偏移) goto/16 :label # 16 位偏移 goto/32 :label # 32 位偏移 if-eq vA, vB, :label # vA == vB if-ne vA, vB, :label # vA != vB if-lt vA, vB, :label # vA < vB(有符号) if-ge vA, vB, :label # vA >= vB if-gt vA, vB, :label # vA > vB if-le vA, vB, :label # vA <= vB if-eqz vA, :label # vA == 0 if-nez vA, :label # vA != 0 if-ltz vA, :label # vA < 0 if-gez vA, :label # vA >= 0 if-gtz vA, :label # vA > 0 if-lez vA, :label # vA <= 0 packed-switch vAA, :switch_data # 密集 switch,引用 packed-switch-payload sparse-switch vAA, :switch_data # 稀疏 switch,引用 sparse-switch-payload # 八、比较与运算 # ---- 比较 ---- cmp-long vAA, vBB, vCC # long 比较,结果 -1 / 0 / 1 cmpl-float vAA, vBB, vCC # float 比较,NaN 返回 -1 cmpg-float vAA, vBB, vCC # float 比较,NaN 返回 1 cmpl-double vAA, vBB, vCC # double 比较,NaN 返回 -1 cmpg-double vAA, vBB, vCC # double 比较,NaN 返回 1 # ---- 整数二元运算 ---- add-int vA, vB, vC # 加法 sub-int vA, vB, vC # 减法 mul-int vA, vB, vC # 乘法 div-int vA, vB, vC # 除法 rem-int vA, vB, vC # 取余 and-int vA, vB, vC # 按位与 or-int vA, vB, vC # 按位或 xor-int vA, vB, vC # 按位异或 shl-int vA, vB, vC # 左移 shr-int vA, vB, vC # 算术右移 ushr-int vA, vB, vC # 逻辑右移 # ---- long 二元运算 ---- add-long vA, vB, vC sub-long vA, vB, vC mul-long vA, vB, vC div-long vA, vB, vC rem-long vA, vB, vC and-long vA, vB, vC or-long vA, vB, vC xor-long vA, vB, vC shl-long vA, vB, vC shr-long vA, vB, vC ushr-long vA, vB, vC # ---- float 二元运算 ---- add-float vA, vB, vC sub-float vA, vB, vC mul-float vA, vB, vC div-float vA, vB, vC rem-float vA, vB, vC # ---- double 二元运算 ---- add-double vA, vB, vC sub-double vA, vB, vC mul-double vA, vB, vC div-double vA, vB, vC rem-double vA, vB, vC # ---- /2addr 变体(2 地址格式:vA = vA op vB)---- add-int/2addr vA, vB sub-int/2addr vA, vB mul-int/2addr vA, vB div-int/2addr vA, vB rem-int/2addr vA, vB and-int/2addr vA, vB or-int/2addr vA, vB xor-int/2addr vA, vB shl-int/2addr vA, vB shr-int/2addr vA, vB ushr-int/2addr vA, vB add-long/2addr vA, vB sub-long/2addr vA, vB mul-long/2addr vA, vB div-long/2addr vA, vB rem-long/2addr vA, vB and-long/2addr vA, vB or-long/2addr vA, vB xor-long/2addr vA, vB shl-long/2addr vA, vB shr-long/2addr vA, vB ushr-long/2addr vA, vB add-float/2addr vA, vB sub-float/2addr vA, vB mul-float/2addr vA, vB div-float/2addr vA, vB rem-float/2addr vA, vB add-double/2addr vA, vB sub-double/2addr vA, vB mul-double/2addr vA, vB div-double/2addr vA, vB rem-double/2addr vA, vB # ---- 字面量变体(vA = vB op #lit)---- add-int/lit16 vA, vB, #+CCCC sub-int/lit16 vA, vB, #+CCCC mul-int/lit16 vA, vB, #+CCCC div-int/lit16 vA, vB, #+CCCC rem-int/lit16 vA, vB, #+CCCC and-int/lit16 vA, vB, #+CCCC or-int/lit16 vA, vB, #+CCCC xor-int/lit16 vA, vB, #+CCCC add-int/lit8 vA, vB, #+CCCC sub-int/lit8 vA, vB, #+CCCC mul-int/lit8 vA, vB, #+CCCC div-int/lit8 vA, vB, #+CCCC rem-int/lit8 vA, vB, #+CCCC and-int/lit8 vA, vB, #+CCCC or-int/lit8 vA, vB, #+CCCC xor-int/lit8 vA, vB, #+CCCC shl-int/lit8 vA, vB, #+CCCC shr-int/lit8 vA, vB, #+CCCC ushr-int/lit8 vA, vB, #+CCCC # ---- 一元运算 ---- neg-int vA, vB # vA = -vB not-int vA, vB # vA = ~vB neg-long vA, vB not-long vA, vB neg-float vA, vB neg-double vA, vB # ---- 类型转换 ---- int-to-long vA, vB int-to-float vA, vB int-to-double vA, vB long-to-int vA, vB long-to-float vA, vB long-to-double vA, vB float-to-int vA, vB float-to-long vA, vB float-to-double vA, vB double-to-int vA, vB double-to-long vA, vB double-to-float vA, vB int-to-byte vA, vB int-to-char vA, vB int-to-short vA, vB # 九、返回与异常 return-void # 返回空(void) return vA # 返回 32 位值 return-wide vA # 返回 64 位值 return-object vA # 返回对象引用 throw vA # 抛出 vA 中的异常对象 # 十、监视器(同步块) monitor-enter vA # 进入同步块,锁定 vA 指向的对象 monitor-exit vA # 退出同步块,释放 vA 指向的对象 # 十一、Payload 伪指令 # 这些不是真正的 opcode,而是被 goto/switch/fill-array-data # 引用的"数据块"。反编译产物里常见为 .packed-switch 等。 # ---- packed-switch-payload ---- # 供 packed-switch 使用;被引用的 label 处放置类似: :switch_data .packed-switch 0x0 :case0 :case1 :case2 .end packed-switch # ---- sparse-switch-payload ---- :switch_data .sparse-switch 0x1 -> :case1 0x10 -> :case2 0x64 -> :case3 .end sparse-switch # ---- fill-array-data-payload ---- :array_data .array-data 4 # 元素宽度(1/2/4/8 字节) 0x1 0x2 0x3 0x4 .end array-data # 十二、Try / Catch / Catchall # 示例: # :try_start # invoke-static {}, LFoo;->bar()V # :try_end # .catch Ljava/lang/Exception; {:try_start .. :try_end} :catch_handler # .catchall {:try_start .. :try_end} :catchall_handler # # :catch_handler # move-exception v0 # ... # return-void # # :catchall_handler # move-exception v0 # throw v0 # # 说明: # .catch —— 捕获指定类型异常 # .catchall —— 捕获所有异常(相当于 finally 的兜底) # .catch 必须写在 .catchall 之前(若有多个 .catch,按书写顺序匹配)。 # 十三、Smali 文件结构 / 方法 / 类 / 字段声明 .class public Lcom/example/Foo; .super Ljava/lang/Object; .source "Foo.java" # 接口实现 .implements Ljava/lang/Runnable; # 注解 .annotation runtime Ljava/lang/Deprecated; .end annotation # 字段 .field private static final TAG:Ljava/lang/String; = "Foo" .field private count:I # 方法 .method public constructor ()V .locals 1 invoke-direct {p0}, Ljava/lang/Object;->()V return-void .end method .method public static main([Ljava/lang/String;)V .locals 2 .param p0, "args" # [Ljava/lang/String; .annotation system Ldalvik/annotation/Signature; value = { "[Ljava/lang/String;" } .end annotation .prologue .line 10 # ... 方法体 ... return-void .end method # ---- 常用指令 ---- .locals N # 方法内非参数寄存器数量 .registers N # 总寄存器数(含参数) .param pX, "name" # 参数名(调试用) .prologue # 方法体开始(调试用) .epilogue # 方法体结束(调试用) .line N # 行号(调试用) .local vX, "name":Type # 局部变量名(调试用) .end local vX .restart local vX .source "file.java" # 源文件名 .annotation ... # 注解块 .end annotation .array-data ... # 数组数据块 .end array-data # 十四、ODEX 快速指令(quickened opcodes) # 说明:这些指令只在优化后的 .odex 文件里出现, # 通常是 invoke-virtual / iget 等被替换为的快速形式。 # 反编译 .odex 或从 ART 转储时会看到。 # ---- 快速 invoke 族 ---- invoke-virtual-quick {vC, vD, vE, vF, vG}, vtable@BBBB invoke-virtual-quick/range {vCCCC .. vNNNN}, vtable@BBBB invoke-super-quick {vC, vD, vE, vF, vG}, vtable@BBBB invoke-super-quick/range {vCCCC .. vNNNN}, vtable@BBBB invoke-direct-empty {vC} # 空构造函数调用(优化产物) invoke-object-init/range {vCCCC .. vNNNN}, meth@BBBB invoke-virtual-quick {vC, vD}, vtable@BBBB # ---- 快速字段访问 ---- iget-quick vA, vB, offset@CCCC iget-wide-quick vA, vB, offset@CCCC iget-object-quick vA, vB, offset@CCCC iput-quick vA, vB, offset@CCCC iput-wide-quick vA, vB, offset@CCCC iput-object-quick vA, vB, offset@CCCC # ---- 其它快速指令 ---- execute-inline {vC, vD, vE, vF}, inline@BBBB execute-inline/range {vCCCC .. vNNNN}, inline@BBBB invoke-virtual-quick {vC}, vtable@BBBB throw-verification-error vAA, kind@BBBB # 校验错误占位 return-void-barrier # 内存屏障后返回(优化产物) # 十五、指令格式速查(register / literal 字段宽度) # 4 位寄存器:v0 ~ v15 (vA) # 8 位寄存器:v0 ~ v255 (vAA) # 16 位寄存器:v0 ~ v65535 (vAAAA) # # 4 位字面量: #+B (-8 ~ +7,多用于 const/4) # 16 位字面量: #+BBBB (-32768 ~ +32767) # 32 位字面量: #+BBBBBBBB # 64 位字面量: #+BBBBBBBBBBBBBBBB # # 常见后缀: # /2addr —— 2 地址格式,vA = vA op vB # /lit8 —— 8 位字面量 # /lit16 —— 16 位字面量 # /range —— 寄存器连续区间 {vCCCC .. vNNNN} # /from16 —— 源寄存器扩展到 16 位 # /16 /32 —— 寄存器或偏移字段扩展到 16 / 32 位 # /jumbo —— 字符串 / 类型 / 方法索引扩展到 32 位(DEX ≥ 038) # 十六、操作码区间速查(便于按数值定位) # 00 nop # 01–09 move 族 # 0a–12 move-result / move-exception / return # 13–1c const 族 # 1d–1e monitor-enter / monitor-exit # 1f–20 check-cast / instance-of # 21–23 array-length / new-instance / new-array # 24–26 filled-new-array 族 # 27–28 throw / goto # 29–2a packed-switch / sparse-switch # 2b–2c cmpl-float / cmpg-float # 2d–2e cmpl-double / cmpg-double # 2f–31 cmp-long / if-eq / if-ne # 32–37 if-lt / if-ge / if-gt / if-le / if-eqz / if-nez # 38–3d if-ltz / if-gez / if-gtz / if-lez 等 # 3e–43 aget 族 # 44–4a aput 族 # 4b–51 iget 族 # 52–58 iput 族 # 59–5f sget 族 # 60–66 sput 族 # 67–6d invoke-* 族(5 寄存器) # 6e–72 invoke-* 族(range) # 73 unused # 74–78 invoke-* 族(其它变体 / jumbo 前身) # 79–7a unused # 7b–8f 一元运算 + 类型转换 # 90–af 二元运算(含 /2addr) # b0–cf 二元运算(lit16 / lit8) # d0–d7 int/long 其它运算 # d8–e2 invoke-polymorphic / invoke-custom 等 # e3–f9 unused / 厂商扩展 # fa–ff ODEX 快速指令(执行时替换) # 结束