Android DEX 文件格式与 Smali 语言
最近在探索逆向相关的知识, 总结为一个笔记. 如有大佬路过, 可一起交流.
一、Android 虚拟机概述
1.1 发展历程
| 阶段 | 虚拟机 | 说明 |
|---|---|---|
| Android 4.4 之前 | Dalvik VM | 解释执行 + JIT (Just-In-Time) 编译 |
| Android 5.0 - 6.0 | ART (预置) | 预编译 (AOT, Ahead-Of-Time),安装时编译为机器码 |
| Android 7.0+ | ART (混合模式) | JIT + AOT 混合,Profile Guided 编译 |
| Android 8.0+ | ART (改进) | 更优的 Profile 收集与编译策略 |
1.2 Dalvik vs ART 核心差异
| 特性 | Dalvik | ART |
|---|---|---|
| 执行方式 | JIT 即时编译 + 解释 | AOT 预编译 + JIT + 解释 |
| 安装速度 | 快 | 慢(AOT 模式) |
| 首次启动 | 快(边解释边编译) | 快(已编译为机器码) |
| 占用空间 | 小 | 大(存有机器码) |
| 运行时性能 | 一般 | 好 |
| GC 方式 | 单代,标记-清除为主 | 并发 GC + 压缩 GC |
1.3 Java 字节码 vs Dalvik 字节码
| 对比项 | Java (JVM) | Dalvik/ART (Android) |
|---|---|---|
| 字节码文件 | .class (每个类一个文件) |
.dex (所有类合并到一个文件) |
| 指令集 | 基于栈 (Stack-based) | 基于寄存器 (Register-based) |
| 指令长度 | 1 字节 | 变长 (2/4/6 字节等) |
| 常量池 | 每个 class 独立 | 所有类共享常量池 |
| 设计目标 | 跨平台 | 移动端、低内存 |
为什么 Android 使用基于寄存器的指令集?
基于栈的字节码(JVM)指令短但指令条数多,解释执行时有大量栈操作开销;
基于寄存器的字节码(Dalvik)指令较长但指令条数少,减少了解释器的分派与栈操作开销,更适合 CPU 资源有限的移动设备。
二、DEX 文件格式详解
2.1 DEX 文件整体结构
+---------------------------+
| DEX Header | ← 文件头(固定 0x70 字节)
+---------------------------+
| String Pool | ← 字符串常量池
+---------------------------+
| Type Pool | ← 类型(类名/方法签名)索引池
+---------------------------+
| Proto Pool | ← 方法原型(参数+返回类型)池
+---------------------------+
| Field Pool | ← 字段池
+---------------------------+
| Method Pool | ← 方法池
+---------------------------+
| Class Defs | ← 类定义列表
+---------------------------+
| Data Section | ← 数据区(代码、注解等)
+---------------------------+
| Map Section | ← 映射区(MapItem 列表)
+---------------------------+
2.2 DEX Header 结构 (0x70 字节)
| 偏移 | 大小 | 字段 | 说明 |
|---|---|---|---|
| 0x00 | 8 | magic | DEX 魔数,值为 dex\n035\0 |
| 0x08 | 4 | checksum | ADLER32 校验和 |
| 0x0C | 20 | signature | SHA-1 签名 |
| 0x20 | 4 | file_size | 整个 DEX 文件大小 |
| 0x24 | 4 | header_size | 头部大小(通常 0x70) |
| 0x28 | 4 | endian_tag | 字节序标记 (0x12345678) |
| 0x2C | 4 | link_size | 链接段大小 |
| 0x30 | 4 | link_off | 链接段偏移 |
| 0x34 | 4 | map_off | Map 段偏移 |
| 0x38 | 4 | string_ids_size | 字符串 ID 数量 |
| 0x3C | 4 | string_ids_off | 字符串 ID 列表偏移 |
| 0x40 | 4 | type_ids_size | 类型 ID 数量 |
| 0x44 | 4 | type_ids_off | 类型 ID 列表偏移 |
| 0x48 | 4 | proto_ids_size | 原型 ID 数量 |
| 0x4C | 4 | proto_ids_off | 原型 ID 列表偏移 |
| 0x50 | 4 | field_ids_size | 字段 ID 数量 |
| 0x54 | 4 | field_ids_off | 字段 ID 列表偏移 |
| 0x58 | 4 | method_ids_size | 方法 ID 数量 |
| 0x5C | 4 | method_ids_off | 方法 ID 列表偏移 |
| 0x60 | 4 | class_defs_size | 类定义数量 |
| 0x64 | 4 | class_defs_off | 类定义列表偏移 |
| 0x68 | 4 | data_size | 数据区大小 |
| 0x6C | 4 | data_off | 数据区偏移 |
2.3 各数据池详解
(1) String Pool (字符串池)
-
每个条目是一个 偏移量 (4 字节),指向数据区的 MUTF-8 编码字符串
-
MUTF-8 是修改版 UTF-8,以
0x00结尾,使用 2 字节编码\u0000 -
字符串内容编码格式:
[uleb128 长度] [字符数据...] [0x00 结束]示例:字符串 "hello"
实际存储: 05 68 65 6C 6C 6F 00
↑ ↑
| +-- 字符数据 "hello"
+-- uleb128 长度 = 5
(2) Type Pool (类型池)
- 每个条目是一个 string_id 索引(4 字节),指向字符串池中对应的类型描述字符串
- 类型描述格式与 JVM 一致:
| 类型 | Java 表示 | DEX 类型描述 |
|---|---|---|
| int | int |
I |
| float | float |
F |
| long | long |
J |
| double | double |
D |
| boolean | boolean |
Z |
| char | char |
C |
| short | short |
S |
| byte | byte |
B |
| void | (无) | V |
| 引用类型 | String |
Ljava/lang/String; |
| 数组 | int[] |
[I |
| 二维数组 | int[][] |
[[I |
(3) Proto Pool (方法原型池)
每个原型条目包含:
shorty_idx--- 方法签名简写字符串的偏移(如"(II)V")return_type_idx--- 返回类型索引param_off--- 参数列表偏移(若为 0 表示无参)
(4) Field Pool (字段池)
每个条目包含:
-
class_idx--- 所属类(type_id 索引) -
type_idx--- 字段类型(type_id 索引) -
name_idx--- 字段名(string_id 索引)示例:
public String name;所在类Person
class_idx → type_id → "Lcom/example/Person;"
type_idx → type_id → "Ljava/lang/String;"
name_idx → string_id → "name"
(5) Method Pool (方法池)
每个条目包含:
class_idx--- 所属类(type_id 索引)proto_idx--- 方法原型(proto_id 索引)name_idx--- 方法名(string_id 索引)
(6) Class Defs (类定义列表)
每个类定义条目包含:
| 偏移 | 大小 | 字段 | 说明 |
|---|---|---|---|
| 0x00 | 4 | class_idx | 类 type_id 索引 |
| 0x04 | 4 | access_flags | 访问标志 |
| 0x08 | 4 | superclass_idx | 父类 type_id 索引 |
| 0x0C | 4 | interfaces_off | 接口列表偏移 |
| 0x10 | 4 | source_file_idx | 源文件名(string_id 索引) |
| 0x14 | 4 | annotations_off | 注解数据偏移 |
| 0x18 | 4 | class_data_off | 类数据偏移 |
| 0x1C | 4 | static_values_off | 静态字段初始值偏移 |
访问标志 (access_flags):
| 标志 | 值 | 说明 |
|---|---|---|
ACC_PUBLIC |
0x1 | public |
ACC_PRIVATE |
0x2 | private |
ACC_PROTECTED |
0x4 | protected |
ACC_STATIC |
0x8 | static |
ACC_FINAL |
0x10 | final |
ACC_SYNCHRONIZED |
0x20 | synchronized |
ACC_VOLATILE |
0x40 | volatile |
ACC_BRIDGE |
0x40 | bridge |
ACC_TRANSIENT |
0x80 | transient |
ACC_VARARGS |
0x80 | varargs |
ACC_NATIVE |
0x100 | native |
ACC_INTERFACE |
0x200 | interface |
ACC_ABSTRACT |
0x400 | abstract |
ACC_STRICT |
0x800 | strictfp |
ACC_SYNTHETIC |
0x1000 | synthetic |
ACC_ANNOTATION |
0x2000 | annotation |
ACC_ENUM |
0x4000 | enum |
ACC_CONSTRUCTOR |
0x10000 | 构造方法 |
ACC_DECLARED_SYNCHRONIZED |
0x20000 | declared synchronized |
(7) ULEB128 编码
DEX 文件中大量使用 ULEB128 (Unsigned Little Endian Base 128) 变长编码来压缩整数:
编码规则:
- 每个字节的高位(bit 7)表示是否还有后续字节
- 实际数据存储在低 7 位
- 小端序排列
示例:编码 6245(十六进制 0x1865)
二进制:0001 1000 0110 0101
从低位开始每 7 位一组:
组0:110 0101 = 0x65
组1:011 0000 = 0x30
组2:0
需要2组:组0和组1,小端写入:
字节0:0x65 | 0x80 = 0xE5(高位置1表示还有后续字节)
字节1:0x30(高位为0表示结束)
结果:E5 30
验证:((0x30 & 0x7F) << 7) | (0x65 & 0x7F) = 0x1800 | 0x65 = 0x1865 = 6245 ✓
三、Smali 语言详解
3.1 什么是 Smali
Smali 是 Dalvik 字节码的 可读汇编语言。
| 工具 | 作用 |
|---|---|
| baksmali | 将 .dex 文件反汇编为 .smali 文件 |
| smali | 将 .smali 文件汇编为 .dex 文件 |
APK 文件结构:
classes.dex ← 主 DEX
classes2.dex ← 多 DEX (方法数超过 65535 时)
classes3.dex ...
反编译流程:
APK → 解压 → .dex → baksmali → .smali 文件
.smali 文件 → smali → .dex → 打包 → APK
3.2 基本语法与结构
3.2.1 文件结构与 Java 对照
Java 类:
java
package com.example;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
private String message = "Hello";
public MainActivity() {
super();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public String getMessage() {
return this.message;
}
}
等价的 Smali:
smali
.class public Lcom/example/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"
.field private message:Ljava/lang/String;
.method public constructor <init>()V
.registers 3
.param p0, "this"
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
const-string v0, "Hello"
iput-object v0, p0, Lcom/example/MainActivity;->message:Ljava/lang/String;
return-void
.end method
.method protected onCreate(Landroid/os/Bundle;)V
.registers 3
.param p0, "this"
.param p1, "savedInstanceState"
invoke-direct {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
const v0, 0x7f030001
invoke-virtual {p0, v0}, Lcom/example/MainActivity;->setContentView(I)V
return-void
.end method
.method public getMessage()Ljava/lang/String;
.registers 2
iget-object v0, p0, Lcom/example/MainActivity;->message:Ljava/lang/String;
return-object v0
.end method
3.2.2 Smali 文件结构要素
| 元素 | 语法 | 说明 |
|---|---|---|
| 类声明 | .class [访问标志] [类名] |
声明当前类 |
| 父类 | .super [父类名] |
继承的父类 |
| 源文件 | .source "[文件名]" |
对应的 Java 源文件名 |
| 注解 | .annotation [属性] [类型] |
类、方法或字段上的注解 |
| 字段 | .field [标志] [名称]:[类型] |
声明字段 |
| 方法 | .method [标志] [名称]([参数])[返回类型] |
声明方法 |
| 方法结束 | .end method |
标记方法体结束 |
| 寄存器声明 | .registers N |
该方法需要 N 个寄存器 |
| 局部变量数 | .locals N |
N 个局部变量寄存器 |
| 参数说明 | .param [寄存器], "[名称]" |
为方法参数命名 |
| 行号 | .line N |
对应 Java 源码行号 |
3.2.3 寄存器体系
Dalvik 有两大类寄存器:
-
基础寄存器:
v0-v65535 -
参数寄存器:
p0-pn,映射到高位 v 寄存器寄存器分配规则:
.registers N 表示总寄存器数 N
.locals M 表示局部变量寄存器数为 M局部寄存器使用 v0, v1, ..., v{M-1} 参数寄存器从 v{N-参数个数} 开始,使用 p0, p1, ..., p{n-1}示例:非静态方法
.method public foo(II)V
.registers 5 ; 共5个寄存器
.locals 2 ; v0, v1 是局部变量; 参数映射: ; p0 = v2 → this 引用 ; p1 = v3 → 第一个 int 参数 ; p2 = v4 → 第二个 int 参数 .end method
关键:p0 = this --- 非 static 方法中,p0 总是 this 引用。
3.2.4 类型表示
| 类型 | Smali 表示 | Java 表示 | 占寄存器数 |
|---|---|---|---|
| boolean | Z |
boolean | 1 |
| byte | B |
byte | 1 |
| char | C |
char | 1 |
| short | S |
short | 1 |
| int | I |
int | 1 |
| long | J |
long | 2 |
| float | F |
float | 1 |
| double | D |
double | 2 |
| void | V |
(无返回值) | 0 |
| 对象 | Ljava/lang/String; |
String |
1 |
| 数组 | [I |
int[] |
1 |
3.3 Smali 完整指令集与 Java 对照
3.3.1 数据加载与存储
| Java 代码 | Smali 指令 | 说明 |
|---|---|---|
x = 5 |
const/4 v0, 0x5 |
4 位常量加载 |
x = 65536 |
const/16 v0, 0x10000 |
16 位常量 |
x = "hello" |
const-string v0, "hello" |
字符串常量 |
x = null |
const/4 v0, 0x0 |
加载 null |
obj.field = x |
iput v0, p0, LClass;->field:LType; |
实例字段写入 |
x = obj.field |
iget v0, p0, LClass;->field:LType; |
实例字段读取 |
MyClass.f = x |
sput v0, LMyClass;->f:LType; |
静态字段写入 |
x = MyClass.f |
sget v0, LMyClass;->f:LType; |
静态字段读取 |
arr[i] = x |
aput v0, v1, v2 |
数组写入 |
x = arr[i] |
aget v0, v1, v2 |
数组读取 |
obj = new Obj() |
new-instance v0, LClass; |
创建对象 |
arr = new int[10] |
new-array v0, v1, [I |
创建数组 |
3.3.2 算术运算
| Java 代码 | Smali 指令 | 说明 |
|---|---|---|
a + b |
add-int v0, v1, v2 |
v0 = v1 + v2 |
a - b |
sub-int v0, v1, v2 |
v0 = v1 - v2 |
a * b |
mul-int v0, v1, v2 |
v0 = v1 * v2 |
a / b |
div-int v0, v1, v2 |
v0 = v1 / v2 |
a % b |
rem-int v0, v1, v2 |
v0 = v1 % v2 |
a & b |
and-int v0, v1, v2 |
v0 = v1 & v2 |
| `a | b` | or-int v0, v1, v2 |
a ^ b |
xor-int v0, v1, v2 |
v0 = v1 ^ v2 |
a << b |
shl-int v0, v1, v2 |
v0 = v1 << v2 |
a >> b |
shr-int v0, v1, v2 |
v0 = v1 >> v2 |
a >>> b |
ushr-int v0, v1, v2 |
v0 = v1 >>> v2 |
-a |
neg-int v0, v1 |
v0 = -v1 |
~a |
not-int v0, v1 |
v0 = ~v1 |
long/float/double 类型指令将
int替换为对应类型即可,如add-long,sub-float,mul-double。
完整示例对照:
java
// Java
public int calc(int a, int b) {
return (a + b) * (a - b);
}
smali
.method public calc(II)I
.registers 5
add-int v0, p1, p2 ; v0 = a + b
sub-int v1, p1, p2 ; v1 = a - b
mul-int v2, v0, v1 ; v2 = (a+b)*(a-b)
return v2
.end method
3.3.3 条件跳转
| 指令 | 条件 | 等价的 Java |
|---|---|---|
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 |
== 0 |
if-nez vA, :label |
vA != 0 |
!= 0 |
if-ltz vA, :label |
vA < 0 |
< 0 |
if-gez vA, :label |
vA >= 0 |
>= 0 |
if-gtz vA, :label |
vA > 0 |
> 0 |
if-lez vA, :label |
vA <= 0 |
<= 0 |
if-else 分支对照:
java
// Java
public String checkScore(int score) {
if (score >= 60) return "Pass";
else return "Fail";
}
smali
.method public checkScore(I)Ljava/lang/String;
.registers 3
const/16 v0, 0x3c
if-lt p1, v0, :else
const-string v1, "Pass"
goto :end_if
:else
const-string v1, "Fail"
:end_if
return-object v1
.end method
for 循环对照:
java
// Java
public int sum(int n) {
int total = 0;
for (int i = 0; i < n; i++) total += i;
return total;
}
smali
.method public sum(I)I
.registers 4
const/4 v0, 0x0 ; total = 0
const/4 v1, 0x0 ; i = 0
:loop
if-ge v1, p1, :end ; if i >= n break
add-int/2addr v0, v1 ; total += i
add-int/lit8 v1, v1, 1 ; i++
goto :loop
:end
return v0
.end method
3.3.4 方法调用
| 调用类型 | Smali 指令 | Java 对应 |
|---|---|---|
| 虚方法 | invoke-virtual |
obj.method() |
| 直接方法 | invoke-direct |
private / super / <init> |
| 静态方法 | invoke-static |
Class.staticMethod() |
| 接口方法 | invoke-interface |
interface.method() |
| 父类方法 | invoke-super |
super.method() |
| 多参 | .../range |
4个以上参数时使用 |
调用格式: invoke-{type} {vC, vD, ...}, Lclass;->method(params)returnType
示例对照:
java
// Java
public void test() {
int sum = Utils.add(3, 5);
Utils util = new Utils();
String msg = util.concat("Hello", " World");
System.out.println(msg);
}
smali
.method public test()V
.registers 5
const/4 v0, 0x3
const/4 v1, 0x5
invoke-static {v0, v1}, Lcom/example/Utils;->add(II)I
move-result v0
new-instance v1, Lcom/example/Utils;
invoke-direct {v1}, Lcom/example/Utils;-><init>()V
const-string v2, "Hello"
const-string v3, " World"
invoke-virtual {v1, v2, v3}, Lcom/example/Utils;->concat(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v4
sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v2, v4}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
move-result 系列指令(获取返回值):
| 指令 | 用途 |
|---|---|
move-result vA |
获取 int/float 返回值 |
move-result-object vA |
获取对象返回值 |
move-result-wide vA |
获取 64位(long/double)返回值 |
move-exception vA |
获取 catch 块中的异常对象 |
3.3.5 Switch 语句
packed-switch(密集 case):
smali
.method public getDayName(I)Ljava/lang/String;
.registers 3
packed-switch p1, :p_switch_data
const-string v0, "Unknown"
return-object v0
:pswitch_1
const-string v0, "Monday"
return-object v0
:pswitch_2
const-string v0, "Tuesday"
return-object v0
:p_switch_data
.packed-switch 1
:pswitch_1
:pswitch_2
.end packed-switch
.end method
sparse-switch(稀疏 case):
smali
sparse-switch p1, :s_switch_data
const-string v0, "Unknown"
return-object v0
:sswitch_403
const-string v0, "Forbidden"
return-object v0
:s_switch_data
.sparse-switch
0x193 -> :sswitch_403
0x194 -> :sswitch_404
.end sparse-switch
区别: packed-switch 用 O(1) 索引,sparse-switch 用 O(log n) 二分查找。
3.3.6 异常处理
java
// Java
public String readFile(String path) {
try {
FileInputStream fis = new FileInputStream(path);
byte[] data = new byte[fis.available()];
fis.read(data);
fis.close();
return new String(data);
} catch (FileNotFoundException e) {
return "File not found";
} catch (IOException e) {
return "IO Error";
} finally {
System.out.println("Done");
}
}
smali
:try_start_0
new-instance v0, Ljava/io/FileInputStream;
invoke-direct {v0, p1}, Ljava/io/FileInputStream;-><init>(Ljava/lang/String;)V
invoke-virtual {v0}, Ljava/io/FileInputStream;->available()I
move-result v1
new-array v1, v1, [B
invoke-virtual {v0, v1}, Ljava/io/FileInputStream;->read([B)I
invoke-virtual {v0}, Ljava/io/FileInputStream;->close()V
new-instance v2, Ljava/lang/String;
invoke-direct {v2, v1}, Ljava/lang/String;-><init>([B)V
:try_end_0
.catch Ljava/io/FileNotFoundException; { :try_start_0 .. :try_end_0 } :catch_fnf
.catch Ljava/io/IOException; { :try_start_0 .. :try_end_0 } :catch_io
.catchall { :try_start_0 .. :try_end_0 } :catchall_0
:goto_finally
sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v4, "Done"
invoke-virtual {v3, v4}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-object v2
:catch_fnf
move-exception v5
const-string v2, "File not found"
goto :goto_finally
:catchall_0
move-exception v5
sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v4, "Done"
invoke-virtual {v3, v4}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
throw v5
.end method
3.3.7 synchronized 同步
java
// Java
public class Counter {
private int count = 0;
public synchronized void increment() { count++; }
public void decrement() {
synchronized (this) { count--; }
}
}
smali
# 方法级同步------access_flags 含有 ACC_SYNCHRONIZED
.method public synchronized increment()V
.registers 2
iget v0, p0, Lcom/example/Counter;->count:I
add-int/lit8 v0, v0, 1
iput v0, p0, Lcom/example/Counter;->count:I
return-void
.end method
# 代码块级同步------显式 monitor-enter / monitor-exit
.method public decrement()V
.registers 4
monitor-enter p0
:try_start
iget v0, p0, Lcom/example/Counter;->count:I
add-int/lit8 v0, v0, -1
iput v0, p0, Lcom/example/Counter;->count:I
monitor-exit p0
:try_end
.catchall { :try_start .. :try_end } :catchall
return-void
:catchall
move-exception v0
monitor-exit p0
throw v0
.end method
3.4 注解与内部类
3.4.1 注解 (Annotations)
smali
# 在方法上使用注解
.method public doWork()V
.registers 1
.annotation runtime Lcom/example/Loggable;
level = "DEBUG"
enabled = true
.end annotation
return-void
.end method
3.4.2 内部类
java
// Java
public class Outer {
private String name;
public class Inner {
public void print() { System.out.println(name); }
}
}
编译器处理为两个独立的 .smali 文件:
Outer.smali--- 包含MemberClasses注解Outer$Inner.smali--- 包含this$0引用外部类、EnclosingClass和InnerClass注解
smali
# Outer$Inner.smali
.class public Lcom/example/Outer$Inner;
.super Ljava/lang/Object;
.field final synthetic this$0:Lcom/example/Outer;
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x0
name = "Inner"
.end annotation
.method public <init>(Lcom/example/Outer;)V
iput-object p1, p0, Lcom/example/Outer$Inner;->this$0:Lcom/example/Outer;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public print()V
iget-object v0, p0, Lcom/example/Outer$Inner;->this$0:Lcom/example/Outer;
iget-object v0, v0, Lcom/example/Outer;->name:Ljava/lang/String;
sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
3.5 泛型(类型擦除)
Java 泛型在字节码中擦除为 Object,签名信息保留在注解中:
smali
# Box<T> 擦除后
.field private item:Ljava/lang/Object;
.annotation system Ldalvik/annotation/Signature;
value = "Ljava/lang/Object;TT;>"
.end annotation
.method public get()Ljava/lang/Object;
iget-object v0, p0, Lcom/example/Box;->item:Ljava/lang/Object;
return-object v0
.end method
# 调用端自动插入 check-cast
invoke-virtual {v0}, Lcom/example/Box;->get()Ljava/lang/Object;
move-result-object v1
check-cast v1, Ljava/lang/String;
3.6 调试与常用工具
| 工具 | 用途 | 命令 |
|---|---|---|
| apktool | APK 解包/打包 | apktool d app.apk -o out |
| baksmali | DEX → Smali | java -jar baksmali.jar d classes.dex |
| smali | Smali → DEX | java -jar smali.jar a smali_out -o classes.dex |
| dexdump | DEX 十六进制导出 | dexdump -d classes.dex |
调试日志注入技巧:
smali
# 在方法入口处插入
const-string v0, "DEBUG_TAG"
const-string v1, "进入方法 foo"
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
四、Smali 指令速查表
4.1 加载常量
| 指令 | 范围 | 说明 |
|---|---|---|
const/4 vA, #+B |
-8~7 | 小整数 |
const/16 vA, #+BBBB |
-32768~32767 | 中整数 |
const vA, #+BBBBBBBB |
32位 | 大整数 |
const-wide/16 vA, #+BBBB |
64位低16位 | 小 long |
const-wide/32 vA, #+BBBBBBBB |
64位低32位 | 中 long |
const-wide vA, #+BBBBBBBBBBBBBBBB |
64位 | 大 long |
const-string vA, string_id |
字符串 | 加载字符串 |
const-class vA, type_id |
类型 | 加载 Class |
4.2 数据移动
| 指令 | 说明 |
|---|---|
move vA, vB |
vA = vB |
move-wide vA, vB |
64位值移动 |
move-object vA, vB |
对象引用移动 |
move-result vAA |
获取 invoke 返回值(int) |
move-result-object vAA |
获取 invoke 返回值(对象) |
move-result-wide vAA |
获取 invoke 返回值(long/double) |
move-exception vAA |
获取 catch 异常对象 |
4.3 返回指令
| 指令 | 说明 |
|---|---|
return-void |
void 返回 |
return vAA |
返回 int/float |
return-wide vAA |
返回 long/double |
return-object vAA |
返回对象 |
4.4 数组操作
| 指令 | 说明 |
|---|---|
array-length vA, vB |
vA = vB.length |
new-array vA, vB, type |
创建数组 |
aget vAA, vBB, vCC |
int 数组读 |
aget-wide vAA, vBB, vCC |
long 数组读 |
aget-object vAA, vBB, vCC |
对象数组读 |
aput vAA, vBB, vCC |
int 数组写 |
aput-wide vAA, vBB, vCC |
long 数组写 |
aput-object vAA, vBB, vCC |
对象数组写 |
4.5 实例操作
| 指令 | 说明 |
|---|---|
new-instance vAA, type |
创建新实例 |
instance-of vA, vB, type |
类型检查 (instanceof) |
check-cast vAA, type |
类型转换 |
monitor-enter vAA |
获取锁 |
monitor-exit vAA |
释放锁 |
throw vAA |
抛出异常 |
4.6 类型转换
| 指令 | 说明 |
|---|---|
neg-int / not-int |
整数取负/取反 |
int-to-long / int-to-float / int-to-double |
int 扩展 |
long-to-int / long-to-float / long-to-double |
long 转换 |
float-to-int / float-to-long / float-to-double |
float 转换 |
double-to-int / double-to-long / double-to-float |
double 转换 |
int-to-byte / int-to-char / int-to-short |
int 缩窄 |
五、DEX 文件格式进阶
5.1 Class Data 区详解
类定义中的 class_data_off 指向 Class Data 结构,使用 uleb128 编码:
静态字段数量 (uleb128)
实例字段数量 (uleb128)
直接方法数量 (uleb128) ← private / static / <init>
虚方法数量 (uleb128) ← public / protected / package
// 每个字段编码(先静态再实例)
[field_idx_diff] ← 与上一个 field_id 的差值(uleb128)
[access_flags] ← 访问标志(uleb128)
// 每个方法编码(先 direct 再 virtual)
[method_idx_diff] ← 与上一个 method_id 的差值(uleb128)
[access_flags] ← 访问标志(uleb128)
[code_off] ← CodeItem 偏移量(uleb128,0 表示 native/abstract)
差分编码:由于 field_id / method_id 在表中按 class→name→type 排序,相邻条目的索引值相近,差分后用 uleb128 可大幅压缩空间。
5.2 CodeItem 结构
┌──────────┬────────┬──────────────────────────────┐
│ 偏移 │ 大小 │ 字段 │
├──────────┼────────┼──────────────────────────────┤
│ 0x00 │ 2 │ registers_size │ ← 寄存器总数
│ 0x02 │ 2 │ ins_size │ ← 入参寄存器数
│ 0x04 │ 2 │ outs_size │ ← 出参寄存器数
│ 0x06 │ 2 │ tries_size │ ← try-catch 块数
│ 0x08 │ 4 │ debug_info_off │ ← 调试信息偏移
│ 0x0C │ 4 │ insns_size │ ← 指令长度(2字节单元)
│ 0x10 │ ... │ insns[] │ ← 指令序列
│ ... │ padding│ 对齐到4字节 │
│ ... │ ... │ tries[] │ ← try-catch 列表
│ ... │ ... │ handlers[] │ ← catch 处理器列表
└──────────┴────────┴──────────────────────────────┘
5.3 Map Section (映射区)
DEX 文件的 map_off 指向一个 MapItem 列表,用于快速定位各段:
map_item 结构:
┌──────────┬────────┬─────────────────────────────┐
│ 偏移 │ 大小 │ 字段 │
├──────────┼────────┼─────────────────────────────┤
│ 0x00 │ 2 │ type │ ← 段类型(见下表)
│ 0x02 │ 2 │ unused │
│ 0x04 │ 4 │ size │ ← 条目数
│ 0x08 │ 4 │ offset │ ← 在该段中的偏移
└──────────┴────────┴─────────────────────────────┘
| type 值 | 段类型 | 说明 |
|---|---|---|
| 0x0000 | kDexTypeHeaderItem | DEX 头部 |
| 0x0001 | kDexTypeStringIdItem | 字符串 ID |
| 0x0002 | kDexTypeTypeIdItem | 类型 ID |
| 0x0003 | kDexTypeProtoIdItem | 原型 ID |
| 0x0004 | kDexTypeFieldIdItem | 字段 ID |
| 0x0005 | kDexTypeMethodIdItem | 方法 ID |
| 0x0006 | kDexTypeClassDefItem | 类定义 |
| 0x1000 | kDexTypeCodeItem | 代码 |
| 0x1001 | kDexTypeStringDataItem | 字符串数据 |
| 0x1002 | kDexTypeDebugInfoItem | 调试信息 |
| 0x1003 | kDexTypeAnnotationItem | 注解 |
| 0x2000 | kDexTypeAnnotationSetRefList | 注解引用列表 |
| 0x2001 | kDexTypeAnnotationDirectoryItem | 注解目录 |
| 0x2002 | kDexTypeAnnotationSetItem | 注解集合 |
| 0x2003 | kDexTypeAnnotationOffItem | 注解偏移 |
| 0x2004 | kDexTypeAnnotationSetItem | 注解集合(重) |
| 0x2005 | kDexTypeAnnotationItem | 注解(重) |
5.4 多 DEX (Multi-DEX)
| 概念 | 说明 |
|---|---|
| 65535 限制 | DEX 的 method_id 用 16 位索引,单 DEX 最多 65535 个方法引用 |
| 解决方案 | Android 5.0+ 原生支持多 DEX,低版本需 android.support.multidex |
| 文件命名 | classes.dex, classes2.dex, classes3.dex, ... |
| 主 DEX 规则 | 必须包含 Application、Activity 等四大组件及 MultiDex.install() |
DEX 分包示例:
classes.dex ← 主 DEX,含 Application/Activity/Service/BroadcastReceiver
classes2.dex ← 次 DEX,含第三方库代码
classes3.dex ← 次 DEX,含不常用功能代码
5.5 Compact DEX (CDEX) --- Android 9+
Android 9 (Pie) 引入 Compact DEX 格式,作为 .dex 文件的压缩变体:
| 特性 | 说明 |
|---|---|
| 文件扩展名 | .cdex(打包在 APK 中为 .dex,ART 会在 OTA 时转换) |
| 压缩算法 | 自定义的 LZ4 变体 + 共享字符串表 |
| 优势 | APK 安装包体积更小 |
| 局限 | 只能在 ART 上运行,需由 dex2oat 处理 |
六、Smali 进阶技术与 Java 对照
6.1 算术增强指令(2-in-1 模式)
Dalvik 在标准三寄存器指令外,还提供操作数与目标寄存器相同(就地修改)的增强模式:
/2addr 后缀: vA 同时作为源和目标
| 指令 | 等价于 | 说明 |
|---|---|---|
add-int/2addr v0, v1 |
v0 = v0 + v1 |
就地加法 |
sub-int/2addr v0, v1 |
v0 = v0 - v1 |
就地减法 |
mul-int/2addr v0, v1 |
v0 = v0 * v1 |
就地乘法 |
div-int/2addr v0, v1 |
v0 = v0 / v1 |
就地除法 |
rem-int/2addr v0, v1 |
v0 = v0 % v1 |
就地取模 |
and-int/2addr v0, v1 |
v0 = v0 & v1 |
就地与 |
or-int/2addr v0, v1 |
`v0 = v0 | v1` |
xor-int/2addr v0, v1 |
v0 = v0 ^ v1 |
就地异或 |
shl-int/2addr v0, v1 |
v0 = v0 << v1 |
就地左移 |
shr-int/2addr v0, v1 |
v0 = v0 >> v1 |
就地右移 |
ushr-int/2addr v0, v1 |
v0 = v0 >>> v1 |
就地无符号右移 |
/lit 后缀: 立即数运算
| 指令 | 等价于 |
|---|---|
add-int/lit8 vA, vB, #C |
vA = vB + C(C 为 8 位有符号) |
add-int/lit16 vA, vB, #C |
vA = vB + C(C 为 16 位有符号) |
rsub-int vA, vB, #C |
vA = C - vB(反向减法) |
mul-int/lit8 vA, vB, #C |
vA = vB * C |
div-int/lit8 vA, vB, #C |
vA = vB / C |
rem-int/lit8 vA, vB, #C |
vA = vB % C |
and-int/lit8 vA, vB, #C |
vA = vB & C |
or-int/lit8 vA, vB, #C |
`vA = vB |
java
// Java
public int demo(int x) {
x += 10;
x *= 2;
x -= 5;
return x / 3;
}
smali
.method public demo(I)I
.registers 3
add-int/lit8 v0, p1, 0xa ; v0 = x + 10
const/4 v1, 0x2
mul-int/2addr v0, v1 ; v0 = v0 * 2
add-int/lit8 v0, v0, -5 ; v0 = v0 - 5
const/4 v1, 0x3
div-int/2addr v0, v1 ; v0 = v0 / 3
return v0
.end method
6.2 数组初始化优化
java
// Java 方式1:逐个赋值
int[] arr = new int[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
// Java 方式2:带初始值的简写
int[] arr = {1, 2, 3};
// 或 new int[]{4, 5, 6}
smali
# 方式1:逐个赋值
const/4 v0, 0x3
new-array v0, v0, [I
const/4 v1, 0x1
const/4 v2, 0x0
aput v1, v0, v2
const/4 v1, 0x2
const/4 v2, 0x1
aput v1, v0, v2
# 方式2:filled-new-array(优化方式,仅用于方法参数中的内联数组)
const/4 v0, 0x1
const/4 v1, 0x2
const/4 v2, 0x3
filled-new-array {v0, v1, v2}, [I
move-result-object v0
# 方式3:fill-array-data(编译器常用优化,通过数据区批量填充)
const/4 v0, 0x3
new-array v0, v0, [I
fill-array-data v0, :array_data ; 指向数据区的批量数据
...
:array_data
.array-data 4 ; 每个元素 4 字节
0x00000001 ; 1
0x00000002 ; 2
0x00000003 ; 3
.end array-data
6.3 StringBuilder 与字符串拼接(全展开 vs StringBuilder)
java
// Java
public String greet(String name) {
return "Hello, " + name + "! Welcome!";
}
编译器可能生成两种模式的 Smali:
模式 A:StringBuilder 模式(源码级别)
smali
.method public greet(Ljava/lang/String;)Ljava/lang/String;
.registers 5
new-instance v0, Ljava/lang/StringBuilder;
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
const-string v1, "Hello, "
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invoke-virtual {v0, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
const-string v1, "! Welcome!"
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v0
return-object v0
.end method
模式 B:String.concat 模式(编译器优化后)
smali
.method public greet(Ljava/lang/String;)Ljava/lang/String;
.registers 4
const-string v0, "Hello, "
invoke-virtual {v0, p1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v0
const-string v1, "! Welcome!"
invoke-virtual {v0, v1}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v0
return-object v0
.end method
6.4 compareTo/cmp 比较
java
// Java
public int compare(long a, long b) {
return Long.compare(a, b);
}
smali
.method public compare(JJ)I
.registers 6
.param p0, "this"
.param p1, "a" ; p1(低32位), p2(高32位) = a
.param p3, "b" ; p3(低32位), p4(高32位) = b
cmp-long v0, p1, p3 ; v0 = compare(a, b)
return v0
.end method
浮点比较的 NaN 处理差异:
| 指令 | NaN 时行为 | 适用场景 |
|---|---|---|
cmpl-float/double |
返回 -1 | 用于 < 和 <= 判断(NaN 被视为小于任何值) |
cmpg-float/double |
返回 1 | 用于 > 和 >= 判断(NaN 被视为大于任何值) |
java
// Java
public int check(float a, float b) {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
smali
.method public check(FF)I
.registers 4
cmpl-float v0, p1, p2 ; cmpl: NaN → -1
if-ltz v0, :less
if-gtz v0, :greater
const/4 v0, 0x0
return v0
:less
const/4 v0, -1
return v0
:greater
const/4 v0, 0x1
return v0
.end method
6.5 反射的 Smali 实现
java
// Java
public Object reflectCall(String className, String methodName) throws Exception {
Class<?> clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
return method.invoke(null); // 静态方法,第一个参数为 null
}
smali
.method public reflectCall(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
.registers 8
.param p0, "this"
.param p1, "className"
.param p2, "methodName"
# Class.forName(className)
invoke-static {p1}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
move-result-object v0
# clazz.getDeclaredMethod(methodName)
const/4 v1, 0x0
new-array v1, v1, [Ljava/lang/Class; ; 无参 → 空 Class 数组
invoke-virtual {v0, p2, v1}, Ljava/lang/Class;->getDeclaredMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
move-result-object v2
# method.setAccessible(true)
const/4 v3, 0x1
invoke-virtual {v2, v3}, Ljava/lang/reflect/AccessibleObject;->setAccessible(Z)V
# method.invoke(null) --- 静态方法调用
const/4 v3, 0x0
const/4 v4, 0x0
new-array v4, v4, [Ljava/lang/Object;
invoke-virtual {v2, v3, v4}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
move-result-object v5
return-object v5
.end method
6.6 枚举的 Smali 实现
java
// Java
public enum Color {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF);
final int rgb;
Color(int rgb) { this.rgb = rgb; }
}
编译器将枚举编译为继承 java.lang.Enum 的普通类,同时自动生成 $VALUES 数组和 valueOf/values 方法:
smali
.class public final enum Lcom/example/Color;
.super Ljava/lang/Enum;
.source "Color.java"
# 枚举常量作为静态字段
.field public static final enum RED:Lcom/example/Color;
.field public static final enum GREEN:Lcom/example/Color;
.field public static final enum BLUE:Lcom/example/Color;
# 实例字段
.field final rgb:I
# 编译器合成的 $VALUES 数组
.field private static final synthetic $VALUES:[Lcom/example/Color;
# 静态初始化块(<clinit>)
.method static constructor <clinit>()V
.registers 6
# new Color("RED", 0, 0xFF0000)
new-instance v0, Lcom/example/Color;
const-string v1, "RED"
const/4 v2, 0x0 ; ordinal = 0
const v3, 0xFF0000
invoke-direct {v0, v1, v2, v3}, Lcom/example/Color;-><init>(Ljava/lang/String;II)V
sput-object v0, Lcom/example/Color;->RED:Lcom/example/Color;
# new Color("GREEN", 1, 0x00FF00)
new-instance v0, Lcom/example/Color;
const-string v1, "GREEN"
const/4 v2, 0x1
const v3, 0x00FF00
invoke-direct {v0, v1, v2, v3}, Lcom/example/Color;-><init>(Ljava/lang/String;II)V
sput-object v0, Lcom/example/Color;->GREEN:Lcom/example/Color;
# 初始化 $VALUES 数组
const/4 v0, 0x3
new-array v0, v0, [Lcom/example/Color;
sget-object v1, Lcom/example/Color;->RED:Lcom/example/Color;
const/4 v2, 0x0
aput-object v1, v0, v2
sget-object v1, Lcom/example/Color;->GREEN:Lcom/example/Color;
const/4 v2, 0x1
aput-object v1, v0, v2
sget-object v1, Lcom/example/Color;->BLUE:Lcom/example/Color;
const/4 v2, 0x2
aput-object v1, v0, v2
sput-object v0, Lcom/example/Color;->$VALUES:[Lcom/example/Color;
return-void
.end method
6.7 Lambda 表达式的 Smali 实现
java
// Java
button.setOnClickListener(v -> {
Log.d("TAG", "Clicked!");
});
编译器将 Lambda 转换为 invoke-dynamic + 辅助方法(或直接生成匿名内部类):
smali
# 方式一:invoke-dynamic(Java 8+ 的 Lambda Metafactory)
# 在调用处:
sget-object v0, Lcom/example/MainActivity$1;->INSTANCE:Lcom/example/MainActivity$1;
invoke-interface {v0}, Landroid/view/View$OnClickListener;->onClick(Landroid/view/View;)V
# 同时在同一个类中生成合成方法(lambda$开头):
.method private static synthetic lambda$onCreate$0(Landroid/view/View;)V
.registers 3
const-string v0, "TAG"
const-string v1, "Clicked!"
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
# 方式二:匿名内部类辅助
# 也可以编译为类似匿名内部类的形式
.class Lcom/example/MainActivity$$ExternalSyntheticLambda0;
.implements Landroid/view/View$OnClickListener;
... 实现 onClick 方法并委托给 lambda$onCreate$0
6.8 try-with-resources 的资源关闭模式
java
// Java
public void readFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
System.out.println(br.readLine());
}
}
编译器为 try-with-resources 生成额外的异常抑制逻辑:
smali
.method public readFile(Ljava/lang/String;)V
.registers 8
:try_start_open
new-instance v0, Ljava/io/FileReader;
invoke-direct {v0, p1}, Ljava/io/FileReader;-><init>(Ljava/lang/String;)V
new-instance v1, Ljava/io/BufferedReader;
invoke-direct {v1, v0}, Ljava/io/BufferedReader;-><init>(Ljava/io/Reader;)V
:try_start_body
# 使用资源
invoke-virtual {v1}, Ljava/io/BufferedReader;->readLine()Ljava/lang/String;
move-result-object v2
sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v3, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
# 正常关闭
invoke-virtual {v1}, Ljava/io/BufferedReader;->close()V
:try_end_body
.catchall {:try_start_body .. :try_end_body} :catchall_body
.catch Ljava/lang/Throwable; {:try_start_open .. :try_start_body} :catch_primary
return-void
# 主异常处理 + 资源关闭(含抑制异常)
:catch_primary
move-exception v4
:try_start_close
invoke-virtual {v1}, Ljava/io/BufferedReader;->close()V
:try_end_close
.catch Ljava/lang/Throwable; {:try_start_close .. :try_end_close} :catch_suppressed
throw v4
:catch_suppressed
move-exception v5
invoke-virtual {v4, v5}, Ljava/lang/Throwable;->addSuppressed(Ljava/lang/Throwable;)V
throw v4
# 外层 finally:确保资源关闭
:catchall_body
move-exception v6
throw v6
.end method
6.9 多态与方法的 Smali 表示
java
// Java
public class Animal {
public void speak() { System.out.println("..."); }
}
public class Dog extends Animal {
@Override
public void speak() { System.out.println("Woof!"); }
}
// 调用处
Animal a = new Dog();
a.speak(); // 输出 "Woof!" --- 运行时多态
smali
# Animal 类中
.method public speak()V
.registers 3
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "..."
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
# Dog 类中------invoke-virtual 用于虚方法分发
.method public speak()V
.registers 3
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Woof!"
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
# 调用处------invoke-virtual 实现运行时多态
new-instance v0, Lcom/example/Dog;
invoke-direct {v0}, Lcom/example/Dog;-><init>()V
invoke-virtual {v0}, Lcom/example/Animal;->speak()V ; invoke-virtual 在运行时查找实际类型
invoke-virtual vs invoke-direct:
invoke-virtual:根据对象实际类型(运行时类)查找方法表,支持多态invoke-direct:直接调用指定类的具体方法(private、、super 调用),不进行虚方法分发
6.10 APK 修改实战流程
1. apktool d target.apk -o out/ ← 反编译 APK
2. 编辑 out/smali/com/example/.../ ← 修改 Smali 代码
3. apktool b out/ -o modified.apk ← 重新打包
4. 解压 modified.apk 删除 META-INF/* ← 删除旧签名
5. jarsigner -keystore my.keystore modified.apk alias ← 签名
6. adb install -r modified.apk ← 安装测试
NOP 填充绕过技巧:
smali
# 原始指令(检查 root 权限)
invoke-static {}, Lcom/example/RootDetector;->isRooted()Z
move-result v0
if-eqz v0, :continue ; 不通过则跳走
const-string v0, "Rooted device!"
invoke-static {v0}, Lcom/example/Utils;->exitApp(Ljava/lang/String;)V
:continue
# NOP 绕过(直接跳到 continue 标签)
goto :continue ; 用 goto 无条件跳过检查
七、附录:Java → Smali 快速翻译指南
7.1 常见 Java 结构的 Smali 等价
| Java 结构 | Smali 等价模式 |
|---|---|
int x = 0; |
const/4 v0, 0x0 |
long x = 0L; |
const-wide/16 v0, 0x0 |
String s = "hi"; |
const-string v0, "hi" |
int[] arr = new int[n]; |
new-array v0, v1, [I |
arr.length |
array-length v0, v1 |
obj instanceof T |
instance-of v0, v1, LT; |
(T) obj |
check-cast v0, LT; |
return x; |
return v0 |
return null; |
const/4 v0, 0x0 + return-object v0 |
this.x = x; |
iput v0, p0, LClass;->x:LType; |
ClassName.x = x; |
sput v0, LClassName;->x:LType; |
super.method() |
invoke-super {p0, ...}, LSuper;->method(...) |
new Foo(a, b) |
new-instance v0, LFoo; + invoke-direct {v0, v1, v2}, LFoo;-><init>(...)V |
throw new E(msg) |
new-instance v0, LE; + invoke-direct {v0, v1}, LE;-><init>(String)V + throw v0 |
try { ... } catch(E e) { } |
:try_start + ... + :try_end + .catch LE; { :try_start .. :try_end } :handler + :handler + move-exception v0 |
7.2 寄存器分配快速参考
| 方法类型 | p0 | p1 | p2 | ... |
|---|---|---|---|---|
| 非 static 方法 | this |
第1个参数 | 第2个参数 | ... |
| static 方法 | 第1个参数 | 第2个参数 | 第3个参数 | ... |
构造方法 <init> |
this |
构造参数1 | 构造参数2 | ... |
long/double 占 2 个寄存器: 如
method(long a, int b)中,p0=this, p1/p2=a(long 占 vN, vN+1), p3=b
7.3 Smali 反编译标志速查
| Smali 标志 | 值 | Java 等价 |
|---|---|---|
.class public |
0x1 | public class |
.class final |
0x10 | final class |
.class abstract |
0x400 | abstract class |
.class interface abstract |
0x200+0x400 | interface |
.class enum |
0x4000 | enum |
.class annotation |
0x2000 | @interface |
public static |
0x1+0x8 | public static |
private |
0x2 | private |
protected |
0x4 | protected |
static |
0x8 | static |
final |
0x10 | final |
synchronized |
0x20 | synchronized |
native |
0x100 | native |
abstract |
0x400 | abstract |
constructor |
0x10000 | <init> 构造方法 |
declared-synchronized |
0x20000 | synchronized 方法修饰 |