一 、smali数据类型
1.Dalvik字节码
Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个连续的寄存器表示;
Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)
原始类型:
# 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_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 }
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 }
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