资料
smali的文件语法格式smali常见指令集详解,看完基本毕业
Smali代码语法
smali文件格式
无论是普通类、抽象类、接口类或者内部类,在反编译出来的代码中,他们都以单独的smali文件来存放。
一、smali文件的头3行描述了当前类的一些信息,格式如下:
.class <访问权限>[修饰关键字]<类名>
.super <父类名>
.source <源文件名>
类名开头的L是遵循Dalvik字节码的相关约定,表示后面跟随的字符串为一个类,经过混淆的dex文件,反编译出来的smali代码可能没有源文件信息,因此,.source行可能为空。
二、smali文件中,字段的声明使用“.field”指令
静态字段格式:
#static fields
.field <访问权限> static[修饰关键字]<字段名>:<字段类型>
实例字段格式:
#instance fields
.field <访问权限> static[修饰关键字]<字段名>:<字段类型>
三、smail文件中,方法的声明使用“.method”指令,方法有直接方法与虚方法。
直接方法声明格式:
#direct methods
.method <访问权限>[修饰关键字]<方法原型>
<.locals> //置顶了使用的局部变量的个数
[.parameter] //多个参数则有多个[.parameter]
[.prologue] //指定了代码的开始处,混淆过的代码可能去掉了该指令
[.lime] //置顶了该处指令在源代码中的行号,混淆过的代码可能去除了行号
<代码体>
.end method
虚方法的声明格式:
#virtual methods
.method <访问权限>[修饰关键字]<方法原型>
<.locals> //置顶了使用的局部变量的个数
[.parameter] //多个参数则有多个[.parameter]
[.prologue] //指定了代码的开始处,混淆过的代码可能去掉了该指令
[.lime] //置顶了该处指令在源代码中的行号,混淆过的代码可能去除了行号
<代码体>
.end method
四、如果类实现了接口,在smali文件中使用“.implements”,格式如下:
#interfaces
.implements <接口名>
五、如果类使用了注解,在smali文件中使用“.annotation”,格式如下:
#annotations
.annotatiion[注解属性]<注解类名>
[注解字段 = 值]
.end annotation
注解的作用范围可以是类、方法或字段。如果注解的作用方位是类,“.annotation”指令会直接定义在文件中,如果是方法或字段,“.annotation”指令则包含在方法或字段定义中
指令集
特点
- 1.参数采用从目标到源的方式
- 根据字节码的大小与类型不同,一些字节码添加了名称后缀以消除歧义
32位常规类型的字节码未添加任何内容
64位常规类型的字节码添加-wide后缀
特殊类型的字节码根据具体类型添加后缀。它们可以是-boolean -byte -char -short -int -long -float -double -object -string -void之一。
- 根据字节码的布局与选项不同,一些字节码添加了字节码后缀以消除歧义。这些后缀通过在字节码主名称后缀添加斜杠“/”来分隔开
- 在指令集的描述中,宽度值中每个字母表示宽度为4位
举个例子:move-wide/from16 vAA, vBBBB
该指令表示:
- move为基础字节码,标识这是基本操作。
- wide为名称后缀,标识指令操作的数据宽度(64位)。
- from16为字节码后缀(opcode suffix),标识源为一个16位的寄存器引用变量。
- vAA为目的寄存器,它始终在源的前面,取值范围为v0~v255。
- vBBBB为源寄存器,取值范围为v0~v65535。 指令集中大多数指令用到了寄存器作为目的操作数或源操作数,其中 A/B/C/D/E/F/G/H 代表一个4位的数值, 可用来表示v0~v15的寄存器。
- AA/BB/…/HH代表一个8位的数值。
- AAAA/BBBB/…/HHHH 代表一个16位的数值
数据操作指令
数据操作指令为move。move指令的原型为”move destination, source“, move指令根据字节码的大小与类型不同,后面会跟上不同的后缀。如:
// 将vB寄存器的值赋给vA寄存器, 源寄存器与目的寄存器都为4位
move vA, vB
// 将VBBBB寄存器的值赋给VAA寄存器,源寄存器为16位,目标寄存器为8位
move /from 16 VAA, VBBBB
// 将VBBBB寄存器的值赋给VAAAA,源寄存器和目标寄存器都为16位
move /from 16 VAAAA, VBBBB
// 为4位的寄存器对赋值。源寄存器与目的寄存器为4位
move-wide vA, vB
// 将vB寄存器中的对象引用赋值给vA寄存器,vA寄存器和vB寄存器都是4位
move-object vA, vB
// 将上一个invoke(方法调用)指令,操作的单字(32位)
move-result vAA
// 将上一个invoke指令操作的对象结果赋值给vAA寄存器
move-result-wide vAA
// 保存上一个运行时发生的异常到vAA寄存器
move-exception vAA
数据定义指令
数据定义指令用来定义程序中用到的常量、字符串、类等数据。它的基础字节码为const。
// 将数值符号扩展为32位后赋给寄存器vA
const/4 vA, #+B
// 将数值符号扩展为32位后赋给寄存器 vAA
const/16 vAA, #+BBBB
// 将数值赋给寄存器vAA
const vAA, #+BBBBBBBB
// 将数值右边0扩展为32位后赋给寄存器vAA
const/high16 vAA, #+BBBB0000
// 将数值符号扩展64位后赋给寄存器对vAA
const-wide/16 vAA, #+BBBB
// 将数值赋给寄存器对vAA
const-wide vAA, #+BBBBBBBBBBBBBBBB
// 将数值右边0扩展为64位后赋值给寄存器vAA
const-wide/high16 vAA, #+BBBB000000000000
// 通过字符串索引构造一个字符串并赋给寄存器对vAA
const-string vAA, string[@BBBB]
// 通过字符串索引(较大)构造一个字符串并赋值给寄存器对vAA
const-string/jumbo vAA, string[@BBBBBBBB、
// 通过类型索引获取一个类引用并赋值给寄存器vAA
const-class vAA, type[@BBBB]
// 通过给定的类型那个索引获取一个类搜音并赋值给寄存器vAAAA(这条指令占用两个字节,值为0x00ff,是Android4.0新增的指令)
const-class/jumbo vAAAA, type[@BBBBBBBB]
数据返回指令
返回指令指的是函数结尾时运行的最后一条指令。它的基础字节码为return,共有以下四条指令:
// 表示什么也不返回
return-void
// 表示函数返回一个32位非对象类型的值
return vAA
// 表示函数返回一个64位非对象类型的值
return-wide vAA
// 表示函数返回一个对象类型的值
return-object vAA
数据操作指令
数组操作包括读取数组长度、新建数组、数组赋值、数组元素赋值与赋值等操作。
// 获取给定vB寄存器中数组的长度并将赋值给vA寄存器,数组长度指的是数组的条目个数
array-length vA, vB
// 构造置顶类型(tupe@CCCC)与大小(vB)的数组,并将值赋给vA寄存器
new-array vA, vB, type[@CCCC]、
// 指令功能与上一条指令相同,只是寄存器与指令的索引取值范围更大(Android4.0新增的指令)
new-array/jumbo vAAAA, vBBBB, type@CCCCCCCC
// 构造指定类型(type@BBBB)与大小(vA)的数组并填充数组内容。vA寄存器是隐含使用的,除了指定数组的大小孩制定了参数的个数,vC~vG是使用到的参数寄存器序列
filled-new-array {vC, vD, vE, vF, vG}, type@BBBB
// 指定功能与上一条指令相同,只是参数寄存器使用range字节码后缀指定了取值范围,vC是第一个参数寄存器,N=A+C-1。
filled-new-array/range {vCCCC, ..., vNNNN}, type@BBBB
// 指令功能与上一条指令相同,只是寄存器与指令的索引取值范围更大(Android4.0中新增的指令)
filled-new-array/jumbo {vCCCC, ..., vNNNN}, type@BBBBBBBB
// 用指定的数据来填充数组,vAA寄存器为数组引用,引用必须为基础类型的数组,在指令后面后紧跟一个数据表。
fill-array-data vAA, +BBBBBBBB
// 对vBB寄存器指定的数组元素进入取值与赋值。vCC寄存器指定数组元素索引,vAA寄存器用来寄放读取的货需要设置的数组元素的值。读取元素使用aget类指令,元素赋值使用aput指令,根据数组中存储的类型指令后面紧跟不同的指令后缀,指令列表有aget aget-wide aget-object aget-boolean aget-byte aget-char aget-short aput aput-wide aput-boolean aput-byte aput-char aput-short
arrayop vAA, vBB, vCC
数据转换指令
数据转换指令用于将一种类型的数值转换成另一种类型,它的格式位unop vA, vB。vB寄存器货vB寄存器对存放需要转换的数据,转换后的结果保存在vA寄存器与vA寄存器对中。
neg-int 对整型数求补
not-int 对整型数求反
neg-long 对长整型求补
not-long 对长整型求反
neg-float 对单精度浮点型数求补
neg-double 对双精度浮点型数求补
int-to-long 将整型数转换为长整型
int-to-float 将整型数转换为单精度浮点型
int-to-double 将整型数转换为双精度浮点型
long-to-int 将长整型数转换为整型
long-to-float 将长整型数转换为单精度浮点型
long-to-double 将长整型数转换为双精度浮点型
float-to-int 将单精度浮点型数转换为整型
float-to-long 将单精度浮点型数转换为长整型
float-to-double 将单精度浮点型数转换为双精度浮点型
double-to-int 将双精度浮点型数转换为整型
double-to-long 将双精度浮点型数转换为长整型
double-to-float 将双精度浮点型数转换为单精度浮点型
int-to-byte 将整型转换为字节型
int-to-char 将整型转换为字符串
int-to-short 将整型转换为短整型
数据运算指令
数据运算指令包括算术指令与逻辑运算指令。算术指令主要进行数值间加、减、乘、除、模、移位等运算,逻辑运算主要进行数值间与、或、非、异或等运算。数据运算指令有如下四类(数据运算时可能在寄存器或寄存器对间进行,下面的指令讲解时使用寄存器来描述):
binop vAA,vBB,vCC 将vBB寄存器与vCC寄存器进行运算,结果保存到vAA寄存器
binop/2addr vA,vB 将vA寄存器与vB寄存器进行运算,结果保存到vA寄存器
binop/lit16 vA,vB,#+CCCC 将vB寄存器与常量CCCC进行运算,结果保存到vA寄存器
binop/lit8 vAA,vBB,#+CC 将vBB寄存器与常量CC进行运算,结果保存到vAA寄存器
后面3类指令比第1类指令分别多了addr、lit16、lit8等指令后缀。四类指令中基础字节码后面加上数据类型后缀,如-int或-long分别表示操作的数据类型那个为整型与长整型。第1类指令可归类如下:
add-type vBB寄存器与vCC寄存器值进行加法运算(vBB + vCC)
sub-type vBB寄存器与vCC寄存器值进行减法运算(vBB - vCC)
mul-type vBB寄存器与vCC寄存器值进行乘法运算(vBB * vCC)
div-type vBB寄存器与vCC寄存器值进除法运算(vBB / vCC)
rem-type vBB寄存器与vCC寄存器值进行模运算(vBB % vCC)
and-type vBB寄存器与vCC寄存器值进行与运算(vBB & vCC)
or-type vBB寄存器与vCC寄存器值进行或运算(vBB | vCC)
xor-type vBB寄存器与vCC寄存器值进行异或运算(vBB ^ vCC)
shl-type vBB寄存器(有符号数)左移vCC位(vBB << vCC)
shr-type vBB寄存器(有符号数)右移vCC位(vBB >> vCC)
ushr-type vBB寄存器(无符号数)右移vCC位(vBB >> vCC)
其中基础字节码后面的-type可以是-int、-long、-float、-double。后面3类指令与之类似。
对象操作指令
与对象实例相关的操作,比如对象创建,对象检查等.
- new-instance vAA,type@BBBB 构造一个指定类型对象的新实例,并将对象引用赋值给vAA寄存器,类型符号type指定的类型不能是数组类。
- instance-of vA,vB,type@CCCC 判断vB寄存器中的对象引用是否可以转换成指定的类型,如果可以vA寄存赋值为1,否则vA寄存器为0
- check-cast vAA,type@BBBB 将vAA寄存器中对象的引用转成指定类型,成功则将结果赋值给vAA,否则抛出ClassCastException异常.
跳转指令
跳转指令用于从当前地址跳转到指定的偏移处。Dalvik指令集中有三种跳转指令:无条件跳转(goto),分支跳转(switch)与条件跳转(if)。
goto +AA 无条件跳转到指定偏移处,偏移量AA不能为0
goto/16 +AAAA 无条件跳转到指定偏移处,偏移量AAAA不能为0。 goto/32 +AAAAAAAA 无条件跳转到指定偏移处。
packed-switch vAA,+BBBBBBBB 分支跳转指令。vAA寄存器为switch分支中需要判断的值,BBBBBBBB指向一个packed-switch-payload格式的偏移表,表中的值是有规律递增的。
sparse-switch vAA,+BBBBBBBB 分支跳转指令。vAA寄存器为switch分支中需要判断的值,BBBBBBBB指向一个sparse-switch-payload格式的偏移表,表中的值是无规律的偏移表,表中的值是无规律的偏移量。 if-test vA,vB,+CCCC 条件跳转指令。比较vA寄存器与vB寄存器的值,如果比较结果满足就跳转到CCCC指定的偏移处。偏移量CCCC不能为0。
if-test类型的指令有以下几条:
if-eq 如果vA不等于vB则跳转。Java语法表示为 if(vA == vB)
if-ne 如果vA不等于vB则跳转。Java语法表示为 if(vA != vB)
if-lt 如果vA小于vB则跳转。Java语法表示为 if(vA < vB)
if-le 如果vA小于等于vB则跳转。Java语法表示为 if(vA <= vB)
if-gt 如果vA大于vB则跳转。Java语法表示为 if(vA > vB)
if-ge 如果vA大于等于vB则跳转。Java语法表示为 if(vA >= vB) if-testz vAA,+BBBB 条件跳转指令。拿vAA寄存器与 0 比较,如果比较结果满足或值为0时就跳转到BBBB指定的偏移处。偏移量BBBB不能为0。 if-testz类型的指令有一下几条:
if-nez 如果vAA为 0 则跳转。Java语法表示为 if(vAA == 0)
if-eqz 如果vAA不为 0 则跳转。Java语法表示为 if(vAA != 0)
if-ltz 如果vAA小于 0 则跳转。Java语法表示为 if(vAA < 0)
if-lez 如果vAA小于等于 0 则跳转。Java语法表示为 if(vAA <= 0)
if-gtz 如果vAA大于 0 则跳转。Java语法表示为 if(vAA > 0)
if-gez 如果vAA大于等于 0 则跳转。Java语法表示为 if(vAA >= 0)
比较指令
比较指令用于比较两个寄存器中值的大小,其基本格式格式是cmp+kind-type vAA,vBB,vCC,type表示比较数据的类型,如-long,-float等;kind则代表操作类型,因此有cmpl,cmpg,cmp三种比较指令.coml是compare less的缩写,cmpg是compare greater的缩写,因此cmpl表示vBB小于vCC中的值这个条件是否成立,是则返回1,否则返回-1,相等返回0;cmpg表示vBB大于vCC中的值这个条件是否成立,是则返回1,否则返回-1,相等返回0. cmp和cmpg的语意一致,即表示vBB大于vCC寄存器中的值是否成立,成立则返回1,否则返回-1,相等返回0
eg:
cmpl-float vAA,vBB,vCC 比较两个单精度的浮点数.如果vBB寄存器中的值大于vCC寄存器的值,则返回-1到vAA中,相等则返回0,小于返回1
cmpg-float vAA,vBB,vCC 比较两个单精度的浮点数,如果vBB寄存器中的值大于vCC的值,则返回1,相等返回0,小于返回-1
cmpl-double vAA,vBB,vCC 比较两个双精度浮点数,如果vBB寄存器中的值大于vCC的值,则返回-1,相等返回0,小于则返回1
cmpg-double vAA,vBB,vCC 比较双精度浮点数,和cmpl-float的语意一致
cmp-double vAA,vBB,vCC 等价与cmpg-double vAA,vBB,vCC指令
字段操作指令
**字段操作指令表示对对象字段进行设值和取值操作,就像是你在代码中长些的set和get方法.基本指令是iput-type,iget-type,sput-type,sget-type.type表示数据类型.**
*前缀是i的iput-type和iget-type指令用于普通字段的读写操作.*
iget-byte vA,vB,filed_id 读取vB寄存器中的对象中的filed_id字段值赋值给vA寄存器
iput-byte vA,vB,filed_id 设置vB寄存器中的对象中filed_id字段的值为vA寄存器的值
iget-boolean vA,vB,filed_id
iput-boolean vA,vB,filed_id
iget-long vA,vB,filed_id
iput-long vA,vB,filed_id
前缀是s的sput-type和sget-type指令用于静态字段的读写操作
sget-byte vA,vB,filed_id
sput-byte vA,vB,filed_id
sget-boolean vA,vB,filed_id
sput-boolean vA,vB,filed_id
sget-long vA,vB,filed_id
sput-long vA,vB,filed_id
方法调用指令
Davilk中的方法指令和JVM的中指令大部分非常类似.目前共有五条指令集:
invoke-direct{parameters},methodtocall 调用实例的直接方法,即private修饰的方法.此时需要注意{}中的第一个元素代表的是当前实例对象,即this,后面接下来的才是真正的参数.比如指令invoke-virtual {v3,v1,v4},Test2.method5:(II)V中,v3表示Test2当前实例对象,而v1,v4才是方法参数
invoke-static{parameters},methodtocall 调用实例的静态方法,此时{}中的都是方法参数
invoke-super{parameters},methodtocall 调用父类方法
invoke-virtual{parameters},methodtocall 调用实例的虚方法,即public和protected修饰修饰的方法
invoke-interface{parameters},methodtocall 调用接口方法
这五种指令是基本指令,除此之外,你也会遇到invoke-direct/range,invoke-static/range,invoke-super/range,invoke-virtual/range,invoke-interface/range指令,该类型指令和以上指令唯一的区别就是后者可以设置方法参数可以使用的寄存器的范围,在参数多于四个时候使用.
再此强调一遍对于非静态方法而言{}的结构是{当前实例对象,参数1,参数2,…参数n},而对于静态方法而言则是{参数1,参数2,…参数n}
如果要获取方法执行有返回值,需要通过上面说道的move-result指令获取执行结果
同步指令
同步一段指令序列通常是由java中的synchronized语句块表示,则JVM中是通过monitorenter和monitorexit的指令来支持synchronized关键字的语义的,而在Davilk中同样提供了两条类似的指令来支持synchronized语义:
monitor-enter vAA 为指定对象获取锁操作
monitor-exit vAA 为指定对象释放锁操作
异常指令
throw vAA 抛出vAA寄存器中指定类型的异常
smali文件详解
通过反编译工具反编译出来每个.smali,都对应与java中的一个类,每个smali文件都是Davilk指令组成的,并遵循一定的结构.smali存在很多的指令用于描述对应的java文件,所有的指令都以”.”开头,常用的指令如下:
.filed 定义字段
.method…end method 定义方法
.annotation…end annotation 定义注解
.implements 定义接口指令
.local 指定了方法内局部变量的个数
.registers 指定方法内使用寄存器的总数
.prologue 表示方法中代码的开始处
.line 表示java源文件中指定行
.paramter 指定了方法的参数
.param .paramter含义一致,但是表达格式不同
下面我们来写一个简单的Hello World 来解释一下
JAVA 源代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; private TextView tvShowText; private static final String HELLO = "HELLO"; private static final String WORLD = "WORLD";
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
setListener();
} private void setListener() {
tvShowText.setOnClickListener(this);
} private void initView() {
tvShowText = (TextView) findViewById(R.id.tv_show_text);
}
@Override public void onClick(View v) {
Log.d(TAG, "onClick: TextView");
tvShowText.setText(getText());
} private String getText() { return HELLO + WORLD;
}
}
反编译后smali文件如下 #文件头描述
.class public Lorg/professor/helloworld/MainActivity; #指定基类
.super Landroid/support/v7/app/AppCompatActivity; #源文件名称
.source "MainActivity.java"
#表明实现了View.OnClickListener接口
# interfaces
.implements Landroid/view/View$OnClickListener; #定义String静态字段
# static fields
.field private static final HELLO:Ljava/lang/String; = "HELLO"
.field private static final TAG:Ljava/lang/String; = "MainActivity"
.field private static final WORLD:Ljava/lang/String; = "WORLD"
#定义TextView静态字段
# instance fields
.field private tvShowText:Landroid/widget/TextView; #构造方法
# direct methods
.method public constructor <init>()V
.locals 0 #表示函数中无局部变量
.prologue #表示方法中代码正式开始
.line 9 #表示对应与java源文件的第8行
#调用AppCompatActivity中的init()方法
invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V
>
#调用返回指令,此处没有返回任何值
return-void
.end method #方法结束
.method private getText()Ljava/lang/String;
.locals 1
.prologue
.line 40
#v0寄存器中赋值为HELLOWORLD
const-string v0, "HELLOWORLD"
#调用返回指令,返回v0中的值
return-object v0
.end method
.method private initView()V
.locals 1
.prologue
.line 30
#v0寄存器赋值为0x7f0b005e const v0, 0x7f0b005e
#调用方法findViewById
invoke-virtual {p0, v0}, Lorg/professor/helloworld/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0 #寄存器中对象的引用转成指定类型
check-cast v0, Landroid/widget/TextView; #设置p0寄存器中的对象中tvShowText字段的值为v0寄存器的值
iput-object v0, p0, Lorg/professor/helloworld/MainActivity;->tvShowText:Landroid/widget/TextView;
.line 31
return-void
.end method
.method private setListener()V
.locals 1
.prologue
.line 26
#设置v0寄存器中的对象为p0中tvShowText字段的值
iget-object v0, p0, Lorg/professor/helloworld/MainActivity;->tvShowText:Landroid/widget/TextView; #调用 v0的setOnClickListener
invoke-virtual {v0, p0}, Landroid/widget/TextView;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 27
return-void
.end method # virtual methods
.method public onClick(Landroid/view/View;)V
.locals 2 #表示函数中2局部变量
.param p1, "v" # Landroid/view/View;
.prologue
.line 35
const-string v0, "MainActivity"
const-string v1, "onClick: TextView"
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 36
iget-object v0, p0, Lorg/professor/helloworld/MainActivity;->tvShowText:Landroid/widget/TextView;
invoke-direct {p0}, Lorg/professor/helloworld/MainActivity;->getText()Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
.line 37
return-void
.end method
.method protected onCreate(Landroid/os/Bundle;)V
.locals 1
.param p1, "savedInstanceState" # Landroid/os/Bundle; #参数savedInstancestate
.prologue
.line 19
#调用父类方法onCreate()
invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
.line 20
#v0寄存器赋值为0x7f04001b
const v0, 0x7f04001b
#调用方法setContentView()
invoke-virtual {p0, v0}, Lorg/professor/helloworld/MainActivity;->setContentView(I)V
.line 21
invoke-direct {p0}, Lorg/professor/helloworld/MainActivity;->initView()V
.line 22
invoke-direct {p0}, Lorg/professor/helloworld/MainActivity;->setListener()V
.line 23
return-void
.end method