深入理解Dalvik字節碼指令及Smali文件

今天來介紹有關Dalvik虛擬機相關的知識,首先便是介紹我們最關心的Dalvik字節碼相關知識,進而深入到Android逆向領域.之所以寫這篇文章,是因爲有姑娘要學習這,再加上網上的許多資料太過零散和片面,當然,更重要的是爲以前做個總結.


Dalvik寄存器

在開始之前,首先來了解寄存器相關的知識.Dalvik中用的寄存器都是32位,64位類型數據則用兩個相鄰的32位寄存器表示,也就是對於double這種64位類型的數據,需要用到兩個32位寄存器來存儲.

虛擬機寄存器

我們知道Dalvik最多支持65536個寄存器(編號從0~65535),但是在ARM架構的cpu中只存在37個寄存器,那麼這種不對稱是怎麼解決的呢?
Dalvik中的寄存器是虛擬寄存器, 通過映射真實的寄存器來實現.我們知道每個Dalvik維護了一個調用棧,該調用棧就是用來支持虛擬寄存器和真實寄存器相互映射的.在執行具體函數時,Dalvik會根據.registers指令來確定該函數要用到的寄存器數目.具體的原理,可以自行參考Davilk的實現.

下面我們談到的寄存器都是虛擬寄存器.

寄存器的使用規則

對於一個使用m個寄存器(m=局部變量寄存器個數l+參數寄存器個數n)的方法而言,局部寄存器使用從v0開始的l個寄存器,而參數寄存器則使用最後的n個寄存器.舉個例子說明假設實例方法test(String a,String b)一共使用了5個寄存器:0,1,2,3,4,那麼參數寄存器是能使用2,3,4這三個寄存器,如圖:
這裏寫圖片描述

寄存器的命名

寄存器有兩種不同的命名方法:v字命名法和p字命 名法.這兩種命名法僅僅是影響了字節碼的可讀性.

v字命名法

以小寫字母v開頭的方式表示方法中使用的局部變量和參數.

對於上面實例方法test(String a,String b)來說,v0,v1爲局部變量能夠使用的寄存器,v2,v3,v4爲參數能夠使用的寄存器:
這裏寫圖片描述

p字命名法

以小寫字母p開頭的方式表示參數,參數名稱從p0開始,依次增大.局部變量能夠使用的寄存器仍然是以v開頭.

對於上面實例方法test(String a,String b)來說,v0,v1爲局部變量能夠使用的寄存器,p0,p1,p2爲參數能夠使用的寄存器:
這裏寫圖片描述


Dalvik描述符

與JVM相類似,Davilk字節碼中同樣有一套用於描述類型,方法,字段的方法,這些方法結合Davilk的指令便形成了完整的彙編代碼.

字節碼和數據類型

