• android逆向分析之smali语法


    一 、smali数据类型

    1.Dalvik字节码

    Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个连续的寄存器表示;

    Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)

     原始类型:

             v   void  只能用于返回值类型
              Z   boolean
              B   byte
              S   short
              C   char
              I    int
              J    long(64位)
              F   float
              D   double(64位)
    对象类型:
    Lpackage/name/ObjectName;       相当于java中的package.name.ObjectName;
         L:表示这是一个对象类型
         package/name:该对象所在的包
        ;:表示对象名称的结束
     
    2.数组的表示形式:
    [I  :表示一个整形的一维数组,相当于java的int[];对于多维数组,只要增加[ 就行了,[[I = int[][];注:每一维最多255个;
    对象数组的表示形式:[Ljava/lang/String表示一个String的对象数组;
     
    3.方法的表示形式:
      Lpackage/name/ObjectName;->methodName(III)Z
              Lpackage/name/ObjectName  表示类型
              methodName   表示方法名
              III   表示参数(这里表示为3个整型参数)说明:方法的参数是一个接一个的,中间没有隔开
     
    4.字段的表示形式:
    Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;即表示:包名,字段名和字段类型
     
    5.寄存器指定
    有两种方式指定一个方法中有多少寄存器是可用的:
    .registers  指令指定了方法中寄存器的总数
    .locals    指令表明了方法中非参寄存器的总数,出现在方法中的第一行
     
    6.方法的表示
    方法有直接方法和虚方法两种,直接方法的声明格式如下:
    .method<访问权限>[修饰关键字]<方法原型>
    <.locals>
    [.parameter]
    [.prologue]
    [.line]
    <代码体>
    .end method
     
    访问权限有public、private等,修饰关键字有static、constructor等。方法原型描述了方法的名称、参数与返回值。
    .registers指定了方法中寄存器的总数
    .locals指定了方法中非参寄存器的总数(局部变量的个数);
    .parameter指定了方法的参数;
    .prologue指定了代码的开始处;
    .line指定了该处指令在源代码中的位置。
     
    注意:构造函数的返回类型为V,名字为<init>。
     
    7.方法的传参:
    当一个方法被调用的时候,方法的参数被置于最后N个寄存器中;
    例如:一个方法有2个参数,5个寄存器(v0~v4),那么,参数将置于最后2个寄存器(v3和v4)。非静态方法中的第一个参数总是调用该方法的对象。
    说明:对于静态方法除了没有隐含的this参数外,其他都一样
     
    8.寄存器的命名方式:V命名、P命名
    第一个寄存器就是方法中的第一个参数寄存器。比较:使用P命名是为了防止以后如果在方法中增加寄存器,需要对参数寄存器重新进行编号的缺点。特别说明一下:Long和Double类型是64位的,需要2个连续的寄存器
    例如:对于非静态方法LMyObject->myMethod(IJZ)V,有4个参数:  LMyObject(隐含的),int,long,boolean
    需要5个寄存器(因为long占有2个连续的寄存器)来存储参数:
         P0    this(非静态方法中这个参数是隐含的)
         P1    I (int)
         P2,P3  J (long)
         P4    Z(bool)
     
    如果是静态的就是三个参数
         P0   I (int)
         P1,P2 J (long)
         P3    Z(bool)
     
    二、成员变量
     # static fields             定义静态变量的标记
    # instance fields        定义实例变量的标记
    # direct methods       定义静态方法的标记
    # virtual methods      定义非静态方法的标记
      
    三、控制条件

    "if-eq vA, vB, :cond_**"   如果vA等于vB则跳转到:cond_**
    "if-ne vA, vB, :cond_**"   如果vA不等于vB则跳转到:cond_**
    "if-lt vA, vB, :cond_**"    如果vA小于vB则跳转到:cond_**
    "if-ge vA, vB, :cond_**"   如果vA大于等于vB则跳转到:cond_**
    "if-gt vA, vB, :cond_**"   如果vA大于vB则跳转到:cond_**
    "if-le vA, vB, :cond_**"    如果vA小于等于vB则跳转到:cond_**
    "if-eqz vA, :cond_**"   如果vA等于0则跳转到:cond_**
    "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_**

     z 既可以表示zero(0) 也可以是null、或者false;具体看逻辑。

     四、switch分支语句

     1 .method private packedSwitch(I)Ljava/lang/String;
     2     .locals 1
     3     .parameter "i"
     4     .prologue
     5     .line 21
     6     const/4 v0, 0x0
     7     .line 22
     8     .local v0, str:Ljava/lang/String;  #v0为字符串,0表示null
     9     packed-switch p1, :pswitch_data_0  #packed-switch分支,pswitch_data_0指定case区域
    10     .line 36
    11     const-string v0, "she is a person"  #default分支
    12     .line 39
    13     :goto_0      #所有case的出口
    14     return-object v0 #返回字符串v0
    15     .line 24
    16     :pswitch_0    #case 0
    17     const-string v0, "she is a baby"
    18     .line 25
    19     goto :goto_0  #跳转到goto_0标号处
    20     .line 27
    21     :pswitch_1    #case 1
    22     const-string v0, "she is a girl"
    23     .line 28
    24     goto :goto_0  #跳转到goto_0标号处
    25     .line 30
    26     :pswitch_2    #case 2
    27     const-string v0, "she is a woman"
    28     .line 31
    29     goto :goto_0  #跳转到goto_0标号处
    30     .line 33
    31     :pswitch_3    #case 3
    32     const-string v0, "she is an obasan"
    33     .line 34
    34     goto :goto_0  #跳转到goto_0标号处
    35     .line 22
    36     nop
    37     :pswitch_data_0
    38     .packed-switch 0x0    #case  区域,从0开始,依次递增
    39         :pswitch_0  #case 0
    40         :pswitch_1  #case 1
    41         :pswitch_2  #case 2
    42         :pswitch_3  #case 3
    43     .end packed-switch
    44 .end method

    packed-switch 指令。p1为传递进来的 int 类型的数值,pswitch_data_0 为case 区域,在 case 区域中,第一条指令“.packed-switch”指定了比较的初始值为0 ,pswitch_0~ pswitch_3分别是比较结果为“case 0 ”到“case 3 ”时要跳转到的地址。可以发现,标号的命名采用 pswitch_ 开关,后面的数值为 case 分支需要判断的值,并且它的值依次递增。再来看看这些标号处的代码,每个标号处都使用v0 寄存器初始化一个字符串,然后跳转到了goto_0 标号处,可见goto_0 是所有的 case 分支的出口。另外,“.packed-switch”区域指定的case 分支共有4 条,对于没有被判断的 default 分支,会在代码的 packed-switch指令下面给出。

     至此,有规律递增的 switch 分支就算是搞明白了。最后,将这段 smali 代码整理为Java代码如下。

     1 private String packedSwitch(int i) {
     2     String str = null;
     3     switch (i) {
     4         case 0:
     5             str = "she is a baby";
     6             break;
     7         case 1:
     8             str = "she is a girl";
     9             break;
    10         case 2:
    11             str = "she is a woman";
    12             break;
    13         case 3:
    14             str = "she is an obasan";
    15             break;
    16         default:
    17             str = "she is a person";
    18             break;
    19     }
    20     return str;
    21 }

    现在我们来看看无规律的case 分支语句代码会有什么不同

     1 .method private sparseSwitch(I)Ljava/lang/String;
     2     .locals 1
     3     .parameter "age"
     4     .prologue
     5     .line 43
     6     const/4 v0, 0x0
     7     .line 44
     8     .local v0, str:Ljava/lang/String;
     9     sparse-switch p1, :sswitch_data_0  # sparse-switch分支,sswitch_data_0指定case区域
    10     .line 58
    11     const-string v0, "he is a person"  #case default
    12     .line 61
    13     :goto_0    #case 出口
    14     return-object v0  #返回字符串
    15     .line 46
    16     :sswitch_0    #case 5
    17     const-string v0, "he is a baby"
    18     .line 47
    19     goto :goto_0 #跳转到goto_0标号处
    20     .line 49
    21     :sswitch_1    #case 15
    22     const-string v0, "he is a student"
    23     .line 50
    24     goto :goto_0 #跳转到goto_0标号处
    25     .line 52
    26     :sswitch_2    #case 35
    27     const-string v0, "he is a father"
    28     .line 53
    29     goto :goto_0 #跳转到goto_0标号处
    30     .line 55
    31     :sswitch_3    #case 65
    32     const-string v0, "he is a grandpa"
    33     .line 56
    34     goto :goto_0 #跳转到goto_0标号处
    35     .line 44
    36     nop
    37     :sswitch_data_0
    38     .sparse-switch            #case 区域
    39         0x5 -> :sswitch_0     #case 5(0x5)
    40         0xf -> :sswitch_1     #case 15(0xf)
    41         0x23 -> :sswitch_2    #case 35(0x23)
    42         0x41 -> :sswitch_3    #case 65(0x41)
    43     .end sparse-switch
    44 .end method

    按照分析packed-switch 的方法,我们直接查看 sswitch_data_0 标号处的内容。可以看到“.sparse-switch ”指令没有给出初始case 的值,所有的case 值都使用“case 值 -> case 标号”的形式给出。此处共有4 个case ,它们的内容都是构造一个字符串,然后跳转到goto_0 标号处,代码架构上与packed-switch 方式的 switch 分支一样。

     最后,将这段smali 代码整理为Java 代码如下。

     1 private String sparseSwitch(int age) {
     2     String str = null;
     3     switch (age) {
     4         case 5:
     5             str = "he is a baby";
     6             break;
     7         case 15:
     8             str = "he is a student";
     9             break;
    10         case 35:
    11             str = "he is a father";
    12             break;
    13         case 65:
    14             str = "he is a grandpa";
    15             break;
    16         default:
    17             str = "he is a person";
    18             break;
    19     }
    20     return str;
    21 }

    五、try/catch 语句

      1 .method private tryCatch(ILjava/lang/String;)V
      2     .locals 10
      3     .parameter "drumsticks"
      4     .parameter "peple"
      5     .prologue
      6     const/4 v9, 0x0
      7                                                                                                                                                               .line 19
      8     try_start_0                                                              # 第1个try开始
      9     invoke-static {p2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I   #将第2个参数转换为int 型
     10     :try_end_0                                                               # 第1个try结束
     11     .catch Ljava/lang/NumberFormatException; {:try_start_0 .. :try_end_0} : catch_1  # catch_1
     12      move-result v1          #如果出现异常这里不会执行,会跳转到catch_1标号处
     13                                                                                                                                                                .line 21
     14     .local v1, i:I            #.local声明的变量作用域在.local声明与.end local 之间
     15    :try_start_1               #第2个try 开始
     16     div-int v2, p1, v1        # 第1个参数除以第2个参数
     17     .line 22
     18     .local v2, m:I    
     19     mul-int v5, v2, v1        #m * i
     20     sub-int v3, p1, v5        #v3  = p1 - v5
     21     .line 23
     22     .local v3, n:I
     23     const-string v5, "u5171u6709%du53eau9e21u817fuff0c%d
     24         u4e2au4ebau5e73u5206uff0cu6bcfu4ebau53efu5206u5f97%d
     25         u53eauff0cu8fd8u5269u4e0b%du53ea"   # 格式化字符串
     26     const/4 v6, 0x4
     27     new-array v6, v6, [Ljava/lang/Object;
     28     const/4 v7, 0x0
     29     .line 24
     30     invoke-static {p1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
     31     move-result-object v8
     32     aput-object v8, v6, v7
     33     const/4 v7, 0x1
     34     invoke-static {v1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
     35     move-result-object v8
     36     aput-object v8, v6, v7
     37     const/4 v7, 0x2
     38     invoke-static {v2}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
     39     move-result-object v8
     40     aput-object v8, v6, v7
     41     const/4 v7, 0x3
     42     invoke-static {v3}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
     43     move-result-object v8
     44  
     45     aput-object v8, v6, v7
     46     .line 23
     47     invoke-static {v5, v6}, Ljava/lang/String;
     48         ->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
     49     move-result-object v4
     50     .line 25
     51     .local v4, str:Ljava/lang/String;
     52     const/4 v5, 0x0
     53     invoke-static {p0, v4, v5}, Landroid/widget/Toast;
     54         ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
     55         Landroid/widget/Toast;
     56     move-result-object v5
     57     invoke-virtual {v5}, Landroid/widget/Toast;->show()V # 使用Toast 显示格式化后的结果
     58    :try_end_1                                            #第2个try 结束
     59     .catch Ljava/lang/ArithmeticException; {:try_start_1 .. :try_end_1} : catch_0    # catch_0
     60     .catch Ljava/lang/NumberFormatException; {:try_start_1 .. :try_end_1} : catch_1  # catch_1
     61     .line 33
     62     .end local v1           #i:I
     63     .end local v2           #m:I
     64     .end local v3           #n:I
     65     .end local v4           #str:Ljava/lang/String;
     66     :goto_0 
     67     return-void             # 方法返回
     68     .line 26 
     69     .restart local v1       #i:I
     70     :catch_0   
     71     move-exception v0
     72     .line 27
     73     .local v0, e:Ljava/lang/ArithmeticException;
     74                                                                                                                                                               :try_start_2                                       #第3个try 开始
     75     const-string v5, "u4ebau6570u4e0du80fdu4e3a0"           #“人数不能为0”
     76     const/4 v6, 0x0
     77     invoke-static {p0, v5, v6}, Landroid/widget/Toast;
     78         ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
     79         Landroid/widget/Toast;
     80     move-result-object v5
     81     invoke-virtual {v5}, Landroid/widget/Toast;->show()V         #使用Toast 显示异常原因
     82     :try_end_2                                                   #第3个try 结束
     83     .catch Ljava/lang/NumberFormatException; {:try_start_2 .. :try_end_2} :catch_1
     84      goto :goto_0 #返回
     85     .line 29
     86     .end local v0           #e:Ljava/lang/ArithmeticException;
     87     .end local v1           #i:I
     88     :catch_1 
     89     move-exception v0
     90     .line 30
     91     .local v0, e:Ljava/lang/NumberFormatException;
     92     const-string v5, "u65e0u6548u7684u6570u503cu5b57u7b26u4e32" 
     93     #“无效的数值字符串”
     94     invoke-static {p0, v5, v9}, Landroid/widget/Toast;
     95         ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
     96         Landroid/widget/Toast;
     97     move-result-object v5
     98     invoke-virtual {v5}, Landroid/widget/Toast;->show()V # 使用Toast 显示异 常原因
     99     goto :goto_0                                         #返回
    100 .end method
    代码中的try语句块使用try_start_开头的标号注明,以try_end_开头的标号结束。第一个try语句的开头标号为try_start_0,结束标号为 try_end_0。使用多个try语句块时标号名称后面的数值依次递增,本实例代码中最多使用到了try_end_2。

    在try_end_0 标号下面使用“.catch”指令指定处理到的异常类型与catch的标号,格式如下。

    .catch < 异常类型> {<try起始标号> .. <try 结束标号>} <catch标号>

    查看catch_1标号处的代码发现,当转换 String 到int 时发生异常会弹出“无效的数值字符串”的提示。对于代码中的汉字,baksmali 在反编译时将其使用Unicode进行编码,因此,在阅读前需要使用相关的编码转换工具进行转换。

    仔细阅读代码会发现在try_end_1标号下面使用“.catch”指令定义了 catch_0与catch_1两个catch。catch_0标号的代码开头又有一个标号为try_start_2的try 语句块,其实这个try语句块是虚构的,假如下面的代码。

     1 private void a() {
     2     try {
     3         ……
     4         try {
     5             ……
     6         } catch (XXX) {
     7             ……
     8         }
     9     } catch (YYY) {
    10         ……
    11     }      
    12 }
    当执行内部的try语句时发生了异常,如果异常类型为XXX,则内部catch就会捕捉到并执行相应的处理代码,如果异常类型不是 XXX,那么就会到外层的 catch中去查找异常处理代码,这也就是为什么实例的try_end_1标号下面会有两个catch的原因,另外,如果在执行XXX异常的处理代码时又发生了异常,这个时候该怎么办?此时这个异常就会扩散到外层的catch中去,由于XXX异常的外层只有一个YYY的异常处理,这时会判断发生的异常是否为YYY类型,如果是就会进行处理,不是则抛给应用程序。回到本实例中来,如果在执行内部的ArithmeticException异常处理时再次发生别的异常,就会调用外层的 catch进行异常捕捉,因此在try_end_2标号下面有一个 catch_1就很好理解了。
    最后,将这段 smali 代码整理为Java 代码如下。
     1  private void tryCatch(int drumsticks, String peple) {
     2         try {
     3             int i = Integer.parseInt(peple);
     4             try {
     5                 int m = drumsticks / i;
     6                 int n = drumsticks - m * i;
     7                 String str = String.format("共有%d只鸡腿,%d个人平分,每人可分得%d只,还剩下%d只",drumsticks, i, m, n);
     8                 Toast.makeText(MainActivity.this, str,Toast.LENGTH_SHORT).show();
     9             } catch (ArithmeticException e) {
    10                 Toast.makeText(MainActivity.this, " 人数不能为0",Toast.LENGTH_SHORT).show();
    11             }
    12         } catch (NumberFormatException e) {
    13             Toast.makeText(MainActivity.this, " 无效的数值字符串",Toast.LENGTH_SHORT).show();
    14         }        
    15 }
    finally语句块
    源码:
    1    try {
    2             ServerSocket serverSocket= new ServerSocket(10000);
    3             Socket socket=serverSocket.accept();
    4         } catch (IOException e) {
    5             e.printStackTrace();
    6         }finally{
    7            int abc=5;
    8            Toast.makeText(this, "sssss ", Toast.LENGTH_SHORT).show();
    9     }

    finally 语句块作用:执行一些必要代码。即不管出现异常与否,在finally中的代码都会被执行
    执行时机:针对所有catch语句之后,退出方法之前将被执行(即先执行catch里面的代码,但在throw之前将转向finally)。finally中返回的结果将可以覆盖catch中返回的结果
    对应的smail代码如下: 

     1 :try_start_0
     2     new-instance v2, Ljava/net/ServerSocket;        #ServerSocket v2 = null;   
     3     const/16 v3, 0x2710                             # v3 = 10000;
     4     invoke-direct {v2, v3}, Ljava/net/ServerSocket;-><init>(I)V  # v2 = new ServerSocket(v3);
     5     .line 21
     6     .local v2, serverSocket:Ljava/net/ServerSocket;
     7     invoke-virtual {v2}, Ljava/net/ServerSocket;->accept()Ljava/net/Socket; # v2.accept( );
     8     :try_end_0
     9     .catchall {:try_start_0 .. :try_end_0} :catchall_0  
    10 //上一句处理start_0对应的异常块是catchall_0   也就是finally
    11     .catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0
    12 //上一句处理start_0对应的异常块是catch_0,catch_0异常块先执行,之后再执行catchall_0  

    相对应的smali代码为: 

     1 :try_start_0
     2     new-instance v2, Ljava/net/ServerSocket;
     3     const/16 v3, 0x2710
     4     invoke-direct {v2, v3}, Ljava/net/ServerSocket;-><init>(I)V
     5     .line 21
     6     .local v2, serverSocket:Ljava/net/ServerSocket;
     7     invoke-virtual {v2}, Ljava/net/ServerSocket;->accept()Ljava/net/Socket;
     8     :try_end_0
     9 
    10     .catchall {:try_start_0 .. :try_end_0} :catchall_0
    11     .catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0
    12     .line 27
    13     const/4 v0, 0x5      #正常流程 即未发生异常
    14     .line 28
    15     .local v0, abc:I
    16     const-string v3, "sssss "
    17     invoke-static {p0, v3, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    18     move-result-object v3
    19     invoke-virtual {v3}, Landroid/widget/Toast;->show()V
    20     .line 32
    21     .end local v2        #serverSocket:Ljava/net/ServerSocket;
    22     :goto_0
    23     return-void
    24     .line 22
    25     .end local v0        #abc:I
    26   
    27    :catch_0              #当发生异常时执行
    28     move-exception v1
    29     .line 24
    30     .local v1, e:Ljava/io/IOException;
    31     
    32     :try_start_1
    33       invoke-virtual {v1}, Ljava/io/IOException;->printStackTrace()V
    34     :try_end_1
    35     .catchall {:try_start_1 .. :try_end_1} :catchall_0    #异常部分执行完毕,转而执行finally
    36   
    37     .line 27
    38     const/4 v0, 0x5
    39     .line 28
    40     .restart local v0       #abc:I
    41     const-string v3, "sssss "
    42     invoke-static {p0, v3, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    43     move-result-object v3
    44     invoke-virtual {v3}, Landroid/widget/Toast;->show()V
    45     goto :goto_0
    46     .line 25
    47     .end local v0           #abc:I
    48     .end local v1           #e:Ljava/io/IOException;
    49     
    50   #finally代码定义部分
    51     :catchall_0
    52     move-exception v3
    53     .line 27
    54     const/4 v0, 0x5
    55     .line 28
    56     .restart local v0       #abc:I
    57     const-string v4, "sssss "
    58     invoke-static {p0, v4, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
    59     move-result-object v4
    60     invoke-virtual {v4}, Landroid/widget/Toast;->show()V
    61     .line 30
    62     throw v3

    六、for循环

      1 # virtual methods
      2 .method public onClick(Landroid/view/View;)V
      3 .locals 9
      4 .parameter “v”
      5 
      6 .prologue
      7 .line 36
      8 invoke-virtual {p1}, Landroid/view/View;->getId()I  # 非静态方法参数中隐含的第一个参数p0为this指针, p1为第一个参数, 即View对象
      9 
     10 move-result v6     # 把上次的计算结果给第七个寄存器,v6=p1.getId(), v6中为View对象的id
     11 
     12 packed-switch v6, :pswitch_data_0    # switch(v6)
     13 
     14 # —————– 程序出口开始 ——————
     15 .line 58
     16 :goto_0         # for循环出口
     17 return-void     # return;
     18 # —————– 程序出口结束 ——————
     19 
     20 # —————– 获取控件内容开始 ——————
     21 .line 39
     22 :pswitch_0
     23 iget-object v6, p0, Lcom/sanwho/crackdemo/ForActivity$1;->this$0:Lcom/sanwho/crackdemo/ForActivity;  # v6保存this指针
     24 
     25 const v7, 0x7f080001   # v7 = txtValue1, 该id保存在public.xml中
     26 
     27 invoke-virtual {v6, v7}, Lcom/sanwho/crackdemo/ForActivity;->findViewById(I)Landroid/view/View; # findViewById(txtValue1)
     28 
     29 move-result-object v4  # v4为txtValue1对应的View对象
     30 
     31 check-cast v4, Landroid/widget/EditText;  # 将View对象转换成EditText, 完成后v4中是txtValue1对象, 失败会抛出ClassCastException异常
     32 
     33 .line 40
     34 .local v4, txtValue1:Landroid/widget/EditText;
     35 iget-object v6, p0, Lcom/sanwho/crackdemo/ForActivity$1;->this$0:Lcom/sanwho/crackdemo/ForActivity;
     36 
     37 const v7, 0x7f080003  # v7 = txtValue2
     38 
     39 invoke-virtual {v6, v7}, Lcom/sanwho/crackdemo/ForActivity;->findViewById(I)Landroid/view/View;
     40 
     41 move-result-object v5  # v5为txtValue2对应的View对象
     42 
     43 check-cast v5, Landroid/widget/EditText;  # 将View对象转换成EditText, 完成后v5中是txtValue2对象
     44 
     45 .line 41
     46 .local v5, txtValue2:Landroid/widget/EditText;
     47 invoke-virtual {v4}, Landroid/widget/EditText;->getText()Landroid/text/Editable;    # 根据.line 39处可知,v4中为txtValue1对象
     48 
     49 move-result-object v6   # v6 = txtValue1.getText();
     50 
     51 invoke-interface {v6}, Landroid/text/Editable;->toString()Ljava/lang/String;
     52 
     53 move-result-object v6   # v6 = txtValue1.getText().toString();
     54 
     55 invoke-static {v6}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
     56 
     57 move-result v1    # v1 = Integer.parseInt(v6); 也就是起始数值
     58 
     59 .line 42
     60 .local v1, from:I
     61 invoke-virtual {v5}, Landroid/widget/EditText;->getText()Landroid/text/Editable;    # 根据.line 40处可知,v5中为txtValue2对象
     62 
     63 move-result-object v6  # v6 = txtValue2.getText();
     64 
     65 invoke-interface {v6}, Landroid/text/Editable;->toString()Ljava/lang/String;
     66 
     67 move-result-object v6  # v6 = txtValue2.getText().toString();
     68 
     69 invoke-static {v6}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
     70 
     71 move-result v0    # v0 = Integer.parseInt(v6); 也就是结束数值
     72 
     73 # —————– 获取控件内容结束 ——————
     74 
     75 .line 43
     76 .local v0, end:I
     77 if-le v1, v0, :cond_0   # if v1 <= v0, 即起始数值 <= 结束数值, 则跳到cond_0
     78 
     79 # —————– 起始数值 > 结束数值时开始 ——————
     80 .line 45
     81 iget-object v6, p0, Lcom/sanwho/crackdemo/ForActivity$1;->this$0:Lcom/sanwho/crackdemo/ForActivity;
     82 
     83 const-string v7, “u8d77u59cbu6570u503cu4e0du80fdu5927u4e8eu7ed3u675fu6570u503c!”  # 起始数值不能大于结束数值
     84 
     85 #calls: Lcom/sanwho/crackdemo/ForActivity;->MessageBox(Ljava/lang/String;)V
     86 invoke-static {v6, v7}, Lcom/sanwho/crackdemo/ForActivity;->access$0(Lcom/sanwho/crackdemo/ForActivity;Ljava/lang/String;)V
     87 
     88 goto :goto_0
     89 
     90 # —————– 起始数值 > 结束数值时结束 ——————
     91 
     92 # —————– 起始数值 <= 结束数值时开始 —————–
     93 .line 49
     94 :cond_0
     95 const/4 v3, 0x0   # v3 = 0, 即int sum = 0;
     96 
     97 .line 50
     98 .local v3, sum:I
     99 move v2, v1       # v2 = v1, v2即源码中的i变量
    100 
    101 .local v2, i:I
    102 :goto_1           # for循环主要入口
    103 if-le v2, v0, :cond_1   # if 当前数值 <= 结束数值, 跳到cond_1;  否则循环结束, 显示累加结果
    104 
    105 .line 54
    106 iget-object v6, p0, Lcom/sanwho/crackdemo/ForActivity$1;->this$0:Lcom/sanwho/crackdemo/ForActivity;  # v6指向MessageBox方法
    107 
    108 new-instance v7, Ljava/lang/StringBuilder;    # v7为StringBuilder对象
    109 
    110 const-string v8, “u7d2fu52a0u7ed3u679cuff1a”  # v8 = “累加结果:”
    111 
    112 invoke-direct {v7, v8}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V  # 以v8为参数调用StringBuilder构造函数
    113 
    114 invoke-static {v3}, Ljava/lang/Integer;->toString(I)Ljava/lang/String; # 把int型的sum值转成字符串
    115 
    116 move-result-object v8   # v8 = Integer.toString(v3); 此时v8中为sum的值
    117 
    118 invoke-virtual {v7, v8}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;   # 把累加结果和sum的值进行追加
    119 
    120 move-result-object v7  # v7 为 “累加结果:” + Integer.toString(sum)的StringBuilder对象;
    121 
    122 invoke-virtual {v7}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;  # 将v7转为字符串对象
    123 
    124 move-result-object v7   # v7 = “累加结果:” + Integer.toString(sum);
    125 
    126 #calls: Lcom/sanwho/crackdemo/ForActivity;->MessageBox(Ljava/lang/String;)V
    127 invoke-static {v6, v7}, Lcom/sanwho/crackdemo/ForActivity;->access$0(Lcom/sanwho/crackdemo/ForActivity;Ljava/lang/String;)V  # 调用MessageBox显示字符串
    128 
    129 goto :goto_0  # 跳到goto_0
    130 # —————– 起始数值 <= 结束数值时结束 —————–
    131 
    132 .line 52
    133 :cond_1           # 加1操作入口
    134 add-int/2addr v3, v2   # v3 = v3 + v2, 即sum += i
    135 
    136 .line 50
    137 add-int/lit8 v2, v2, 0x1  # v2 = v2 + 1, , 即i = i + 1
    138 
    139 goto :goto_1   # 跳到for循环入口继续比对
    140 
    141 .line 36
    142 nop
    143 
    144 :pswitch_data_0
    145 .packed-switch 0x7f080004
    146 :pswitch_0
    147 .end packed-switch
    148 .end method

    源码解释

     1 Button.OnClickListener onClickListener = new Button.OnClickListener()
     2 {
     3     @Override
     4     public void onClick(View v)
     5     {
     6         switch (v.getId())
     7         {
     8         case R.id.btnSubmit:
     9             EditText txtValue1 = (EditText) findViewById(R.id.txtValue1);
    10             EditText txtValue2 = (EditText) findViewById(R.id.txtValue2);
    11             int from = Integer.parseInt(txtValue1.getText().toString());
    12             int end = Integer.parseInt(txtValue2.getText().toString());
    13             if (from > end){
    14                 MessageBox("起始数值不能大于结束数值!");
    15             }
    16             else
    17             {
    18                  int sum = 0;
    19                  for (int i = from; i <= end; i++){
    20                     
    21                      sum += i;
    22                  }
    23                  MessageBox("累加结果:" + Integer.toString(sum));
    24             }
    25             break;
    26         }
    27     }
    28 };
    29 
    30 private void MessageBox(String str)
    31 {        
    32     Toast.makeText(this, str, Toast.LENGTH_LONG).show();
    33 }

    如果看不懂access$0或者this$0等请看下一章节

    八、内部类

    Java 语言允许在一个类的内部定义另一个类,这种在类中定义的类被称为内部类(Inner Class)。内部类可分为成员内部类、静态嵌套类、方法内部类、匿名内部类。前面我们曾经说过,baksmali 在反编译dex 文件的时候,会为每个类单独生成了一个 smali 文件,内部类作为一个独立的类,它也拥有自己独立的smali 文件,只是内部类的文件名形式为“[外部类]$[内部类].smali ”,例如下面的类。

    1 class Outer {
    2    class Inner{}
    3 }

    baksmali 反编译上述代码后会在同一目录生成两个文件:Outer.smali 与Outer$Inner.smali。

      1 public class MainActivity extends Activity {    
      2     private Button btnAnno;
      3     private Button btnCheckSN;
      4     private EditText edtSN;  
      5     @Override
      6     public void onCreate(Bundle savedInstanceState) {
      7         super.onCreate(savedInstanceState);
      8         setContentView(R.layout.activity_main);
      9         btnAnno = (Button) findViewById(R.id.btn_annotation);
     10         btnCheckSN = (Button) findViewById(R.id.btn_checksn);
     11         edtSN = (EditText) findViewById(R.id.edt_sn);
     12         btnAnno.setOnClickListener(new OnClickListener() {
     13            @Override
     14             public void onClick(View v) {
     15                 getAnnotations();                
     16             }
     17         });
     18         
     19         btnCheckSN.setOnClickListener(new OnClickListener() {
     20             @Override
     21             public void onClick(View v) {
     22                 SNChecker checker = new SNChecker(edtSN.getText().toString());
     23                 String str = checker.isRegistered() ? "注册码正确" : "注册码错误";
     24                 Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
     25             }
     26         });
     27     }
     28     
     29     private void getAnnotations() {
     30         try {
     31             Class<?> anno = Class.forName("com.droider.anno.MyAnno");
     32             if (anno.isAnnotationPresent(MyAnnoClass.class)) {
     33                 MyAnnoClass myAnno = anno.getAnnotation(MyAnnoClass.class);
     34                 Toast.makeText(this, myAnno.value(), Toast.LENGTH_SHORT).show();
     35             }
     36             Method method = anno.getMethod("outputInfo", (Class[])null);
     37             if (method.isAnnotationPresent(MyAnnoMethod.class)) {
     38                 MyAnnoMethod myMethod = method.getAnnotation(MyAnnoMethod.class);
     39                 String str = myMethod.name() + " is " + myMethod.age() + " years old.";
     40                 Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
     41             }
     42             Field field = anno.getField("sayWhat");
     43             if (field.isAnnotationPresent(MyAnnoField.class)) {
     44                 MyAnnoField myField = field.getAnnotation(MyAnnoField.class);
     45                 Toast.makeText(this, myField.info(), Toast.LENGTH_SHORT).show();
     46             }
     47         } catch (Exception e) {
     48             e.printStackTrace();
     49         }
     50     }
     51 
     52     @Override
     53     public boolean onCreateOptionsMenu(Menu menu) {
     54         getMenuInflater().inflate(R.menu.activity_main, menu);
     55         return true;
     56     }
     57     
     58     public class SNChecker {
     59         private String sn;
     60         public SNChecker(String sn) {
     61             this.sn = sn;
     62         }
     63         
     64         public boolean isRegistered() {
     65             boolean result = false;
     66             char ch = '';
     67             int sum = 0;
     68             if (sn == null || (sn.length() < 8)) return result;
     69             int len = sn.length();
     70             if (len == 8) {
     71                 ch = sn.charAt(0);
     72                 switch (ch) {
     73                     case 'a':
     74                     case 'f':
     75                         result = true;
     76                         break;
     77                     default:
     78                         result = false;
     79                         break;
     80                 }
     81                 if (result) {
     82                     ch = sn.charAt(3);
     83                     switch (ch) {
     84                         case '1':
     85                         case '2':
     86                         case '3':
     87                         case '4':
     88                         case '5':
     89                             result = true;
     90                             break;
     91                         default:
     92                             result = false;
     93                             break;
     94                     }
     95                 }
     96             } else if (len == 16) {
     97                 for (int i = 0; i < len; i++) {
     98                     char chPlus = sn.charAt(i);
     99                     sum += (int) chPlus;
    100                 }
    101                 result = ((sum % 6) == 0) ? true : false;
    102             }
    103             return result;
    104         }
    105     }
    106 }

    MainActivity$ SNChecker.smali 文件,这个SNChecker 就是MainActivity的一个内部类。打开这个文件,代码结构如下。

      1 .class public Lcom/droider/crackme0502/MainActivity$SNChecker;
      2 .super Ljava/lang/Object;
      3 .source "MainActivity.java"
      4 
      5 
      6 # annotations
      7 .annotation system Ldalvik/annotation/EnclosingClass;
      8     value = Lcom/droider/crackme0502/MainActivity;
      9 .end annotation
     10 
     11 .annotation system Ldalvik/annotation/InnerClass;
     12     accessFlags = 0x1
     13     name = "SNChecker"
     14 .end annotation
     15 
     16 
     17 # instance fields
     18 .field private sn:Ljava/lang/String;
     19 
     20 .field final synthetic this$0:Lcom/droider/crackme0502/MainActivity;
     21 
     22 
     23 # direct methods
     24 .method public constructor <init>(Lcom/droider/crackme0502/MainActivity;Ljava/lang/String;)V
     25     .locals 0
     26     .parameter
     27     .parameter "sn"
     28 
     29     .prologue
     30     .line 83
     31     iput-object p1, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->this$0:Lcom/droider/crackme0502/MainActivity;
     32 
     33     invoke-direct {p0}, Ljava/lang/Object;-><init>()V
     34 
     35     .line 84
     36     iput-object p2, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
     37 
     38     .line 85
     39     return-void
     40 .end method
     41 
     42 
     43 # virtual methods
     44 .method public isRegistered()Z
     45     .locals 10
     46 
     47     .prologue
     48     const/16 v9, 0x8
     49 
     50     const/4 v7, 0x0
     51 
     52     .line 88
     53     const/4 v4, 0x0
     54 
     55     .line 89
     56     .local v4, result:Z
     57     const/4 v0, 0x0
     58 
     59     .line 90
     60     .local v0, ch:C
     61     const/4 v6, 0x0
     62 
     63     .line 91
     64     .local v6, sum:I
     65     iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
     66 
     67     if-eqz v8, :cond_0
     68 
     69     iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
     70 
     71     invoke-virtual {v8}, Ljava/lang/String;->length()I
     72 
     73     move-result v8
     74 
     75     if-ge v8, v9, :cond_1
     76 
     77     :cond_0
     78     move v5, v4
     79 
     80     .line 126
     81     .end local v4           #result:Z
     82     .local v5, result:I
     83     :goto_0
     84     return v5
     85 
     86     .line 92
     87     .end local v5           #result:I
     88     .restart local v4       #result:Z
     89     :cond_1
     90     iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
     91 
     92     invoke-virtual {v8}, Ljava/lang/String;->length()I
     93 
     94     move-result v3
     95 
     96     .line 93
     97     .local v3, len:I
     98     if-ne v3, v9, :cond_3
     99 
    100     .line 94
    101     iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    102 
    103     invoke-virtual {v8, v7}, Ljava/lang/String;->charAt(I)C
    104 
    105     move-result v0
    106 
    107     .line 95
    108     sparse-switch v0, :sswitch_data_0
    109 
    110     .line 101
    111     const/4 v4, 0x0
    112 
    113     .line 104
    114     :goto_1
    115     if-eqz v4, :cond_2
    116 
    117     .line 105
    118     iget-object v7, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    119 
    120     const/4 v8, 0x3
    121 
    122     invoke-virtual {v7, v8}, Ljava/lang/String;->charAt(I)C
    123 
    124     move-result v0
    125 
    126     .line 106
    127     packed-switch v0, :pswitch_data_0
    128 
    129     .line 115
    130     const/4 v4, 0x0
    131 
    132     :cond_2
    133     :goto_2
    134     move v5, v4
    135 
    136     .line 126
    137     .restart local v5       #result:I
    138     goto :goto_0
    139 
    140     .line 98
    141     .end local v5           #result:I
    142     :sswitch_0
    143     const/4 v4, 0x1
    144 
    145     .line 99
    146     goto :goto_1
    147 
    148     .line 112
    149     :pswitch_0
    150     const/4 v4, 0x1
    151 
    152     .line 113
    153     goto :goto_2
    154 
    155     .line 119
    156     :cond_3
    157     const/16 v8, 0x10
    158 
    159     if-ne v3, v8, :cond_2
    160 
    161     .line 120
    162     const/4 v2, 0x0
    163 
    164     .local v2, i:I
    165     :goto_3
    166     if-lt v2, v3, :cond_4
    167 
    168     .line 124
    169     rem-int/lit8 v8, v6, 0x6
    170 
    171     if-nez v8, :cond_5
    172 
    173     const/4 v4, 0x1
    174 
    175     :goto_4
    176     goto :goto_2
    177 
    178     .line 121
    179     :cond_4
    180     iget-object v8, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    181 
    182     invoke-virtual {v8, v2}, Ljava/lang/String;->charAt(I)C
    183 
    184     move-result v1
    185 
    186     .line 122
    187     .local v1, chPlus:C
    188     add-int/2addr v6, v1
    189 
    190     .line 120
    191     add-int/lit8 v2, v2, 0x1
    192 
    193     goto :goto_3
    194 
    195     .end local v1           #chPlus:C
    196     :cond_5
    197     move v4, v7
    198 
    199     .line 124
    200     goto :goto_4
    201 
    202     .line 95
    203     :sswitch_data_0
    204     .sparse-switch
    205         0x61 -> :sswitch_0
    206         0x66 -> :sswitch_0
    207     .end sparse-switch
    208 
    209     .line 106
    210     :pswitch_data_0
    211     .packed-switch 0x31
    212         :pswitch_0
    213         :pswitch_0
    214         :pswitch_0
    215         :pswitch_0
    216         :pswitch_0
    217     .end packed-switch
    218 .end method

    发现它有两个注解定义块“Ldalvik/annotation/EnclosingClass;”与“Ldalvik/annotation/ InnerClass; ”、两个实例字段sn 与this$0 、一个直接方法 init()、一个虚方法isRegistered() 。注解定义块我们稍后进行讲解。先看它的实例字段,sn 是字符串类型,this$0 是MainActivity类型,synthetic 关键字表明它是“合成”的,那 this$0 到底是个什么东西呢?

    其实this$0 是内部类自动保留的一个指向所在外部类的引用。左边的 this 表示为父类的引用,右边的数值0 表示引用的层数。我们看下面的类。

    1 public class Outer {    //this$0 
    2      public class FirstInner {        //this$1 
    3             public class SecondInner {      //this$2 
    4                 public class ThirdInner {
    5                 } 
    6         } 
    7     }
    8 }

    每往里一层右边的数值就加一,如 ThirdInner类访问 FirstInner 类的引用为this$1 。在生成的反汇编代码中,this$X 型字段都被指定了synthetic 属性,表明它们是被编译器合成的、虚构的,代码的作者并没有声明该字段。

    我们再看看MainActivity$SNChecker的构造函数,看它是如何初始化的。代码如下。

     1 # direct methods
     2 .method public constructor <init>(Lcom/droider/crackme0502/MainActivity;Ljava/lang/String;)V
     3     .locals 0
     4     .parameter       #第一个参数MainActivity引用
     5     .parameter "sn"  #第二个参数字符串sn
     6 
     7     .prologue
     8     .line 83
     9     #将MainActivity引用赋值给this$0
    10     iput-object p1, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->this$0:Lcom/droider/crackme0502/MainActivity;
    11 
    12     #调用默认的构造函数
    13     invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    14 
    15     .line 84
    16     #将sn字符串的值赋给sn字段
    17     iput-object p2, p0, Lcom/droider/crackme0502/MainActivity$SNChecker;->sn:Ljava/lang/String;
    18 
    19     .line 85
    20     return-void
    21 .end method

    对于一个非静态的方法而言,会隐含的使用p0寄存器当作类的this 引用。因此,这里的确是使用了3 个寄存器:p0表示MainActivity$SNChecker自身的引用,p1表示MainActivity的引用,p2表示sn 字符串。另外,从 MainActivity$SNChecker的构造函数可以看出,内部类的初始化共有以下 3 个步骤:首先是保存外部类的引用到本类的一个 synthetic字段中,以便内部类的其它方法使用,然后是调用内部类的父类的构造函数来初始化父类,最后是对内部类自身进行初始化。

      

    一个方法中指定的寄存器个
    在一个方法(method)中有两中方式指定有多少个可用的寄存器。指令.registers指令指定了在这个方法中有多少个可用的寄存器,指令.locals指明了在这个方法中非参(non-parameter)寄存器的数量。然而寄存器的总数也包括保存方法参数的寄存器。

    参数是如何传递的?
    当一个方法被调用时,该方法的参数被保存在最后N个寄存器中。如果一个方法有2个参数和5个寄存器(V0-V4),参数将被保存在最后的2个寄存器内V3和V4.

    非静态方法的第一个参数,总是被方法调用的对象。
    例如,你写了一个非静态方法LMyObject;->callMe(II)V。这个方法有2个int参数,但在这两个整型参数前面还有一个隐藏的参数LMyObject;所以这个方法总共有3个参数。

    比如说,在方法中指定有5个寄存器(V0-V4),只用.register指令指定5个,或者使用.locals指令指定2个(2个local寄存器+3个参数寄存器)。该方法被调用的时候,调用方法的对象(即this引用)会保存在V2中,第一个参数在V3中,第二个参数在v4中。

    除了不包含this隐藏参数,对于静态方法都是相同的。

    寄存器名称
    有两种寄存器的命名方式,对于参数寄存器有普通的V命名方式和P命名方式。在方法(method)中第一个参数寄存器,是使用P方式命名的第一个寄存器,让我们回到前面的例子中,有三个参数和5个寄存器,下面的这个表显示了对每个寄存器的普通V命名方式,后面是P方式命名的参数寄存器。

    v0   the first local register
    v1   the second local register
    v2 p0 the first parameter register
    v3 p1 the second parameter register
    v4 p2 the third parameter register

    You can reference parameter registers by either name - it makes no difference.

    你可以使用名称引用参数寄存器,他们没有区别。

    引入参数寄存器的目的
    P命名方式被引入去解决,在编辑smail代码时候共同的烦恼。

    假设你有一个方法(mehtod),这个方法带有一些参数,并且你需要添加一些代码到这个方法中,这时发现需要一些额外的寄存器,你会想“没有什么大不了的。我只需要使用.registers指令添加寄存器数量就可以了。”
    不幸的是没有想象的那么容易,请记住,方法中方法的参数被保存在最后的寄存器里。如果你增加了寄存器的数量,达到让寄存器中的参数被传入的目的。所以你不得不使用.registers指令重新分配参数寄存器的编号。
    但如果在方法中P命名方式,被用来引用参数寄存器。你将很容易的在方法中去修改寄存器数量,而不用去担心现有寄存器的编号。

    注意:在默认的baksmali中,参数寄存器将使用P命名方式,如果出于某种原因你要禁用P命名方式,而要强制使用V命名方式,应当使用-p/--no-parameter-registers选项。

    Long/Double values
    正如前面提到的,long和double类型都是64位,需要2个寄存器。当你引用参数的时候一定要记住,例如:你有一个非静态方法LMyObject;->MyMethod(IJZ)V,LMyObject方法的参数为int、long、bool。所以这个方法的所有参数需要5个寄存器。

    p0 this
    p1 I
    p2, p3 J
    p4 Z

    另外当你调用方法后,你必须在寄存器列表,调用指令中指明,两个寄存器保存了double-wide宽度的参数。

      

    关于几个调用方法指令: invoke-virtual、invoke-direct、invoke-super介绍。

    涉及到Java强大的动态扩展能力,这一特性使得可以在类运行期间才能确定某些目标方法的实际引用,称为动态连接;也有一部分方法的符号引用在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。

    在Java语言中,符合“编译器可知,运行期不可变”这个要求的方法主要有静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法都不可能通过继承或别的方式重写出其他的版本,因此它们都适合在类加载阶段进行解析。 

    • invoke-static 是类静态方法的调用,编译时,静态确定的;
    • invoke-virtual 虚方法调用,调用的方法运行时确认实际调用,和实例引用的实际对象有关,动态确认的,一般是带有修饰符protected或public的方法;
    • invoke-direct 没有被覆盖方法的调用,即不用动态根据实例所引用的调用,编译时,静态确认的,一般是private或<init>方法;
    • invoke-super 直接调用父类的虚方法,编译时,静态确认的。
    • invokeinterface 调用接口方法,调用的方法运行时确认实际调用,即会在运行时才确定一个实现此接口的对象。

     

    参考:

    http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

    http://www.cnblogs.com/Fang3s/p/3782903.html

    http://www.52pojie.cn/thread-233852-1-1.html

    http://book.2cto.com/201212/12474.html

    http://book.2cto.com/201212/12475.html

    https://blog.csdn.net/l25000/article/details/46842013

    https://blog.csdn.net/l25000/article/details/46891799

  • 相关阅读:
    [MST] Test mobx-state-tree Models by Recording Snapshots or Patches
    [MST] Attach Behavior to mobx-state-tree Models Using Actions
    [MST] Describe Your Application Domain Using mobx-state-tree(MST) Models
    [Angular] Configure an Angular App at Compile Time with the Angular CLI
    [Angular] Configure an Angular App at Runtime
    [Test] Easy automated testing in NodeJS with TestCafe
    [React] Optimistic UI update in React using setState()
    [Javascript] Required function arguments in Javascript
    [Transducer] Lazyness in Transduer
    [Transducer] Create a Sequence Helper to Transduce Without Changing Collection Types
  • 原文地址:https://www.cnblogs.com/eustoma/p/8991297.html
Copyright © 2020-2023  润新知