天天看點

Android Dalvik虛拟機之Dalvik指令集-Smali彙編解析

1. 指令特點

Dalvik指定在調用格式上模仿了C語言的調用約定。Dalvik指令的文法與助詞符有如下特點:

  • 參數采用從目标(destination)到源(source)的方式。
  • 根據位元組碼的大小與類型不同,一些位元組碼添加了名稱字尾以消除岐義。
    • 32位正常類型的位元組碼末添加任何字尾。
    • 64位正常類型的位元組碼添加 -wide字尾。
    • 特殊類型的位元組碼根據具體類型添加字尾。它們可以是 -boolean,-byte,-char,-short,-int,-long,-float,-double,-object,-string,-class,-void之一。
  • 根據位元組碼的布局與選項不同,一些位元組碼添加了位元組碼字尾以消除岐義。這些字尾通過在位元組碼主名稱後添加斜杠“/”來分隔開。
  • 在指令集的描述中,寬度值中每個字母表示寬度為4位。

例如這條指令:“move-wide/from16 vAA, vBBBB”:

move為基礎位元組碼(base opcode),辨別這是基本操作。wide為名稱字尾(name suffix),辨別指令操作的資料寬度(64位)。from16為位元組碼字尾(opcode suffix),辨別源為一個16位的寄存器引用變量。vAA為目的寄存器,它始終在源的前面,取值範圍為v0~v255。vBBBB為源寄存器,取值範圍為v0~v65535。

Dalvik指令集中大多數指令用到了寄存器作為目的操作數或源操作數,其中 A/B/C/D/E/F/G/H 代表一個4位的數值,可用來表示0~15的數值或v0~v15的寄存器,而 AA/BB/CC/DD/EE/FF/GG/HH 代表一個8位的數值,可用來表示0~255的數值或v0~v255的寄存器,AAAA/BBBB/CCCC/DDDD/EEEE/FFFF/GGGG/HHHH 代表一個16位的數值,可用來表示0~65535的數值或v0~v65535的寄存器。注意:Android官方指令文檔描述寄存器時,對不同取值範圍的寄存器以括号說明其大小,如A:destination register(4 bits),A:destination register(16 bits)。請注意,Dalvik虛拟機中的每個寄存器都是32位的。描述指令時所說的位數表示的是寄存器數值的取值範圍。

2. 空操作指令

空操作指令的助記符為nop。它的值為00,通常nop指令被用來作對齊代碼之用,無實際操作。

3. 資料操作指令

資料操作指令為move。move指令的原型為“move destination,source”,move指令根據位元組碼的大小與類型不同,後面會跟上不同的字尾。

  • “move vA, vB”:将vB寄存器的值賦給vA寄存器,源寄存器與目的寄存器都為4位。
  • “move/from16 vAA, vBBBB”:将vBBBB寄存器的值賦給vAA寄存器,源寄存器為16位,目的寄存器為8位。
  • “move/16 vAAAA, vBBBB”:将vBBBB寄存器的值賦給vAAAA寄存器,源寄存器與目的寄存器都為16位。
  • “move-wide vA, vB”:為4位的寄存器對指派。源寄存器與目的寄存器都為4位。
  • “move-wide/from16 vAA, vBBBB”與“move-wide/16 vAAAA, vBBBB”實作與“move-wide”相同。
  • “move-object vA, vB”:為對象指派。源寄存器與目的寄存器都為4位。
  • “move-object/from16 vAA, vBBBB”:為對象指派。源寄存器為16位,目的寄存器為8位。
  • “move-object/16 vAA, vBBBB”:為對象指派。源寄存器與目的寄存器都為16位。
  • “move-result vAA”:将上一個invoke類型指令操作的單字非對象結果賦給vAA寄存器。
  • “move-result-wide vAA”:将上一個invoke類型指令操作的雙字非對象結果賦給vAA寄存器。
  • “move-result-object vAA":将上一個invoke類型指令操作的對象結果賦給vAA寄存器。
  • “move-exception vAA”:儲存一個運作時發生的異常到vAA寄存器,這條指令必須是異常發生時的異常處理器的一條指令。否則的話,指令無效。

4. 傳回指令 

