这篇文章上次修改于 258 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

Smali背景

  Smali,Baksmali分别是指安卓系统里的Java虚拟机(Dalvik)所使用的一种.dex格式文件的汇编器,反汇编器。其语法是一种宽松式的Jasmin/dedexer语法,而且它实现了.dex格式所有功能(注解,调试信息,线路信息等)。
  Smali,Baksmali分别是冰岛语中编译器,反编译器的叫法。也许你会问为什么是冰岛语呢,因为Dalvik是一个冰岛渔村名字。

简单介绍

  Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;
  根据一些文档说明,以及实践检测,Dalvik字节码有两种类型:原始类型;引用类型。熟悉JAVA的同学,应该很快能够反应过来,原始类型也就是我们JAVA中的8中基础类型;引用类型,在smali中主要是对象和数组。事实上smali语法就是根据java来的,可以理解成java的一种变种。

基本类型(原始类型)

类型关键字对应Java中的类型说明位数
Vvoid0
Zboolean4
Bbyte8
Sshort16
Cchar16
Iint32
Jlong64
Ffloat32
Ddouble64

引用类型

对象类型

  Object类型,即引用类型的对象,在引用时,使用L开头,后面紧接着的是完整的包名,比如:java.lang.String对应的Smali语法则是Ljava/lang/String

数组类型

  一维数组在类型的左边加一个方括号,比如:[I等同于Java的int[],每多一维就加一个方括号,最多可以设置255维

方法声明及调用

Lpackage/name/ObjectName;->MethodName(III)Z

  第一部分Lpackage/name/ObjectName;用于声明具体的类型,以便JVM寻找
  第二部分MethodName(III)Z,其中MethodName为具体的方法名,()中的字符,表示了参数数量和类型,即3个int型参数,Z为返回值的类型,即返回Boolean类型

如果需要调用构造方法,则MethodName为:<init>

寄存器

寄存器声明及使用

  在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double
  声明可使用的寄存器数量的方式为:.registers N,N代表需要的寄存器的总个数,同时,还有一个关键字.locals,它用于声明非参数的寄存器个数(包含在registers声明的个数当中),也叫做本地寄存器,只在一个方法内有效,但不常用,一般使用registers即可

确定寄存器的个数

  由于非static方法,需要占用一个寄存器以保存this指针,那么这类方法的寄存器个数,最低就为1,如果还需要处理传入的参数,则需要再次叠加,此时还需要考虑Double和Float这种需要占用两个寄存器的参数类型。

寄存器的命名方式

有两种方式——V命名方式和P命名方式。P命名方式中的第一个寄存器就是方法中的第一个参数寄存器。在下表中我们用这两种命名方式来表示有5个寄存器和3个参数的方法。

对应V寄存器对应P寄存器寄存器类型
v0 第一个local register
v1 第二个local register
v2p0第一个parameter register
v3p1第二个parameter register
v4p2第三个parameter register

寄存器的命名

.local vA, "name":type;

将vA命名为name name为type类型;
例如:

.local v0, "bu":Landroid/widget/Button;

v0 相当于 Button bu;

 .param pA, "name"  # type;

将pA命名为name name 为type类型;
例如

.param p1, "a"  # I

p1 相当于 int a;

Dalvik指令集

https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions
以下寄存器的位数与指令注意事项可在官方文档中查看

一般的指令格式为:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选),比如:move v1,v2move-wide/from16 v1,v2

移位

