Smali 語法解析——數(shù)學(xué)運(yùn)算,條件判斷和循環(huán)

Smali —— 基本語法

通過上一篇 Smali 語法解析——Hello World 的學(xué)習(xí),了解了 Smali 文件的基本格式。這一篇從最基本的數(shù)學(xué)運(yùn)算,條件判斷,循環(huán)等開始,更加詳細(xì)的了解 Smali 語法。

數(shù)學(xué)運(yùn)算

加法

先看源文件:

public class BaseSmali {
    private float add() {
        int a = 1;
        float b = 1.5f;
        return  a + b;
    }
}

通過 javac dxbaksmali 工具生成對(duì)應(yīng)的 smali 文件,具體方法在 上一篇 中有所介紹。我們看一下生成的 smali 文件:

.class public LBaseSmali;
.super Ljava/lang/Object;
.source "BaseSmali.java"


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

    .prologue
    .line 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V

    return-void
.end method

.method private add()F
    .registers 3 // 使用 3 個(gè)寄存器

    .prologue
    .line 5
    const/4 v0, 0x1 // 將 0x1 放入 v0

    .line 6
    const/high16 v1, 0x3fc00000    # 1.5f 將 1.5f 放入 v1

    .line 7
    int-to-float v0, v0 // 將 v0 中的 int 值強(qiáng)轉(zhuǎn)為 float 再存入 v0

    add-float/2addr v0, v1 // 將 v0 和 v1 中的值相加再存入 v0

    return v0 // 返回 v0 中的值
.end method

代碼邏輯很簡(jiǎn)單,可以看到 int 值和 float 值相加的過程中會(huì)先將 int 值強(qiáng)轉(zhuǎn)為 float,再進(jìn)行加法。這里用到了數(shù)據(jù)定義,強(qiáng)轉(zhuǎn),加法三種 smali 語法。

數(shù)據(jù)定義指令

Dalvik 虛擬機(jī)中每個(gè)寄存器都是 32 位的。int 等 4字節(jié)表示的數(shù)據(jù)類型一個(gè)寄存器就可以表示,而 double 等 64 位的數(shù)據(jù)類型則需要兩個(gè)寄存器來表示。數(shù)據(jù)定義指令用到的基本字節(jié)碼是 const,一般帶 -wide 后綴表示的是 64 位數(shù)據(jù),不帶 -wide 后綴則是 32 位數(shù)據(jù)。上面的例子中定義了 兩種基本數(shù)據(jù)類型。 const/4 v0, 0x1表示將數(shù)值 0x1 擴(kuò)展為 32 位之后賦給寄存器 v0。const/high16 v1, 0x3fc00000 ,表示將 0x3fc00000 右邊零擴(kuò)展至 32 位賦給寄存器 v1。0x3fc000001.5f 在內(nèi)存中的表示,如果你了解 float 數(shù)值在內(nèi)存中的表示方法的話,就會(huì)理解這里為什么要右邊零擴(kuò)展了。不理解的話可以閱讀我的文章,先挖一個(gè)坑吧,還沒有寫 。下面介紹一些常見的數(shù)據(jù)定義指令(來自官網(wǎng)):

語法 參數(shù) 說明
const/4 vA, #+B A: 目標(biāo)寄存器(8 位) B: 有符號(hào)整數(shù)(8 位) 將給定的字面值(符號(hào)擴(kuò)展為 32 位)移到指定的寄存器中。
const/16 vAA, #+BBBB A: 目標(biāo)寄存器(8 位) B: 有符號(hào)整數(shù)(16 位) 將給定的字面值(符號(hào)擴(kuò)展為 32 位)移到指定的寄存器中。
const vAA, #+BBBBBBBB A: 目標(biāo)寄存器(8 位) B: 任意 32 位常量 將給定的字面值移到指定的寄存器中。
const/high16 vAA, #+BBBB0000 A: 目標(biāo)寄存器(8 位) B: 有符號(hào)整數(shù)(16 位) 將給定的字面值(右零擴(kuò)展為 32 位)移到指定的寄存器中。
const-wide/16 vAA, #+BBBB A: 目標(biāo)寄存器(8 位) B: 有符號(hào)整數(shù)(16 位) 將給定的字面值(符號(hào)擴(kuò)展為 64 位)移到指定的寄存器對(duì)中。
const-wide/32 vAA, #+BBBBBBBB A: 目標(biāo)寄存器(8 位) B: 有符號(hào)整數(shù)(32 位) 將給定的字面值(符號(hào)擴(kuò)展為 64 位)移到指定的寄存器對(duì)中。
const-wide vAA, #+BBBBBBBBBBBBBBBB A: 目標(biāo)寄存器(8 位) B: 任意雙字寬度(64 位)常量 將給定的字面值移到指定的寄存器對(duì)中。
const-wide/high16 vAA, #+BBBB000000000000 A: 目標(biāo)寄存器(8 位) B: 有符號(hào)整數(shù)(16 位) 將給定的字面值(右零擴(kuò)展為 64 位)移到指定的寄存器對(duì)中。
const-string vAA, string@BBBB A: 目標(biāo)寄存器(8 位) B: 字符串索引 將通過給定的索引獲取的字符串引用移到指定的寄存器中。
const-string/jumbo vAA, string@BBBBBBBB A: 目標(biāo)寄存器(8 位) B: 字符串索引 將通過給定的索引獲取的字符串引用移到指定的寄存器中。
const-class vAA, type@BBBB A: 目標(biāo)寄存器(8 位) B: 類型索引 將通過給定的索引獲取的類引用移到指定的寄存器中。如果指定的類型是原始類型,則將存儲(chǔ)對(duì)原始類型的退化類的引用。