傳回指令指的是函數結尾時運作的最後一條指令。它的基礎位元組碼為teturn,共有以下四條傳回指令:

  • "return-void":表示函數從一個void方法傳回。
  • “return vAA”:表示函數傳回一個32位非對象類型的值,傳回值寄存器為8位的寄存器vAA。
  • “return-wide vAA”:表示函數傳回一個64位非對象類型的值,傳回值為8位的寄存器對vAA。
  • “return-object vAA”:表示函數傳回一個對象類型的值。傳回值為8位的寄存器vAA。

5. 資料定義指令

資料定義指令用來定義程式中用到的常量,字元串,類等資料。它的基礎位元組碼為const。

  • “const/4 vA, #+B”:将數值符号擴充為32位後賦給寄存器vA。
  • “const/16 vAA, #+BBBB”:将資料符号擴充為32位後賦給寄存器vAA。
  • “const vAA, #+BBBBBBBB”:将數值賦給寄存器vAA。
  • “const/high16 vAA, #+BBBB0000“:将數值右邊零擴充為32位後賦給寄存器vAA。
  • “const-wide/16 vAA, #+BBBB”:将數值符号擴充為64位後賦給寄存器對vAA。
  • “const-wide/32 vAA, #+BBBBBBBB”:将數值符号擴充為64位後賦給寄存器對vAA。
  • “const-wide vAA, #+BBBBBBBBBBBBBBBB”:将數值賦給寄存器對vAA。
  • “const-wide/high16 vAA, #+BBBB000000000000”:将數值右邊零擴充為64位後賦給寄存器對vAA。
  • “const-string vAA, [email protected]”:通過字元串索引構造一個字元串并賦給寄存器vAA。
  • “const-string/jumbo vAA, [email protected]”:通過字元串索引(較大)構造一個字元串并賦給寄存器vAA。
  • “const-class vAA, [email protected]”:通過類型索引擷取一個類引用并賦給寄存器vAA。
  • “const-class/jumbo vAAAA, [email protected]”:通過給定的類型索引擷取一個類引用并賦給寄存器vAAAA。這條指令占用兩個位元組,值為0xooff(Android4.0中新增的指令)。

6. 鎖指令 

鎖指令多用在多線程程式中對同一對象的操作。Dalvik指令集中有兩條鎖指令:

  • "monitor-enter vAA":為指定的對象擷取鎖。
  • “monitor-exit vAA”:釋放指定的對象的鎖。

7. 執行個體操作指令 

與執行個體相關的操作包括執行個體的類型轉換,檢查及建立等:

  • “check-cast vAA, [email protected]”:将vAA寄存器中的對象引用轉換成指定的類型,如果失敗會抛出ClassCastException異常。如果類型B指定的是基本類型,對于非基本類型的A來說,運作時始終會失敗。
  • “instance-of vA, vB, [email protected]”:判斷vB寄存器中的對象引用是否可以轉換成指定的類型,如果可以vA寄存器指派為1,否則vA寄存器指派為0。
  • “new-instance vAA, [email protected]”:構造一個指定類型對象的新執行個體,并将對象引用指派給vAA寄存器,類型符type指定的類型不能是數組類。
  • “check-cast/jumbo vAAAA, [email protected]”:指令功能與“check-cast vAA, [email protected]”相同,隻是寄存器值與指令的索引取值範圍更大(Android4.0中新增的指令)。
  • “instance-of/jumbo vAAAA, vBBBB, [email protected]”:指令功能與“instance-of vA, vB, [email protected]”相同,隻是寄存器值與指令的索引取值範圍更大(Android4.0中新增的指令)。
  • “new-instance/jumbo vAAAA, [email protected]”:指令功能與“new-instance vAA, [email protected]”相同,隻是寄存器值與指令的索引取值範圍更大(Android4.0中新增的指令)。

8. 數組操作指令

