smali 语法 02

classess.dex:源文件代码

 

注释

 

1 #

 

 

 

类声明

复制代码
1 .class +权限修饰符 +类名;

例如:
.class public Lcom/test/Test;

# 类名Test
# public公共属性
# 凡是L开头全包名路径结尾都需要加分号
# com/test/Test Test类的全包名路径
复制代码

比如以下java代码:

1 public class Test
2 {
3 }

用smali代码表示为:

1 .class public LTest;#声明类 (必须)
# 类名Test
# Test类路径默认顶包下,所以直接LTest
2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须) 3 .implements Ljava/lang/CharSequence; #如果实现了接口 则添加接口代码 4 .source "Test.java" # 对应java源码文件名 (非必须)

关于分号;

凡是L开头全包名路径结尾都需要加分号

字段声明(成员/全局变量)

1 .field 权限修饰符+静态修饰符 +变量名:变量全类名路径;

案例:
.field public a:strig #这样写不行,变量名a,它是string型,strig类型需要全包名路劲
field public a:Lcom/long/string #这样才对

比如以下java代码:

1 public class Test
2 {
3     private static String a;
4 }

用smali代码表示为:

.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 默认继承Object (必须)
.source "Test.java" # 源码文件 (非必须)

# 如果是非静态,只需将static去掉即可
.field private static a:Ljava/lang/String;  #变量a属于某包路径下的String类型

补充:

复制代码
基本数据类型示例:
.method public final pubFinalMethod()V //返回值
.field private boType:Z    // boolean
.field private byteType:B  // byte
.field private shortType:S // short
.field private charType:C  // char
.field private intType:I   // int
.field private longType:J  //long
.field private floatType:F // float
.field private doubleType:D // double
复制代码

常量声明

1 .field 权限修饰符+静态修饰符 +final+变量名:变量全类名路径;=常量值

比如以下java代码:

1 public class Test
2 {
3     private static final String a=”hello“;
4 }

用smali代码表示为:

1 .class public LTest;#声明类 (必须)
2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
3 
4 .field public static final a:Ljava/lang/String; = "hello"  
#固定值a,常量a等于某包路径下的字符串"hello",字符串类型String

方法声明

复制代码
1 .method 权限修饰符+静态修饰符 +方法名(参数类型、叫参数签名也行)返回值类型
2 #方法体
3 .end method #方法结尾标志



案例:注意寄存器数量的问题,静态方法没有参数this
.method public static getHello()Ljava/long/String;
.registers 1 #用到的寄存器的数量为1
    const-string v0, "hello"# 将字符串赋值给容器v0,容器v0就是寄存器,将寄存器理解为容器就行 return object v0.end method




案例:注意寄存器数量的问题,非静态方法,有默认参数this
.method public getHello()Ljava/long/String; #去掉static后,它是一个对象的方法,getHello()默认有一个参数this ,它需要容器p0来接受
.registers 2 #所以此时寄存器数量为2
    const-string v0, "hello"# return object v0.end method

#参数都要有对应的容器来接受,即寄存器


复制代码

比如以下java代码:

1 public class Test
2 {
3     public static void getName(){}  #静态的空参方法,没有返回值
4 }

用smali代码表示为:

复制代码
1 .class public LTest;#声明类 (必须)
2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
3 
4 # 如果是非静态,只需将static去掉即可
5 .method public static getName()V
6  
7     return-void
8 .end method


案例:
静态,方法名getName,空参,无返回值
.method public static getName()V #V无返回值 return-void #固定写法无返回值的固定写法 .end method

案例:
2个参数的写法,string类型和int类型,返回值类型I
.method public static getName(Ljava/lang/string;I)I  
复制代码

如果是带参并且带有返回值的方法

比如以下java代码:

1 public class Test
2 {
3     public  String getName(String p){
4         return "hello";
5     }
6 }

用smali代码表示为:

1 .method public getName(Ljava/lang/String;)Ljava/lang/String;
2 
3     const-string v0, "hello"   #字符串声明
4 
5     return-object v0
6 .end method

