1. 程式人生 > >史上超詳細的smali檔案解讀

史上超詳細的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等等。