數組操作包括擷取數組長度,建立數組,數組指派,數組元素取值與指派等操作。

  • “array-length vA, vB”:擷取給定vB寄存器中數組的長度并将值賦給vA寄存器,數組長度指的是數組的條目個數。
  • “new-array vA, vB, [email protected]”:構造指定類型([email protected])與大小(vB)的數組,并将值賦給vA寄存器。
  • “filled-new-array {vC, vD, vE, vF, vG},[email protected]”構造指定類型([email protected])與大小(vA)的數組并填充數組内容。vA寄存器是隐含使用的,除了指定數組的大小外還指定了參數的個數,vC~vG是使用到的參數寄存序列。
  • “filled-new-array/range {vCCCC  ..vNNNN}, [email protected]”指令功能與“filled-new-array {vC, vD, vE, vF, vG},[email protected]”相同,隻是參數寄存器使用range位元組碼字尾指定了取值範圍 ,vC是第一個參數寄存器,N = A +C -1。
  • "fill-array-data vAA, +BBBBBBBB"用指定的資料來填充數組,vAA寄存器為數組引用,引用必須為基礎類型的數組,在指令後面會緊跟一個資料表。
  • "new-array/jumbo vAAAA, vBBBB,[email protected]"指令功能與“new-array vA,vB,[email protected]”相同,隻是寄存器值與指令的索引取值範圍更大(Android4.0中新增的指令)。
  • "filled-new-array/jumbo {vCCCC  ..vNNNN},[email protected]"指令功能與“filled-new-array/range {vCCCC  ..vNNNN},[email protected]”相同,隻是索引取值範圍更大(Android4.0中新增的指令)。
  • "arrayop vAA, vBB, vCC"對vBB寄存器指定的數組元素進入取值與指派。vCC寄存器指定數組元素索引,vAA寄存器用來存放讀取的或需要設定的數組元素的值。讀取元素使用aget類指令,元素指派使用aput類指定,根據數組中存儲的類型指令後面會緊跟不同的指令字尾,指令清單有 aget, aget-wide, aget-object, aget-boolean, aget-byte,aget-char, aget-short, aput, aput-wide, aput-object, aput-boolean, aput-byte, aput-char, aput-short。

9. 異常指令

Dalvik指令集中有一條指令用來抛出異常。

  • “throw vAA”抛出vAA寄存器中指定類型的異常。

10. 跳轉指令

跳轉指令用于從目前位址跳轉到指定的偏移處。Dalvik指令集中有三種跳轉指令:無條件跳轉(goto),分支跳轉(switch)與條件跳轉(if)。

  • “goto +AA”:無條件跳轉到指定偏移處,偏移量AA不能為0。
  • “goto/16 +AAAA”:無條件跳轉到指定偏移處,偏量AAAA不能為0。
  • “goto/32 +AAAAAAAA”:無條件跳轉到指定偏移處。
  • “packed-switch vAA, +BBBBBBBB”:分支跳轉指令。vAA寄存器為switch分支中需要判斷的值,BBBBBBBB指向一個packed-switch-payload格式的偏移表,表中的值是有規律遞增的。
  • “sparse-switch vAA, +BBBBBBBB”:分支跳轉指令。vAA寄存器為switch分支中需要判斷的值,BBBBBBBB指向一個sparse-switch-payload格式的偏移表,表中的值是無規律的偏移量。
  • “if-test vA, vB, +CCCC”:條件跳轉指令。比較vA寄存器與vB寄存器的值,如果比較結果滿足就跳轉到CCCC指定的偏移處。偏移量CCCC不能為0。if-test類型的指令有以下幾條:
    • “if-eq”:如果vA等于vB則跳轉。Java文法表示為“if(vA == vB)”
    • "if-ne":如果vA不等于vB則跳轉。Java文法表示為“if(vA != vB)”
    • “if-lt”:如果vA小于vB則跳轉。Java文法表示為“if(vA < vB)”
    • “if-ge”:如果vA大于等于vB則跳轉。Java文法表示為“if(vA >= vB)”
    • “if-gt”:如果vA大于vB則跳轉。Java文法表示為“if(vA > vB)”
    • “if-le”:如果vA小于等于vB則跳轉。Java文法表示為“if(vA <= vB)”
  • “if-testz vAA, +BBBB”:條件跳轉指令。拿vAA寄存器與0比較,如果比較結果滿足或值為0時就跳轉到BBBB指定的偏移處。偏移量BBBB不能為0。if-testz類型的指令有以下幾條:
    • “if-eqz”:如果vAA為0則跳轉。Java文法表示為“if(vAA == 0)”
    • "if-nez":如果vAA不為0則跳轉。Java文法表示為“if(vAA != 0)”
    • "if-ltz":如果vAA小于0則跳轉。Java文法表示為“if(vAA < 0)”
    • “if-gez”:如果vAA大于等于0則跳轉。Java文法表示為“if(vAA >= 0)”
    • “if-gtz”:如果vAA大于0則跳轉。Java文法表示為“if(vAA > 0)”
    • “if-lez”:如果vAA小于等于0則跳轉。Java文法表示為“if(vAA <= 0)”

