smail基本语法示例
准备工作
工欲善其事必先利其器,所以要学好smail语法,好的工具是必须的,这里推荐两个工具,一个是Smali2Java,也就是将smail文件转换成java的工具;另一个工具是J2S2J1.3,这个工具可以将简单的java或者smail代码进行转换。
案例
因为我是一开始直接看源码的,所以我们今天也是直接从源码开始:
.super Ljava/lang/Object;
.source "Test.java"
# instance fields
.field private name:Ljava/lang/String;
# direct methods
.method public constructor <init>()V
    .registers 2
    .prologue
    .line 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    .line 2
    const-string v0, "test"
    iput-object v0, p0, LTest;->name:Ljava/lang/String;
    return-void
.end method
.method public static main([Ljava/lang/String;)V
    .registers 4
    .prologue
    const/4 v2, 0x1
    .line 14
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v1, "Hello World!"
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 15
    new-instance v0, LTest;
    invoke-direct {v0}, LTest;-><init>()V
    invoke-virtual {v0, v2, v2}, LTest;->test(IZ)Ljava/lang/String;
    move-result-object v0
    .line 16
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 17
    return-void
.end method
# virtual methods
.method public test(IZ)Ljava/lang/String;
    .registers 4
    .prologue
    .line 21
    const-string v0, "test"
    return-object v0
.end method
上面这些代码其实很简单,就是创建一个Test类,定义了一个私有成员属性name,并进行了赋值,然后定义了一个main方法,main方法中进行了Hello World!打印输出操作,对应的java代码:
import java.io.PrintStream;
public class Test{
    public Test()    {
        name = "test";
        id = 1;
    }
    public static void main(String args[]){
        System.out.println("Hello World!");
String result = new Test().test(1, true);
System.out.println(result);
    }
    public String test(int i, boolean flag) {
return "test";
    }
    private int id;
    private String name;
}
代码拆解
下面我们来逐步讲解下smail的语法,方便大家建立它与java代码语法直接的关系。
smail文件,我的理解是它其实就类似于java编译之后的.class文件,也是一种字节码文件。
基本信息
我们先看前三行:
.super Ljava/lang/Object;
.source "Test.java"
# instance fields
.field private name:Ljava/lang/String;
目前没有找到官方的相关文档,只能结合网上的资料和自己的推测进行分析,如果有不合理的地方,希望大家不吝指教。
第一行.super表示当前类的父类,表示继承关系的
第二行.source表示当前字节码文件对应的源码文件名
第三行.field表示定义一个属性(字段),具体语法是
.field 访问权限 字段名:字段类型;
字段类型的取值范围如下:
| Dalvik字节码类型 | java基本数据类型 | 
|---|---|
| V | void | 
| Z | boolean | 
| B | byte | 
| C | char | 
| S | short | 
| I | int | 
| J | long | 
| F | float | 
| D | double | 
| L | java类类型 | 
| [ | 数组类型 | 
我们这里的Ljava/lang/String就表示String类型的数据,如果是int类型,那语法应该是这样的:
.field private id:I
这里的对照关系,和class字节码绝大部分都一样
构造方法
紧接着,下面是构造方法对应的smail代码
# direct methods
.method public constructor <init>()V
    .registers 2
    .prologue
    .line 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    .line 2
    const-string v0, "test"
    iput-object v0, p0, LTest;->name:Ljava/lang/String;
    return-void
.end method
.method
其中.method表示方法的开始,具体语法是:
.method 访问权限 方法名()返回结果
这里的返回结果和字段的类型取值一样,可以参考上面的表格
.end method表示方法的结束,与.method相对应,两者之间的内容为方法体
.registers
.registers 2用于声明当前方法的寄存器个数,寄存器是用来存放方法的入参和方法的局部变量的,具体语法为:
.registers N
N代表需要的寄存器的总个数
同时,还有一个关键字.locals,它用于声明非参数的寄存器个数(也就是举报变量的个数,包含在registers声明的个数当中),也叫做本地寄存器,语法是一样的。
.prologue
.prologue表示方法代码的开始处,所以在方法中增加代码,只能在.prologue下面的区域进行
.line
.line 1用于标记java代码中的行数,没有实际含义
.invoke-direct
invoke-direct {p0}, Ljava/lang/Object;-><init>()V表示调用private或init方法,p0表示java中的this,所以这个方法的含义是调用Object的init方法,并把返回值赋给p0,这里需要补充一个小知识点:
在smali里的所有操作都必须经过寄存器来进行,本地寄存器用v开头数字结尾的符号来表示,如v0、v1、v2、...
参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2、...特别注意的是,p0不一定是函数中的第一个参数,在非static函数中, p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…,而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)。
本地寄存器没有限制,理论上是可以任意使用的。
这里需要注意的是,再使用vx和px的时候一定不要超过定义的限制(也就是.registers和.locals),虽然超过代码编译时不会报错,但是在运行时会报错,而且在代码反编译的时候也会报错:

虽然这种报错不一定是参数个数超了,但如果之前反编译正常,修改之后不正常,这块可以作为一个检查点
const-string
const-string v0, "test"表示定义一个String常量,并把值赋给v0,也就是第一个本地寄存器。
下面再补充一些和const-string相关的内容
const
- 定义常量
const/4,4代表4个字节,最大只允许存放4位数值(4个二进制位),取值范围为 -8 and 7
# 定义常量2,并将值赋给v2寄存器,
const/4 v2, 0x02
const/16 同上,最大值允许存放16位数值 第一位默认为符号位,所以计算后15位的数值,比如short类型数据 取值范围为-32768~32767
# 定义定义一个容器,将数字123123赋给v0
const/16 v0 , 0x123123
const 最大只允许存放32位数据,比如int类型数据, 取值范围-2147483647~2147483647
# 定义一个容器 将数字10赋值给v0
const v0 , 0x10
const/high16 最大只允许存放高16位数值
#定义一个容器  比如0xFFFF0000末四位补0 存入高四位0XFFFF
const/high16 v0,0xFFFF0000
const-wide 占用两个寄存器vx和vx+1,共64位,数值必须以L结尾,否则编译不通过
const-wide v0,30 #占用v0和v1
const-wide/16 定义两个相连容器,最大只允许存放16位数据
const-wide/32 定义两个相连容器,最大只允许存放32位数据
const-wide 定义两个相连容器,最大只允许存放64位数据
const-wide/high16 定义两个相连容器,只允许存放高16位数据
iput-object
iput-object v0, p0, LTest;->name:Ljava/lang/String;表示把v0的值(也就是字符串常量test)赋给p0(也就是this)的name字段,Ltest标记的是p0的类型,:Ljava/lang/String是标记name的类型。
具体语法是:
iput-object 要设置的值, 要设置值的对象, 对象类型;->字段名:字段类型;
这里的iput表示设置非静态成员变量,设置静态变量要用sput,语法如下
sput-object 要设置的值, 类型;->字段名:字段类型;
这里的-object表示对非基本类型赋值,如果是基本类型可以通过iput、sput、iput-boolean、sput-boolean进行设置,语法是一样的。
iget-obejct
与iput-object对应,iget-object是为了获取数据,具体语法是:
iget-object 接收值的寄存器, 接受值来源对象(谁的字段), 对象类型;->字段名:字段类型;
例如:
iget-object v0, p0, Lcom/syske/android/Activity;->_view:Lcom/syske/common/View;
静态变量的字段也是一样的:
sget-object 接收值的寄存器, 静态对象类型;->字段名:字段类型;
基本类型的字段也是类似的,需要通过iget、sget、iget-boolean、sget-boolean来获取
return-void
return-void表示方法没有返回值,也就是void,同时返回值还可以是:
- return vx:返回- vx寄存器中的值。
- return-wide vx:返回在- vx,vx+1寄存器的- doubl e/long值。
- return-object vx:返回在 vx 寄存器的对象引用。
| smali方法返回关键字 | java | 
|---|---|
| return | byte | 
| return | short | 
| return | int | 
| return-wide | long | 
| return | float | 
| return-wide | double | 
| return | char | 
| return | boolean | 
| return-void | void | 
| return-object | 数组 | 
| return-object | object | 
静态方法
自此,构造方法相关方法我们算是基本讲解完了,下面我们分析下静态main方法的源码,前面已经分享的内容这里直接忽略了。
.method public static main([Ljava/lang/String;)V
    .registers 4
    .prologue
    const/4 v2, 0x1
    .line 14
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
    const-string v1, "Hello World!"
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 15
    new-instance v0, LTest;
    invoke-direct {v0}, LTest;-><init>()V
    invoke-virtual {v0, v2, v2}, LTest;->test(IZ)Ljava/lang/String;
    move-result-object v0
    .line 16
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    .line 17
    return-void
.end method
invoke-virtual
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V表示
调用protected或public方法(非私有实例方法),这里的v0就表示java/lang/System, v1表示方法的入参,具体语法如下:
invoke-virtual {对象实例, 方法入参}, 实例类型;->方法名(入参类别;)返回值
如果参数是多参数则直接写多个参数即可,如上面的源码:
invoke-virtual {v0, v2, v2}, LTest;->test(IZ)Ljava/lang/String;
另外与invoke-virtual类似的语句还有几个在,这里做一个简单的总结:
| 方法 | 说明 | 
|---|---|
| invoke-virtual | 用于非私有实例方法的调用 | 
| invoke-direct | 用于构造方法以及私有方法的调用 | 
| invoke-static | 调用静态方法 | 
| invoke-super | 调用父类的方法 | 
| invoke-interface | 调用接口方法 | 
new-instance
new-instance v0, LTest;表示创建LTest的实例对象,并将对象引用赋值给v0寄存器
紧接着,调用LTets的实例化方法:
invoke-direct {v0}, LTest;-><init>()V表示调用实例化方法,并把返回值赋给v0
move-result-object
move-result-object v0表示将上一次(紧挨着的)方法执行结果指向v0,也就是接收方法返回值。
扩展知识
好了,至此我们已经把今天示例代码基本分析完了,同时也对smail的基本语法有了一些初步的认识,基于以上这些知识点,我们基本上已经可以阅读smail源码了,但是为了更全面地学习smail的语法,我觉得还是有必要补充一些额外的知识点,比如条件语句的语法:
条件语句
| 语句 | 说明 | 
|---|---|
| if-eq vA, vB, :cond_** | 如果vA等于vB则跳转到:cond_** #equal | 
| if-ne vA, vB, :cond_** | 如果vA不等于vB则跳转到:cond_** # not equal | 
| if-lt vA, vB, :cond_** | 如果vA小于vB则跳转到:cond_** #less than | 
| if-ge vA, vB, :cond_** | 如果vA大于等于vB则跳转到:cond_** # greater equal | 
| if-gt vA, vB, :cond_** | 如果vA大于vB则跳转到:cond_** # greater than | 
| if-le vA, vB, :cond_** | 如果vA小于等于vB则跳转到:cond_** # less equal | 
| if-eqz vA, :cond_** | 如果vA等于0则跳转到:cond_** #zero | 
| if-nez vA, :cond_** | 如果vA不等于0则跳转到:cond_** | 
| if-ltz vA, :cond_** | 如果vA小于0则跳转到:cond_** | 
| if-gez vA, :cond_** | 如果vA大于等于0则跳转到:cond_** | 
| if-gtz vA, :cond_** | 如果vA大于0则跳转到:cond_** | 
| if-lez vA, :cond_** | 如果vA小于等于0则跳转到:cond_** | 
其中,代码中:cond_**语句对应if条件中的:cond_**,表示该条件语句结束:

语法关键词
下面是一些常用的语法关键词
| 关键词 | 说明 | 
|---|---|
| .class | 定义java类名 | 
| .super | 定义父类名 | 
| .source | 定义Java源文件名 | 
| .filed | 定义字段 | 
| .method | 定义方法开始 | 
| .end method | 定义方法结束 | 
| .annotation | 定义注解开始 | 
| .end annotation | 定义注解结束 | 
| .implements | 定义接口指令 | 
| .local | 指定了方法内局部变量的个数 | 
| .registers | 指定方法内使用寄存器的总数 | 
| .prologue | 表示方法中代码的开始处 | 
| .line | 表示java源文件中指定行 | 
| .paramter | 指定了方法的参数 | 
| .param | 和.paramter含义一致,但是表达格式不同 | 
总结
至此,我们的smail语法学习之路暂时告一段落,但是还是要多实践,多探索,毕竟学语法就是为了更好的实践,还是建议各位小伙伴找几个安卓项目亲自动手实践下,好了,今天的内容就到这里吧,感谢各位小伙伴的支持!!!
在梳理这些知识点的时候,我发现有位大佬的笔记写的很完整,感兴趣的小伙伴可以去看下:

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号