從精準化測試看ASM在Android中的強勢插入-字節碼

字節碼是ASM的基礎,要想熟練的使用ASM,那麼瞭解字節碼就是必備基礎。java

Class的文件格式

Class文件做爲Java虛擬機所執行的直接文件,內部結構設計有着固定的協議,每個Class文件只對應一個類或接口的定義信息。數組

每一個Class文件都以8位爲單位的字節流組成,下面是一個Class文件中所包括的內容,在Class文件中,各項內容按照嚴格順序連續存放,Java虛擬機只要按照協議順序來讀取便可。markdown

ClassFile { 
    u4 magic; 
    u2 minor_version; 
    u2 major_version; 
    u2 constant_pool_count; 
    cp_info constant_pool[constant_pool_count-1]; 
    u2 access_flags; 
    u2 this_class; 
    u2 super_class; 
    u2 interfaces_count; 
    u2 interfaces[interfaces_count]; 
    u2 fields_count; 
    field_info fields[fields_count]; 
    u2 methods_count; 
    method_info methods[methods_count]; 
    u2 attributes_count; 
    attribute_info attributes[attributes_count]; 
}
複製代碼

在Class文件結構中,上面各項的含義以下。網絡

Name 含義
magic 做爲一個魔數,肯定這個文件是不是一個能被虛擬機接受的class文件,值固定爲0xCAFEBABE。
minor_version,major_version 分別表示class文件的副,主版本號,不一樣版本的虛擬機實現支持的Class文件版本號不一樣。
constant_pool_count 常量池計數器,constant_pool_count的值等於常量池表中的成員數加1。
constant_pool 常量池,constant_pool是一種表結構,包含class文件結構及其子結構中引用的全部字符常量、類或接口名、字段名和其餘常量。
access_flags access_flags是一種訪問標誌,表示這個類或者接口的訪問權限及屬性,包括有ACC_PUBLIC,ACC_FINAL,ACC_SUPER等等。
this_class 類索引,指向常量池表中項的一個索引。
super_class 父類索引,這個值必須爲0或者是對常量池中項的一個有效索引值,若是爲0,表示這個class只能是Object類,只有它是惟一沒有父類的類。
interfaces_count 接口計算器,表示當前類或者接口的直接父接口數量。
interfaces[] 接口表,裏面的每一個成員的值必須是一個對常量池表中項的一個有效索引值。
fields_count 字段計算器,表示當前class文件中fields表的成員個數,每一個成員都是一個field_info。
fields 字段表,每一個成員都是一個完整的fields_info結構,表示當前類或接口中某個字段的完整描述,不包括父類或父接口的部分。
methods_count 方法計數器,表示當前class文件methos表的成員個數。
methods 方法表,每一個成員都是一個完整的method_info結構,能夠表示類或接口中定義的全部方法,包括實例方法,類方法,以及類或接口初始化方法。
attributes_count 屬性表,其中是每個attribute_info,包含如下這些屬性,InnerClasses,EnclosingMethod,Synthetic,Signature,Annonation等。

以上內容來自網絡,我也不知道從哪copy來的。函數

字節碼和Java代碼仍是有很大區別的。oop

  • 一個字節碼文件只能描述一個類,而一個Java文件中能夠則包含多個類。當一個Java文件是描述一個包含內部類的類,那麼該Java文件則會被編譯爲兩個類文件,文件名上經過「$」來區分,主類文件中包含對其內部類的引用,定義了內部方法的內部類會包含外部引用
  • 字節碼文件中不包含註釋,只有有效的可執行代碼,例如類、字段、方法和屬性
  • 字節碼文件中不包含package和import部分, 全部類型名字都必須是徹底限定的
  • 字節碼文件還包含常量池(constant pool),這些內容是編譯時生成的,常量池本質上就是一個數組存儲了類中出現的全部數值、字符串和類型常量,這些常量僅須要在這個常量池部分中定義一次,就能夠利用其索引,在類文件中的全部其餘各部分進行引用

字節碼的執行過程

