一、Smali 文件结构
每一个 .smali
文件代表一个 Java 类,它是 Dalvik 字节码(DEX)反编译后的汇编形式。
1.1 Smali 文件结构总览
一个 .smali
文件 = 一个 Java 类
格式是 “类头部 + 字段定义 + 方法定义”,如下:
.class public Lcom/example/MyClass;
.super Ljava/lang/Object;
.source "MyClass.java"
.field private myField:I
.method public constructor <init>()V
.locals 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public myMethod(I)V
.locals 1
return-void
.end method
1.2 Smali 文件的位置和命名
当用工具(如 apktool)反编译 APK 后,会生成如下结构:
smali/
└── com/
└── example/
└── MyClass.smali
-
MyClass.smali
对应 Java 类com.example.MyClass
-
Smali 的类名采用
L
+ 包名路径 +类名
+;
的形式
.class public Lcom/example/MyClass;
1.3 Smali 文件组成部分详解
1) .class
声明类
.class public Lcom/example/MyClass;
-
修饰符(如 public、final、abstract)
-
类名:必须是
L包名/类名;
的格式 -
不包含
.java
后缀
2).super
声明父类
.super Ljava/lang/Object;
-
类继承自哪个父类
-
语法和类名一样,用
L...;
格式
3).source
可选,原始源文件名
.source "MyClass.java"
-
显示源 Java 文件名
-
可省略,仅用于调试辅助
4).field
成员变量(字段)
.field private myField:I
.field public static sValue:Ljava/lang/String;
语法:
.field [修饰符] 字段名:类型
类型符号 | 含义 |
---|---|
I | int |
Z | boolean |
F | float |
D | double |
J | long |
Ljava/lang/String; | String |
Lcom/example/MyClass; | 自定义类 |
5).method
方法定义
.method public myMethod(I)V
.locals 1
return-void
.end method
语法结构:
.method [修饰符] 方法名(参数类型)返回类型
.locals N
指令代码
.end method
修饰符:
-
public / private / static / final / constructor(构造方法)
参数和返回类型(与 Java 的签名对应):
符号 | 含义 |
---|---|
I | int |
V | void |
Z | boolean |
Ljava/lang/String; | String |
L...; | 对象 |
([类型])返回类型 | 方法签名格式 |
6)指令区(方法体)
.locals 1
const v0, 0x10
return-void
-
.locals N
定义本地寄存器数量(v0
,v1
...) -
然后是 Smali 指令(如
const
,move
,invoke
)
1.4 构造方法结构
构造方法名为 <init>
,必须调用父类构造:
.method public constructor <init>()V
.locals 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
1.5 完整示例
.class public Lcom/example/DemoClass; # 定义一个公开类,类名为 com.example.DemoClass
.super Ljava/lang/Object; # 指定该类继承自 java.lang.Object
.source "DemoClass.java" # 源文件名,仅用于调试信息
.field public static final TAG:Ljava/lang/String; = "SmaliDemo" # 定义一个静态常量 TAG,类型是 String,值为 "SmaliDemo"
.method public constructor <init>()V # 定义构造方法(构造函数名固定为 <init>),无参数,返回 void
.locals 1 # 分配 1 个局部寄存器(v0 可用)
invoke-direct {p0}, Ljava/lang/Object;-><init>()V # 调用父类 Object 的构造方法,p0 是 this 指针
return-void # 返回 void,构造函数结束
.end method # 方法定义结束
.method public showToast(Ljava/lang/String;)V # 定义一个公开方法 showToast,接收一个字符串参数,返回 void
.locals 2 # 分配 2 个局部寄存器(v0, v1 可用)
return-void # 方法体为空,直接返回
.end method # 方法定义结束
分析:
-
类名为
com.example.DemoClass
-
继承自
java.lang.Object
-
定义一个静态常量字段
TAG
-
一个构造方法
<init>()
-
一个方法
showToast(String msg)
,参数是字符串,返回 void
1.6 小结
-
Smali 修改一定要注意寄存器数量(.locals)是否足够,否则会运行崩溃。
-
修改后建议用 baksmali/smali 重新编译测试 或用 apktool rebuild。
-
学会快速定位:类文件路径 =
L包名/类名.smali
,方法签名用grep
快速找函数。
部分 | 用途 |
---|---|
.class | 定义类 |
.super | 继承父类 |
.field | 成员变量 |
.method | 定义方法 |
.locals | 本地寄存器数目 |
指令代码 | 方法的实际执行逻辑 |
二、基本语法结构:
Smali 的基本语法结构,包括:
-
类定义(.class、.super、.source)
-
方法定义(.method、参数、返回类型、.locals、.end method)
-
指令语法(Smali 的汇编语法结构)
2.1 类定义(Class Definition)
Smali 中,每个 .smali
文件对应一个 Java 类,用以下三条指令声明:
.class public Lcom/example/MyClass;
.super Ljava/lang/Object;
.source "MyClass.java"
说明:
语法 | 含义 |
---|---|
.class | 定义类本身,修饰符 + 类名 |
.super | 指定该类继承的父类 |
.source | 原始 Java 文件名(调试用,可省略) |
类名格式:
Smali 中类名是用 L完整包路径/类名;
表示:
-
Java 类:
com.example.MyClass
-
Smali:
Lcom/example/MyClass;
2.2 方法定义(Method Definition)
一个类里可以有多个方法,每个方法使用 .method
开头,.end method
结束。
基本结构:
.method public myMethod(ILjava/lang/String;)V
.locals 2
...(方法体的 Smali 指令)...
.end method
方法定义语法详解:
项目 | 示例 | 含义 |
---|---|---|
.method | .method public | 方法修饰符(public/private/static 等) |
方法名 | myMethod | 方法名称 |
参数类型 | (I Ljava/lang/String;) | 参数是 int + String |
返回类型 | V | 表示返回 void |
常见类型缩写:
类型 | Smali 表达 |
---|---|
int | I |
boolean | Z |
float | F |
double | D |
long | J |
void | V |
String | Ljava/lang/String; |
自定义类 | Lcom/example/MyClass; |
数组 | [I 表示 int[],[Ljava/lang/String; 表示 String[] |
.locals N
说明
.locals 2
-
声明该方法最多使用 v0 到 vN-1 这几个寄存器
-
如果你写了
v2
,那.locals
至少要写成3
2.3 Smali 指令语法(Instruction Syntax)
Smali 是 Dalvik 字节码的汇编语言,其指令一般结构如下:
<指令> <目标寄存器>, <来源/参数寄存器或值>
关键点:
-
操作数之间用 逗号分隔
-
使用的寄存器以
v
(局部变量)或p
(方法参数)为前缀,如v0
,p1
-
数字、常量用十六进制或十进制都可:
0x1A
,26
示例指令语法
指令 | 示例 | 说明 |
---|---|---|
const | const v0, 0x10 | 将常量 0x10 存入 v0 |
move | move v1, v0 | 把 v0 复制给 v1 |
return-void | return-void | 无返回值方法的结束 |
return | return v0 | 返回一个值 |
invoke-virtual | invoke-virtual {p0}, Landroid/app/Activity;->finish()V | 调用方法 |
iget | iget v0, p0, Lcom/example/MyClass;->field:I | 访问成员变量(获取) |
sput | sput-object v0, Lcom/example/MyClass;->TAG:Ljava/lang/String; | 设置静态变量 |
if-eq | if-eq v0, v1, :label | 如果 v0 == v1,跳转到 label |
goto | goto :label | 无条件跳转 |
标签语法(跳转用)
:label_1
const v0, 0x1
-
:
开头的语句是标签(label),用于跳转指令中 -
不会执行,是跳转锚点
2.4 补充说明:参数寄存器与局部寄存器
方法中的寄存器可以分两类:
类型 | 前缀 | 说明 |
---|---|---|
参数寄存器 | p0 , p1 , p2 ... | 代表方法参数,从 p0 开始,一般 p0 是 this |
局部寄存器 | v0 , v1 , v2 ... | 方法内部定义的寄存器,用于存放局部变量、临时结果等 |
2.5 小结
部分 | 用法 | 示例 |
---|---|---|
类定义 | .class , .super , .source | 定义类和父类 |
方法定义 | .method , .locals , .end method | 定义函数结构 |
参数/返回类型 | ([类型])返回 | (I)V , (Ljava/lang/String;)I |
寄存器 | v0 , p0 | 本地变量 vs 参数 |
常用指令 | const , move , invoke , return , iget , sput , if-eq , goto | 用于数据赋值、跳转、调用函数等 |
三、常见指令集:
3.1 常见指令总览
指令 | 作用 | Java 等价 |
---|---|---|
const | 给寄存器赋常量值 | int a = 10; |
move | 数据寄存器间复制 | b = a; |
return / return-void | 返回方法 | return a; / return; |
invoke-* | 方法调用 | obj.method() |
iget | 读取实例字段 | this.a |
sput | 写入静态字段 | ClassName.staticField = value; |
if-eq | 条件跳转(相等) | if (a == b) |
goto | 无条件跳转 | goto label; |
3.2 每条指令详解 + 示例
1)const
const v0, 0x1A # v0 = 26(十六进制 1A)
const v1, 123 # v1 = 123
这个指令支持 int
、float
、long
等,常用的是 int
。
Java 等价:
int a = 26;
2)move
功能: 将一个寄存器的值复制给另一个寄存器。
move v1, v0 # v1 = v0
注意是浅拷贝,类似于 Java 的引用复制。
Java 等价:
b = a;
3)return
/ return-void
功能: 从方法中返回,可能带返回值。
return v0 # 返回 v0 中的值
return-void # 无返回值的方法使用
Java 等价:
return a; // return v0;
return; // return-void;
4)invoke-*
功能: 调用方法
Smali 中方法调用有多个形式,取决于方法类型(虚方法、静态方法、接口等):
常见形式:
invoke-virtual {p0}, Landroid/app/Activity;->finish()V
invoke-static {v0}, Ljava/lang/Math;->abs(I)I
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
格式说明:
invoke-<类型> {参数列表}, 类名->方法名(参数类型)返回类型
类型 | 用法 |
---|---|
virtual | 实例方法 |
static | 静态方法 |
direct | 构造器 / 私有方法 |
interface | 接口调用 |
super | 父类方法 |
Java 等价:
obj.finish();
int b = Math.abs(a);
super.onCreate();
5)iget
功能: 读取对象的实例字段
iget v1, p0, Lcom/example/MyClass;->age:I
-
从
p0
(this)读取字段age
到v1
-
I
表示int
类型
Java 等价:
int b = this.age;
6)sput
功能: 设置静态字段的值
sput-object v0, Lcom/example/MyClass;->TAG:Ljava/lang/String;
- 将
v0
的值赋给MyClass
中的TAG
静态字段
Java 等价:
MyClass.TAG = "Smali";
7)if-eq
功能: 判断两个寄存器值是否相等,如果相等则跳转
if-eq v0, v1, :cond_equal
:cond_equal
const v2, 0x1
Java 等价:
if (a == b) {
c = 1;
}
8)goto
功能: 无条件跳转到指定标签位置
goto :end
:skip
const v0, 0x1
:end
return-void
Java 等价:
// goto 等价于代码块跳过、break、continue、return 等
3.3 实战示例整合
.method public checkFlag(I)V
.locals 1
const v0, 0x1 # v0 = 1
if-eq p1, v0, :ok # if p1 == v0 跳转
return-void # 不相等则 return
:ok
invoke-static {v0}, Lcom/example/Logger;->logFlag(I)V
return-void
.end method
Java 等价:
public void checkFlag(int flag) {
if (flag == 1) {
Logger.logFlag(1);
}
}
3.4 小结
指令 | 作用 | Java 等价 |
---|---|---|
const v0, 0x10 | 常量赋值 | int a = 16; |
move v1, v0 | 值传递 | b = a; |
return v0 | 返回值 | return a; |
invoke-virtual {p0}, LClass;->method()V | 方法调用 | obj.method(); |
iget v0, p0, LClass;->a:I | 获取字段 | this.a; |
sput-object v0, LClass;->TAG:Ljava/lang/String; | 设置静态字段 | Class.TAG = ...; |
if-eq v0, v1, :label | 条件跳转 | if (a == b) |
goto :label | 跳转 | goto |
四、Register 寄存器理解(v0
、p0
参数寄存器)
在 Smali 中,寄存器就像 Java 中的变量,用于临时保存数据,如参数、返回值、局部变量、对象引用等。
Smali 有两种主要寄存器:
类型 | 命名方式 | 用途 |
---|---|---|
pX 寄存器 | p0 , p1 , p2 … | 方法参数传递 |
vX 寄存器 | v0 , v1 , v2 … | 局部变量、临时值 |
4.1 参数寄存器 p0
, p1
, p2
...
功能:
-
用于存放传入方法的参数(包括
this
对象)
规则:
-
如果是 实例方法(非 static),
p0
=this
-
后续参数依次是
p1
,p2
... -
如果是 静态方法(static),从
p0
开始就是第一个真正的参数
示例一:非静态方法
.method public test(ILjava/lang/String;)V
.locals 1
# p0 -> this
# p1 -> int 参数
# p2 -> String 参数
.end method
Java 等价:
public void test(int a, String b) {
// this = p0
// a = p1
// b = p2
}
示例二:静态方法
.method public static test(ILjava/lang/String;)V
.locals 1
# p0 -> int 参数
# p1 -> String 参数
.end method
Java 等价:
public static void test(int a, String b) {
// a = p0
// b = p1
}
注意:long、double 会占用 2 个寄存器!
.method public test(JD)V
# p0 -> this
# p1 & p2 -> long 参数
# p3 & p4 -> double 参数
4.2 局部寄存器 v0
, v1
, v2
...
功能:
-
存放方法内部的变量、临时值、运算结果等
声明方式:
在方法体开头使用 .locals N
来声明局部寄存器的个数。
.locals 2 # 表示 v0 和 v1 可用
示例:
.method public test()V
.locals 2
const v0, 0x10 # v0 = 16
const v1, 0x20 # v1 = 32
.end method
4.3 参数与局部的总和限制
在每个方法中,寄存器总数不能超过 locals + 参数寄存器个数
。
有时候你也会看到 .registers N
,这个会声明总寄存器数(包括参数 + 局部),是 .locals
+ 参数个数的替代写法。
实战案例:理解寄存器使用
Java 代码
public void printSum(int a, int b) {
int c = a + b;
Log.d("TAG", String.valueOf(c));
}
对应 Smali 代码:
.method public printSum(II)V # 定义一个公开实例方法,参数为两个 int,无返回值(V)
.locals 2 # 声明局部变量寄存器 v0 和 v1
add-int v0, p1, p2 # v0 = p1 + p2,对传入参数相加:int c = a + b;
invoke-static {v0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
# 调用 String.valueOf(int) 方法,把 int 转为 String
move-result-object v1 # 将 valueOf 的返回值(String)保存到 v1 中
const-string v0, "TAG" # 将字符串常量 "TAG" 加载到 v0
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
# 调用 Log.d(String tag, String msg),用于打印日志
return-void # 方法无返回值,结束方法
.end method # 方法定义结束
寄存器 | 内容 |
---|---|
p0 | this |
p1 | int a |
p2 | int b |
v0 | 计算结果 / 常量 |
v1 | 字符串结果 |
4.4 小结
名称 | 作用 | 特点 |
---|---|---|
pX | 参数寄存器 | 方法参数传入,从 p0 (实例)或 p0 (静态)开始 |
vX | 局部寄存器 | 方法内部临时变量,用 .locals 声明数量 |
.locals N | 局部变量个数 | v0 ~ v(N-1) 可用 |
.registers N | 所有寄存器总数 | 替代 .locals + 参数数量 |