指令说明
move v1,v2将v2中的值移入到v1寄存器中(4位,支持int型)
move/from16 v1,v2将16位的v2寄存器中的值移入到8位的v1寄存器中
move/16 v1,v2将16位的v2寄存器中的值移入到16位的v1寄存器中
move-wide v1,v2将寄存器对(一组,用于支持双字型)v2中的值移入到v1寄存器对中(4位,猜测支持float、double型)
move-wide/from16 v1,v2将16位的v2寄存器对(一组)中的值移入到8位的v1寄存器中
move-wide/16 v1,v2将16位的v2寄存器对(一组)中的值移入到16位的v1寄存器中
move-object v1,v2将v2中的对象指针移入到v1寄存器中
move-object/from16 v1,v2将16位的v2寄存器中的对象指针移入到v1(8位)寄存器中
move-object/16 v1,v2将16位的v2寄存器中的对象指针移入到v1(16位)寄存器中
move-result v1将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用)
move-result-object v1将上条计算结果的对象指针移入v1寄存器
move-result-wide v1将上条计算结果(双字)的对象指针移入v1寄存器
move-exception v1将异常移入v1寄存器,用于捕获try-catch语句中的异常

返回

指令说明
return-void返回void,即直接返回
return v1返回v1寄存器中的值
return-object v1返回v1寄存器中的对象指针
return-wide v1返回双字型结果给v1寄存器

常量

指令说明
const/4 vA, #+B将4位常量B符号扩展为32位赋值给vA寄存器
const/16 vAA, #+BBBB将16位常量B符号扩展为32位赋值给vA寄存器
const vAA, #+BBBBBBBB将32位常量B赋值给vA寄存器
const/high16 vAA, #+BBBB0000将16位常量B右零扩展(右边添0)为32位赋值给vA寄存器
const-wide/16 vAA, #+BBBB将16位常量B符号扩展为64位赋值给vA寄存器
const-wide/32 vAA, #+BBBBBBBB将32位常量B符号扩展为64位赋值给vA寄存器
const-wide vAA, #+BBBBBBBBBBBBBBBB将64位常量B赋值给vA寄存器
const-wide/high16 vAA, #+BBBB000000000000将16位常量B右零扩展(右边添0)为64位赋值给vA寄存器
const-string(/jumbo) vAA string@BBBBBBBB将字符串常量赋给vA寄存器,过长时需要加上jumbo
const-class vA La/b/TargetClass将Class常量a.b.TargetClass赋值给vA,等价于a.b.TargetClass.class

判断

指令说明
if-eq v1,v2判断两个寄存器中的值是否相等
if-ne v1,v2判断两个寄存器中的值是否不相等
if-lt v1,v2判断v1寄存器中的值是否小于v2寄存器中的值(lt == less than)
if-ge v1,v2判断v1寄存器中的值是否大于或等于v2寄存器中的值(ge == great than or equals)
if-gt v1,v2判断v1寄存器中的值是否大于v2寄存器中的值(gt == great than)
if-le v1,v2判断v1寄存器中的值是否小于或等于v2寄存器中的值(le == less than or equals)

属性

属性操作的分为:取值(get)和赋值(put)
目标类型分为:数组(array)、实例(instance)和静态(static)三种,对应的缩写前缀就是a、i、s
长度类型分为:默认(什么都不写)、wide(宽,64位)、object(对象)、boolean、byte、char、short
指令格式:[指令名] [源寄存器], [目标字段所在对象寄存器], [字段指针]
下面列出用于实例字段的指令,其中i都可以换成a或者s,分别用于操作数组字段或者静态字段

指令说明
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类型

调用

基本格式:invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
其中,BBBB代表方法引用(参见上面介绍的方法定义及调用),vC~G为需要的参数

指令说明
invoke-virtual用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针
invoke-super用于调用父类中的方法,其他和invoke-virtual保持一致
invoke-direct用于调用private修饰的方法,或者构造方法
invoke-static用于调用静态方法,比如一些工具类
invoke-interface用于调用interface中的方法

数学运算

运算码 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 转换为 int32NaN-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;
按位 OR。
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;
按位 OR。
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

其他

