Dalvik是Google公司自己設計的用于Android平臺的虛擬機。Dalvik虛擬機是Google等廠商合作開發的Android移動設備平臺的核心組成部分之一。它可以支持已轉換為 .dex(即Dalvik Executable)格式的Java應用程序的運行,.dex格式是專為Dalvik設計的一種壓縮格式,適合內存和處理器速度有限的系統。Dalvik 經過優化,允許在有限的內存中同時運行多個虛擬機的實例,并且每一個Dalvik 應用作為一個獨立的Linux 進程執行。獨立的進程可以防止在虛擬機崩潰的時候所有程序都被關閉。
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位的數值,可用來表示0255的數值或v0v255的寄存器; -
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 相同 |
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, string@BBBB | 通過字符串索引構造一個字符串并賦給寄存器vAA |
const-string/jumbo vAA, string@BBBBBBBB | 通過字符串索引(較大)構造一個字符串并賦給寄存器vAA |
const-class vAA, type@BBBB | 通過類型索引獲取一個類引用并賦給寄存器vAA |
const-class/jumbo vAAAA, type@BBBBBBBB | 通過給定的類型索引獲取一個類引用并賦給寄存器vAAAA。 這條指令占用兩個字節,值為0xooff(Android4.0中新增的指令) |
6. 鎖指令
鎖指令多用在多線程程序中對同一對象的操作。Dalvik指令集中有兩條鎖指令:
指令 | 說明 |
---|---|
monitor-enter vAA | 為指定的對象獲取鎖 |
monitor-exit vAA | 釋放指定的對象的鎖 |
7. 實例操作指令
與實例相關的操作包括實例的類型轉換,檢查及新建等:
指令 | 說明 |
---|---|
check-cast vAA, type@BBBB | 將vAA寄存器中的對象引用轉換成指定的類型,如果失敗會拋出ClassCastException異常。 如果類型B指定的是基本類型,對于非基本類型的A來說,運行時始終會失敗 |
instance-of vA, vB, type@CCCC | 判斷vB寄存器中的對象引用是否可以轉換成指定的類型,如果可以vA寄存器賦值為1,否則vA寄存器賦值為0 |
new-instance vAA, type@BBBB | 構造一個指定類型對象的新實例,并將對象引用賦值給vAA寄存器,類型符type指定的類型不能是數組類 |
check-cast/jumbo vAAAA, type@BBBBBBBB | 指令功能與check-cast vAA, type@BBBB 相同,只是寄存器值與指令的索引取值范圍更大(Android4.0中新增的指令) |
instance-of/jumbo vAAAA, vBBBB, type@CCCCCCCC | 指令功能與instance-of vA, vB, type@CCCC 相同,只是寄存器值與指令的索引取值范圍更大(Android4.0中新增的指令) |
new-instance/jumbo vAAAA, type@BBBBBBBB | 指令功能與new-instance vAA, type@BBBB 相同,只是寄存器值與指令的索引取值范圍更大(Android4.0中新增的指令) |
8. 數組操作指令
數組操作包括獲取數組長度,新建數組,數組賦值,數組元素取值與賦值等操作。
指令 | 說明 |
---|---|
array-length vA, vB | 獲取給定vB寄存器中數組的長度并將值賦給vA寄存器,數組長度指的是數組的條目個數 |
new-array vA, vB, type@CCCC | 構造指定類型(type@CCCC)與大小(vB)的數組,并將值賦給vA寄存器 |
filled-new-array {vC, vD, vE, vF, vG},type@BBBB | 構造指定類型(type@BBBB)與大小(vA)的數組并填充數組內容。 vA寄存器是隱含使用的,除了指定數組的大小外還指定了參數的個數,vC~vG是使用到的參數寄存序列 |
filled-new-array/range {vCCCC ..vNNNN}, type@BBBB | 指令功能與filled-new-array {vC, vD, vE, vF, vG},type@BBBB 相同,只是參數寄存器使用range字節碼后綴指定了取值范圍 ,vC是第一個參數寄存器,N = A +C -1 |
fill-array-data vAA, +BBBBBBBB | 用指定的數據來填充數組,vAA寄存器為數組引用,引用必須為基礎類型的數組,在指令后面會緊跟一個數據表 |
new-array/jumbo vAAAA, vBBBB,type@CCCCCCCC | 指令功能與new-array vA,vB,type@CCCC 相同,只是寄存器值與指令的索引取值范圍更大(Android4.0中新增的指令) |
filled-new-array/jumbo {vCCCC ..vNNNN},type@BBBBBBBB | 指令功能與filled-new-array/range {vCCCC ..vNNNN},type@BBBB 相同,只是索引取值范圍更大(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-testz vAA, +BBBB | 條件跳轉指令。 拿vAA寄存器與0比較,如果比較結果滿足或值為0時就跳轉到BBBB指定的偏移處。 偏移量BBBB不能為0 |
if-test類型的指令有以下幾條:
指令 | 說明 | Java語法 |
---|---|---|
if-eq | 如果vA等于vB則跳轉 | if(vA == vB) |
if-ne | 如果vA不等于vB則跳轉 | if(vA != vB) |
if-lt | 如果vA小于vB則跳轉 | if(vA < vB) |
if-ge | 如果vA大于等于vB則跳轉 | if(vA >= vB) |
if-gt | 如果vA大于vB則跳轉 | if(vA > vB) |
if-le | 如果vA小于等于vB則跳轉 | if(vA <= vB) |
if-testz類型的指令有以下幾條:
指令 | 說明 | Java語法 |
---|---|---|
if-eqz | 如果vAA為0則跳轉 | if(vAA == 0) |
if-nez | 如果vAA不為0則跳轉 | if(vAA != 0) |
if-ltz | 如果vAA小于0則跳轉 | if(vAA < 0) |
if-gez | 如果vAA大于等于0則跳轉 | if(vAA >= 0) |
if-gtz | 如果vAA大于0則跳轉 | if(vAA > 0) |
if-lez | 如果vAA小于等于0則跳轉 | 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, fidld@CCCC
與sstaticop vAA, field@BBBB
。
普通字段指令的指令前綴為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, field@CCCCCCCC
與sstaticop/jumbo vAAAA, field@BBBBBBBB
兩類指令,它們與上面介紹的兩類指令作用相同,只是在指令中增加了jumbo字節碼后綴,且寄存器值與指令的索引取值范圍更大。
13. 方法調用指令
方法調用指令負責調用類實例的方法。它的基礎指令為invoke
,方法調用指令有invoke-kind {vC, vD, vE, vF, vG},meth@BBBB
與invoke-kind/range {vCCCC .. vNNNN},meth@BBBB
兩類,兩類指令在作用上并無不同,只是后者在設置參數寄存器時使用了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},meth@BBBBBBBB
這類指令,它與上面介紹的兩類指令作用相同,只是在指令中增加了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后綴,增加了寄存器與常量的取值范圍。