smali學習筆記

/*

author:少仲

blog: http://blog.csdn.net/py_panyu

weibo: http://weibo.com/u/3849478598

歡迎轉載,轉載請註明出處.

*/


0x1 smali概述

  Dalvik 虛擬機 (Dalvik VM) 是 Google 專門爲 Android 平臺設計的一套虛擬機.區別於標準 Java 虛擬機 JVM 的 class 文件格式,Dalvik  VM 擁有專屬的 DEX 可執行文件格式和指令集代碼.smali和 baksmali 則是針對 DEX 執行文件格式的彙編器和反彙編器,反彙編後 DEX 文件會產生.smali 後綴的代碼文件,smali 代碼擁有特定的格式與語法,smali 語言是對Dalvik 虛擬機字節碼的一種解釋.關於smali語法的格式,在AOSP/Dalvik/docs 目錄下提供了一份文檔 instruction-formats.html, 裏面詳細列舉了 Dalvik 虛擬機字節碼指令的所有格式,網上已經有了翻譯後的中文文檔.


0x2 使用smali和baksmali

smali 和 baksmali 這兩個工具彙編和反彙編DEX 文件的使用非常簡單,我們使用

baksmali.jar 反彙編 HelloWorld.dex 只需輸入以下命令:

java -jar baksmali.jar -o HelloWorldOutHelloWorld.dex
命令執行成功後會在 HelloWorldOut 目錄下生成相應 smali 文件,我們在修改完 smali

代碼後,使用 smali.jar 重新彙編成 HelloWorld.dex,輸入命令:

java -jar smali.jar -o HelloWorld.dexHelloWoldOut
我們只需把生成的 HelloWorld.dex 通過adb 命令 push 到手機上,並使用 dalvikvm 命令

便可以運行這個 DEX 文件,執行的命令如下:

adb push HelloWorld.dex /data/local/
adb shell dalvikvm -cp/data/local/HelloWorld.dex HelloWorld

0x3 Dalvik字節碼