強(qiáng)轉(zhuǎn)指令

強(qiáng)轉(zhuǎn)的語法比較簡(jiǎn)單,直接看官網(wǎng)截圖:

int-to-float.png

除了常見的基本類型之間的強(qiáng)制轉(zhuǎn)換,還有 neg 求補(bǔ),not 求反,也同樣適用這一語法。

加法指令

add-float/2addr v0, v1 // 將 v0 和 v1 中的值相加再存入 v0

加法指令還有一種三個(gè)參數(shù)的寫法,如下所示:

add-float v0, v1, v2 // 將 v1 和 v2 中的值相加再存入 v0

這里的 float 可以替換為其他基本數(shù)據(jù)類型, add 也可以替換為其他數(shù)學(xué)運(yùn)算操作。同樣,還是用過官網(wǎng)截圖來了解一下支持的運(yùn)算語法:

smali_math.png

一個(gè)加法延伸出來不少知識(shí),看到這里,不知道你有沒有一個(gè)疑問,想想最初的 java 源代碼:

private float add() {
    int a = 1;
    float b = 1.5f;
    return  a + b;
}

代碼中定義了兩個(gè)變量 ab,可是 smali 中的這兩個(gè)變量呢?虛擬機(jī)中的編譯器,不論是 JVM 還是 DVM,都會(huì)竭盡所能的在編譯階段對(duì)代碼進(jìn)行優(yōu)化以提升運(yùn)行速度。ab 這兩個(gè)變量在 add() 方法中并不是必須存在的,所以 DVM 不會(huì)浪費(fèi)時(shí)間和空間再去申明這兩個(gè)變量。如果變量 b 也是 int 類型的話,DVM 甚至連加法都會(huì)省略,直接返回 a+b 的數(shù)值,大家可以動(dòng)手試一下。那么,如果在學(xué)習(xí)過程中想了解每一句代碼的 smali 指令該怎么辦呢?使用 IDEAjava2smali 插件,就不會(huì)存在這些優(yōu)化了。

減法

源代碼:

    private double sub(){
        int a = 1;
        double b = 2.5;
        return a-b;
    }

Smali 代碼:

.method private sub()D
    .registers 5

    .prologue
    .line 11
    const/4 v0, 0x1

    .line 12
    const-wide/high16 v2, 0x4004000000000000L    # 2.5

    .line 13
    int-to-double v0, v0

    sub-double/2addr v0, v2

    return-wide v0
.end method

減法指令用 sub 表示。

另外這里要注意的是 const-widereturn-wide,添加了 -wide 后綴的操作符表示的是 64 位數(shù)據(jù)類型。上面例子中定義了 double 類型常量,返回值也是 double 類型。

乘法

源代碼:

    private double mul(){
        float a = 1.5f;
        double b = 2;
        return a * b;
    }

Smali 代碼:

.method private mul()D
    .registers 5

    .prologue
    .line 17
    const/high16 v0, 0x3fc00000    # 1.5f

    .line 18
    const-wide/high16 v2, 0x4000000000000000L    # 2.0

    .line 19
    float-to-double v0, v0

    mul-double/2addr v0, v2

    return-wide v0
.end method

乘法指令用 mul 表示

除法

源代碼:

    private int div() {
        int a = 3;
        int b = 2;
        int c = a / b;
        return c;
    }

Smali 代碼:

.method private div()I
    .registers 2

    .prologue
    .line 23
    .line 25
    const/4 v0, 0x1

    .line 26
    return v0
.end method

顯然,編譯器對(duì)這段代碼進(jìn)行了優(yōu)化,提前計(jì)算了 3/2 ,在 div() 方法中直接返回結(jié)果。我們?cè)谕ㄟ^ java2smali 插件看一下未經(jīng)優(yōu)化的 Smali 代碼:

.method private div()I
    .registers 4

    .prologue
    .line 28
    const/4 v0, 0x3

    .line 29
    .local v0, "a":I
    const/4 v1, 0x2

    .line 30
    .local v1, "b":I
    div-int v2, v0, v1

    .line 31
    .local v2, "c":I
    return v2
