奇門遁甲之字節(jié)碼與JVM指令
奇門遁甲之ASM操縱字節(jié)碼
奇門遁甲之Transform API
最近在研究ASM 字節(jié)碼增強(qiáng)技術(shù),要掌握ASM 必須要先連接Java字節(jié)碼結(jié)構(gòu)茎毁、JVM棧幀和常用JVM指令退疫。
本章就Java字節(jié)碼和JVM棧幀和JVM指令做一些梳理和整理加以沉淀,方便將來(lái)復(fù)習(xí)查閱万伤。
一肪笋、ByteCode字節(jié)碼
1.1月劈、字節(jié)碼碼結(jié)構(gòu)
Java代碼最終都會(huì)被編譯成class文件(字節(jié)碼),這些字節(jié)碼可以被JVM正確識(shí)別和解析。
JVM 對(duì)java字節(jié)碼有嚴(yán)格的規(guī)定,如下圖所示:
- Magic: 該項(xiàng)存放了一個(gè) Java 類文件的魔數(shù)(magic number)和版本信息藤乙。一個(gè) Java 類文件的前 4 個(gè)字節(jié)被稱為它的魔數(shù)猜揪。每個(gè)正確的 Java 類文件都是以 0xCAFEBABE 開頭的,這樣保證了 Java 虛擬機(jī)能很輕松的分辨出 Java 文件和非 Java 文件坛梁。
- Version: 該項(xiàng)存放了 Java 類文件的版本信息而姐,它對(duì)于一個(gè) Java 文件具有重要的意義。因?yàn)?Java 技術(shù)一直在發(fā)展划咐,所以類文件的格式也處在不斷變化之中拴念。類文件的版本信息讓虛擬機(jī)知道如何去讀取并處理該類文件。
- Constant Pool: 該項(xiàng)存放了類中各種文字字符串褐缠、類名政鼠、方法名和接口名稱、final 變量以及對(duì)外部類的引用信息等常量队魏。虛擬機(jī)必須為每一個(gè)被裝載的類維護(hù)一個(gè)常量池公般,常量池中存儲(chǔ)了相應(yīng)類型所用到的所有類型、字段和方法的符號(hào)引用器躏,因此它在 Java 的動(dòng)態(tài)鏈接中起到了核心的作用俐载。常量池的大小平均占到了整個(gè)類大小的 60% 左右。
- Access_flag: 該項(xiàng)指明了該文件中定義的是類還是接口(一個(gè) class 文件中只能有一個(gè)類或接口)登失,同時(shí)還指名了類或接口的訪問(wèn)標(biāo)志遏佣,如 public,private, abstract 等信息揽浙。
- This Class: 指向表示該類全限定名稱的字符串常量的指針状婶。
- Super Class: 指向表示父類全限定名稱的字符串常量的指針。
- Interfaces: 一個(gè)指針數(shù)組馅巷,存放了該類或父類實(shí)現(xiàn)的所有接口名稱的字符串常量的指針膛虫。
- Fields: 該項(xiàng)對(duì)類或接口中聲明的字段進(jìn)行了細(xì)致的描述。需要注意的是钓猬,fields 列表中僅列出了本類或接口中的字段稍刀,并不包括從超類和父接口繼承而來(lái)的字段。
- Fields: 該項(xiàng)對(duì)類或接口中聲明的字段進(jìn)行了細(xì)致的描述。需要注意的是账月,fields 列表中僅列出了本類或接口中的字段综膀,并不包括從超類和父接口繼承而來(lái)的字段。
- Class attributes: 該項(xiàng)存放了在該文件中類或接口所定義的屬性的基本信息局齿。
1.2剧劝、舉個(gè)栗子
以TestCost 為例
package com.sogou.iot.trplugin;
/**
* 文件名:TestCost
* 創(chuàng)建者:baixuefei
* 創(chuàng)建日期:2021/1/21 9:52 AM
* 職責(zé)描述:
*/
class TestCost {
public int add(int a,int b) {
return a+b;
}
}
生成字節(jié)碼
javac -g TestCost.java
-g 參數(shù)表示生成所有調(diào)試信息
生成TestCoast.class文件
查看字節(jié)碼的信息
javap -verbose TestCost.class
-verbose 表示輸出輸出附加信息
TestCost.class 解析后的信息
Classfile /Users/feifei/Desktop/TM/Demo/TrPlugin/app/src/main/java/com/sogou/iot/trplugin/TestCost.class
Last modified 2021-1-24; size 401 bytes
MD5 checksum 684202ea0bfc50a7d404275086566aaf
Compiled from "TestCost.java"
class com.sogou.iot.trplugin.TestCost
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #3.#18 // java/lang/Object."<init>":()V
#2 = Class #19 // com/sogou/iot/trplugin/TestCost
#3 = Class #20 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/sogou/iot/trplugin/TestCost;
#11 = Utf8 add
#12 = Utf8 (II)I
#13 = Utf8 a
#14 = Utf8 I
#15 = Utf8 b
#16 = Utf8 SourceFile
#17 = Utf8 TestCost.java
#18 = NameAndType #4:#5 // "<init>":()V
#19 = Utf8 com/sogou/iot/trplugin/TestCost
#20 = Utf8 java/lang/Object
{
com.sogou.iot.trplugin.TestCost();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sogou/iot/trplugin/TestCost;
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 16: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lcom/sogou/iot/trplugin/TestCost;
0 4 1 a I
0 4 2 b I
}
SourceFile: "TestCost.java"
TestCost.class文件如何解讀呢?這還需要先了解JVM的棧幀結(jié)構(gòu)
1.3、JVM棧幀
JVM中每調(diào)用一個(gè)方法 就會(huì)生成一個(gè)棧幀抓歼。棧幀中包含的內(nèi)容包括:局部變量表讥此、操作棧、動(dòng)態(tài)鏈接谣妻、和方法返回地址四部分萄喳。
棧幀是用來(lái)存儲(chǔ)數(shù)據(jù)和部分過(guò)程結(jié)果的數(shù)據(jù)結(jié)構(gòu),同時(shí)也用來(lái)處理動(dòng)態(tài)連接蹋半、方法返回值和異常分派取胎。
棧幀隨著方法調(diào)用而創(chuàng)建,隨著方法結(jié)束而銷毀——無(wú)論方法正常完成還是異常完成都算作方法結(jié)束湃窍。
棧幀的存儲(chǔ)空間由創(chuàng)建它的線程分配在Java虛擬機(jī)棧之中,每一個(gè)棧幀都有自己的本地變量表(局部變量表)匪傍、操作數(shù)棧和指向當(dāng)前方法所屬的類的運(yùn)行時(shí)常量池的引用您市。
1.3.1、局部變量表
局部變量表(Local Variable Table)是一組變量值存儲(chǔ)空間役衡,用于存放方法參數(shù)和方法內(nèi)定義的局部變量茵休。局部變量表的容量以變量槽(Variable Slot)為最小單位,Java虛擬機(jī)規(guī)范并沒(méi)有定義一個(gè)槽所應(yīng)該占用內(nèi)存空間的大小手蝎,但是規(guī)定了一個(gè)槽應(yīng)該可以存放一個(gè)32位以內(nèi)的數(shù)據(jù)類型榕莺。
虛擬機(jī)通過(guò)索引定位的方法查找相應(yīng)的局部變量,索引的范圍是從0~局部變量表最大容量棵介。
在Java程序編譯為Class文件時(shí),就在方法的Code屬性中的max_locals數(shù)據(jù)項(xiàng)中確定了該方法所需分配的局部變量表的最大容量钉鸯。
public int add(int a,int b) {
return a+b;
}
以add方法為例,其局部變量有幾個(gè)?a、b分別算一個(gè),類的實(shí)例方法通常還有有一個(gè)隱藏的參數(shù)就是this邮辽。所以add方法有三個(gè)參數(shù)唠雕,分別是this,a,b。
所以add方法堆棧對(duì)應(yīng)的局部變量表大小為3,如LocalVariableTable所示:
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 16: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lcom/sogou/iot/trplugin/TestCost;
0 4 1 a I
0 4 2 b I
1.3.2吨述、操作數(shù)棧
操作數(shù)棧(Operand Stack)也常稱為操作棧岩睁,它是一個(gè)后入先出棧(LIFO)。同局部變量表一樣揣云,操作數(shù)棧的最大深度也在編譯的時(shí)候?qū)懭氲椒椒ǖ腃ode屬性的max_stacks數(shù)據(jù)項(xiàng)中捕儒。
當(dāng)一個(gè)方法剛剛開始執(zhí)行時(shí),其操作數(shù)棧是空的邓夕,隨著方法執(zhí)行和字節(jié)碼指令的執(zhí)行刘莹,會(huì)從局部變量表或?qū)ο髮?shí)例的字段中復(fù)制常量或變量寫入到操作數(shù)棧阎毅,再隨著計(jì)算的進(jìn)行將棧中元素出棧到局部變量表或者返回給方法調(diào)用者,也就是出棧/入棧操作栋猖。一個(gè)完整的方法執(zhí)行期間往往包含多個(gè)這樣出棧/入棧的過(guò)程净薛。
JVM指令
- load 命令:用于將局部變量表的指定位置的相應(yīng)類型變量加載到操作數(shù)棧頂;
- store命令:用于將操作數(shù)棧頂?shù)南鄳?yīng)類型數(shù)據(jù)保入局部變量表的指定位置蒲拉;
- invokevirtual:調(diào)用實(shí)例方法
- ireturn: 當(dāng)前方法返回int
a = b + c 的字節(jié)碼執(zhí)行過(guò)程中操作數(shù)棧以及局部變量表的變化如下圖所示
1.3.3肃拜、動(dòng)態(tài)連接
在一個(gè)class文件中,一個(gè)方法要調(diào)用其他方法雌团,需要將這些方法的符號(hào)引用轉(zhuǎn)化為其在內(nèi)存地址中的直接引用燃领,而符號(hào)引用存在于方法區(qū)中的運(yùn)行時(shí)常量池.
Java虛擬機(jī)棧中,每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧所屬方法的符號(hào)引用锦援,持有這個(gè)引用的目的是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接(Dynamic Linking)猛蔽。
這些符號(hào)引用一部分會(huì)在類加載階段或者第一次使用時(shí)就直接轉(zhuǎn)化為直接引用,這類轉(zhuǎn)化稱為靜態(tài)解析灵寺。另一部分將在每次運(yùn)行期間轉(zhuǎn)化為直接引用曼库,這類轉(zhuǎn)化稱為動(dòng)態(tài)連接。
1.3.4略板、方法返回
當(dāng)一個(gè)方法開始執(zhí)行時(shí)毁枯,可能有兩種方式退出該方法:正常完成出口和異常完成出口。
- 正常完成出口是指方法正常完成并退出叮称,沒(méi)有拋出任何異常(包括Java虛擬機(jī)異常以及執(zhí)行時(shí)通過(guò)throw語(yǔ)句顯示拋出的異常)种玛。
- 異常完成出口 是指方法執(zhí)行過(guò)程中遇到異常,并且這個(gè)異常在方法體內(nèi)部沒(méi)有得到處理瓤檐,導(dǎo)致方法退出赂韵。
無(wú)論是Java虛擬機(jī)拋出的異常還是代碼中使用athrow指令產(chǎn)生的異常,只要在本方法的異常表中沒(méi)有搜索到相應(yīng)的異常處理器挠蛉,就會(huì)導(dǎo)致方法退出祭示。
方法退出過(guò)程實(shí)際上就等同于把當(dāng)前棧幀出棧,因此退出可以執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧碌秸,把返回值(如果有的話)壓如調(diào)用者的操作數(shù)棧中绍移,調(diào)整PC計(jì)數(shù)器的值以指向方法調(diào)用指令后的下一條指令。
二讥电、常用的JVM指令
2.1蹂窖、棧操作相關(guān)
load和store
- load 命令:用于將局部變量表的指定位置的相應(yīng)類型變量加載到棧頂;
- store命令:用于將棧頂?shù)南鄳?yīng)類型數(shù)據(jù)保入局部變量表的指定位置恩敌;
變量進(jìn)棧 | 含義 | 變量保存 | 含義 | |
---|---|---|---|---|
iload | 第1個(gè)int型變量進(jìn)棧 | istore | 棧頂int數(shù)值存入第1局部變量 | |
iload_0 | 第1個(gè)int型變量進(jìn)棧 | istore_0 | 棧頂int數(shù)值存入第1局部變量 | |
iload_1 | 第2個(gè)int型變量進(jìn)棧 | istore_1 | 棧頂int數(shù)值存入第2局部變量 | |
iload_2 | 第3個(gè)int型變量進(jìn)棧 | istore_2 | 棧頂int數(shù)值存入第3局部變量 | |
iload_3 | 第4個(gè)int型變量進(jìn)棧 | istore_3 | 棧頂int數(shù)值存入第4局部變量 | |
lload | 第1個(gè)long型變量進(jìn)棧 | lstore | 棧頂long數(shù)值存入第1局部變量 | |
fload | 第1個(gè)float型變量進(jìn)棧 | fstore | 棧頂float數(shù)值存入第1局部變量 | |
dload | 第1個(gè)double型變量進(jìn)棧 | dstore | 棧頂double數(shù)值存入第1局部變量 | |
aload | 第1個(gè)ref型變量進(jìn)棧 | astore | 棧頂ref對(duì)象存入第1局部變量 |
const瞬测、push和ldc
- const、push:將相應(yīng)類型的常量放入棧頂
- ldc:則是從常量池中將常量壓入操作數(shù)棧棧頂
變量進(jìn)棧 | 含義 | 變量保存 | 含義 |
---|---|---|---|
iload | 第1個(gè)int型變量進(jìn)棧 | istore | 棧頂int數(shù)值存入第1局部變量 |
iload_0 | 第1個(gè)int型變量進(jìn)棧 | istore_0 | 棧頂int數(shù)值存入第1局部變量 |
iload_1 | 第2個(gè)int型變量進(jìn)棧 | istore_1 | 棧頂int數(shù)值存入第2局部變量 |
iload_2 | 第3個(gè)int型變量進(jìn)棧 | istore_2 | 棧頂int數(shù)值存入第3局部變量 |
iload_3 | 第4個(gè)int型變量進(jìn)棧 | istore_3 | 棧頂int數(shù)值存入第4局部變量 |
lload | 第1個(gè)long型變量進(jìn)棧 | lstore | 棧頂long數(shù)值存入第1局部變量 |
fload | 第1個(gè)float型變量進(jìn)棧 | fstore | 棧頂float數(shù)值存入第1局部變量 |
dload | 第1個(gè)double型變量進(jìn)棧 | dstore | 棧頂double數(shù)值存入第1局部變量 |
aload | 第1個(gè)ref型變量進(jìn)棧 | astore | 棧頂ref對(duì)象存入第1局部變量 |
常量池操作 | 含義 |
---|---|
ldc | int、float或String型常量從常量池推送至棧頂 |
ldc_w | int穷躁、float或String型常量從常量池推送至棧頂(寬索引) |
ldc2_w | long或double型常量從常量池推送至棧頂(寬索引) |
pop和dup
- pop用于棧頂數(shù)值出棧操作问潭;
- dup用于復(fù)制棧頂?shù)闹付▊€(gè)數(shù)的數(shù)值婚被,并將其壓入棧頂指定次數(shù)
棧頂操作 | 含義 |
---|---|
pop | 棧頂數(shù)值出棧(不能是long/double) |
pop2 | 棧頂數(shù)值出棧(long/double型1個(gè)址芯,其他2個(gè)) |
dup | 復(fù)制棧頂數(shù)值谷炸,并壓入棧頂 |
dup_x1 | 復(fù)制棧頂數(shù)值旬陡,并壓入棧頂2次 |
dup_x2 | 復(fù)制棧頂數(shù)值季惩,并壓入棧頂3次 |
dup2 | 復(fù)制棧頂2個(gè)數(shù)值画拾,并壓入棧頂 |
dup2_x1 | 復(fù)制棧頂2個(gè)數(shù)值青抛,并壓入棧頂2次 |
dup2_x2 | 復(fù)制棧頂2個(gè)數(shù)值蜜另,并壓入棧頂3次 |
swap | 棧頂?shù)膬蓚€(gè)數(shù)值互換举瑰,且不能是long/double |
dup2對(duì)于long、double類型的數(shù)據(jù)就是一個(gè)蔬螟,對(duì)于其他類型的數(shù)據(jù)此迅,才是真正的兩個(gè),這個(gè)的2代表的是2個(gè)slot的數(shù)據(jù)。
2.2耸序、對(duì)象相關(guān)
字段調(diào)用
字段調(diào)用 | 含義 |
---|---|
getstatic | 獲取類的靜態(tài)字段,將其值壓入棧頂 |
putstatic | 給類的靜態(tài)字段賦值 |
getfield | 獲取對(duì)象的字段坎怪,將其值壓入棧頂 |
putfield | 給對(duì)象的字段賦值 |
方法調(diào)用
方法調(diào)用 | 作用 | 解釋 |
---|---|---|
invokevirtual | 調(diào)用實(shí)例方法 | 虛方法分派 |
invokestatic | 調(diào)用類方法 | static方法 |
invokeinterface | 調(diào)用接口方法 | 運(yùn)行時(shí)搜索合適方法調(diào)用 |
invokespecial | 調(diào)用特殊實(shí)例方法 | 包括實(shí)例初始化方法搅窿、父類方法 |
invokedynamic | 由用戶引導(dǎo)方法決定 | 運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用方法 |
方法返回
方法返回 | 含義 | 解釋 |
---|---|---|
ireturn | 當(dāng)前方法返回int | 虛方法分派 |
lreturn | 當(dāng)前方法返回long | static方法 |
freturn | 當(dāng)前方法返回float | 運(yùn)行時(shí)搜索合適方法調(diào)用 |
dreturn | 當(dāng)前方法返回double | 包括實(shí)例初始化方法、父類方法 |
areturn | 當(dāng)前方法返回ref | 運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用方法 |
對(duì)象和數(shù)組
- 創(chuàng)建類實(shí)例: new
- 創(chuàng)建數(shù)組:newarray戈钢、anewarray痹仙、multianewarray
- 數(shù)組元素 加載到 操作數(shù)棧:xaload (x可為b,c,s,i,l,f,d,a)
- 操作數(shù)棧的值 存儲(chǔ)到數(shù)組元素: xastore (x可為b,c,s,i,l,f,d,a)
- 數(shù)組長(zhǎng)度:arraylength
- 類實(shí)例類型:instanceof、checkcast
2.3殉了、運(yùn)算指令
運(yùn)算指令是用于對(duì)操作數(shù)棧上的兩個(gè)數(shù)值進(jìn)行某種運(yùn)算开仰,并把結(jié)果重新存入到操作棧頂
運(yùn)算 | int | long | float | double |
---|---|---|---|---|
加法 | iadd | ladd | fadd | dadd |
減法 | isub | lsub | fsub | dsub |
乘法 | imul | lmul | fmul | dmul |
除法 | idiv | ldiv | fdiv | ddiv |
求余 | irem | lrem | frem | drem |
取反 | ineg | lneg | fneg | dneg |
其他運(yùn)算:
- 位移:ishl,ishr,iushr,lshl,lshr,lushr
- 按位或: ior,lor
- 按位與: iand, land
- 按位異或: ixor, lxor
- 自增:iin
- 比較:dcmpg,dcmpl,fcmpg,fcmpl,lcmp
2.4、類型轉(zhuǎn)換
類型轉(zhuǎn)換用于將兩種不同類型的數(shù)值進(jìn)行轉(zhuǎn)換薪铜。
類型轉(zhuǎn)換指令:i2b, i2c,f2i等等众弓。
2.5、流程控制
控制指令是指有條件或無(wú)條件地修改PC寄存器的值隔箍,從而達(dá)到控制流程的目標(biāo)
- 條件分支:ifeq谓娃、iflt、ifnull蜒滩、ifnonnull等
- 復(fù)合分支:tableswitch滨达、lookupswitch
- 無(wú)條件分支:goto、goto_w俯艰、jsr捡遍、jsr_w、ret
2.6竹握、同步與異常
- Java程序顯式拋出異常: athrow指令画株。
- Java虛擬機(jī)的指令集中通過(guò)monitorenter和monitorexit兩條指令來(lái)完成synchronized的功能
三庸推、參考文章
http://gityuan.com/2015/10/24/jvm-bytecode-grammar/
https://zhuanlan.zhihu.com/p/45354152
https://zhuanlan.zhihu.com/p/94498015?utm_source=wechat_timeline