(1).類型


 

  每個 Dalvik 寄存器都是 32 位大小, 對於小於或者等於 32 位長度的類型來說, 一個寄存器就可以存放該類型的值, 而像 J、 D 等 64 位的類型, 它們的值是使用相鄰兩個寄存器來存儲的,如 v0 與 v1、v3 與 v4 等.Java 中的對象在 smali 中以 Lpackage/name/ObjectName;的形式表示.前面的 L 表示這是一個對象類型,package/name/表示該對象所在的包,ObjectName是對象的名字,”;”表示對象名稱的結束. 相當於 java 中的 package.name.ObjectName. 例  如: Ljava/lang /String;相當於java.lang.String.”[“類型可以表示所有基本類型的數組.[I 表示一個整型一維數組,相當於 java 中的int[].對於多維數組,只要增加[就行了,[[I 相當於 int[][],[[[I 相當於 int[][][].注意每一維的最多 255 個.對象數組的表示:[Ljava/lang /String;表示一個 String 對象數組.

 

(2).方法

方法調用的表示格式:

Lpackage/name/ObjectName;->MethodName(III)Z.Lpackage/name/ObjectName;

表示類型,MethodName 是方法名,III 爲參數(在此是 3 個整型參數) ,Z 是返回類型(bool 型) .函數的參數是一個接一個的,中間沒有隔開.一個更復雜的例子:method(I[[IILjava/lang /String;[Ljava/lang /Object;)Ljava/lang/String;在 java 中則爲:String method(int, int[][], int, String, Object[])

 

(3).字段

字段,即 java 中類的成員變量,表示格式:

Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;  即包名, 字段名和字段類型,字段名與字段類型是以冒號”:”分隔.

 

(4). 兩種不同的寄存器表示法

    在 Dalvik 虛擬機字節碼中寄存器的命名法中主要有 2 種:v 命名法和 p 命名法.假設一個函數使用到 M 個寄存器,並且該函數有 N 個入參,根據 Dalvik 虛擬機參數傳遞方式中的規定:入參使用最後的 N 個寄存器中,局部變量使用從 v0 開始的前 M-N 個寄存器.比如,某函數 A 使用了 5 個寄存器, 2 個顯式的整形參數, 如果函數 A 是非靜態方法, 函數被調用時會傳入一個隱式的對象引用, 因此實際傳入的參數個數是 3 個. 根據傳參規則, 局部變量將使用前 2 個寄存器,參數會使用後 3 個寄存器.

    v 命名法採用小寫字母”v”開頭的方式表示函數中用到的局部變量與參數,所有的寄存器命名從 v0 開始,依次遞增.對於上文的函數 A,v 命名法會用到 v0、v1、v2、v3、v4等 5 個寄存器,v0 與 v1 表示函數 A 的局部變量,v2 表示傳入的隱式對象引用,v3 與 v4 表示實際傳入的 2 個整形參數.

    P 命名法對函數的局部變量寄存器命名沒有影響,它的命名規則是:函數的入參從 p0開始命名,依次遞增.對於上文的函數 A,p 命名法會用到 v0、v1、p0、p1、p2 等 5 個寄存器,v0 與 v1 表示函數 A 的局部變量,p0 表示傳入的隱式對象引用,p1 與 p2 表示實際傳入的 2 個整形參數. 此時, p0、 p1、 p2 實際上分別表示 v2、 v3、 v4, 只是命名不一樣而已.

    在實際的 Smali 文件中, 幾乎都是使用了 p 命名法, 主要原因是使用 p 命名法能夠通過寄存器的名字前綴就能很容易判斷寄存器到底是局部變量還是函數的入參.初次學習 smali語法時容易對寄存器 p0 表示的意義出現混亂,這主要體現在靜態方法和非靜態方法中.其實只要理解 p 命名法的定義後就可以很清楚的理解.在 smali 語法中,在調用非靜態方法時需要傳入該方法所在對象的引用,因此此時 p0 表示的是傳入的隱式對象引用,從 p1 開始纔是實際傳入的入參. 但是在調用靜態方法時, 由於靜態方法不需要構建對象的引用, 因而也就不需要傳入該方法所在對象的引用,因此此時從 p0 開始就是實際傳入的入參.

    在 Dalvik 指令中使用”v 加數字”的方法來索引寄存器,如:v0、v1、v15、v255,但每條指令使用的寄存器索引範圍都有限制(因爲 Dalvik 指令字節碼必須字節對齊) ,這裏我們使用一個大寫字母來表示 4 位數據寬度的取值範圍,如:指令 move vA, vB,目的寄存器 vA可使用 v0  ~  v15 的寄存器,源寄存器 vB 可以使用 v0  ~  v15 寄存器.指令move/from16  vAA,vBBBBB,目的寄存器 vAA 可使用 v0 ~ v255的寄存器,源寄存器 vB 可以使用 v0 ~ v65535 寄存器.簡而言之,當目的寄存器和源寄存器中有一個寄存器的編號大於 15 時,即需要加上/from16 指令才能得到正確運行.初次學習 Smali 語法時也容易對這一點不能理解,不注意就會導致 Smali 文件彙編爲 dex 文件的時候出現編譯錯誤. 比如, 按照前面總結的 p 命名法,當 p0 實際表示的寄存器編號大於 15 時,此時 Smali 語句 move v0,p0 就會編譯出錯.

 

0x4 實例

//Hello.java
public class Hello
{
         publicint foo(int a, int b)
         {
                   return(a + b) * (a - b);
         }
 
         publicstatic void main(String[] argc)
         {
                   Hellohello = new Hello();
                   System.out.println(hello.foo(5,3));
         }
}

//Hello.smali
.class public LHello;                 #class的名字
.super Ljava/lang/Object;             #這個類繼承的對象
.source "Hello.java"                  #java文件名


# direct methods                      #直接方法
.method public constructor <init>()V  #class的構造函數
    .registers 1                      #寄存器數

    .prologue                         #然而並沒有什麼卵用 -_-!
    .line 1                           #行號
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
                                      #調用Object的構造方法,p0相當於"this"指針
    return-void                       #返回空
.end method

.method public static main([Ljava/lang/String;)V                    #main方法實現
    .registers 5
    .parameter

    .prologue
    .line 10                                          
    new-instance v0, LHello;                                        #new一個對象

    invoke-direct {v0}, LHello;-><init>()V                          #調用Hello類的構造函數,參數是v0

    .line 11
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;   #讀取輸出值到v1

    const/4 v2, 0x5                                                 #讀4位立即數0x5到v2

    const/4 v3, 0x3                                                 #讀4位立即數0x3到v3

    invoke-virtual {v0, v2, v3}, LHello;->foo(II)I                  #調用foo函數,傳遞參數v0,v2,v3

    move-result v0                                                  #調用返回值到v0

    invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V     #調用虛方法,參數v1,v0

    .line 12
    return-void
.end method


# virtual methods                     #foo方法
.method public foo(II)I
    .registers 5
    .parameter
    .parameter

    .prologue
    .line 5
    add-int v0, p1, p2                #計算p1+p2到v0

    sub-int v1, p1, p2                #計算p1-p2到v1

    mul-int/2addr v0, v1              #計算v0*v1到v0

    return v0                         #返回v0
.end method



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