11. 比較指令 

比較指令用于對兩個寄存器的值(浮點型或長整型)進行比較。它的格式為“cmpkind vAA, vBB, vCC”,其中vBB寄存器與vCC寄存器是需要比較的兩個寄存器或寄存器對,比較的結果放到vAA寄存器。Dalvik指令集中共有5條比較指令:

  • “cmpl-float”:比較兩個單精度浮點數。如果vBB寄存器大于vCC寄存器,結果為-1,相等則結果為0,小于的話結果為1
  • “cmpg-float”:比較兩個單精度浮點數。如果vBB寄存器大于vCC寄存器,則結果為1,相等則結果為0,小于的話結果為-1
  • “cmpl-double”:比較兩個雙精度浮點數。如果vBB寄存器對大于vCC寄存器對,則結果為-1,相等則結果為0,小于則結果為1
  • “cmpg-double”:比較兩個雙精度浮點數。如果vBB寄存器對大于vCC寄存器對,則結果為1,相等則結果為0,小于的話,則結果為-1
  • “cmp-long”:比較兩個長整型數。如果vBB寄存器大于vCC寄存器,則結果為1,相等則結果為0,小則結果為-1

12. 字段操作指令

字段操作指令用來對對象執行個體的字段進入讀寫操作。字段的類型可以是Java中有效的資料類型。對普通字段與靜态字段操作有兩種指令集,分别是“iinstanceop vA, vB, [email protected]” 與 “sstaticop vAA, [email protected]”。

普通字段指令的指令字首為i,如對普通字段讀操作使用 iget 指令,寫操作使用 iput 指令;靜态字段的指令字首為s,如對靜态字段讀操作使用 sget 指令,寫操作使用 sput 指令。

根據通路的字段類型不同,字段操作指令後面會緊跟字段類型的字尾,如 iget-byte指令表示讀取執行個體字段 的值類型為位元組類型,iput-short指令表示設定執行個體字段的值類型為短整型。兩類指令操作結果都是一樣,隻是指令字首與操作的字段類型不同。

普通字段操作指令有:iget,iget-wide,iget-object,iget-boolean,iget-byte,iget-char,iget-short,iput,iput-wide,iput-object,iput-boolean,iput-byte,iput-char,iput-short。

靜态字段操作指令有:sget,sget-wide,sget-object,sget-boolean,sget-byte,sget-char,sget-short,sput,sput-wide,sput-object,sput-boolean,sput-byte,sput-char,sput-short。

在Android4.0系統中,Dalvik指令集中增加了“iinstanceop/jumbo vAAAA, vBBBB, [email protected]”與"sstaticop/jumbo vAAAA, [email protected]"兩類指令,它們與上面介紹的兩類指令作用相同,隻是在指令中增加了jumbo位元組碼字尾,且寄存器值與指令的索引取值範圍更大。

13. 方法調用指令

方法調用指令負責調用類執行個體的方法。它的基礎指令為 invoke,方法調用指令有“invoke-kind {vC, vD, vE, vF, vG},[email protected]”與“invoke-kind/range {vCCCC  .. vNNNN},[email protected]”兩類,兩類指令在作用上并無不同,隻是後者在設定參數寄存器時使用了range來指定寄存器的範圍。根據方法類型的不同,共有如下五條方法調用指令:

  • “invoke-virtual” 或 “invoke-virtual/range”調用執行個體的虛方法。
  • “invoke-super”或"invoke-super/range"調用執行個體的父類方法。
  • “invoke-direct”或“invoke-direct/range”調用執行個體的直接方法。
  • “invoke-static”或“invoke-static/range”調用執行個體的靜态方法。
  • “invoke-interface”或“invoke-interface/range”調用執行個體的接口方法。

在Android4.0系統中,Dalvik指令集中增加了“invoke-kind/jumbo {vCCCC  .. vNNNN},[email protected]”這類指令,它與上面介紹的兩類指令作用相同,隻是在指令中增加了jumbo位元組碼字尾,且寄存器值與指令的索引取值範圍更大。

方法調用指令的傳回值必須使用move-result*指令來擷取。如下面兩條指令:

invoke-static {}, Landroid/os/Parcel;->obtain() Landroid/os/Parcel;
move-result-object v0
           

14. 資料轉換指令