关于方法返回关键字

主要有以下四种

1 return-void    # void的返回值
2 return-object  # 对象的返回值
3 return
4 return-wide

 

方法取值

1 第一步:调用方法  invoke--
2 第二步:返回值的参数  move-result-object v0    #返回值是string类型用object,赋值给v0

 

数据类型对应关系表如下:

smali方法返回关键字java
return byte   32
return short  32
return int   32
return-wide long  64位数据类型
return float   32
return-wide double  64位数据类型
return char  32
return boolean  32
return-void void
return-object 数组
return-object (字符串要返回这个类型) object

 

 

 

 

 

 

 

 

 

 

 

 

 

 

构造方法声明

1 .method 权限修饰符 +constructor <init>(参数类型)返回值类型
2 #方法体
3 .end method #方法结尾标志

# constructor <init> 构造方法的固定写法,与普通方法的区别就是没有了方法名,init代替了
# constructor 构造器的意思
# (参数类型) 里面有个默认参数this是固定,一般不写出来,默认的,this是第一个参数的位置,后面p0就是指this参数

比如以下java代码:

1 public class Test
2 {
3    public Test(String a){     #Test构造方法
4    }
5 }

用smali代码表示为:

复制代码
.class public LTest;#声明类 (必须)
.super Ljava/lang/Object;#声明父类 test方法默认继承父类Object (必须)


.method public constructor <init>(Ljava/lang/String;)V  #所有构造方法返回值都是V
  
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法 

    return-void   #所有构造方法返回值都是void
.end method

# invoke-direct 调用direct方法,
# 所有的构造方法,要先初始化父类的构造方法


复制代码

静态代码块的声明

复制代码
1 .method static +constructor <clinit>()V
2 #方法体
3 .end method #方法结尾标志

#跟构造方法2个区别:
#1:加了个static,
#2: init 变为clinit


#静态代码块优先于构造方法执行
复制代码

比如以下java代码:

复制代码
1 public class Test
2 {
3    4 
5    static{
6    
7    }
8 }

1 public class Test
2 {
3     public static string a="a" #静态变量初始化必须子init构造方法里初始化
4 
5    static{
6    
7    }
8 }
复制代码

用smali代码表示为:

复制代码
1 .class public LTest;#声明类 (必须)
2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
3 
4 
5 .method public static constructor <clinit>()V
6   
7  
8     return-void
9 .end method
复制代码

方法调用

关键字

1 invoke-virtual //用于非私有实例方法的调用 ,也叫调用虚方法
2 invoke-direct //用于构造方法以及私有方法的调用
3 invoke-static //调用静态方法
4 invoke-super //调用父类的方法.
5 invoke-interface //调用接口方法

#所有方法调用都已invoke开头

非私有实例方法的调用

invoke-virtual {参数}, 方法所属类名;->方法名(参数类型)返回值类型;

# 就是调用某类下的某方法

比如以下java代码:

复制代码
1 public class Test
2 {
3   public Test(String a){    #Test构造方法
4     getName();
5    }
6   public String getName(){  #在构造方法里进行成员方法的调用
7     return "hello";
8    }
9 }
复制代码

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 
 4 
 5 .method public constructor <init>( Ljava/lang/String;)V  #构造方法
#init里的第一个参数是默认参数this
6 7 invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类的构造方法 8 invoke-virtual {p0}, LTest;->getName()Ljava/lang/String;
# 通过invoke-virtual调用Test类下的getName方法
# p参数的意思,p0 指带构造方法里的默认参数this,由于this是第一个参数,就是p0
# getName方法的返回值是strig类型
9 10 return-void 11 .end method 12 13 #声明getName方法 #在构造方法里调用getname方法 14 .method public getName()Ljava/lang/String; 15 16 const-string v0, "hello"# 定义局部字符串常量 17 18 return-object v0 # 返回常量 19 .end method
复制代码

