史上超詳細的smali檔案解讀
smali檔案的頭3行
# 指定了當前類的類名,訪問許可權為public,類名開頭的L是遵循Dalvik位元組碼的相關約定
.class public Lcom/example/administrator/myapplication/Demo;
# super指令指定了當前類的父類,父類為Object
.super Ljava/lang/Object;
//指定了當前類的原始檔名。注意:經過混淆的dex檔案,反編譯出來的smali程式碼可能沒有原始檔資訊,source可能為空。
.source "Demo.java"
欄位的宣告:
smali檔案的欄位的宣告使用".field"指令,欄位有靜態欄位和例項欄位兩種:
靜態欄位格式: .field 訪問許可權 static 修飾關鍵字 欄位名 欄位型別
.field public static HELLO:Ljava/lang/String;
上面smali程式碼轉為java程式碼為:
public static String HELLO = "hello";
例項欄位格式: .field 訪問許可權 修飾關鍵字 欄位名 欄位型別
.field private button:Landroid/widget/Button;
.field public number:I
上面的smali程式碼轉為java程式碼為:
private Button button;
public int number =5;
方法的宣告
使用".method"指令,分為直接方法(用private修飾的)和虛方法(用public和protected修飾的),直接方法和虛方法的宣告是一樣的。在呼叫函式時,有invoke-direct,invoke-virtual,invoke-static、invoke-super以及invoke-interface等幾種不同的指令。還有invoke-XXX/range 指令的,這是引數多於4個的時候呼叫的指令,比較少見:
.method private static getCount(II)V .registers 2 .param p0, "x" .param p1, "y" .prologue .line 28 return-void .end method
第一行為方法的開始處,此處方法的修飾符是static,訪問許可權是private,方法名是getCount,有兩個引數,都是int型別的(I代表int),V代表無返回值。
第二行指定暫存器的大小。
第三行和第四行為方法的引數,每有一個引數,就寫一個引數,此處有兩個引數。
第五行為 方法的主體部分(.prologue)
第六行指定了該處指令在原始碼中的行號,這裡是從java原始碼中底28行開始的
第七行return-void表示無返回值
第八行(.end method)方法結束
上面的smali轉為java程式碼為:
private static void getCount(int x,int y){
}
類實現介面
如果一個類實現了介面,會在smali 檔案中使用“.implements ”指令指出,相應的格式宣告如下:
# interfaces
.implements Lcom/example/administrator/myapplication/TestParent;
上面smali程式碼表明瞭實現了TestParent這個介面。
類使用註解
如果一個類使用了註解,會在 smali 檔案中使用“.annotation ”指令指出,註解的格式宣告如下:
# annotations
.annotation [ 註解屬性] < 註解類名>
[ 註解欄位 = 值]
.end annotation
註解的作用範圍可以是類、方法或欄位。如果註解的作用範圍是類,“.annotation ”指令會直接定義在smali 檔案中,如果是方法或欄位,“.annotation ”指令則會包含在方法或欄位定義中。
.field public sayWhat:Ljava/lang/String;
.annotation runtime Lcom/droider/anno/MyAnnoField;
info = ”Hello my friend”
.end annotation
.end field
如上String 型別 它使用了 com.droider.anno.MyAnnoField 註解,註解欄位info 值 為“Hello my friend”
轉換成java程式碼為:
@com.droider.anno.MyAnnoField(info = ”Hello my friend”)
public String sayWhat;
程式中的類
1)內部類:內部類可以有成員內部類,靜態巢狀類,方法內部類,匿名內部類。
格式: 外部類$內部類.smali
class Outer{
class Inner{}
}
baksmali反編譯上面的程式碼後會生成兩個檔案:Outer.smali和Outer$Inner.smali
Inner.smali檔案如下:
.class public Lcom/example/myapplication/Outer$Inner;
.super Ljava/lang/Object;
.source "Outer.java"
# annotations
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lcom/example/myapplication/Outer;
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x1
name = "Inner"
.end annotation
# instance fields
.field final synthetic this$0:Lcom/example/myapplication/Outer;
# direct methods
.method public constructor <init>(Lcom/example/myapplication/Outer;)V
.registers 2
.param p1, "this$0" # Lcom/example/myapplication/Outer;
.prologue
.line 4
iput-object p1, p0, Lcom/example/myapplication/Outer$Inner;->this$0:Lcom/example/myapplication/Outer;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
this$0是Outer型別,syntheitc關鍵字表明它是“合成的”。
.field final synthetic this$0:Lcom/example/myapplication/Outer;
this$0是什麼東西呢?
this$0是內部類自動保留的一個指向所在外部類的隱隱個,左邊的this表示為父類的引用,右邊的數值0表示引用的層數:
public class Outer {
public class FirstInner{} //this$0
public class SecondInner{} //this$1
public class ThirdInner{} //this$2
}
沒往裡一層右邊的數值就加一,如ThirdInner類訪問FirstInner類的引用為this$1.在生成的反彙編程式碼中,this$X型欄位都被指定了synthetic屬性,表明它們是被編譯器合成的,虛構的,程式碼的作者並沒有生命該欄位。
緊接著來看Inner.smali的建構函式:
從下面的建構函式中可以看到.param指定了一個引數,卻使用了p0和p1兩個暫存器,因為Dalvik虛擬機器對於一個非靜態的方法而言,會隱含的使用p0暫存器當做類的this使用,因此,這裡的確是使用了2個暫存器(.registers 2),p0表示Outer$Inner.smali自身的引用,p1表示this$0,也就是Outer的引用。
# direct methods
.method public constructor <init>(Lcom/example/myapplication/Outer;)V
.registers 2
.param p1, "this$0" # Lcom/example/myapplication/Outer;
.prologue
.line 4
iput-object p1, p0, Lcom/example/myapplication/Outer$Inner;->this$0:Lcom/example/myapplication/Outer;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
分析如下:
.param p1, "this$0" # Lcom/example/myapplication/Outer;
這個是預設的建構函式,沒有引數,但是有一個預設的引數就是this$0,他是Outer的應用,如果建構函式有引數的話,會在
.param p1,"this$0"下面繼續列出。
iput-object p1, p0, Lcom/example/myapplication/Outer$Inner;->this$0:Lcom/example/myapplication/Outer;
將Outer引用賦值給this$0
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
呼叫預設的建構函式。
2)監聽器
Android程式開發中使用了大量的監聽器,比如Button的點選事件OnClickListener等等,由於監聽器只是臨時使用一次,沒有什麼服用價值,因此,編寫程式碼中多使用匿名內部類的形式來實現。
java原始碼:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
相應的smali程式碼:
.method protected onCreate(Landroid/os/Bundle;)V
.registers 4
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.prologue
.line 12
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 13
const v1, 0x7f09001c
invoke-virtual {p0, v1}, Lcom/example/myapplication/MainActivity;->setContentView(I)V
.line 15
const v1, 0x7f070022
invoke-virtual {p0, v1}, Lcom/example/myapplication/MainActivity;->findViewById(I)Landroid/view/View;
move-result-object v0
check-cast v0, Landroid/widget/Button;
.line 16
.local v0, "button":Landroid/widget/Button;
#新建一個MainActivity$1例項
new-instance v1, Lcom/example/myapplication/MainActivity$1;
invoke-direct {v1, p0}, Lcom/example/myapplication/MainActivity$1;-><init>(Lcom/example/myapplication/MainActivity;)V
#設定按鈕點選事件監聽器
invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.line 22
return-void
.end method
MainActivity$1程式碼如下:
.class Lcom/example/myapplication/MainActivity$1;
.super Ljava/lang/Object;
.source "MainActivity.java"
# interfaces
.implements Landroid/view/View$OnClickListener;
# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
value = Lcom/example/myapplication/MainActivity;->onCreate(Landroid/os/Bundle;)V
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x0
name = null
.end annotation
# instance fields
.field final synthetic this$0:Lcom/example/myapplication/MainActivity;
# direct methods
.method constructor <init>(Lcom/example/myapplication/MainActivity;)V
.registers 2
.param p1, "this$0" # Lcom/example/myapplication/MainActivity;
.prologue
.line 16
iput-object p1, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public onClick(Landroid/view/View;)V
.registers 2
.param p1, "v" # Landroid/view/View;
.prologue
.line 20
return-void
.end method
在MainActivity$1.smali檔案的開頭使用了“,implements”指令指定該類實現了按鈕點選事件的監聽器介面,因此,這個類實現了它的OnClick()方法,這時在分析程式時關心的地方。程式中的註解與監聽器的建構函式都是編譯器為我們自己生成的,實際分析過程中不必關心。
3)R.java
下面是R.java檔案的一部分:
public final class R {
public static final class anim {
public static final int abc_fade_in=0x7f010000;
}
}
由於這些資原始檔類都是R類的內部類,因此他們都會獨立生成一個類檔案,在反編譯出的程式碼中,可以發現有R.smali,R$attr.smali,R$dimen.smali,R$drawable.smali等等。