馬上加入Android 台灣中文網,立即免費下載應用遊戲。
您需要 登錄 才可以下載或查看,沒有帳號?註冊
x
一 、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分支語句
- .method private packedSwitch(I)Ljava/lang/String;
- .locals 1
- .parameter "i"
- .prologue
- .line 21
- const/4 v0, 0x0
- .line 22
- .local v0, str:Ljava/lang/String; #v0為字符串,0表示null
- packed-switch p1, :pswitch_data_0 #packed-switch分支,pswitch_data_0指定case區域
- .line 36
- const-string v0, "she is a person" #default分支
- .line 39
- :goto_0 #所有case的出口
- return-object v0 #返回字符串v0
- .line 24
- :pswitch_0 #case 0
- const-string v0, "she is a baby"
- .line 25
- goto :goto_0 #跳轉到goto_0標號處
- .line 27
- :pswitch_1 #case 1
- const-string v0, "she is a girl"
- .line 28
- goto :goto_0 #跳轉到goto_0標號處
- .line 30
- :pswitch_2 #case 2
- const-string v0, "she is a woman"
- .line 31
- goto :goto_0 #跳轉到goto_0標號處
- .line 33
- :pswitch_3 #case 3
- const-string v0, "she is an obasan"
- .line 34
- goto :goto_0 #跳轉到goto_0標號處
- .line 22
- nop
- :pswitch_data_0
- .packed-switch 0x0 #case 區域,從0開始,依次遞增
- :pswitch_0 #case 0
- :pswitch_1 #case 1
- :pswitch_2 #case 2
- :pswitch_3 #case 3
- .end packed-switch
- .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代碼如下。
- private String packedSwitch(int i) {
- String str = null;
- switch (i) {
- case 0:
- str = "she is a baby";
- break;
- case 1:
- str = "she is a girl";
- break;
- case 2:
- str = "she is a woman";
- break;
- case 3:
- str = "she is an obasan";
- break;
- default:
- str = "she is a person";
- break;
- }
- return str;
- }
複製代碼
現在我們來看看無規律的case 分支語句代碼會有什麼不同
- .method private sparseSwitch(I)Ljava/lang/String;
- .locals 1
- .parameter "age"
- .prologue
- .line 43
- const/4 v0, 0x0
- .line 44
- .local v0, str:Ljava/lang/String;
- sparse-switch p1, :sswitch_data_0 # sparse-switch分支,sswitch_data_0指定case區域
- .line 58
- const-string v0, "he is a person" #case default
- .line 61
- :goto_0 #case 出口
- return-object v0 #返回字符串
- .line 46
- :sswitch_0 #case 5
- const-string v0, "he is a baby"
- .line 47
- goto :goto_0 #跳轉到goto_0標號處
- .line 49
- :sswitch_1 #case 15
- const-string v0, "he is a student"
- .line 50
- goto :goto_0 #跳轉到goto_0標號處
- .line 52
- :sswitch_2 #case 35
- const-string v0, "he is a father"
- .line 53
- goto :goto_0 #跳轉到goto_0標號處
- .line 55
- :sswitch_3 #case 65
- const-string v0, "he is a grandpa"
- .line 56
- goto :goto_0 #跳轉到goto_0標號處
- .line 44
- nop
- :sswitch_data_0
- .sparse-switch #case 區域
- 0x5 -> :sswitch_0 #case 5(0x5)
- 0xf -> :sswitch_1 #case 15(0xf)
- 0x23 -> :sswitch_2 #case 35(0x23)
- 0x41 -> :sswitch_3 #case 65(0x41)
- .end sparse-switch
- .end method
複製代碼
按照分析packed-switch 的方法,我們直接查看 sswitch_data_0 標號處的內容。可以看到「.sparse-switch 」指令沒有給出初始case 的值,所有的case 值都使用「case 值 -> case 標號」的形式給出。此處共有4 個case ,它們的內容都是構造一個字符串,然後跳轉到goto_0 標號處,代碼架構上與packed-switch 方式的 switch 分支一樣。
最後,將這段smali 代碼整理為Java 代碼如下。
- private String sparseSwitch(int age) {
- String str = null;
- switch (age) {
- case 5:
- str = "he is a baby";
- break;
- case 15:
- str = "he is a student";
- break;
- case 35:
- str = "he is a father";
- break;
- case 65:
- str = "he is a grandpa";
- break;
- default:
- str = "he is a person";
- break;
- }
- return str;
- }
複製代碼 五、try/catch 语句
- .method private tryCatch(ILjava/lang/String;)V
- .locals 10
- .parameter "drumsticks"
- .parameter "peple"
- .prologue
- const/4 v9, 0x0
- .line 19
- try_start_0 # 第1個try開始
- invoke-static {p2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I #將第2個參數轉換為int 型
- :try_end_0 # 第1個try結束
- .catch Ljava/lang/NumberFormatException; {:try_start_0 .. :try_end_0} : catch_1 # catch_1
- move-result v1 #如果出現異常這裡不會執行,會跳轉到catch_1標號處
- .line 21
- .local v1, i:I #.local聲明的變量作用域在.local聲明與.end local 之間
- :try_start_1 #第2個try 開始
- div-int v2, p1, v1 # 第1個參數除以第2個參數
- .line 22
- .local v2, m:I
- mul-int v5, v2, v1 #m * i
- sub-int v3, p1, v5 #v3 = p1 - v5
- .line 23
- .local v3, n:I
- const-string v5, "u5171u6709%du53eau9e21u817fuff0c%d
- u4e2au4ebau5e73u5206uff0cu6bcfu4ebau53efu5206u5f97%d
- u53eauff0cu8fd8u5269u4e0b%du53ea" # 格式化字符串
- const/4 v6, 0x4
- new-array v6, v6, [Ljava/lang/Object;
- const/4 v7, 0x0
- .line 24
- invoke-static {p1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
- move-result-object v8
- aput-object v8, v6, v7
- const/4 v7, 0x1
- invoke-static {v1}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
- move-result-object v8
- aput-object v8, v6, v7
- const/4 v7, 0x2
- invoke-static {v2}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
- move-result-object v8
- aput-object v8, v6, v7
- const/4 v7, 0x3
- invoke-static {v3}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
- move-result-object v8
-
- aput-object v8, v6, v7
- .line 23
- invoke-static {v5, v6}, Ljava/lang/String;
- ->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
- move-result-object v4
- .line 25
- .local v4, str:Ljava/lang/String;
- const/4 v5, 0x0
- invoke-static {p0, v4, v5}, Landroid/widget/Toast;
- ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
- Landroid/widget/Toast;
- move-result-object v5
- invoke-virtual {v5}, Landroid/widget/Toast;->show()V # 使用Toast 顯示格式化後的結果
- :try_end_1 #第2個try 結束
- .catch Ljava/lang/ArithmeticException; {:try_start_1 .. :try_end_1} : catch_0 # catch_0
- .catch Ljava/lang/NumberFormatException; {:try_start_1 .. :try_end_1} : catch_1 # catch_1
- .line 33
- .end local v1 #i:I
- .end local v2 #m:I
- .end local v3 #n:I
- .end local v4 #str:Ljava/lang/String;
- :goto_0
- return-void # 方法返回
- .line 26
- .restart local v1 #i:I
- :catch_0
- move-exception v0
- .line 27
- .local v0, e:Ljava/lang/ArithmeticException;
- :try_start_2 #第3個try 開始
- const-string v5, "u4ebau6570u4e0du80fdu4e3a0" #「人數不能為0」
- const/4 v6, 0x0
- invoke-static {p0, v5, v6}, Landroid/widget/Toast;
- ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
- Landroid/widget/Toast;
- move-result-object v5
- invoke-virtual {v5}, Landroid/widget/Toast;->show()V #使用Toast 顯示異常原因
- :try_end_2 #第3個try 結束
- .catch Ljava/lang/NumberFormatException; {:try_start_2 .. :try_end_2} :catch_1
- goto :goto_0 #返回
- .line 29
- .end local v0 #e:Ljava/lang/ArithmeticException;
- .end local v1 #i:I
- :catch_1
- move-exception v0
- .line 30
- .local v0, e:Ljava/lang/NumberFormatException;
- const-string v5, "u65e0u6548u7684u6570u503cu5b57u7b26u4e32"
- #「無效的數值字符串」
- invoke-static {p0, v5, v9}, Landroid/widget/Toast;
- ->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)
- Landroid/widget/Toast;
- move-result-object v5
- invoke-virtual {v5}, Landroid/widget/Toast;->show()V # 使用Toast 顯示異 常原因
- goto :goto_0 #返回
- .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語句塊是虛構的,假如下面的代碼。
- private void a() {
- try {
- ……
- try {
- ……
- } catch (XXX) {
- ……
- }
- } catch (YYY) {
- ……
- }
- }
複製代碼
當執行內部的try語句時發生了異常,如果異常類型為XXX,則內部catch就會捕捉到並執行相應的處理代碼,如果異常類型不是 XXX,那麼就會到外層的 catch中去查找異常處理代碼,這也就是為什麼實例的try_end_1標號下面會有兩個catch的原因,另外,如果在執行XXX異常的處理代碼時又發生了異常,這個時候該怎麼辦?此時這個異常就會擴散到外層的catch中去,由於XXX異常的外層只有一個YYY的異常處理,這時會判斷發生的異常是否為YYY類型,如果是就會進行處理,不是則拋給應用程式。回到本實例中來,如果在執行內部的ArithmeticException異常處理時再次發生別的異常,就會調用外層的 catch進行異常捕捉,因此在try_end_2標號下面有一個 catch_1就很好理解了。
最後,將這段 smali 代碼整理為Java 代碼如下。
- private void tryCatch(int drumsticks, String peple) {
- try {
- int i = Integer.parseInt(peple);
- try {
- int m = drumsticks / i;
- int n = drumsticks - m * i;
- String str = String.format("共有%d隻雞腿,%d個人平分,每人可分得%d只,還剩下%d只",drumsticks, i, m, n);
- Toast.makeText(MainActivity.this, str,Toast.LENGTH_SHORT).show();
- } catch (ArithmeticException e) {
- Toast.makeText(MainActivity.this, " 人數不能為0",Toast.LENGTH_SHORT).show();
- }
- } catch (NumberFormatException e) {
- Toast.makeText(MainActivity.this, " 無效的數值字符串",Toast.LENGTH_SHORT).show();
- }
- }
複製代碼
finally語句塊
- try {
- ServerSocket serverSocket= new ServerSocket(10000);
- Socket socket=serverSocket.accept();
- } catch (IOException e) {
- e.printStackTrace();
- }finally{
- int abc=5;
- Toast.makeText(this, "sssss ", Toast.LENGTH_SHORT).show();
- }
複製代碼
finally 語句塊作用:執行一些必要代碼。即不管出現異常與否,在finally中的代碼都會被執行
執行時機:針對所有catch語句之後,退出方法之前將被執行(即先執行catch裡面的代碼,但在throw之前將轉向finally)。finally中返回的結果將可以覆蓋catch中返回的結果
對應的smail代碼如下:
:try_start_0
new-instance v2, Ljava/net/ServerSocket; #ServerSocket v2 = null;
const/16 v3, 0x2710 # v3 = 10000;
invoke-direct {v2, v3}, Ljava/net/ServerSocket;-><init>(I)V # v2 = new ServerSocket(v3);
.line 21
.local v2, serverSocket:Ljava/net/ServerSocket;
invoke-virtual {v2}, Ljava/net/ServerSocket;->accept()Ljava/net/Socket; # v2.accept( );
:try_end_0
.catchall {:try_start_0 .. :try_end_0} :catchall_0 //處理start_0對應的異常塊是catchall_0 也就是finally
.catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0//處理start_0對應的異常塊是catch_0,catch_0異常塊先執行,之後再執行catchall_0
想對應的smali代碼為:
:try_start_0
new-instance v2, Ljava/net/ServerSocket;
const/16 v3, 0x2710
invoke-direct {v2, v3}, Ljava/net/ServerSocket;-><init>(I)V
.line 21
.local v2, serverSocket:Ljava/net/ServerSocket;
invoke-virtual {v2}, Ljava/net/ServerSocket;->accept()Ljava/net/Socket;
:try_end_0
.catchall {:try_start_0 .. :try_end_0} :catchall_0
.catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0
.line 27
const/4 v0, 0x5 #正常流程 即未發生異常
.line 28
.local v0, abc:I
const-string v3, "sssss "
invoke-static {p0, v3, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v3
invoke-virtual {v3}, Landroid/widget/Toast;->show()V
.line 32
.end local v2 #serverSocket:Ljava/net/ServerSocket;
:goto_0
return-void
.line 22
.end local v0 #abc:I
:catch_0 #當發生異常時執行
move-exception v1
.line 24
.local v1, e:Ljava/io/IOException;
:try_start_1
invoke-virtual {v1}, Ljava/io/IOException;->printStackTrace()V
:try_end_1
.catchall {:try_start_1 .. :try_end_1} :catchall_0 #異常部分執行完畢,轉而執行finally
.line 27
const/4 v0, 0x5
.line 28
.restart local v0 #abc:I
const-string v3, "sssss "
invoke-static {p0, v3, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v3
invoke-virtual {v3}, Landroid/widget/Toast;->show()V
goto :goto_0
.line 25
.end local v0 #abc:I
.end local v1 #e:Ljava/io/IOException;
#finally代碼定義部分
:catchall_0
move-exception v3
.line 27
const/4 v0, 0x5
.line 28
.restart local v0 #abc:I
const-string v4, "sssss "
invoke-static {p0, v4, v5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v4
invoke-virtual {v4}, Landroid/widget/Toast;->show()V
.line 30
throw v3 |