字節碼在Java虛擬機中是以堆棧的方式進行運算的,相似CPU中的寄存器,在Java虛擬機中,它使用堆棧來完成運算,例如實現「a+b」的加法操做,在Java虛擬機中,首先會將「a」push到堆棧中,而後再將「b」push到堆棧中,最後執行「ADD」指令,取出用於計算的兩個變量,完成計算後,將返回值「a+b」push到堆棧中,完成指令。網站

類型描述符

咱們在Java代碼中的類型,在字節碼中,有相應的表示協議。this

Java Type Type description
boolean Z
char C
byte B
short S
int I
float F
long J
double D
object Ljava/lang/Object;
int[] [I
Object[][] [[Ljava/lang/Object;
void V
引用類型 L
  • Java基本類型的描述符是單個字符,例如Z表示boolean、C表示char
  • 類的類型的描述符是這個類的全限定名,前面加上字符L , 後面跟上一個「;」,例如String的類型描述符爲Ljava/lang/String;
  • 數組類型的描述符是一個方括號後面跟有該數組元素類型的描述符,多維數組則使用多個方括號

藉助上面的協議分析,想要看到字節碼中參數的類型,就比較簡單了。spa

方法描述符

方法描述符(方法簽名)是一個類型描述符列表,它用一個字符串描述一個方法的參數類型和返回類型。設計

方法描述符以左括號開頭,而後是每一個形參的類型描述符,而後是是右括號,接下來是返回類型的類型描述符,例如,該方法返回void,則是V,要注意的是,方法描述符中不包含方法的名字或參數名。

Java方法聲明 方法描述符 說明
void m(int i, float f) (IF)V 接收一個int和float型參數且無返回值
int m(Object o) (Ljava/lang/Object;)I 接收Object型參數返回int
int[] m(int i, String s) (ILjava/lang/String;)[I 接受int和String返回一個int[]
Object m(int[] i) ([I)Ljava/lang/Object; 接受一個int[]返回Object

字節碼示例

咱們來看下這段簡單的代碼,在字節碼下是怎樣的。

image-20210623103259980

經過ASMPlugin,咱們看下生成的字節碼,以下所示。

image-20210623103419893

能夠發現,這裏主要分紅了兩個部分——init和onCreate。

Java中的每個方法在執行的時候,Java虛擬機都會爲其分配一個「棧幀」,棧幀是用來存儲方法中計算所須要的全部數據的。

其中第0個元素就是「this」,若是方法有參數傳入會排在它的後面。

字節碼中有不少指令,下面對一些比較經常使用的指令進行下講解。

  • ALOAD 0:這個指令是LOAD系列指令中的一個,它的意思表示push當前第0個元素到堆棧中。代碼上至關於使用「this」,A表示這個數據元素的類型是一個引用類型。相似的指令還有:ALOAD,ILOAD,LLOAD,FLOAD,DLOAD,它們的做用就是針對不用數據類型而準備的LOAD指令
  • INVOKESPECIAL:這個指令是調用系列指令中的一個。其目的是調用對象類的方法。後面須要給上父類的方法完整籤
  • INVOKEVIRTUAL:這個指令區別於INVOKESPECIAL的是,它是根據引用調用對象類的方法
  • INVOKESTATIC:調用類的靜態方法

你們不用徹底掌握這些指令,結合代碼來看的話,仍是能看懂的,咱們須要的是修改字節碼,而不是從0開始。

對於Java源文件:若是隻有一個方法,編譯生成時,也會有兩個方法,其中一個是默認構造函數 對於Kotlin源文件:若是隻有一個方法,編譯生成時,會產生四個方法,一個是默認構造函數,還有兩個是kotlin合成的方法,以及退出時清除內存的默認函數

ASM Code

再結合ASM Code來看,仍是上面的例子。

默認的構造函數。

image-20210623105109646

onCreate:

image-20210623105143214

這裏面有些生成的代碼,例如:

Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(9, label0);
methodVisitor.visitLocalVariable("this", "Lcom/yw/asmtest/MainActivity;", null, label0, label4, 0);
複製代碼

這些都是調試代碼和寫入變量表的方法,咱們沒必要關心。

剩下的代碼,就是咱們能夠在ASM中所須要的代碼。

向你們推薦下個人網站 xuyisheng.top/ 專一 Android-Kotlin-Flutter 歡迎你們訪問

相關文章
相關標籤/搜索