私有方法或者构造方法的调用

1 invoke-direct {参数}, 方法所属类名;->方法名(参数类型)返回值类型;

私有方法调用:

比如以下java代码:

复制代码
 1 public class Test
 2 {
 3   public Test(String a){
 4     getName();
 5    }
 6    //私有方法
 7   private String getName(){
 8     return "hello";
 9    }
10 }
复制代码

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 
 4 
 5 .method public constructor <init>(Ljava/lang/String;)V
 6   
 7     invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类的构造方法 
 8     invoke-direct {p0}, LTest;->getName()Ljava/lang/String;# 调用私有getName方法
 9 
10     return-void
11 .end method
12 
13 #声明getName方法
14 .method private getName()Ljava/lang/String;
15   
16     const-string v0, "hello"# 定义局部字符串常量
17     
18     return-object v0 # 返回常量
19 .end method
复制代码

构造方法调用:

比如以下java代码:

复制代码
 1 public class Test
 2 {
 3   public Test(String a){
 4     new Test2("hello"); 
 5    }
 6     public class Test2
 7     {
 8       public Test2(String a){    
 9        }
10     }
11 }
复制代码

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 
 4 # 匿名内部类的声明
 5 .annotation system Ldalvik/annotation/MemberClasses;
 6     value = {
 7         LTest$Test2;
 8     }
 9 .end annotation
10 
11 
12 # 构造方法
13 .method public constructor <init>(Ljava/lang/String;)V
14     # 初始化父类构造方法
15     invoke-direct {p0}, Ljava/lang/Object;-><init>()V
16     # 创建对象
17     new-instance v0, LTest$Test2;
18     # 定义常量
19     const-string v1, "hello"
20     # 调用构造方法
21     invoke-direct {v0, p0, v1}, LTest$Test2;-><init>(LTest;Ljava/lang/String;)V
22 
23     return-void
24 .end method
复制代码

静态方法的调用并获取返回值(不区分私有公有 静态优先)

1 invoke-static {参数}, 方法所属类名;->方法名(参数类型)返回值类型;

比如以下java代码:

复制代码
 1 public class Test
 2 {
 3   public Test(String a){
 4      String b=getName();
 5      System.out.print(b);
 6    }
 7   private static String getName(){
 8     return "hello";
 9    }
10 
11 }
复制代码

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 
 4 
 5 .method public constructor <init>(Ljava/lang/String;)V
 6   
 7     invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法 
 8     invoke-static {p0}, LTest;->getName()Ljava/lang/String;# 调用普通成员getName方法
 9     move-result-object v0 #将返回值赋给v0
10     return-void
11 .end method
12 
13 #声明getName方法
14 .method public getName()Ljava/lang/String;
15   
16     const-string v0, "hello"# 定义局部字符串常量
17     
18     return-object v0 # 返回常量
19 .end method
复制代码

父类成员的方法调用

1 invoke-super

比如以下java代码

1 @Override
2 protected void onCreate(Bundle savedInstanceState) {
3     super.onCreate(savedInstanceState);
4 
5 }

用smali代码表示为

1 .method protected onCreate(Landroid/os/Bundle;)V
2     .registers 2
3  
4     invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
5     return-void
6 .end method

接口的调用

1 invoke-interface  {参数}, 方法所属类名;->方法名(参数类型)返回值类型;

比如以下java代码:

复制代码
 1 public class Test
 2 {
 3     private  InterTest a=new Test2();
 4     public Test(String a){
 5     }
 6     public void setAa(){
 7        InterTest aa=a;
 8        # 调用接口方法
 9         aa.est2();
10    }
11   public class Test2 implements InterTest
12   {
13         public Test2(){}
14             
15           public void est2(){}
16    }
17   interface InterTest
18   {
19       public void est2();
20   }
21 }
复制代码

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 
 4 
 5 .method public constructor <init>(Ljava/lang/String;)V
 6   
 7     invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法 
 8     invoke-static {p0}, LTest;->getName()Ljava/lang/String;
