目录
Android代码在 Dalvik虚拟机 运行
baksmali 是安卓系统中Dalvik中所用的汇编器 和 反汇编器
dex文件 反汇编后得到 smali文件
smali代码拥有自己特定的格式语法
smali目前在Google code是一个开源项目,被广泛的用于广告注入、汉化、破解等方面
1. smali 数据类型
| java 数据类型 | Type descriptor 字节码类型描述符 |
|---|---|
| int | I |
| float | F |
| double | D |
| boolean | Z |
| char | C |
| byte | B |
| short | S |
| long | J |
| void | V |
2. smali 对象(引用类型的对象)
| java 对象类型 | Type descriptor 字节码类型描述符 |
|---|---|
| package.name.Object | Lpackage/name/Object; |
| L表示 对象类型,这是一个对象 | |
| xxx.yyy.zzz 变成 xxx/yyy/zzz 格式的目录层级结构 | |
| ; 表示对象名称结束 |
String字符串本身是个对象
3. smali 数组
| java 数据类型 | Type descriptor 字节码类型描述符 |
|---|---|
| int [ ] | [ I |
| object [ ] [ ] | [ [ Ljava/lang/Object |
二维数组:
在java中:

在smali中:
#声明了三个不同类型的二维数组
.field private double_int:[[I
.field private double_obj:[[Ljava/lang/Object;
.field private double_string:[[Ljava/lang/String;





4. smali 方法的表现形式
方法在smali中以.method 指令声明
# direct methods
.method <访问权限> [关键字] 方法名(参数)返回值类型
.locals 指定局部变量的个数
.param 方法的参数
.line 代码在原文件中的行号
.end 结尾
Lcom/example/hello/MainActivity;->methodName(IZD)Z
- Lcom/example/hello/MainActivity;:用于声明具体的类型,以便JVM寻找
- methodName:表示方法名称
- IZD:连写的,没有分隔符,表示该方法需要三个参数,分别为int、boolean、double
- Z:最后括号后的Z,表示这个函数的返回对象类型是boolean型,如果是V,表示返回值为空
- (如果需要调用构造方法,则MethodName为:<init>)
5. smali 字段的表现形式
| java 中字段的表现形式 | smali 中字段的表现形式 |
|---|---|
| private objectType fieldName; | .field fieldName:objectType |
# instance fields 实例字段
.field <访问权限> [修饰关键字] <字段名> : 类型
# static field 静态字段
.field <访问权限> static [修饰关键字] <字段名> : 类型
例如:字段的声明对比如下:
在java中:

smali中:

文件格式:
无论是 普通类、抽象类、接口类、内部类,反编译是,都会用单独的smali文件存放。
java中:

smali中:

.class <访问权限> [修饰关键字] <类名称>
.super <父类>
.source <源文件名称>
每个smali文件开头都是如下三行:
.class public Lcom/example/hello/MainActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "MainActivity.java"
- 第一行:.class 指令 指定当前的类名称。此处是public
- 第二行:.super 指令 指定当前类的父类
- 第三行:.source 指令指定当前的源文件名称
6. smali 寄存器指令
在Dalvil虚拟机字节码中,寄存器有两种命名方式:
- v 命名法(v0、v1、v2……)
- p 命名法(p0、p1、p2……)
| v 命名法 | p 命名法 | 寄存器含义 |
|---|---|---|
| v0 | v0 | 第一个局部变量寄存器 |
| v1 | v1 | 第二个局部变量寄存器 |
| …… | …… | 中间的局部变量寄存器依次递增且名称相同 |
| vM-N | p0 | 第一个参数寄存器 |
| …… | …… | 中间的参数寄存器分别依次递增 |
| vM-1 | pN-1 | 第N局部变量寄存器 |
比较v命名法与p命名法:
| v 命名法 | p 命名法 |
|---|---|
| 以 .registers开头 | 以 .limit开头 |
| 以 v2 作为this的引用 | 以 p0 作为this的引用 |
| 以M-N命名N 个参数的寄存器 | 用 p 命名法 |
v 指向寄存器
p 一般是某个java原有的代码有参数,或者指向this的或者直接变量
Dalvil参数传递规则:如果一个函数使用了M个寄存器、拥有N个参数,则参数使用最后N个寄存器,局部变量从v0开始一直递增到前M-N个(被传入的“隐藏”的对象引用this)
7. smali 指令
7.1 空指令:
| 指令助记符 | 指令功能描述 |
|---|---|
| nop | 代码对齐,无操作 |
7.2 数据操作指令(此类操作常用于赋值):
指令基本格式:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选)
| 指令助记符 | 指令功能描述 |
|---|---|
| move v0,v1 | 将v1寄存器的数据赋值给v0寄存器,非对象类型 |
| move-object v0,v1 | 将v1寄存器的数据赋值给v0寄存器,对象类型 |
| move-wide v0,v1 | 将寄存器对v1中的值移入到v0寄存器对中 |
| move-result v1 | 将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用) |
| move-result-object v1 | 将上条计算结果的对象指针移入v1寄存器(多用于函数的返回值) |
| move-result-wide v1 | 将上条计算结果(双字)的对象指针移入v1寄存器 |
| move-exception v1 | 将异常移入v1寄存器,用于捕获try-catch语句中的异常 |
7.3 返回操作指令:
| 指令助记符 | 指令功能描述 |
|---|---|
| return-void | 返回void |
| return v1 | 返回v1寄存器中的值 |
| return-object v1 | 返回v1寄存器中的对象指针 |
| return-void | 返回void |
7.4 数据定义指令:
- const:数据定义 指令
- /number:表示赋值到寄存器的数据的长度
- vA:寄存器
- #+B……:表示数据
| 指令助记符 | 指令功能描述 |
|---|---|
| const/4 vA,#+B | 将4位宽度的立即数 带符号 扩展到32位,赋值到vA寄存器 |
| const/16 vA,#+BBBB | 将16位宽度的立即数 带符号 扩展到32位,赋值到vA寄存器 |
| const vA,#+BBBBBBBB | 将32位宽度的立即数 赋值到vA寄存器 |
| const wide/16 vA,#+BBBB | 将16位宽度的立即数 带符号 扩展64位,赋值到vA寄存器 |
| const wide/32 vA,#+BBBB | 将32位宽度的立即数 带符号 扩展到64位,赋值到vA寄存器 |
| const wide vA,#+BBBBBB | 将64位宽度的立即数 ,赋值到vA寄存器 |
| const-string vA,string@aaaa | 将字符串常量的引用 赋值给vA |
| const/high16 vA+BBBB0000 | 将16位宽度的立即数 右扩展扩展到32位,赋值给vA寄存器 |
| const-class vA,type@AAA | 将一个类class 的引用赋值给vA寄存器 |
const/4和const/16的区别:
-
const/4 表示半个字节,也就是4位。-23~23-1 -8~7
-
const/16 表示两个字节,16位。-215~215-1 -32768~32767
局部变量定义:
.local v0,“a”:I:将v0的值赋值给 变量a
7.5 方法调用指令:
调用方法的基本格式:invoke-kind{vA,vB,vC,vD},meth@BBBB
- invoke:调用方法 指令
- -xxxx:调用的方法 的类型
- vA~vD:为需要的参数,根据顺序一一对应
- BBBB:代表方法引用
| 指令助记符 | 指令功能描述 |
|---|---|
| invoke-virtual | 调用实例的虚方法,通常成员对象实例的方法都有该指令调用(用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针) |
| invode-super | 调用父类的虚拟方法(用于调用父类中的方法,其他和invoke-virtual保持一致) |
| invoke-direct | 调用直接方法,通常私有方法都以该指令调用(用于调用private修饰的方法,或者构造方法) |
| invoke-static | 调用静态方法(比如一些工具类) |
| invoke-interface | 用于调用interface中的方法 |
| invoke-kind / range {vB-vN},method@BBB | 参数列表是连续的寄存器列表 |
7.6 比较指令:
cmp:比较两个寄存器中值的大小,并将结果存储在目标寄存器中。
基本格式为:cmp 目标寄存器 vB vC
| 指令助记符 | 指令功能描述 |
|---|---|
| cmpl vA,vB,vC(less than) | 比较 vB,vC 较小值。如果vB=vC ,则vA=0 ;如果 vC较小 ,则vA存储正数。vB小 ,则vA存储负数 |
| cmpg vA,vB,vC(greater than) | 比较 vB,vC 较大值。如果vB=vC ,则vA=0 ;如果 vC较大 ,则vA存储正数。vB大 ,则vA存储负数 |
| 指令助记符 | 指令功能描述 |
|---|---|
| cmpl-float vA,vB,vC | 比较浮点型和长整型的数据。 |
| cmpg-float vA,vB,vC | 比较浮点型和长整型的数据。 |
| cmpl-double vA,vB,vC | 比较double型的数据 |
| cmpg-double vA,vB,vC | 比较double型的数据 |
| cmpg-long vA,vB,vC | 比较long型的数据 |
7.7 跳转指令:
| 指令助记符 | 指令功能描述 |
|---|---|
| if-test vA,vB,+CCC | 条件跳转指令,比较两个寄存器vA和vB的值,然后进行条件跳转 |
| if-eq v1,v2,:cond_0 | 如果v1 = v2,则进入分支cond_0 |
| if-ne v1,v2,:cond_0 | 如果v1 != v2,则进入分支cond_0 |
| if-gt v1,v2,:cond_0 | 如果v1 > v2,则进入分支cond_0 |
| if-ge v1,v2,:cond_0 | 如果v1 >= v2,则进入分支cond_0 |
| if-lt v1,v2,:cond_0 | 如果v1 < v2,则进入分支cond_0 |
| if-le v1,v2,:cond_0 | 如果v1 <= v2,则进入分支cond_0 |
| 就与0比较: | |
| if-testz vA,+cccc | 条件跳转指令,比较寄存器vA与0的值,满足后跳转 |
| if-eqz v1,:cond_0 | 如果寄存器v1的值 ==0,则跳转到cond_0 |
| if-gtz v1,:cond_0 | 如果寄存器v1的值 >0,则跳转到cond_0 |
| if-gez v1,:cond_0 | 如果寄存器v1的值 >=0,则跳转到cond_0 |
| if-ltz v1,:cond_0 | 如果寄存器v1的值 <0,则跳转到cond_0 |
| if-lez v1,:cond_0 | 如果寄存器v1的值 <=0,则跳转到cond_0 |
| 其他跳转: | |
| goto +cccc | 无条件跳转到指定位置,偏移量为8位 宽度,且不为0 |
| goto/16 +cccc | 无条件跳转到指定位置,偏移量为16位 宽度,且不为0 |
| goto/32 +cccc | 无条件跳转到指定位置,偏移量为32位 宽度,且不为0 |
| packed-switch vA,+bbbbbb | 根据+bbbbbb给定的跳转偏移列表进行匹配vA寄存器的值,跳转到指定指令。其中,偏移量的匹配值是 有规律递增的 |
| sparse-switch vA,+bbbbbb | 根据+bbbbbb给定的跳转偏移列表进行匹配vA寄存器的值,跳转到指定指令。其中,偏移量的匹配值是 无规律的 |
作业1:
在java中:
# 实际中不这样写,需要用一个函数,这里只说逻辑
private int age = 20;
private String identity;
if (age>18){
identity = "成年";
}else{
identity = "未成年";
}
在smali中:
.field private age:I
.field private identity:Ljava/lang/String;
const/4 v0,0x14
const/4 v1,0x12
if-le v0,v1,:cond_0 #这里与原java代码是反着判断的
const-string v4,"成年"
cond_0:
const-string v3,"未成年"
7.8 字段操作指令:
基本格式:iinstance-op vA,vB,field @CCCC
- vA寄存器存放读的结果或写的数据
- vB字段的引用
- field是字段值的引用
- type是类型后缀,如char、byte、short、
| 指令助记符 | 指令功能描述 |
|---|---|
| 普通字段操作: | |
| iget | 取值,用于操作int这种的值类型 |
| iget-wide | 取值,用于操作wide型字段 |
| iget-object | 取值,用于操作对象引用 |
| iget-boolean | 取值,用于操作布尔类型 |
| iget-byte | 取值,用于操作字节类型 |
| iget-char | 取值,用于操作字符类型 |
| iget-short | 取值,用于操作short类型 |
| iput | 赋值,用于操作int这种的值类型 |
| iput-wide | 赋值,用于操作wide型字段 |
| iput-object | 赋值,用于操作对象引用 |
| iput-boolean | 赋值,用于操作布尔类型 |
| iput-byte | 赋值,用于操作字节类型 |
| iput-char | 赋值,用于操作字符类型 |
| iput-short | 赋值,用于操作short类型 |
以上的 i 都可以换成 a 或者 s,分别用于操作 数组字段 或者 静态字段。
作业2:
在java中:
private String name1 = "zhangsan";
private String name2 = "lisi";
private int age1 = 20;
private Object names[]= {name1,name2,"wangwu"};
在smali中:
# instance fields
.field private name1:Ljava/lang/String;
.field private name2:Ljava/lang/String;
.field private age1:I
.field private names:[Ljava/lang/Object;
# direct methods
.method public constructor <init>()V
.locals 9
#调用了一个方法
invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;-><init>()V
#上面是声明了,这里需要为声明的对象赋值
const-string v0, "zhangsan"
iput-object v0, p0, Lcom/example/hello/Regist;->name1:Ljava/lang/String;
const-string v1, "lisi"
iput-object v1, p0, Lcom/example/hello/Regist;->name2:Ljava/lang/String;
const/4 v2,0x14
# 用来放数组长度
const/4 v6,0x3
# 声明一个新数组,寄存器位置为v5,长度为v6
new-array v5, v6, [Ljava/lang/Object;
const/4 v7,0x0
aput-object v0, v5, v7 # 将v0的数据放入v5中,下标位置为v7
const/4 v8,0x1
aput-object v1, v5, v8 # 将v1的数据放入v5中,下标位置为v8
const/4 v9,0x2
const-string v4,"wangwu"
aput-object v4, v5, v9 # 将v4的数据放入v5中,下标位置为v9
# 将v5中的数据放入对象names中
iput-object v5, p0, Lcom/example/hello/MainActivity;->names:[Ljava/lang/Object;
return-void
.end method
作业3:
循环:
private int a=2;
private int aa;
public void test(){
for(int i=0;i<4;i++){}
aa=a;
}
.class public Lcom/example/hello/Regist;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "Regist.java"
# instance fields
.field private a:I
.field private aa:I
# direct methods
.method public constructor <init>()V
invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;-><init>()V
const/16 v0, 0x2
iput v0, p0, Lcom/example/hello/Regist;->a:I
.end method
# virtual methods
.method public test()V
#准备一个0
const/4 v1, 0x0
#准备一个i
.local v2, "i":I
#开始循环
:goto_0
# 准备一个4
const/4 v1, 0x4
#判断i>=4
if-ge v2, v1, :cond_0
#这里中间是循环体中的内容
# i自增
add-int/lit8 v2, v2, 0x1
#再跳转到循环
goto :goto_0
.end local v2 # "i":I
#判断不成功,跳出循环
:cond_0
iget v0, p0, Lcom/example/hello/LoginSuccess;->a:I # 取出a的值
iput v0, p0, Lcom/example/hello/LoginSuccess;->aa:I # 赋值给aa
return-void
.end method
7.9 数据运算指令:
基本格式:bin-op vA,vB,vC(对寄存器vB和vC进行binop算数运算,结果复制到vA寄存器)
| 指令助记符 | 指令功能描述 |
|---|---|
| 算数运算: | |
| add-type | 加法(type有:int、long、float、double) |
| sub-type | 减法 |
| mul-type | 乘法 |
| div-type | 除法 |
| rem-type | 取模 |
| 逻辑运算与位运算: | |
| and-type2 | 逻辑与(type2只有:int、long) |
| or-type2 | 逻辑或 |
| xor-type2 | 逻辑非 |
| shl-type2 | 有符号数左移 |
| shr-type2 | 有符号数右移 |
| ushr-type2 | 无符号数右移 |
| binop/lit8 vA,vB,#+cccc | 将寄存器vB的值与常量cccc进行算数运算,结果赋值给vA |
| binop/lit16 vA,vB,#+cccc | |
| binop/2addr vA,vB | 将vA寄存器与vB寄存器的值进行binop运算,值赋值给vA |
7.10 数据转换指令
| 指令助记符 | 指令功能描述 |
|---|---|
| unop vA,vB | 数据类型转换 |
| neg-type vx,vy | 计算 vx =- vy(type-后可以接类型int、long、float、double) |
| not-type vx,vy | |
| type1-to-type2 | 转换类型 |
| int-to-type | |
| int-to-char | |
| int-to-short | |
| 例如: | |
| double-to-int v0,v1 |
7.11 锁与异常操作指令
| 指令助记符 | 指令功能描述 |
|---|---|
| 锁:(多用在多线程程序中,对同一对象操作) | |
| monitor-enter vA | 为指定的对象获取锁 |
| monitor-exit vA | 释放指定对象的锁 |
| 异常: | |
| throw | 抛出一个指定类型的异常 |
7.12 实例操作指令
| 指令助记符 | 指令功能描述 |
|---|---|
| new-instance vA,type@BB | 构造一个指定类型的实例,并把引用值给寄存器 |
| instance-of vA,vB,type@CC | 判断vB寄存器的引用对象是否可以转为指定类型,是vA就复制1,否vA复制0 |
| check-cast vA,type@BB | 把寄存器引用对象转为指定类型,不行就抛出异常 |
**指令学习英文文档**:http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
本文深入解析Smali代码的结构和语法,涵盖数据类型、对象、数组、方法、字段的表示形式,以及各种指令如寄存器、数据操作、返回、数据定义、方法调用、比较、跳转、字段操作、数据运算、数据转换、锁与异常、实例操作等。通过对比Java代码,提供详细的Smali代码示例。
1万+

被折叠的 条评论
为什么被折叠?