.end method

可以看到除法指令用 div 表示

布爾運(yùn)算

源代碼:

    private boolean bool(boolean a, boolean b,boolean c) {
        return a && b || c;
    }

Smali 代碼:

.method private bool(ZZZ)Z
    .registers 5

    .prologue
    .line 35
    if-eqz p1, :cond_4 // 如果 p1 = 0, 跳至 cond_4 處

    if-nez p2, :cond_6 // 如果 p2 != 0,跳至 cond_6 處

    :cond_4
    if-eqz p3, :cond_8 // 如果 p3 = 0,跳至 cond_8 處

    :cond_6
    const/4 v0, 0x1 // 將 0x1 賦給 v0

    :goto_7
    return v0 // 返回 v0 的值

    :cond_8
    const/4 v0, 0x0 // 將 0x1 賦給 v0

    goto :goto_7 // 跳至 goto_7 處
.end method

布爾運(yùn)算在 smali 中被轉(zhuǎn)化為一系列的條件判斷加指令跳轉(zhuǎn)。上面例子中使用了兩種跳轉(zhuǎn)指令,if 判斷之后的條件跳轉(zhuǎn)和 goto 表示的無條件跳轉(zhuǎn),表示從當(dāng)前地址跳轉(zhuǎn)到指定的偏移處。條件判斷指令在后面會(huì)具體羅列。

好像還沒提到過參數(shù)寄存器,這里用到三個(gè)參數(shù)寄存器,p1 p2 p3 ,再加上一個(gè)局部變量寄存器 v0,看起來只用了四個(gè)寄存器,但是 .registers 5 卻告訴我們這個(gè)方法用了五個(gè)寄存器,往上翻翻之前的 Smali 代碼,你會(huì)發(fā)現(xiàn),都無緣無故 “消失” 了一個(gè)寄存器。其實(shí)那是 p0 寄存器,函數(shù)被調(diào)用時(shí)會(huì)傳入一個(gè)隱式的對(duì)當(dāng)前對(duì)象的引用,存儲(chǔ)在 p0 寄存器當(dāng)中。

其他運(yùn)算

源代碼:

    private void other(int a) {
        int or = a | 1;
        int and = a & 1;
        int right = a >> 2;
        int left = a << 2;
        int mod = a % 2;
    }

Smali 代碼:

.method private other(I)V
    .registers 3

    .prologue
    .line 39
    or-int/lit8 v0, p1, 0x1

    .line 40
    and-int/lit8 v0, p1, 0x1

    .line 41
    shr-int/lit8 v0, p1, 0x2

    .line 42
    shl-int/lit8 v0, p1, 0x2

    .line 43
    rem-int/lit8 v0, p1, 0x2

    .line 44
    return-void
.end method

or 或 ,and 與 , shr 右移 , shl 左移 , rem 取模

條件判斷

條件判斷在之前的布爾運(yùn)算中已經(jīng)演示過,這里羅列一些具體的判斷指令:

指令 說明
if-eq vA, vB, +CCCC 如果 vA=vB,跳轉(zhuǎn)指定偏移量
if-ne vA != vB
if-lt vA < vB
if-ge vA >= vB
if-gt vA > vB
if-le vA <= vB
if-eqz vA, +BBBB vA = 0
if-nez vA != 0
if-ltz vA < 0
if-gez vA >= 0
if-gtz vA > 0
if-lez vA <= 0

循環(huán)

源代碼:

    private void loop(){
        for (int i=0;i<10;i++){
            System.out.println(i);
        }
    }

Smali 代碼:

.method private loop()V
    .registers 3

    .prologue
    .line 47
    const/4 v0, 0x0 // v0 = 0

    :goto_1
    const/16 v1, 0xa // v1 = 10

    if-ge v0, v1, :cond_d // 如果 v0 >= v1,跳至 cond_d 處

    .line 48
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V

    .line 47
    add-int/lit8 v0, v0, 0x1 // v0++

    goto :goto_1 // 跳轉(zhuǎn)至 goto_1 處

    .line 50
    :cond_d
    return-void
.end method

顯然,循環(huán)也是通過條件判斷和指令跳轉(zhuǎn)來完成的。

本節(jié)中學(xué)習(xí)了 Smali 的數(shù)學(xué)運(yùn)算,條件判斷和循環(huán)的語法,也基本涵蓋了大部分的 Smali 基本語法。下一篇學(xué)習(xí) Smali 中類的用法。 傳送門:Smali 語法解析 —— 類

文中所有示例代碼地址: https://github.com/lulululbj/android-reverse

文章同步更新于微信公眾號(hào): 秉心說 , 專注 Java 、 Android 原創(chuàng)知識(shí)分享,LeetCode 題解,歡迎關(guān)注!

green.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容