指令说明
monitor-enter vAA获取指定对象的监视锁
monitor-exit vAA释放指定对象的监视锁
check-cast vAA, type@BBBB如果VA寄存器中的引用不能转型为type指定的类型,则抛出 ClassCastException
instance-of vA, vB, type@CCCC如果VB是给定type类型的实例,则给VA赋值 1,否则赋值 0
array-length vA, vB将指定数组的长度(条目个数)赋值给给定目标寄存器
new-instance vAA, type@BBBB构造新type实例赋值给VA寄存器(类型必须引用非数组类)
new-array vA, vB, type@CCCC根据指定的type类型和VB大小构造新数组VA(类型必须引用数组类)
throw vAA抛出指定异常
goto(/16,/32) +AA无条件跳转

常用smali代码

Toast

.method private toast()V
    .locals 2
    .prologue
    .line 40
    const-string v0, "toast test..."
    const/4 v1, 0x1
    invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
 
    move-result-object v0
 
    invoke-virtual {v0}, Landroid/widget/Toast;->show()V
 
    .line 41
    return-void
.end method

Log

.method private log()V
    .locals 2
    .prologue
    .line 36
    const-string v0, "tag"
    const-string v1, "Message"
    invoke-static {v0, v1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
 
    .line 37
    return-void
.end method

LogCallStack

.method private logCallStack()V
    .locals 2
    .prologue
    .line 44
    const-string v0, "call stack:"
    new-instance v1, Ljava/lang/Exception;
 
    invoke-direct {v1}, Ljava/lang/Exception;-><init>()V
 
    invoke-static {v1}, Landroid/util/Log;->getStackTraceString(Ljava/lang/Throwable;)Ljava/lang/String;
 
    move-result-object v1
 
    invoke-static {v0, v1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
 
    .line 45
    return-void
.end method

AlertDialog

new-instance v1,Landroid/app/AlertDialog$Builder;

invoke-direct {v1,p0}, Landroid/app/AlertDialog$Builder;-><init>(Landroid/content/Context;)V

.line 29
.local v1,builder:Landroid/app/AlertDialog$Builder;
const-string v2,"Title"

invoke-virtual {v1,v2}, Landroid/app/AlertDialog$Builder;->setTitle(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;

.line 31
const-string v2,"Message"

invoke-virtual {v1,v2},Landroid/app/AlertDialog$Builder;->setMessage(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;

.line 52
invoke-virtual {v1},Landroid/app/AlertDialog$Builder;->create()Landroid/app/AlertDialog;

move-result-object v2

invoke-virtual {v2},Landroid/app/AlertDialog;->show()V

BroadcastReceiver

.# static fields

.field private intentFilter:Landroid/content/IntentFilter;

.field private reciver:Lcom/example/mytest/MyReciver;


method protected onCreate(Landroid/os/Bundle;)V


   new-instance v0, Landroid/content/IntentFilter;


    invoke-direct {v0}, Landroid/content/IntentFilter;-><init>()V


    iput-object v0, p0, Lcom/test/SearchActivity;->intentFilter:Landroid/content/IntentFilter;


    iget-object v0, p0, Lcom/test/SearchActivity;->intentFilter:Landroid/content/IntentFilter;


    const-string v1, "android.intent.action.search"


    invoke-virtual {v0, v1}, Landroid/content/IntentFilter;->addAction(Ljava/lang/String;)V


    new-instance v0, Lcom/example/mytest/MyReciver;


    invoke-direct {v0, p0}, Lcom/example/mytest/MyReciver;-><init>(Landroid/app/Activity;)V


    iput-object v0, p0,Lcom/test/SearchActivity;->reciver:Lcom/example/mytest/MyReciver;


    iget-object v0, p0, Lcom/test/SearchActivity;->reciver:Lcom/example/mytest/MyReciver;


    iget-object v1, p0, Lcom/test/SearchActivity;->intentFilter:Landroid/content/IntentFilter;


    invoke-virtual {p0, v0, v1}, Lcom/test/SearchActivity;->registerReceiver(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;


..method public onDestroy()V


iget-object v0, p0, Lcom/test/SearchActivity;->reciver:Lcom/example/mytest/MyReciver;


invoke-virtual {p0, v0}, Lcom/test/SearchActivity;->unregisterReceiver(Landroid/content/BroadcastReceiver;)V