# 调用普通成员getName方法
9 10 return-void 11 .end method 12 13 #声明setAagetName方法 14 .method public setAa()V 15 .registers 2 16 17 18 iget-object v0, p0, LTest;->a:LTest$InterTest; 19 # 调用接口方法 20 invoke-interface {v0}, LTest$InterTest;->est2()V 21 22 return-void 23 .end method
复制代码

 

创建对象

对象的创建分多步进行:

复制代码
1 # 第一步声明实例,关键字new-instance声明一个变量,以变量名也就是寄存器接受
2 new-instance +变量名, 对象全包名路径;

3 # 第二步,创建的对象通过构造方法的参数传入,通过调用构造方法 (如果构造方法内还定义了成员变量,那么在调用之前需要提前声明,然后在invoke的时候当作参数一并传入) 4 invoke-direct {变量名}, 对象全包名路径;-><init>(参数)返回类型

new Test():

案例:
# 第一步 声明一个容器v0,用于接受Test对象,假设Test对象它在顶包下,
# 第二步 调用Test类的构造方法当做参数传给所对应的容器v0当做参数传入进来,Test对象它在顶包下,调用构造方法<init>,空参
new-instance v0,LTest;
invoke-direct {v0},LTest;-><init>()V

#先声明、再调用构造方法



