文章目录
smali文件解读
描述类信息
在打开Smali文件时,它的头三行描述了当前类的一些信息:
.class public Lcom/test/helloworld16/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"
- 第一行表示的含义是当前class的包名和类名
- 第二行表示的是父类的包名和类名
- 第三行表示的是源文件的名称
静态字段
Smali文件类的主体部分:一个类由字段和方法组成。.field字段声明,字段分两类:静态字段和实例字段
语法格式如下
.field < 访问权限> static [ 修饰关键字]< 字段名>:< 字段类型>
实例字段
实例字段相比于静态字段少了一个static的静态声明,其他均相同
直接方法
smali的方法声明使用的.method指令,方法分为直接方法和虚方法两种。
方法格式如下:
.method <访问权限> [ 修饰关键字] < 方法原型>
其他字段含义
- **.registers:**指令指定了方法中寄存器的总数,这个数量是参数和本地变量总和
- **.param :**表明了方法的参数,每个.param指令表示一个参数,方法使用了几个参数就有几个.parameter指令
- **.prologue:**指定了代码的开始处,混淆过的代码可能去掉了该指令
- **.line:**指明了该处代码在源代码中的行号,同样,混淆后的代码可能去掉了行号
- **.local:**使用这个指定表明方法中非参寄存器
虚方法
虚方法:是指从父类中继承的方法或者实现的接口的方法,它的声明跟直接方法相同,只是起始的初始为virtual methods ,格式如下
.method <访问权限> [ 修饰关键字] < 方法原型>
接口
如果一个类实现了一个接口,那么会在smali文件中用.implements指令指出
Smali基础语法
寄存器
Dalvik中的寄存器都是32位大小,支持所有类型。对于小于或等于32位类型,使用一个寄存器就可以了,对64位(long和double)类型,需要使用两个相邻的寄存器来存储。
寄存器的命名法有两种:V命名法和P命名法:
v命名法:局部变量寄存器V0-Vn,参数寄存器是Vn-Vn+m。
p命名法:参数寄存器P0-Pn,变量寄存器V0-Vn。
- p0->this
- p1,p2参数
- v0,v1局部变量
数据类型
在smali代码中数据类型以类型描述符来表示
基本数据类型标识方法如下:
引用数据类型标识方法如下:
- String–>Ljava/lang/String;
- int[]–>I[
- String[]–>Ljava/lang/String;[
例如:
.method protected onCreate(Landroid/os/Bundle;)V
上面的smali代码,翻译成JAVA源码则是下面的方式
protected void onCreate(Bundle bundle);
字段和方法的表示
字段格式:类型(包名+类名)+字段名称+字段类型
字段描述
在JAVA中假设有一个类:com.test.Person
,类有一个属性:int id
,在smali中则用下面的方法表示:
Lcom/test/Person;->id:I
语法为:类名->属性名:属性类型
方法描述
在JAVA中假设有一个类:com.test.Person
,类有一个方法:void setId(int id);
,在smali中则用下面的方法表示:
Lcom/test/Person;->setId(I)V
语法为:完整类名->方法名(参数类型)返回类型
Smali指令集
Dalvik的指令格式:
基础字节码 - 名称后缀/字节码后缀 目的寄存器 源寄存器
名称后缀是wide,表示数据宽度为64位。字节码后缀是from16,表示源寄存器为16位。
Dalvik指令集中大多数指令用到了寄存器作为目的操作数或者是源操作数,其中A/B/C/D/E/F/G表示一个4位的数值,AA/BB/HH表示一个8位的数值,AAAA/BBBB表示16位的数值
空操作指令
空操作指令的助记符为nop,它的值为00,通常nop指令被用来做对齐代码之用,无实际操作含义
数据操作指令
数据操作指令为move。move指令根据字节码大小和类型不同,后面会跟上不同的后缀,表达的意义也不同
- **“move vA, vB”:**将vB寄存器的值赋给vA寄存器,源寄存器与目的寄存器都为4位
- **“move/from16 vAA, vBBBB”:**将vBBBB寄存器的值赋给vAA寄存器,寄存器为16位,目的寄存器为8位
- **“move/16 vAAAA, vBBBB”:**将vBBBB寄存器的值赋给vAAAA寄存器,源寄存器与目的寄存器都为16位。
- **“move-wide vA, vB”:**为4位的寄存器对赋值。源寄存器与目的寄存器都为4位。
- **“move-object vA, vB”:**object是对象的意思,出现这个词即表示为对象赋值。源寄存器与目的寄存器都为4位
- **“move-result vAA”:**将上一个invoke类型指令操作的单字非对象结果赋给vAA寄存器
- **“move-exception vAA”:**保存运行时发生的异常到vAA寄存器。这条指令必须是异常发生时的异常处理器的一条指令,否则指令无效。
总结起来move指令有三种作用:
- 进行赋值操作
- 接收返回值操作
- 处理异常的操作
返回指令
返回指令即return指令,指的是函数结尾时运行的最后一条指令。共有以下四条返回指令 :
- **“return-void”:**表示函数从一个void方法返回,返回值为空
- **“return vAA”:**表示函数返回一个32位非对象类型的值,返回值寄存器为8位的寄存器vAA。
- **“return-wide vAA”:**表示函数返回一个64位非对象类型的值,返回值为8位的寄存器对vAA
- **“return-object vAA”:**表示函数返回一个对象类型的值,返回值为8位的寄存器vAA
数据定义指令
数据定义指令用来定义程序中用到的常量,字符串,类等数据。它的基础字节码为const
- **“const/4 vA, #+B”:**将数值符号扩展为32位后赋给寄存器vA。
- **“const/16 vAA, #+BBBB”:**将数据符号扩展为32位后赋给寄存器vAA
- **“const-wide/16 vAA, #+BBBB”:**将数值符号扩展为64位后赋给寄存器对vAA
- **“const-string vAA, string@BBBB”:**通过字符串索引构造一个字符串并赋给寄存器vAA
- **“const-class vAA, type@BBBB”:**通过类型索引获取一个类引用并赋给寄存器vAA
跳转指令
if条件跳转有如下几种
跟x86的JCC跳转有异曲同工之妙
方法调用指令
方法调用指令负责调用类实例的方法,它的基础指令为invoke
- invoke-virtual或invoke-virtual/range :调用实例的虚方法
- invoke-super或invoke-super/range :调用实例的父类/基类方法
- invoke-direct或invoke-direct/range :调用实例的直接方法
- invoke-static或invoke-static/range :调用实例的静态方法
- invoke-interface或invoke-interface/range :调用实例的接口方法