資料轉換指令用于将一種類型的數值轉換成另一種類型。它的格式為“unop vA, vB”,vB寄存器或vB寄存器對存放需要轉換的資料,轉換後的結果儲存在vA寄存器或vA寄存器對中。

  • “neg-int”:對整型數求補。
  • “not-int”:對整型數求反。
  • “neg-long”:對長整型數求補。
  • “not-long”:對長整型數求反。
  • “neg-float”:對單精度浮點型數求補。
  • “neg-double”:對雙精度浮點型數求補。
  • “int-to-long”:将整型數轉換為長整型。
  • “int-to-float”:将整型數轉換為單精度浮點型數。
  • “int-to-dobule”:将整型數轉換為雙精度浮點數。
  • “long-to-int”:将長整型數轉換為整型。
  • “long-to-float”:将長整型數轉換為單精度浮點型。
  • “long-to-double”:将長整型數轉換為雙精度浮點型。
  • “float-to-int”:将單精度浮點數轉換為整型。
  • “float-to-long”:将單精度浮點數轉換為長整型數。
  • “float-to-double”:将單精度浮點數轉換為雙精度浮點型數。
  • “double-to-int”:将雙精度浮點數轉換為整型。
  • “double-to-long”:将雙精度浮點數轉換為長整型。
  • “double-to-float”:将雙精度浮點數轉換為單精度浮點型。
  • “int-to-byte”:将整型轉換為位元組型。
  • “int-to-char”:将整型轉換為字元型。
  • “int-to-short”:将整型轉換為短整型。

15. 資料運作指令

資料運算指令包括算術運算指令與邏輯運算指令。算術運算指令主要進行數值間如加,減,乘,除,模,移位等運算。邏輯運算指令主要進行數值間與,或,非,抑或等運算。資料運算指令有如下四類(資料運算時可能是在寄存器或寄存器對間進行,下面的指令作用講解時使用寄存器來描述):

  • “binop vAA, vBB, vCC”:将vBB寄存器與vCC寄存器進行運算,結果儲存到vAA寄存器。
  • “binop/2addr vA, vB”:将vA寄存器與vB寄存器進行運算,結果儲存到vA寄存器。
  • “binop/lit16 vA, vB, #+CCCC”:将vB寄存器與常量 CCCC進行運算,結果儲存到vA寄存器。
  • “binop/lit8 vAA, vBB, #+CC”:将vBB寄存器與常量CC進行運算,結果儲存到vAA寄存器。

後面3類指令比第1類指令分别多出了2addr,lit16,lit8等指令字尾。四類指令中基礎位元組碼相同的指令執行的運算操作是類似的,第1類指令中,根據資料的類型不同會在基礎位元組碼後面加上資料類型字尾,如 -int 或 -long 分别表示操作的資料類型為整型與長整型。第1類指令可歸類如下:

  • “add-type”:vBB寄存器與vCC寄存器值進行加法運算(vBB + vCC)
  • "sub-type":vBB寄存器與vCC寄存器值進行減法運算(vBB - vCC)
  • "mul-type":vBB寄存器與vCC寄存器值進行乘法運算(vBB * vCC)
  • "div-type":vBB寄存器與vCC寄存器值進行除法運算(vBB / vCC)
  • "rem-type":vBB寄存器與vCC寄存器值進行模運算(vBB % vCC)
  • "and-type":vBB寄存器與vCC寄存器值進行與運算(vBB & vCC)
  • "or-type":vBB寄存器與vCC寄存器值進行或運算(vBB | vCC)
  • "xor-type":vBB寄存器與vCC寄存器值進行異或運算(vBB ^ vCC)
  • "shl-type":vBB寄存器值(有符号數)左移vCC位(vBB << vCC )
  • "shr-type":vBB寄存器值(有符号)右移vCC位(vBB >> vCC)
  • "ushr-type":vBB寄存器值(無符号數)右移vCC位(vBB >>> vCC)

其中基礎位元組碼後面的-type可以是-int,-long, -float,-double。後面3類指令與之類似。 

至此,Dalvik虛拟機支援的所有指令就介紹完了。在android4.0系統以前,每個指令的位元組碼隻占用一個位元組,範圍是0x0~0x0ff。在android4.0系統中,又擴充了一部分指令,這些指令被稱為擴充指令,主要是在指令助記符後添加了jumbo字尾,增加了寄存器與常量的取值範圍。

原文:https://my.oschina.net/fhd/blog/365530

繼續閱讀