上一篇學習了 Smali 的數學運算,條件判斷和循環,接下來學習類的基本使用,包括接口,抽象類,內部類等等。java
直接上代碼吧,抽象類 Car.java
:數組
public abstract class Car {
protected String brand;
abstract void run();
}
複製代碼
接口 IFly.java
:bash
public interface IFly {
void fly();
}
複製代碼
類 BMW.java
:微信
public class BMW extends Car implements IFly{
private String brand = "BMW";
@Override
void run() {
System.out.println(brand + " run!");
}
@Override
public void fly() {
System.out.println("I can fly!");
}
public static void main(String[] args){
BMW bmw=new BMW();
bmw.run();
bmw.fly();
}
}
複製代碼
javac
編譯以後使用 dx
生成 dex 文件,和以往不一樣的是,此次是三個 class 文件生成一個 dex,具體命令以下:app
dx --dex --output=BMWCar.dex Car.class IFly.class BMW.class
複製代碼
最後再使用 baksmali
生成 Smali 文件:ide
baksmali d BMWCar.dex
複製代碼
能夠看到在當前目錄 out
文件夾下生成了三個文件 Car.smali
IFly.smaii
和 BMW.smali
。下面逐一進行分析。函數
Car.smali
:工具
.class public abstract LCar; // 代表爲抽象類
.super Ljava/lang/Object;
.source "Car.java"
# instance fields
.field protected brand:Ljava/lang/String; // proteced String brand
# direct methods
.method public constructor <init>()V
.registers 1
.prologue
.line 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method abstract run()V // abstract void run()
.end method
複製代碼
抽象類的表示沒有什麼特殊的地方,第一行中會使用 abstract
來聲明。post
IFly.smali
:學習
.class public interface abstract LIFly; // 代表是接口
.super Ljava/lang/Object;
.source "IFly.java"
# virtual methods
.method public abstract fly()V // abstract void fly()
.end method
複製代碼
接口使用 interface
聲明。從上面的 smali 代碼也能夠看到接口中的方法默認是 abstract
修飾的。
BMW.smali
:
.class public LBMW;
.super LCar; // 父類是 Car
.source "BMW.java"
# interfaces
.implements LIFly; // 實現了接口 IFly
# instance fields
.field private brand:Ljava/lang/String; // priva String brand
# direct methods
.method public constructor <init>()V
.registers 2
.prologue
.line 1
invoke-direct {p0}, LCar;-><init>()V
.line 3
const-string v0, "BMW"
iput-object v0, p0, LBMW;->brand:Ljava/lang/String; // String brand = "BMW";
return-void
.end method
.method public static main([Ljava/lang/String;)V // main 方法
.registers 2
.prologue
.line 16
new-instance v0, LBMW; // 新建 BMW 對象,並將其引用存入 v0
invoke-direct {v0}, LBMW;-><init>()V // 執行對象的構造函數
.line 17
invoke-virtual {v0}, LBMW;->run()V // 執行 run() 方法
.line 18
invoke-virtual {v0}, LBMW;->fly()V // 執行 fly() 方法
.line 19
return-void
.end method
# virtual methods
.method public fly()V // fly() 方法
.registers 3
.prologue
.line 12
// 下面三行字節碼執行了 System.out.println("I can fly!");
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "I can fly!"
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 13
return-void
.end method
.method run()V // run() 方法
.registers 4
.prologue
.line 7
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; // 獲取 Sysem.out 對象
new-instance v1, Ljava/lang/StringBuilder; // 新建 StringBuilder 對象,並將其引用存到 v1
invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V // 初始化
iget-object v2, p0, LBMW;->brand:Ljava/lang/String; // 獲取當前類的 brand 對象
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // append()
move-result-object v1 // 將上一步中執行 append() 返回的對象賦給 v1,這裏指的是 StringBuilder 對象
const-string v2, " run!"
invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; // append()
move-result-object v1 // 這裏的 v1 存儲的仍然是 StringBuilder 對象的引用
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; // toString()
move-result-object v1 // 這裏 v1 存儲的是 StringB.toString() 執行的結果
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V // 打印語句
.line 8
return-void
.end method
複製代碼
BMW
類中出現了幾個還沒遇到過的指令,逐一分析一下:
invoke-direct {p0}, LCar;-><init>()V
p0
存儲的是當前類的引用,這句話表示執行當前類中 Car 對象的 init() 方法。
通用表示:
invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
表示調用指定的方法,以後一般會跟一句 move-result*
來獲取方法的返回值。例如:
invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
move-result-object v1
複製代碼
第一句中執行了 StringBuilder.toString()
方法,返回值是一個 String
對象,緊接着後面的 move-result-object
將返回的 String
對象引用存在了 v1
中。
invoke-virtual
指調用正常的虛方法(不是 private,static,final,也不是構造函數)。除此以外,還有一些方法調用指令,在下面的表格中列舉出來:
指令 | 說明 |
---|---|
invoke-virtual | 調用正常的虛方法 |
invoke-super | 調用最近超類的虛方法 |
invoke-direct | 調用 private 方法或構造函數 |
invoke-static | 調用 static 方法 |
invoke-interface | 調用 interface 方法 |
iput-object v0, p0, LBMW;->brand:Ljava/lang/String;
v0
中存儲的是 BMW
字符串,賦給 p0
的 brand 字段。
通用用法:
iinstanceop vA, vB, field@CCCC
複製代碼
vA
能夠是源寄存器,也能夠是目的寄存器。當使用 iput
命令時, vA
就是源寄存器,使用 iget
命令時, vA
就是目的寄存器。
此命令還有一個一樣用法的變種 sput
和 sget
,從名字就能夠看出來是對靜態字段的操做,i
是對實例字段的操做。
new-instance v1, Ljava/lang/StringBuilder;
建立一個 StringBuilder 對象,並將其引用存在 v1
寄存器。
通用用法:
new-instance vAA, type@BBBB
複製代碼
注意這裏的 type
不能是數組類型。數組使用的是 new-array
指令:
new-array vA, vB, type@CCCC
複製代碼
從 run()
方法的 smali 代碼中咱們也學習到了一個小知識點。咱們都知道在 Java 中,使用 =
進行字符串拼接是很低效的,run()
方法中執行的是 System.out.println(brand + " run!");
,然而虛擬機並無傻傻的使用 =
去拼接,而是自動使用 StringBuilder
去拼接,提升運行效率。
內部類你們應該都不陌生,在 Android 開發中使用最多的要數匿名內部類了,除此以後,還有靜態內部類,成員內部類等。下面的 Outer.java
中便使用了這三種內部類:
public class Outer {
// 成員內部類
private class Inner {
private void in() {
System.out.println("I am inner class.");
}
}
// 靜態內部類
private static class StaticInner {
private void staticIn() {
System.out.println("I am static inner class.");
}
}
public static void main(String[] args) {
Outer outer = new Outer();
Inner inner = outer.new Inner();
inner.in();
StaticInner staticInner = new StaticInner();
staticInner.staticIn();
// 匿名內部類
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
複製代碼
還記得內部類的使用方法嗎?靜態內部類能夠直接使用 new
關鍵字進行實例化,如 new StaticInner()
。而成員內部類則不行,必須經過外部類來初始化,如 out.new Inner()
。javac
編譯以後會生成以下四個 class
文件:
OUter.class
Outer$Inner.clss
Outer$StaticInner.class
Outer$1.class
一樣,使用 baksmali
反彙編以後也會生成四個 smali
文件。
首先來看外部類 Outer.smali
:
.class public LOuter;
.super Ljava/lang/Object;
.source "Outer.java"
# annotations 系統自動添加的註解,表示內部類列表
.annotation system Ldalvik/annotation/MemberClasses;
value = {
LOuter$StaticInner;,
LOuter$Inner;
}
.end annotation
# 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 public static main([Ljava/lang/String;)V // main() 方法
.registers 4
.prologue
const/4 v2, 0x0
.line 17
new-instance v0, LOuter; // 建立外部類 Outer 對象,存入 v0
invoke-direct {v0}, LOuter;-><init>()V // 執行 Outer 對象的構造函數
.line 19
new-instance v1, LOuter$Inner; // 建立內部類 Outer$Inner 對象,存入 v1
// 獲取外部類對象的引用
invoke-virtual {v0}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
// 執行內部類 Out$Inner 的構造函數,注意這裏執行的不是無參構造,後面具體說明
invoke-direct {v1, v0, v2}, LOuter$Inner;-><init>(LOuter;LOuter$1;)V
.line 20
# invokes: LOuter$Inner;->in()V
// 執行 Outer$Inner 的 access$100() 方法,這個方法是自動生成的,
// 實際執行的就是 in() 方法
invoke-static {v1}, LOuter$Inner;->access$100(LOuter$Inner;)V
.line 22
new-instance v0, LOuter$StaticInner; // 建立靜態內部類 Out$StaticInner 對象
// 執行靜態內部類的構造函數,這裏也是有參構造
invoke-direct {v0, v2}, LOuter$StaticInner;-><init>(LOuter$1;)V
.line 23
# invokes: LOuter$StaticInner;->staticIn()V
// 執行 Outer$StaticInner 的 access$300() 方法,這個方法是自動生成的,
// 實際執行的就是 staticIn() 方法
invoke-static {v0}, LOuter$StaticInner;->access$300(LOuter$StaticInner;)V
.line 25
new-instance v0, Ljava/lang/Thread; // 建立 Thread 對象
new-instance v1, LOuter$1; // 建立 Out$1 對象
invoke-direct {v1}, LOuter$1;-><init>()V // 執行 Out$1 對象的構造函數
invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V // 執行 Thread 對象的構造函數
.line 30
invoke-virtual {v0}, Ljava/lang/Thread;->start()V // 執行 Thread.start() 方法
.line 31
return-void
.end method
複製代碼
看完外部類 Outer
的 smali 代碼,咱們發現每一個內部類初始化時執行的都是有參構造,可是咱們並無顯示的聲明任何有參構造。咱們從內部類的 smali 代碼中找找答案。
Out$Inner.smali
:
.class LOuter$Inner;
.super Ljava/lang/Object;
.source "Outer.java"
# annotations
# EnclosingClass 註解,系統自動生成,value 值表明其做用範圍
.annotation system Ldalvik/annotation/EnclosingClass;
value = LOuter;
.end annotation
# InnerClass 註解,系統自動生成,表示內部類
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x2 // private
name = "Inner"
.end annotation
# instance fields
# synthetic 表示 this$0 是「合成」 的,並不是源碼中就有的
.field final synthetic this$0:LOuter;
# direct methods
# 有參構造
.method private constructor <init>(LOuter;)V
.registers 2
.prologue
.line 3
iput-object p1, p0, LOuter$Inner;->this$0:LOuter;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# 有參構造
.method synthetic constructor <init>(LOuter;LOuter$1;)V
.registers 3
.prologue
.line 3
invoke-direct {p0, p1}, LOuter$Inner;-><init>(LOuter;)V
return-void
.end method
# 編譯器生成的 access$100() 方法
.method static synthetic access$100(LOuter$Inner;)V
.registers 1
.prologue
.line 3
invoke-direct {p0}, LOuter$Inner;->in()V // 調用 in() 方法
return-void
.end method
// in() 方法
.method private in()V
.registers 3
.prologue
.line 5
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "I am inner class."
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 6
return-void
.end method
複製代碼
重點看一下成員內部類的有參構造函數:
.method private constructor <init>(LOuter;)V
.registers 2
.prologue
.line 3
iput-object p1, p0, LOuter$Inner;->this$0:LOuter;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
複製代碼
有參構造函數的參數是 LOuter;
,p0
存儲的是當前類的引用,p1
存儲的是構造函數的參數,即外部類的引用。這個構造函數一共執行了兩步:
this$0
init()
方法由此能夠,成員內部類是持有外部類的引用的,這也就解釋了爲何成員內部類能夠調用外部類的屬性和方法。
再回頭看一下 Outer.smali
中成員內部類 Outer$Inner
的初始化過程:
const/4 v2, 0x0
invoke-direct {v1, v0, v2}, LOuter$Inner;-><init>(LOuter;LOuter$1;)V
複製代碼
發現調用的並非上面那個構造函數,而是一個兩個參數的構造函數:
.method synthetic constructor <init>(LOuter;LOuter$1;)V
.registers 3
.prologue
.line 3
invoke-direct {p0, p1}, LOuter$Inner;-><init>(LOuter;)V
return-void
.end method
複製代碼
這個構造函數中仍是調用了 init(LOuter;)
函數,而且也沒有使用 LOuter$1
參數。這個 LOuter$1
參數是什麼呢?看到最後就知道,這是匿名內部類的引用。可是在實際調用中傳入的是 0x0
, 關於傳遞這個參數的意義,不知道有沒有讀者知道,能夠討論一下。
緊接着編譯器爲成員內部類自動生成了一個靜態方法:
.method static synthetic access$100(LOuter$Inner;)V
.registers 1
.prologue
.line 3
invoke-direct {p0}, LOuter$Inner;->in()V // 調用 in() 方法
return-void
.end method
複製代碼
方法參數是 p0
存儲的當前類的引用,而後經過 p0
直接調用 in()
方法。一樣,在外部類中調用成員內部類的 in()
方法,是經過 invoke-static
調用這個靜態方法來執行的,以下所示:
# invokes: LOuter$Inner;->in()V
invoke-static {v1}, LOuter$Inner;->access$100(LOuter$Inner;)V
複製代碼
Outer$StaticInner.smali
:
.class LOuter$StaticInner;
.super Ljava/lang/Object;
.source "Outer.java"
# annotations
# EnclosingClass 註解,系統自動生成,value 值表明其做用範圍
.annotation system Ldalvik/annotation/EnclosingClass;
value = LOuter;
.end annotation
# InnerClass 註解,系統自動生成,表示內部類
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0xa # private static
name = "StaticInner"
.end annotation
# direct methods
# 無參構造
.method private constructor <init>()V
.registers 1
.prologue
.line 9
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# 有參構造
.method synthetic constructor <init>(LOuter$1;)V
.registers 2
.prologue
.line 9
invoke-direct {p0}, LOuter$StaticInner;-><init>()V
return-void
.end method
# 編譯器生成的 static 方法
.method static synthetic access$300(LOuter$StaticInner;)V
.registers 1
.prologue
.line 9
invoke-direct {p0}, LOuter$StaticInner;->staticIn()V
return-void
.end method
// staticIn() 方法
.method private staticIn()V
.registers 3
.prologue
.line 11
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "I am static inner class."
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 12
return-void
.end method
複製代碼
結構與成員內部類基本類似,有兩點區別。
第一, accessFlags
的不一樣。在系統生成的 InnerClass
註解中有 accessFlags
值。成員內部類 Outer$Inner
值爲 0x2
,表示 private
,靜態內部類 Outer$StaticInner
值爲 0xa
,表示 private static
。關於 accessFlags
的表示方式,在個人另外一篇文章 Class 文件格式詳解 中有具體介紹。
第二,靜態內部類不持有外部類的引用。Outer$StaticInner.smali
中並無定義 this$0
字段,有參構造中的參數也不包含外部類引用。因此,這也驗證了靜態內部類只能調用外部類的靜態屬性和靜態方法。
外部類對靜態內部類方法的調用也是自動生成了一個靜態方法,再經過這個靜態方法來調用,以下所示:
# invokes: LOuter$StaticInner;->staticIn()V
invoke-static {v0}, LOuter$StaticInner;->access$300(LOuter$StaticInner;)V
複製代碼
Outer$1.smali
:
.class final LOuter$1; // 匿名內部類是 final 的
.super Ljava/lang/Object;
.source "Outer.java"
# interfaces
.implements Ljava/lang/Runnable; // 實現了 Runnable 接口
# annotations
# 做用域在 main() 方法中
.annotation system Ldalvik/annotation/EnclosingMethod;
value = LOuter;->main([Ljava/lang/String;)V
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x8
name = null
.end annotation
# direct methods
# 無參構造
.method constructor <init>()V
.registers 1
.prologue
.line 25
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
# run() 方法
.method public run()V
.registers 3
.prologue
.line 28
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-static {}, Ljava/lang/Thread;->currentThread()Ljava/lang/Thread;
move-result-object v1
invoke-virtual {v1}, Ljava/lang/Thread;->getName()Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 29
return-void
.end method
複製代碼
看起來並無什麼特別的地方,run()
方法中執行的 System.out.println(Thread.currentThread().getName());
也很容易看懂。只有一個無參構造。對,只有一個無參構造,若是你的 Java 基礎學的還能夠的話,應該記得匿名內部類會持有外部類的引用,能夠這裏爲何只有一個無參構造呢?別忘了,這裏是 main()
方法,是 static
方法。靜態方法中只能引用類中的靜態屬性。若是換成一個普通方法,生成的 smali 代碼中確定會有 this$0
字段和有參構造。在 Outer.java
中添加以下代碼:
public void test(){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
複製代碼
生成的 smali 代碼中多了一個文件 Outer$2.smali
:
.class LOuter$2;
.super Ljava/lang/Object;
.source "Outer.java"
# interfaces
.implements Ljava/lang/Runnable;
# annotations
.annotation system Ldalvik/annotation/EnclosingMethod;
value = LOuter;->test()V
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0x0
name = null
.end annotation
# instance fields
.field final synthetic this$0:LOuter; // 外部類引用
# direct methods
# 有參構造
.method constructor <init>(LOuter;)V
.registers 2
.prologue
.line 34
iput-object p1, p0, LOuter$2;->this$0:LOuter;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public run()V
.registers 3
.prologue
.line 37
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-static {}, Ljava/lang/Thread;->currentThread()Ljava/lang/Thread;
move-result-object v1
invoke-virtual {v1}, Ljava/lang/Thread;->getName()Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 38
return-void
.end method
複製代碼
注意看兩處添加註釋的地方,這就驗證了匿名內部類持有外部類的引用的說法。
關於 Smali 的學習就到這裏了,在這個過程當中,也驗證了一些咱們曾經熟知的知識點,加深了咱們對 Java 的理解。固然在 Android 逆向過程當中,咱們碰到的要比這裏說的複雜的多,這就須要咱們積累足夠的經驗了。下一篇,正式進入 Android 領域了,介紹一些經常使用的 Android 逆向工具。
文章同步更新於微信公衆號:
秉心說
, 專一 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!