复制代码
数组的创建
复制代码
 1 const/4 v0, 0x4
 2 new-array v0, v0, [I
 3 
 4 fill-array-data v0, :array_a
 5 
 6   :array_a
 7     .array-data 4 # 表示占用四个字节
 8         0x0
 9         0x1
10         0x2
11         0x3
12     .end array-data
复制代码

数据的定义

分三大类

复制代码
1 字符串类型数据

案例:注意寄存器数量的问题,静态方法没有参数this
.method public static getHello()Ljava/long/String;
.registers 1 #用到的寄存器的数量为1:局部代码里的参数个数
    const-string v0, "hello"
# 将字符串赋值给容器v0,容器v0就是寄存器,将寄存器理解为容器就行 return object v0 #注意返回值字符串用对象object
.end method



案例:注意寄存器数量的问题,非静态方法,有默认参数this=p0
.method public getHello()Ljava/long/String;
#去掉static后,它是一个对象的方法,getHello()默认有一个参数this ,它需要容器p0来接受
.registers 2 #所以此时寄存器数量为2
    const-string v0, "hello" return object v0
.end method

#参数都要有对应的容器来接受,即寄存器


2 字节码数据




3 数值型数据







复制代码

数值类型数据拆分

复制代码
 1 第一种 const开头 占用一个容器(寄存器) 32位/容器
 2 const v0,30
 3   * const/4 最大只允许存放4位数值(4个二进制位) 1 111 7
 4   * const/16 最大值允许存放16位数值 第一位默认为符号位 所以计算后15位的数值
 5   * const 32位 最大32位
 6   * const/high16 v0,0xFF7f0000
 7   
 8 
 9 第二种 const-wide 占用两个容器 64位
10 const-wide v0,30 #占用v0和v1
复制代码

总结

复制代码
 1 const-string  v0 , "hello"# 定义字符串 将字符串hello赋值给v0
 2 
 3 const-class v0,LGoActivity; # 定义字节码对象 将GoActivity.class对象赋值给v0
 4 
 5 # 以下数据定义高位默认为符号位
 6 const/4 v0,0x2 # 定义一个容器 最大只允许存放半字节4位数据 取值范围为 -8 and 7
 7 const/16 v0 , 0xABCD # 定义定义一个容器 最大只允许存放16位数据 比如short类型数据 取值范围为-32768~32767
 8 const v0 , 0xA# 定义一个容器 最大只允许存放32位数据,比如int类型数据 将数字10赋值给v0 取值范围-2147483647~2147483647
 9 const/high16 #定义一个容器 最大只允许存放高16位数值 比如0xFFFF0000末四位补0 存入高四位0XFFFF
10 
11 # const-wide 占用两个寄存器vx和vx+1, 数值必须以L结尾 否则编译不通过
12 const-wide/16 # 定义两个相连容器 最大只允许存放16位数据
13 const-wide/32 # 定义两个相连容器 最大只允许存放32位数据
14 const-wide # 定义两个相连容器 最大只允许存放64位数据
15 const-wide/high16 # 定义两个相连容器 只允许存放高16位数据
复制代码

数据取值范围算法

复制代码
 1 1000 → -8 2 1001 → -7 3 1010 → -6 4 1011 → -5 5 1100 → -4 6 1101 → -3 7 1110 → -2 8 1111 → -1 9 
10 0000011 0001112 0010213 0011314 0100415 0101516 0110617 01117
复制代码

算法:正数的符号位是0,负数的符号位是1。正数的反码、补码与原码一样。负数的反码是让符号位不变,数据位按位取反;补码是将反码加1。

静态字段赋值

分多步进行 关键代码:

1 sput-object # s代指static

比如以下java代码:

1 public class Test
2 {
3     private static String a=”hello“;
4 }

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 .source "Test.java" # 源码文件 (非必须)
 4 
 5 # 声明静态字段
 6 .field private static a:Ljava/lang/String;
 7 
 8 #类初始化方法 被jvm执行 优先于构造方法
 9 .method static constructor <clinit>()V
10 
11     const-string v0, "hello"# 定义常量值
12 
13     sput-object v0, LTest;->a:Ljava/lang/String;#常量赋值
14 
15     return-void
16 .end method
复制代码

类非静态字段赋值

分多步进行 关键代码:

1 iput-object  # i代表instance

比如以下java代码:

复制代码
 1 public class Test
 2 {
 3     private  String a="g";
 4     public Test(String a){
 5        
 6     }
 7     public void setAa(){
 8         a="b"; 
 9    }
10     
11 }
复制代码

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 .source "Test.java" # 源码文件 (非必须)
 4 
 5 # 声明字段
 6 .field private a:Ljava/lang/String;
 7 
 8 # 构造方法初始化值a="g"
 9 .method public constructor <init>(Ljava/lang/String;)V
10     .registers 3
11     # 初始化父类构造方法
12     invoke-direct {p0}, Ljava/lang/Object;-><init>()V
13     # 声明字符串内容
14     const-string v0, "g"
15     # 赋值
16     iput-object v0, p0, LTest;->a:Ljava/lang/String;
17 
18   
19     return-void
20 .end method
21 
22 # 成员方法修改变量a="b"
23 .method public setAa()V
24     .registers 2
25 
26     .prologue
27 
28     const-string v0, "b"
29 
30     iput-object v0, p0, LTest;->a:Ljava/lang/String;
31 
32     return-void
33 .end method
复制代码

静态字段取值

关键代码

1 sget-object # s代指static

比如以下java代码:

复制代码
 1 public class Test
 2 {
 3     private  static  String a="hello";
 4     public Test(String a){
 5     }
 6     public void getA(){
 7        String aa=a;
 8    }
 9     
10 }
复制代码

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 .source "Test.java" # 源码文件 (非必须)
 4 
 5 # 声明静态字段
 6 .field private static a:Ljava/lang/String;
 7 
 8 #类初始化方法 被jvm执行 优先于构造方法
 9 .method static constructor <clinit>()V
10 
11     const-string v0, "hello"# 定义常量值
12 
13     sput-object v0, LTest;->a:Ljava/lang/String;#常量赋值
14 
15     return-void
16 .end method
17 
18 # 取值方法
19 .method public getA()V
20     .registers 2
21 
22     # 静态字段取值
23     sget-object v0, LTest;->a:Ljava/lang/String;
24 
25     return-void
26 .end method
复制代码

类非静态字段取值

关键代码:

1 iget-object  # i代表instance

 

比如以下java代码:

复制代码
 1 public class Test
 2 {
 3     private  String a="hello";
 4     public Test(String a){
 5     }
 6     public void getA(){
 7        String aa=a;
 8    }
 9     
10 }
复制代码

用smali代码表示为:

复制代码
 1 .class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 .source "Test.java" # 源码文件 (非必须)
 4 
 5 # 声明静态字段
 6 .field private static a:Ljava/lang/String;
 7 
 8 #构造方法
 9 .method public constructor <init>(Ljava/lang/String;)V
10     .registers 3
11 
12     .prologue
13 
14     invoke-direct {p0}, Ljava/lang/Object;-><init>()V
15 
16     const-string v0, "hello"
17     # 初始化成员变量
18     iput-object v0, p0, LTest;->a:Ljava/lang/String;
19 
20     return-void
21 .end method
22 
23 
24 # 取值方法
25 .method public getA()V
26     .registers 2
27 
28     # 类非静态字段取值
29     iget-object v0, LTest;->a:Ljava/lang/String;
30 
31     return-void
32 .end method
复制代码

注意:以上取值赋值方法都是以String对象举例,如果是基本数据类型,那么按照如下表处理:

值定义

1 const/4 v0, 0x1 # 实例变量值内容定义 值皆为十六进制

取值:

1 iget #实例变量int型取值
2 sget #静态变量int型取值

赋值

1 iput #实例变量int型赋值
2 sput #静态变量int型赋值

 

下表以实例变量举例:

 

smali取值赋值和值定义关键字java
iget-byte
iput-byte
const/4
byte
iget-short
iput-short
const/4
short
iget
iput
const/4
int
iget-wide
iput-wide
const-wide/16
long
iget-
iput
const/high16
float
iget-wide-
iput-wide
const/high16
double
iget-char-
iput-char
const/16
char
iget-boolean-
iput-boolean
const/4
boolean
#### 如果是基本数据类型,那么按照如下表处理:  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

smali取值赋值和值定义关键字java
iget-object-
iput-object
new-array v0, v0, [数据类型签名

fill-array-data v0, :array_c
数组
iget-object-
iput-object

以下两步为类对象定义
new-instance v0, 全包名类路径;

invoke-direct #调用构造方法
类和接口
iget-object-
iput-object
sget-object
枚举
iget-object-
iput-object
const-string
String

 

 

 

 

 

 

 

 

 

 

 

 

 

以上表结果示例java代码如下,可自行试验:

复制代码
 1 public class Test
 2 {
 3      private  Test2 a=Test2.a;
 4     public Test(String a){
 5     }
 6     public void setAa(){
 7        Test2 aa=a;
 8    }
 9       public enum Test2
10         {
11             a,b;
12         }
13 }
复制代码

$$

$$

复制代码
 1 public class Test
 2 {
 3     private  String a="a";
 4     public Test(String a){
 5     }
 6     public void setAa(){
 7        String aa=a;
 8    }
 9     
10 }
复制代码

逻辑语句之条件跳转分支

复制代码
 1 "if-eq vA, vB, :cond_**"  如果vA等于vB则跳转到:cond_** #equal
 2 
 3 "if-ne vA, vB, :cond_**"  如果vA不等于vB则跳转到:cond_**  # not  equal
 4 
 5 "if-lt vA, vB, :cond_**"  如果vA小于vB则跳转到:cond_**    #less than
 6 
 7 "if-ge vA, vB, :cond_**"  如果vA大于等于vB则跳转到:cond_**  # greater equal
 8 
 9 "if-gt vA, vB, :cond_**"  如果vA大于vB则跳转到:cond_**  # greater than
10 
11 "if-le vA, vB, :cond_**"  如果vA小于等于vB则跳转到:cond_** # less equal
12 
13 "if-eqz vA, :cond_**"  如果vA等于0则跳转到:cond_** #zero
14 "if-nez vA, :cond_**"  如果vA不等于0则跳转到:cond_**
15 "if-ltz vA, :cond_**"  如果vA小于0则跳转到:cond_**
16 "if-gez vA, :cond_**"  如果vA大于等于0则跳转到:cond_**
17 "if-gtz vA, :cond_**"  如果vA大于0则跳转到:cond_**
18 "if-lez vA, :cond_**"  如果vA小于等于0则跳转到:cond_**



案例:
if-le p1,p2, :cond_9 #如果p1小于等于p2,则执行cond_9代码块内容456,123不执行
1 xxxxx
2 xxxxx
3 xxxxx
:cond_9
4 xxxx
5 xxxx
6 xxxx


J2S2J工具:Java代码转smali代码,smali代码转java代码
java2Smali
复制代码

逻辑语句之循环

比如以下java代码

1 public class Test {
2     public static void main(String[] args) {
3    
4         for(int i=0; i<3;i++){
5         }
6     }
7 }

对应的smali代码为:

复制代码
 1 .method public static main([Ljava/lang/String;)V
 2 
 3     const/4 v0, 0x0
 4 
 5     :goto_1
 6     const/4 v1, 0x3
 7 
 8     if-ge v0, v1, :cond_7
 9 
10     add-int/lit8 v0, v0, 0x1 # 加法运算符 v0=v0+0x1
11 
12     goto :goto_1
13 
14     :cond_7
15     return-void
16 .end method
复制代码

如果将int改成long, 结果又不一样,这里使用到了比较运算符cmp(comparable)

复制代码
 1 .method public static main([Ljava/lang/String;)V
 2     .registers 5
 3 
 4     .prologue
 5     .line 4
 6     const-wide/16 v0, 0x0
 7 
 8     :goto_2
 9     const-wide/16 v2, 0x3
10 
11     cmp-long v2, v0, v2 # cmp-long为固定写法 如果v0大于v2 则返回1 赋值给v2  等于为0 小于则为-1
12 
13     if-gez v2, :cond_c
14 
15     const-wide/16 v2, 0x1
16 
17     add-long/2addr v0, v2
18 
19     goto :goto_2
20 
21     .line 6
22     :cond_c
23     return-void
24 .end method
复制代码

smali语法关键字

.line N

表示与java源文件代码的映射关系,比如:

1 .line 3  # 即.line3到.line4之间的代码还原成java代码在源文件的第三行
2 const/4 v0, 0x1
3 
4 iput v0, p0, LTest;->a:I
5 .line 4

删除该关键字不影响程序执行,该关键字在反编译时能很好地帮助我们阅读smali代码,以该关键字当作代码块的分割线,方便快速阅读执行内容

:cond_N

条件分支,配合if使用

.prologue

表示程序的开始 可省略

:goto_0

goto跳转分支,配合goto关键字使用

:goto_2
1xx
2xx
3xx
goto :goto_2  #程序执行到这,跳到goto_2代码块执行123
4xx
5xx
6xx

 

.local

显示局部变量别名信息,作用等同.line

1 move-result-object v0 # 调用方法后结果储存在v0中
2 .local v0, "b":Ljava/lang/String;  # 局部变量v0别名为b 是一个String类型 也就是 String b=v0

.locals N

注意这个和上面local的区别多加了一个s

标明了你在这个函数中最少要用到的本地寄存器的个数 也即是指明了在这个方法中非参(non-parameter)寄存器的数量

locals和registers具体区别参见:点击跳转

.registers N

在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double

声明可使用的寄存器数量的方式为:.registers N,N代表需要的寄存器的总个数

.registers N 寄存器的数量(局部+参数)

.locals N 寄存器的数量 (局部)

二者都可以表示寄存器的数量

示例:

复制代码
1 .method private test(I)V
2     .registers 4  # 声明总共需要使用4个寄存器
3 
4     const-string v0, "LOG"  # 将v0寄存器赋值为字符串常量"LOG"
5 
6     move v1, p1  # 将int型参数的值赋给v1寄存器
7 
8     return-void
9 .end method
复制代码

那么,如何确定需要使用的寄存器的个数?

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

如果一个Java方法声明如下:

1 myMethod(int p1, float p2, boolean p3)1

那么对应的Smali则为:

1 method LMyObject;->myMethod(IJZ)V1

此时,寄存器的对应情况如下:

寄存器名称对应的引用
p0 this,非静态方法默认
p1 int型的p1参数
p2, p3 float型的p2参数
p4 boolean型的p3参数

那么最少需要的寄存器个数则为:5

如果方法体内含有常量、变量等定义,则需要根据情况增加寄存器个数,数量只要满足需求,保证需要获取的值不被后面的赋值冲掉即可,方法有:存入类中的字段中(存入后,寄存器可被重新赋值),或者长期占用一个寄存器

寄存器数量只能多不能少

Dalvik指令集

如果需要使用Smali编写程序,还需要掌握常用的Dalvik虚拟机指令,其合集称为Dalvik指令集。这些指令有点类似x86汇编的指令,但指令更多,使用也非常简单方便。最详尽的介绍,可以参考Android官方的Dalvik相关文档:

https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions

 

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

这里也列举一些常用的指令,并结合Smali进行说明:

  • 移位操作:

此类操作常用于赋值

指令说明
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语句中的异常

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 返回操作:

用于返回值,对应Java中的return语句

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

另外

android studio自带的.class转smali就非常好用,如图:

 

 

从错误总学习

1 SLog.smali[24,4] Invalid register: v-1. Must be between v0 and v15, inclusive.

寄存器命名从v0-v15 一共15个

1 SLog.smali[17,0] A .registers or .locals directive must be present for a non-abstract/non-final method

.registers或者.locals必须存在, 除非是抽象方法或者final方法

1 java.lang.VerifyError: Rejecting class com.pangshu.SLog because it failed compile-time verification (declaration of 'com.pangshu.SLog' appears in /sdcard/ex.dex)
2         at com.pangshu.HelloTest.main(HelloTest.java

 

这种错误一般很难定位,因为没有提示具体原因或者具体的行数,有可能是静态方法调用你写成了虚方法的调用,或者是构造函数调用没有加尖括号, 甚至是寄存器数量过少 等等

思考

为什么方法中包括参数在内需要3个寄存器,但是在定义的时候只写了两个却也不报错呢?

如:

复制代码
 1 .method public static print(Ljava/lang/String;)V
 2     .registers 2 #不报错
 3     .prologue
 4     
 5     invoke-static {p0},Lcom/pangshu/SLog;->wrapTag(Ljava/lang/String;)Ljava/lang/String;
 6     move-result-object v1 #如果这个地方改成v2以上那么报错
 7 
 8     # log---System.out.print()
 9     sget-object v0,Ljava/lang/System;->out:Ljava/io/PrintStream;
10 
11     
12     # 方法调用
13     invoke-virtual {v0,v1},Ljava/io/PrintStream;->print(Ljava/lang/String;)V
14     
15 return-void
16 .end method
复制代码

答案是:系统会更具最大寄存器的位置进行判断,从v0到vN,数量必须大于N,

 

.class public LTest;#声明类 (必须)
 2 .super Ljava/lang/Object;#声明父类 默认继承Object (必须)
 3 
 4 
 5 .method public constructor <init>( Ljava/lang/String;)V  #构造方法
 6  
 7     invoke-direct {p0}, Ljava/lang/Object;-><init>()V #调用父类构造方法 
 8     invoke-virtual {p0}, LTest;->getName()Ljava/lang/String;# 通过invoke-virtual调用普通成员getName方法
# p参数的意思,p0 指带构造方法里的默认参数this,由于this是第一个参数,就是p0
9 10 return-void 11 .end method 12 13 #声明getName方法 #在构造方法里调用getname方法 14 .method public getName()Ljava/lang/String; 15 16 const-string v0, "hello"# 定义局部字符串常量 17 18 return-object v0 # 返回常量 19 .end method
posted @   AK行动者  阅读(603)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示