Davilk字節碼只有兩種類型:基本類型和引用類型.對象和數組都是引用類型,Davilk中對字節碼類型的描述和JVM中的描述符規則一致:對於基本類型和無返回值的void類型都是用一個大寫字母表示,對象類型則用字母L加對象的全限定名來表示.數組則用[來表示,具體規則如下所示:

全限定名是什麼
以String爲例,其完整名稱是java.lang.String,那麼其全限定名就是java/lang/String;,即java.lang.String的”.”用”/”代替,並在末尾添加分號”;”做結束符.

java類型 類型描述符
boolean Z
byte B
short S
char C
int I
long J
float F
double D
void V
對象類型 L
數組類型 [

這裏我們重點解釋對象類型和數組類型:

對象類型

L可以表示java類型中的任何類.在java代碼中以package.name.ObjectName的方式引用,而在Davilk中其描述則是以Lpackage/name/ObjectName;的形式表示.L即上面定義的java類類型,表示後面跟着的是累的全限定名.比如java中的java.lang.String對應的描述是Ljava/lang/String;.

數組類型

[類型用來表示所有基本類型的數組,[後跟着是基本類型的描述符.每一維度使用一個前置的[.
比如java中的int[] 用匯編碼表示便是[I;.二維數組int[][]爲[[I;,三維數組則用[[[I;表示.

對於對象數組來說,[後跟着對應類的全限定符.比如java當中的String[]對應的是[java/lang/String;.

字段的描述

Davilk中對字段的描述分爲兩種,對基本類型字段的描述和對引用類型的描述,但兩者的描述格式一樣:
對象類型描述符->字段名:類型描述符;
比如com.sbbic.Test類中存在String類型的name字段及int類型的age字段,那麼其描述爲:

Lcom/sbbic/Test;->name:Ljava/lang/String;
Lcom/sbbic/test;->age:I

方法的描述

java中方法的簽名包括方法名,參數及返回值,在Davilk相應的描述規則爲:
對象類型描述符->方法名(參數類型描述符)返回值類型描述符

下面我們通過幾個例子來說明,以java.lang.String爲例:

java方法:public char charAt(int index){...}
Davilk描述:Ljava/lang/String;->charAt(I)C

java方法:public void getChars(int srcBegin,int srcEnd,char dst[],int dstBegin){...}
Davilk描述:Ljava/lang/String;->getChars(II[CI)V

java方法:public boolean equals(Object anObject){...}
Davilk描述:Ljava/lang/String;->equals(Ljava/lang/Object)Z

Dalvik指令集

掌握以上的字段和方法的描述,只能說我們懂了如何描述一個字段和方法,而關於方法中具體的邏輯則需要了解Dalvik中的指令集.因爲Dalvik是基於寄存器的架構的,因此指令集和JVM中的指令集區別較大,反而更類似x86的中的彙編指令.

數據定義指令

數據定義指令用於定義代碼中使用的常量,類等數據,基礎指令是const

指令 描述
const/4 vA,#+B 將數值符號擴展爲32後賦值給寄存器vA
const-wide/16 vAA,#+BBBB 將數值符號擴展爲64位後賦值個寄存器對vAA
const-string vAA,string@BBBB 通過字符串索引高走字符串賦值給寄存器vAA
const-class vAA,type@BBBB 通過類型索引獲取一個類的引用賦值給寄存器vAA

數據操作指令

move指令用於數據操作,其表示move destination,source,即數據數據從source寄存器(源寄存器)移動到destionation寄存器(源寄存器),可以理解java中變量間的賦值操作.根據字節碼和類型的不同,move指令後會跟上不同的後綴.

指令 描述
move vA,vB 將vB寄存器的值賦值給vA寄存器,vA和vB寄存器都是4位
move/from16 vAA,VBBBB 將vBBBB寄存器(16位)的值賦值給vAA寄存器(7位),from16表示源寄存器vBBBB是16位的
move/16 vAAAA,vBBBB 將寄存器vBBBB的值賦值給vAAAA寄存器,16表示源寄存器vBBBB和目標寄存器vAAAA都是16位
move-object vA,vB 將vB寄存器中的對象引用賦值給vA寄存器,vA寄存器和vB寄存器都是4位
move-result vAA 將上一個invoke指令(方法調用)操作的單字(32位)非對象結果賦值給vAA寄存器
move-result-wide vAA 將上一個invoke指令操作的雙字(64位)非對象結果賦值給vAA寄存器
mvoe-result-object vAA 將上一個invoke指令操作的對象結果賦值給vAA寄存器
move-exception vAA 保存上一個運行時發生的異常到vAA寄存器

對象操作指令

與對象實例相關的操作,比如對象創建,對象檢查等.

指令 描述
new-instance vAA,type@BBBB 構造一個指定類型的對象將器引用賦值給vAA寄存器.此處不包含數組對象
instance-of vA,vB,type@CCCC 判斷vB寄存器中對象的引用是否是指定類型,如果是,將v1賦值爲1,否則賦值爲0
check-cast vAA,type@BBBB 將vAA寄存器中對象的引用轉成指定類型,成功則將結果賦值給vAA,否則拋出ClassCastException異常.

數組操作指令

在實例操作指令中我們並沒有發現創建對象的指令.Davilk中設置專門的指令用於數組操作.

指令 說明
new-array vA,vB,type@CCCC 創建指定類型與指定大小(vB寄存器指定)的數組,並將其賦值給vA寄存器
fill-array-data vAA,+BBBBBBBB 用指定的數據填充數組,vAA代表數組的引用(數組的第一個元素的地址)

數據運算指令

數據運算主要包括兩種:算數運算和邏輯運算.
1. 算術運算指令

指令 說明
add-type 加法指令
sub-type 減法指令
mul-type 乘法指令
div-type 除法指令
rem-type

2. 邏輯元算指令

指令 說明
and-type 與運算指令
or-type 或運算指令
xor-type 異或元算指令

3. 位移指令

指令 說明
shl-type 有符號左移指令
shr-type 有符號右移指令
ushr-type 無符號右移指令

上面的-type表示操作的寄存器中數據的類型,可以是-int,-float,-long,-double等.

比較指令

比較指令用於比較兩個寄存器中值的大小,其基本格式格式是cmp+kind-type vAA,vBB,vCC,type表示比較數據的類型,如-long,-float等;kind則代表操作類型,因此有cmpl,cmpg,cmp三種比較指令.coml是compare less的縮寫,cmpg是compare greater的縮寫,因此cmpl表示vBB小於vCC中的值這個條件是否成立,是則返回1,否則返回-1,相等返回0;cmpg表示vBB大於vCC中的值這個條件是否成立,是則返回1,否則返回-1,相等返回0.
cmp和cmpg的語意一致,即表示vBB大於vCC寄存器中的值是否成立,成立則返回1,否則返回-1,相等返回0
來具體看看Davilk中的指令:

指令 說明
cmpl-float vAA,vBB,vCC 比較兩個單精度的浮點數.如果vBB寄存器中的值大於vCC寄存器的值,則返回-1到vAA中,相等則返回0,小於返回1
cmpg-float vAA,vBB,vCC 比較兩個單精度的浮點數,如果vBB寄存器中的值大於vCC的值,則返回1,相等返回0,小於返回-1
cmpl-double vAA,vBB,vCC 比較兩個雙精度浮點數,如果vBB寄存器中的值大於vCC的值,則返回-1,相等返回0,小於則返回1
cmpg-double vAA,vBB,vCC 比較雙精度浮點數,和cmpl-float的語意一致
cmp-double vAA,vBB,vCC 等價與cmpg-double vAA,vBB,vCC指令

字段操作指令

字段操作指令表示對對象字段進行設值和取值操作,就像是你在代碼中長些的set和get方法.基本指令是iput-type,iget-type,sput-type,sget-type.type表示數據類型.

普通字段讀寫操作

前綴是i的iput-type和iget-type指令用於字段的讀寫操作.

指令 說明
iget-byte vX,vY,filed_id 讀取vY寄存器中的對象中的filed_id字段值賦值給vX寄存器
iput-byte vX,vY,filed_id 設置vY寄存器中的對象中filed_id字段的值爲vX寄存器的值
iget-boolean vX,vY,filed_id
iput-boolean vX,vY,filed_id
iget-long vX,vY,filed_id
iput-long vX,vY,filed_id

靜態字段讀寫操作

前綴是s的sput-type和sget-type指令用於靜態字段的讀寫操作

指令 說明
sget-byte vX,vY,filed_id
sput-byte vX,vY,filed_id
sget-boolean vX,vY,filed_id
sput-boolean vX,vY,filed_id
sget-long vX,vY,filed_id
sput-long vX,vY,filed_id

方法調用指令

Davilk中的方法指令和JVM的中指令大部分非常類似.目前共有五條指令集:

指令 說明
invoke-direct{parameters},methodtocall 調用實例的直接方法,即private修飾的方法.此時需要注意{}中的第一個元素代表的是當前實例對象,即this,後面接下來的纔是真正的參數.比如指令invoke-virtual {v3,v1,v4},Test2.method5:(II)V中,v3表示Test2當前實例對象,而v1,v4纔是方法參數
invoke-static{parameters},methodtocall 調用實例的靜態方法,此時{}中的都是方法參數
invoke-super{parameters},methodtocall 調用父類方法
invoke-virtual{parameters},methodtocall 調用實例的虛方法,即public和protected修飾修飾的方法
invoke-interface{parameters},methodtocall 調用接口方法

這五種指令是基本指令,除此之外,你也會遇到invoke-direct/range,invoke-static/range,invoke-super/range,invoke-virtual/range,invoke-interface/range指令,該類型指令和以上指令唯一的區別就是後者可以設置方法參數可以使用的寄存器的範圍,在參數多於四個時候使用.

再此強調一遍對於非靜態方法而言{}的結構是{當前實例對象,參數1,參數2,…參數n},而對於靜態方法而言則是{參數1,參數2,…參數n}

需要注意,如果要獲取方法執行有返回值,需要通過上面說道的move-result指令獲取執行結果.

方法返回指令

在java中,很多情況下我們需要通過Return返回方法的執行結果,在Davilk中同樣提供的return指令來返回運行結果:

指令 說明
return-void 什麼也不返回
return vAA 返回一個32位非對象類型的值
return-wide vAA 返回一個64位非對象類型的值
return-object vAA 反會一個對象類型的引用

同步指令

同步一段指令序列通常是由java中的synchronized語句塊表示,則JVM中是通過monitorenter和monitorexit的指令來支持synchronized關鍵字的語義的,而在Davilk中同樣提供了兩條類似的指令來支持synchronized語義:

指令 說明
monitor-enter vAA 爲指定對象獲取鎖操作
monitor-exit vAA 爲指定對象釋放鎖操作

異常指令

很久以前,VM也是用過jsr和ret指令來實現異常的,但是現在的JVM中已經拋出原先的做法,轉而採用異常表來實現異常.而Davilk仍然使用指令來實現:

指令 說明
throw vAA 拋出vAA寄存器中指定類型的異常

跳轉指令

跳轉指令用於從當前地址條狀到指定的偏移處,在if,switch分支中使用的居多.Davilk中提供了goto,packed-switch,if-test指令用於實現跳轉操作

指令 操作
goto +AA 無條件跳轉到指定偏移處(AA即偏移量)
packed-switch vAA,+BBBBBBBB 分支跳轉指令.vAA寄存器中的值是switch分支中需要判斷的,BBBBBBBB則是偏移表(packed-switch-payload)中的索引值,
spare-switch vAA,+BBBBBBBB 分支跳轉指令,和packed-switch類似,只不過BBBBBBBB偏移表(spare-switch-payload)中的索引值
if-test vA,vB,+CCCC 條件跳轉指令,用於比較vA和vB寄存器中的值,如果條件滿足則跳轉到指定偏移處(CCCC即偏移量),test代表比較規則,可以是eq.lt等.

在條件比較中,if-test中的test表示比較規則.該指令用的非常多,因此我們簡單的坐下說明:

指令 說明
if-eq vA,vB,target vA,vB寄存器中的相等,等價於java中的if(a==b),比如if-eq v3,v10,002c表示如果條件成立,則跳轉到current position+002c處.其餘的類似
if-ne vA,vB,target 等價與java中的if(a!=b)
if-lt vA,vB,target vA寄存器中的值小於vB,等價於java中的if(a<b)
if-gt vA,vB,target 等價於java中的if(a>b)
if-ge vA,vB,target 等價於java中的if(a>=b)
if-le vA,vB,target 等價於java中的if(a<=b)

除了以上指令之外,Davilk還提供可一個零值條件指令,該指令用於和0比較,可以理解爲將上面指令中的vB寄存器的值固定爲0.

指令 說明
if-eqz vAA,target 等價於java中的if(a==0)或者if(!a)
if-nez vAA,target 等價於java中的if(a!=0)或者if(a)
if-ltz vAA,target 等價於java中的if(a<0)
if-gtz vAA,target 等價於java中的if(a>0)
if-lez vAA,target 等價於java中的if(a<=0)
if-gtz vAA,target 等價於java中的if(a>=0)

附:
上面我們說道兩張偏移表packed-switch-payload和spare-switch-payload,兩者唯一的區別就是表中的值是否有序,後面我們會在下文中進行詳細的說明.

數據轉換指令

數據類型轉換對任何java開發者都是非常熟悉的,用於實現兩種不同數據類型的相互轉換.其基本指令格式是:unop vA,vB,表示對vB寄存器的中值進行操作,並將結果保存在vA寄存器中.

指令 說明
int-to-long 整形轉爲長整型
float-to-int 單精度浮點型轉爲整形
int-to-byte 整形轉爲字節類型
neg-int 求補指令,對整數求補
not-int 求反指令,對整數求反

到現在爲止,我們對Davilk中的指令做了簡單的說明.Davilk的指令在很大程度上結合了x86指令和JVM的指令結構和語意,因此總體來說Davilk中的指令還是非常容易學習.更多更詳細的指令參考請參考:Davilk指令集大全


詳解smali文件

上面我們介紹了Dalvik的相關指令,下面我們則來認識一下smali文件.儘管我們使用java來寫Android應用,但是Dalvik並不直接加載.class文件,而是通過dx工具將.class文件優化成.dex文件,然後交由Dalvik加載.這樣說來,我們無法通過分析.class來直接分析apk文件,而是需要藉助工具baksmali.jar反編譯dex文件來獲得對應smali文件,smali文件可以認爲是Davilk的字節碼文件,但是並兩者並不完全等同.

通過baksmali.jar反編譯出來每個.smali,都對應與java中的一個類,每個smali文件都是Davilk指令組成的,並遵循一定的結構.smali存在很多的指令用於描述對應的java文件,所有的指令都以”.”開頭,常用的指令如下:

關鍵詞 說明
.filed 定義字段
.method…end method 定義方法
.annotation…end annotation 定義註解
.implements 定義接口指令
.local 指定了方法內局部變量的個數
.registers 指定方法內使用寄存器的總數
.prologue 表示方法中代碼的開始處
.line 表示java源文件中指定行
.paramter 指定了方法的參數
.param 和.paramter含義一致,但是表達格式不同

在這裏很多人對.local和.register感到困惑,如果你也是請重新看上面的有關寄存器的點.

下面我們就簡單的說明一下smali文件的結構:

1. 文件頭描述

smali文件的前三行描述了當前類的信息:

.class <訪問權限修飾符> [非權限修飾符] <類名>
.super <父類名>
.source <源文件名稱>

<>中的內容表示必不可缺的,[]表示的是可選擇的.
訪問權限修飾符即所謂的public,protected,private即default.而非權限修飾符則指的是final,abstract.
舉例說明:

.class public final Lcom/sbbic/demo/Device;
.super Ljava/lang/Object;
.source "Device.java"

2. 文件正文

在文件頭之後便是文件的正文,即類的主體部分,包括類實現的接口描述,註解描述,字段描述和方法描述四部分.下面我們就分別看看字段和方法的結構.(別忘了我們在Davilk中說過的方法和字段的表示)

接口描述

如果該類實現了某個接口,則會通過.implements定義,其格式如下:

#interfaces
.implements <接口名稱>

舉例說明:

# interfaces
.implements Landroid/view/View$OnClickListener;

smali爲其添加了#Interface註釋

註解描述

如果一個類中使用註解,會用.annotation定義:其格式如下:

#annotations
.annotation [註解的屬性] <註解類名>
    [註解字段=值]
    ...
.end

字段描述

smali中使用.field描述字段,我們知道java中分爲靜態字段(類屬性)和普通字段(實例屬性),它們在smali中的表示如下:

1. 普通字段:

#instance fields
.field <訪問權限修飾符> [非權限修飾符] <字段名>:<字段類型>

訪問權限修飾符相比各位已經非常熟了,而此處非權限修飾符則可是final,volidate,transient.
舉例說明:

# instance fields
.field private TAG:Ljava/lang/String;

2. 靜態字段
靜態字段知識在普通字段的的定義中添加了static,其格式如下:

#static fields
.field <訪問權限> static [修飾詞] <字段名>:<字段類型>

舉例說明:

# static fields
.field private static final pi:F = 3.14f

需要注意:smali文件還爲靜態字段,普通字段分別添加#static field和#instan filed註釋.

方法描述

smali中使用.method描述方法.具體定義格式如下:

1. 直接方法
直接方法即所謂的direct methods,還記的Davilk中方法調用指令invoke-direct麼?忘記的童鞋自行翻看,這裏就不做說明了.

#direct methods
.method <訪問權限修飾符> [非訪問權限修飾符] <方法原型>
      <.locals>
      [.parameter]
      [.prologue]
      [.line]
      <代碼邏輯>
.end

重點解釋一下parameter:
parameter的個數和方法參數的數量相對應,即有幾個參數便有幾個.parameter,默認從1開始,即p1,p2,p2….
熟悉java的童鞋一定會記得該類型的方法有個默認的參數指向當前對象,在smali中,方法的默認對象參數用p0表示.

舉例說明:

# direct methods
.method public constructor <init>()V
    .registers 2

    .prologue
    .line 8
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    .line 10
    const-string v0, "MainActivity"

    iput-object v0, p0, Lcom/social_touch/demo/MainActivity;->TAG:Ljava/lang/String;

    .line 13
    const/4 v0, 0x0

    iput-boolean v0, p0, Lcom/social_touch/demo/MainActivity;->running:Z

    return-void
.end method

需要注意smali爲其添加了#direct method註釋

2. 虛方法
虛方法的定義會和直接方法唯一的不同就是註釋不同:#virtual methods,其格式如下:

#virtual methods
.method <訪問權限> [修飾關鍵詞] <方法原想>
      <.locals>
      [.parameter1]
      [.parameter2]
      [.prologue]
      [.line]
      <代碼邏輯>
.end

3. 內部類的smali文件結構

內部類的smali文件稍有不同,具體表現在內部類對應的smali文件的的文件名爲[外部類名稱$內部類名稱.smali]更詳細的說明見下文.

4. 實例演示

smali文件的結構也是非常清晰明瞭的,熟悉之後讀起來也是非常不錯的.下面我們來看個簡單的smali文件.爲了方便理解,我們首先貼一段java代碼:

public class MainActivity extends Activity implements View.OnClickListener {

    private String TAG = "MainActivity";
    private static final float pi = (float) 3.14;

    public volatile boolean running = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onClick(View view) {
        int result = add(4, 5);
        System.out.println(result);

        result = sub(9, 3);

        if (result > 4) {
            log(result);
        }
    }

    public int add(int x, int y) {
        return x + y;
    }

    public synchronized int sub(int x, int y) {
        return x + y;
    }

    public static void log(int result) {
        Log.d("MainActivity", "the result:" + result);
    }


}

接下來我們來看該段代碼反編譯出來的smali文件.需要注意的是不同的反編譯工具反編譯的文件可能稍有不同,比如使用.param而不是使用.paramter,不存在.register等,但是總體來說含義是相同的.

#文件頭描述
.class public Lcom/social_touch/demo/MainActivity;
.super Landroid/app/Activity;#指定MainActivity的父類
.source "MainActivity.java"#源文件名稱

#表明實現了View.OnClickListener接口
# interfaces
.implements Landroid/view/View$OnClickListener;

#定義float靜態字段pi
# static fields
.field private static final pi:F = 3.14f

#定義了String類型字段TAG
# instance fields
.field private TAG:Ljava/lang/String;

#定義了boolean類型的字段running
.field public volatile running:Z

#構造方法,如果你還納悶這個方法是怎麼出來的化,就去看看jvm的基礎知識吧
# direct methods
.method public constructor <init>()V
    .locals 1#表示函數中使用了一個局部變量

    .prologue#表示方法中代碼正式開始
    .line 8#表示對應與java源文件的低8行
    #調用Activity中的init()方法
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    .line 10
    const-string v0, "MainActivity"

    iput-object v0, p0, Lcom/social_touch/demo/MainActivity;->TAG:Ljava/lang/String;

    .line 13
    const/4 v0, 0x0

    iput-boolean v0, p0, Lcom/social_touch/demo/MainActivity;->running:Z

    return-void
.end method

#靜態方法log()
.method public static log(I)V
    .locals 3
    .parameter "result"#表示result參數

    .prologue
    .line 42
    #v0寄存器中賦值爲"MainActivity"
    const-string v0, "MainActivity"
    #創建StringBuilder對象,並將其引用賦值給v1寄存器
    new-instance v1, Ljava/lang/StringBuilder;

    #調用StringBuilder中的構造方法
    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V

    #v2寄存器中賦值爲ther result:
    const-string v2, "the result:"

    #{v1,v2}大括號中v1寄存器中存儲的是StringBuilder對象的引用.
    #調用StringBuilder中的append(String str)方法,v2寄存器則是參數寄存器.
    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    #獲取上一個方法的執行結果,此時v1中存儲的是append()方法執行後的結果,此處之所以仍然返回v1的    #原因在與append()方法返回的就是自身的引用
    move-result-object v1

    #繼續調用append方法(),p0表示第一個參數寄存器,即上面提到的result參數
    invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

    #同上
    move-result-object v1

    #調用StringBuilder對象的toString()方法
    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    #獲取上一個方法執行結果,toString()方法返回了一個新的String對象,因此v1中此時存儲了String對象的引用
    move-result-object v1

    #調用Log類中的靜態方法e().因爲e()是靜態方法,因此{v0,v1}中的成了參數寄存器
    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    .line 43
    #調用返回指令,此處沒有返回任何值
    return-void
.end method


# virtual methods
.method public add(II)I
    .locals 1
    .parameter "x"#第一個參數
    .parameter "y"#第二個參數

    .prologue
    .line 34

    #調用add-int指令求和之後將結果賦值給v0寄存器
    add-int v0, p1, p2

    #返回v0寄存器中的值
    return v0
.end method


.method public onClick(Landroid/view/View;)V
    .locals 4
    .parameter "view" #參數view

    .prologue
    const/4 v3, 0x4 #v3寄存器中賦值爲4

    .line 23#java源文件中的第23const/4 v1, 0x5#v1寄存器中賦值爲5

    #調用add()方法
    invoke-virtual {p0, v3, v1}, Lcom/social_touch/demo/MainActivity;->add(II)I

    #從v0寄存器中獲取add方法的執行結果
    move-result v0

    .line 24#java源文件中的24行
    .local v0, result:I

    #v1寄存器中賦值爲PrintStream對象的引用out
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    #執行out對象的println()方法
    invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V

    .line 26

    const/16 v1, 0x9#v1寄存器中賦值爲9
    const/4 v2, 0x3#v2寄存器中賦值爲3

    #調用sub()方法,{p0,v1,v2},p0指的是this,即當前對象,v1,v2則是參數
    invoke-virtual {p0, v1, v2}, Lcom/social_touch/demo/MainActivity;->sub(II)I
    #從v0寄存器中獲取sub()方法的執行結果
    move-result v0

    .line 28
    if-le v0, v3, :cond_0#如果v0寄存器的值小於v3寄存器中的值,則跳轉到cond_0處繼續執行

    .line 29

    #調用靜態方法log()
    invoke-static {v0}, Lcom/social_touch/demo/MainActivity;->log(I)V

    .line 31
    :cond_0
    return-void
.end method

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 1
    .parameter "savedInstanceState" #參數savedInstancestate

    .prologue
    .line 17

    #調用父類方法onCreate()
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    .line 18

    const v0, 0x7f04001a#v0寄存器賦值爲0x7f04001a

    #調用方法setContentView()
    invoke-virtual {p0, v0}, Lcom/social_touch/demo/MainActivity;->setContentView(I)V

    .line 19
    return-void
.end method

#declared-synchronized表示該方法是同步方法
.method public declared-synchronized sub(II)I
    .locals 1
    .parameter "x"
    .parameter "y"

    .prologue
    .line 38

    monitor-enter p0#爲該方法添加鎖對象p0
     add-int v0, p1, p2
    #釋放鎖對象
    monitor-exit p0

    return v0
.end method

結束語

仍然感覺有很多點沒寫明白,後面再做補充吧.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章