一 、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, "\u5171\u6709%d\u53ea\u9e21\u817f\uff0c%d 24 \u4e2a\u4eba\u5e73\u5206\uff0c\u6bcf\u4eba\u53ef\u5206\u5f97%d 25 \u53ea\uff0c\u8fd8\u5269\u4e0b%d\u53ea" # 格式化字符串 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, "\u4eba\u6570\u4e0d\u80fd\u4e3a0" #“人數不能為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, "\u65e0\u6548\u7684\u6570\u503c\u5b57\u7b26\u4e32" 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, “\u8d77\u59cb\u6570\u503c\u4e0d\u80fd\u5927\u4e8e\u7ed3\u675f\u6570\u503c!” # 起始數值不能大於結束數值 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, “\u7d2f\u52a0\u7ed3\u679c\uff1a” # 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 = '\0'; 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