Smali —— 數學運算,條件判斷,循環

經過上一篇 Smali 語法解析——Hello World 的學習,瞭解了 Smali 文件的基本格式。這一篇從最基本的數學運算,條件判斷,循環等開始,更加詳細的瞭解 Smali 語法。java

數學運算

加法

先看源文件:android

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

複製代碼

經過 javac dxbaksmali 工具生成對應的 smali 文件,具體方法在 上一篇 中有所介紹。咱們看一下生成的 smali 文件:git

.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 個寄存器

    .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 值強轉爲 float 再存入 v0

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

    return v0 // 返回 v0 中的值
.end method
複製代碼

代碼邏輯很簡單,能夠看到 int 值和 float 值相加的過程當中會先將 int 值強轉爲 float,再進行加法。這裏用到了數據定義,強轉,加法三種 smali 語法。github

數據定義指令

Dalvik 虛擬機中每一個寄存器都是 32 位的。int 等 4字節表示的數據類型一個寄存器就能夠表示,而 double 等 64 位的數據類型則須要兩個寄存器來表示。數據定義指令用到的基本字節碼是 const,通常帶 -wide 後綴表示的是 64 位數據,不帶 -wide 後綴則是 32 位數據。上面的例子中定義了 兩種基本數據類型。 const/4 v0, 0x1表示將數值 0x1 擴展爲 32 位以後賦給寄存器 v0。const/high16 v1, 0x3fc00000 ,表示將 0x3fc00000 右邊零擴展至 32 位賦給寄存器 v1。0x3fc000001.5f 在內存中的表示,若是你瞭解 float 數值在內存中的表示方法的話,就會理解這裏爲何要右邊零擴展了。不理解的話能夠閱讀個人文章,先挖一個坑吧,尚未寫 。下面介紹一些常見的數據定義指令(來自官網):bash

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

強轉指令

強轉的語法比較簡單,直接看官網截圖:微信

int-to-float.png

除了常見的基本類型之間的強制轉換,還有 neg 求補,not 求反,也一樣適用這一語法。ide

加法指令

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

加法指令還有一種三個參數的寫法,以下所示:函數

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

這裏的 float 能夠替換爲其餘基本數據類型, add 也能夠替換爲其餘數學運算操做。一樣,仍是用過官網截圖來了解一下支持的運算語法:工具

smali_math.png

一個加法延伸出來很多知識,看到這裏,不知道你有沒有一個疑問,想一想最初的 java 源代碼:oop

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

代碼中定義了兩個變量 ab,但是 smali 中的這兩個變量呢?虛擬機中的編譯器,不管是 JVM 仍是 DVM,都會竭盡所能的在編譯階段對代碼進行優化以提高運行速度。ab 這兩個變量在 add() 方法中並非必須存在的,因此 DVM 不會浪費時間和空間再去申明這兩個變量。若是變量 b 也是 int 類型的話,DVM 甚至連加法都會省略,直接返回 a+b 的數值,你們能夠動手試一下。那麼,若是在學習過程當中想了解每一句代碼的 smali 指令該怎麼辦呢?使用 IDEAjava2smali 插件,就不會存在這些優化了。

減法

源代碼:

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 位數據類型。上面例子中定義了 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 複製代碼

顯然,編譯器對這段代碼進行了優化,提早計算了 3/2 ,在 div() 方法中直接返回結果。咱們在經過 java2smali 插件看一下未經優化的 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 表示

布爾運算

源代碼:

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 複製代碼

布爾運算在 smali 中被轉化爲一系列的條件判斷加指令跳轉。上面例子中使用了兩種跳轉指令,if 判斷以後的條件跳轉和 goto 表示的無條件跳轉,表示從當前地址跳轉到指定的偏移處。條件判斷指令在後面會具體羅列。

好像還沒提到過參數寄存器,這裏用到三個參數寄存器,p1 p2 p3 ,再加上一個局部變量寄存器 v0,看起來只用了四個寄存器,可是 .registers 5 卻告訴咱們這個方法用了五個寄存器,往上翻翻以前的 Smali 代碼,你會發現,都平白無故 「消失」 了一個寄存器。其實那是 p0 寄存器,函數被調用時會傳入一個隱式的對當前對象的引用,存儲在 p0 寄存器當中。

其餘運算

源代碼:

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 取模

條件判斷

條件判斷在以前的布爾運算中已經演示過,這裏羅列一些具體的判斷指令:

指令 說明
if-eq vA, vB, +CCCC 若是 vA=vB,跳轉指定偏移量
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

循環

源代碼:

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 // 跳轉至 goto_1 處

    .line 50
    :cond_d
    return-void
.end method
複製代碼

顯然,循環也是經過條件判斷和指令跳轉來完成的。

本節中學習了 Smali 的數學運算,條件判斷和循環的語法,也基本涵蓋了大部分的 Smali 基本語法。下一篇學習 Smali 中類的用法。傳送門 —— Smali 語法解析 —— 類

文中全部示例代碼地址: github.com/lulululbj/a…

文章同步更新於微信公衆號: 秉心說 , 專一 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!

相關文章
相關標籤/搜索