奇門遁甲之字節(jié)碼與JVM指令
奇門遁甲之ASM操縱字節(jié)碼
奇門遁甲之Transform API
本文記錄對ASM 字節(jié)碼操控框架的梳理和總結(jié),方便需要時查看幔荒。
一碉怔、什么是ASM
ASM 是一個 Java 字節(jié)碼操控框架未荒。它能被用來動態(tài)生成類或者增強既有類的功能盾碗。ASM 可以直接產(chǎn)生二進制 class 文件甩恼,也可以在類被加載入 Java 虛擬機之前動態(tài)改變類行為弱左。Java class 被存儲在嚴格格式定義的 .class 文件里翎冲,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱华畏、方法君编、屬性以及 Java 字節(jié)碼(指令)跨嘉。ASM 從類文件中讀入信息后,能夠改變類行為吃嘿,分析類信息祠乃,甚至能夠根據(jù)用戶要求生成新類。
ASM設(shè)計了兩種類型兑燥,一種是基于Tree API亮瓷,一種是基于Visitor API(visitor pattern)
- Tree API將class的所有結(jié)構(gòu)信息讀取到內(nèi)存中,構(gòu)建一個樹形結(jié)構(gòu)降瞳,然后需要處理Method嘱支、Field等元素時,到樹形結(jié)構(gòu)中定位到某個元素挣饥,進行操作除师,然后把操作再寫入新的class文件。
- Visitor API則將通過接口的方式扔枫,分離讀class和寫class的邏輯汛聚,一般通過一個ClassReader負責讀取class字節(jié)碼,然后ClassReader通過一個ClassVisitor接口短荐,將字節(jié)碼的每個細節(jié)按順序通過接口的方式倚舀,傳遞給ClassVisitor(你會發(fā)現(xiàn)ClassVisitor中有多個visitXXXX接口),這個過程就像ClassReader帶著ClassVisitor游覽了class字節(jié)碼的每一個指令忍宋。
Visitor Api 相比Tree Api 訪問效率要高很多,本節(jié)重點闡述Tree Api的基本用法痕貌。
二、ASM Visitor Api
ASM 是基于訪問者模式的糠排,基本組成如下:
- Opcodes接口定義了一些常量舵稠,尤其是版本號,訪問標示符,字節(jié)碼操作碼等信息柱查;
- ClassReader 用于讀取Class文件廓俭。
主要用于Class文件的分析,可接受一個ClassVisitor;ClassReader會將解析過程中產(chǎn)生的類的部分信息唉工,比如訪問標識符研乒,字段,方法逐個送入ClassVisitor,后者在接收到對應(yīng)的信息后淋硝,進行各自的處理雹熬。
- ClassVisitor 是一個抽象類,用于訪問類的屬性信息、方法信息以及其他信息谣膳。通常會定義一個ClassVisitor的子類(Adapter類),用于將改變現(xiàn)有類的一些信息(屬性竿报、方法等),將改變后的信息沿著訪問鏈條繼續(xù)傳遞下去,獲得的就是改變后的class。
- ClassWriter 是ClassVisitor的子類,位于Visitor鏈條的末端继谚,用于將類信息轉(zhuǎn)換成ByteArray,或者寫入到一個輸出流中烈菌。
ClassReader、ClassAdapter花履、ClassWriter的訪問鏈條如下所示:
常規(guī)調(diào)用鏈:ClassReader->ClassAdapter->ClassWriter->toByteArray()
//移除原有類中的一個方法
fun scanClassRemoveMethod(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
//構(gòu)造ClassReader對象
var cr = ClassReader(inputStream)
//構(gòu)造ClassWriter對象
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
//構(gòu)造ClassAdapter對象,指定其傳遞鏈的下一級為ClassWriter
var classAdapter =
RemoveMethodClassVisitor(Opcodes.ASM7, cw)
//ClassReader讀取類的信息,指定讀取到的類信息傳遞到ClassAdapter
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
//最終的類轉(zhuǎn)換成ByteArray
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
2.1芽世、事件訪問順序
2.1.1、ClassVisitor訪問順序
ClassVisitor的方法調(diào)用遵循一定的順序,如下
visit
visitSource?
visitOuterClass?
( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd
This means that visit must be called first, followed by at most one call to visitSource, followed by at most one call to visitOuterClass, followed by any number of calls in any order to visitAnnotation and visitAttribute, followed by any number of calls in any order to visitInnerClass, visitField and visitMethod, and terminated by a single call to visitEnd.
2.1.2诡壁、MethodVisitor訪問順序
visitMethod()
visitCode()
....
visitInsn(RETURN)
visitMax()
visitEnd()
visitCode() 為訪問Method的開始 visitMax() 為訪問Method的結(jié)束 visitEnd() 為MethodVisitor最后調(diào)用的方法
2.2济瓢、如何生成一個全新的Class
以自定義Comparable類為例,我們看怎么通過ASM 來從0開始,生成一個Comparable類:
package com.sogou.iot.testasm;
/**
* 文件名:Comparable
* 創(chuàng)建者:baixuefei
*/
public class Comparable {
int LESS = -1;
int compareTo(Object o){
return -1;
}
}
ClassWriter類 按照一定的順序 調(diào)用接口,就可以構(gòu)建一個完整的類出來。關(guān)鍵點是需要調(diào)用哪些接口,生成哪些指令妹卿。
ASM 是基于class字節(jié)碼指令的,只要將Comparable類的字節(jié)碼指令 按照ASM的語法翻譯一遍就可能通過ASM生成完整的類信息旺矾。
通過javap 來查看Comparable.java 的字節(jié)碼文件
javac -g Comparable.java
javap -v Comparable.class
Classfile /Users/feifei/Desktop/TM/Demo/TrPlugin/asm_demo/src/main/java/com/sogou/iot/asm_demo/Comparable.class
Last modified 2021-1-24; size 468 bytes
MD5 checksum 9fb6157669e4dd90332cb4fd79c0b614
Compiled from "Comparable.java"
class com.sogou.iot.asm_demo.Comparable
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // com/sogou/iot/asm_demo/Comparable.LESS:I
#3 = Class #22 // com/sogou/iot/asm_demo/Comparable
#4 = Class #23 // java/lang/Object
#5 = Utf8 LESS
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/sogou/iot/asm_demo/Comparable;
#14 = Utf8 compareTo
#15 = Utf8 (Ljava/lang/Object;)I
#16 = Utf8 o
#17 = Utf8 Ljava/lang/Object;
#18 = Utf8 SourceFile
#19 = Utf8 Comparable.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // LESS:I
#22 = Utf8 com/sogou/iot/asm_demo/Comparable
#23 = Utf8 java/lang/Object
{
int LESS;
descriptor: I
flags:
com.sogou.iot.asm_demo.Comparable();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_m1
6: putfield #2 // Field LESS:I
9: return
LineNumberTable:
line 11: 0
line 13: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/sogou/iot/asm_demo/Comparable;
int compareTo(java.lang.Object);
descriptor: (Ljava/lang/Object;)I
flags:
Code:
stack=1, locals=2, args_size=2
0: iconst_m1
1: ireturn
LineNumberTable:
line 15: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/sogou/iot/asm_demo/Comparable;
0 2 1 o Ljava/lang/Object;
}
SourceFile: "Comparable.java"
翻譯后的ASM 代碼如下(kotlin語言編寫)
//生成一個新的類
fun generateClass(out: File) {
var outputStream = FileOutputStream(out)
val cw = ClassWriter(0)
var fv: FieldVisitor
var mv: MethodVisitor
var av0: AnnotationVisitor
cw.visit(
V1_7,
ACC_PUBLIC + ACC_SUPER,
"com/sogou/iot/asm_demo/Comparable",
null,
"java/lang/Object",
null
)
cw.visitSource("Comparable.java", null)
kotlin.run {
fv = cw.visitField(0, "LESS", "I", null, null)
fv.visitEnd()
}
kotlin.run {
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
mv.visitCode()
val l0 = Label()
mv.visitLabel(l0)
mv.visitLineNumber(11, l0)
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
val l1 = Label()
mv.visitLabel(l1)
mv.visitLineNumber(12, l1)
mv.visitVarInsn(ALOAD, 0)
mv.visitInsn(ICONST_M1)
mv.visitFieldInsn(PUTFIELD, "com/sogou/iot/asm_demo/Comparable", "LESS", "I")
mv.visitInsn(RETURN)
val l2 = Label()
mv.visitLabel(l2)
mv.visitLocalVariable("this", "Lcom/sogou/iot/asm_demo/Comparable;", null, l0, l2, 0)
mv.visitMaxs(2, 1)
mv.visitEnd()
}
kotlin.run {
mv = cw.visitMethod(0, "compareTo", "(Ljava/lang/Object;)I", null, null)
mv.visitCode()
val l0 = Label()
mv.visitLabel(l0)
mv.visitLineNumber(14, l0)
mv.visitInsn(ICONST_M1)
mv.visitInsn(IRETURN)
val l1 = Label()
mv.visitLabel(l1)
mv.visitLocalVariable("this", "Lcom/sogou/iot/asm_demo/Comparable;", null, l0, l1, 0)
mv.visitLocalVariable("o", "Ljava/lang/Object;", null, l0, l1, 1)
mv.visitMaxs(1, 2)
mv.visitEnd()
}
cw.visitEnd()
val bytes = cw.toByteArray()
outputStream.write(bytes)
outputStream.close()
}
幸運的是 ASM Bytecode Outline工具可以幫助我們方便的查看,java文件編譯之后的字節(jié)碼,以及對應(yīng)到的ASM指令碼.
但ASM Bytecode Outline工具有一個缺點,它無法查看kotlin代碼生的字節(jié)碼和ASM指令。這就尷尬了夺克。
后來誕生了一個新插件ASM Bytecode Viewer Support Kotlin 可以查看Java和Kotlin代碼的字節(jié)碼和ASM指令
使用方式:Code->ASM ByteCode Viewer
直接將dump方法中的代碼copy出來箕宙,就是ASM生成class文件的指令代碼
2.3、如何刪除原class中的屬性和方法
2.3.1懊直、針對ClassVisitor中無返回值類型的方法,僅保留一個空方法扒吁,會去掉生成的類中對應(yīng)的方法。
public class RemoveDebugAdapter extends ClassVisitor {
public RemoveDebugAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public void visitSource(String source, String debug) {
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
}
@Override
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
}
}
For example the following class adapter removes the information about outer and inner classes, as well as the name of the source file from which the class was compiled (the resulting class remains fully functional, because these elements are only used for debugging purposes). This is done by not forwarding anything in the appropriate visit methods:
This strategy does not work for fields and methods, because the visitField and visitMethod methods must return a result. In order to remove a field or method, you must not forward the method call, and return null to the caller
2.3.2室囊、 如果ClassVisitor的回調(diào)方法有返回值,那么返回null值魁索,可以去掉生成class中的對應(yīng)的方法
這里以ComponentManager類為例,演示如何移除其中的toDeleteMethod()方法和toDeleteFiled屬性
public class ComponentManager {
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
public void calculate(){
Long start = System.currentTimeMillis();
int i = 10;
int j = 100+i;
Long end = System.currentTimeMillis();
System.out.println("calucate cost:"+(end-start));
}
public String toDeleteFiled;
}
- 移除類中的方法
//移除原有類中的一個方法
fun scanClassRemoveMethod(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
//構(gòu)造ClassReader對象
var cr = ClassReader(inputStream)
//構(gòu)造ClassWriter對象
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
//構(gòu)造ClassAdapter對象,指定其傳遞鏈的下一級為ClassWriter
var classAdapter =
RemoveMethodClassVisitor(Opcodes.ASM7, cw)
//ClassReader讀取類的信息,指定讀取到的類信息傳遞到ClassAdapter
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
//最終的類轉(zhuǎn)換成ByteArray
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
class RemoveMethodClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
System.out.println("visitMethod --- :${name},descriptor:${descriptor}")
if (name?.equals("toDeleteMethod") == true && descriptor.equals("()V") == true) {
return null
} else {
//刪除一個方關(guān)鍵的兩點:
// (1)不調(diào)用 cv.visitMethod(),調(diào)用cv.visitMethod()會產(chǎn)生一個MethodVisitor 生成對應(yīng)的方法
// (2) 不將methodVisitor返回
var mv = cv.visitMethod(access, name, descriptor, signature, exceptions)
return mv
}
}
}
修改后的ComponentManager
public class ComponentManager {
public String toDeleteFiled;
public ComponentManager() {
}
public synchronized void initComponet() {
}
}
- 移除類中的屬性
//移除一個屬性
fun scanClassRemoveFiled(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
var cr = ClassReader(inputStream)
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
var classAdapter =
RemoveFiledClassVisitor(Opcodes.ASM7, cw)
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
class RemoveFiledClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
override fun visitField(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
value: Any?
): FieldVisitor? {
System.out.println("visitField --- access:${access}:${name},descriptor:${descriptor},signature:${signature},value:${value}")
if (name.equals("toDeleteFiled") && descriptor.equals("Ljava/lang/String;")) {
//刪除一個屬性,只需要特定屬性 visitField 返回null即可
return null
} else {
return cv.visitField(access, name, descriptor, signature, value)
}
}
}
修改后的ComponentManager
package com.sogou.iot.trplugin;
public class ComponentManager {
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
2.4融撞、如何新增屬性和方法
為已知類新增屬性和方法的最好的方式就是在ClassVisitor的visitEnd()方法中,新增visitField()或visitMethod()方法, 但是必須保證沒有重復(fù)的屬性和方法粗蔚。唯一的方式就是遍歷所有的屬性(或方法) 然后進行比較尝偎。而visitEnd()方法會在所有的方法和屬性都被訪問時才會回調(diào)。同時visitEnd()方法肯定會被回調(diào)。所以visitEnd()最適合新增屬性和方法
仍然以ComponentManager類為例致扯,為其新增addedFileld屬性和addedMethod方法肤寝。
2.4.1、 新增屬性
//新增一個屬性
fun scanClassAddField(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
var cr = ClassReader(inputStream)
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
var classAdapter =
AddFiledClassVisitor(Opcodes.ASM7, cw)
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
class AddFiledClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
val toAddFiledName = "addedFileld"
val toAddFiledDes = "Ljava/lang/String;"
var conflict: Boolean = false
override fun visitField(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
value: Any?
): FieldVisitor {
if (name.equals(toAddFiledName) && descriptor.equals(toAddFiledDes)) {
conflict = true
}
return super.visitField(access, name, descriptor, signature, value)
}
//增加一個屬性注意兩點:
//(1)新增的屬性和類中已有的屬性不能沖突(如重名等)
//(2)理論上在訪問該類的任何時機都可以新增特定屬性,但是考慮到(1)新增屬性不能和現(xiàn)有屬性沖突,所有最好在visitEnd中 排除屬性沖突后,執(zhí)行新增屬性的任務(wù)
//(3)新增屬性只需要調(diào)用 cv.visitField()和fv.visitEnd() 就可完整新增一個屬性
override fun visitEnd() {
System.out.println("visitEnd ,conflict:${conflict},${Type.getType(Array<String>::class.java)}")
if (conflict == false) {
//生成實例屬性
val filedVisitor = cv.visitField(
Opcodes.ACC_PUBLIC,
"addedFileld",
Type.getType(Array<String>::class.java).descriptor,
null,
null
)
//生成靜態(tài)屬性
//val staticfiledVisitor = cv.visitField((Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC),"addedFileld","Ljava/lang/String;",null,null)
filedVisitor.visitEnd()
}
super.visitEnd()
}
}
修改前的ComponentManager
package com.sogou.iot.trplugin;
public class ComponentManager {
public String toDeleteFiled;
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
修改后的ComponentManager類
public class ComponentManager {
public String toDeleteFiled;
public String[] addedFileld;
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
2.4.2抖僵、 新增方法
//新增方法
fun scanClassAddMethod(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
var cr = ClassReader(inputStream)
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
var classAdapter =
AddMethodClassVisitor(Opcodes.ASM7, cw)
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
//增加方法
class AddMethodClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
val toAddMethodName = "addedMethod"
val toAddMethodDescriptor = "()V"
var confict = false
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
if (name.equals(toAddMethodName) && descriptor.equals(toAddMethodDescriptor)) {
confict = true
}
return super.visitMethod(access, name, descriptor, signature, exceptions)
}
override fun visitEnd() {
if (confict == false) {
var methodVisitor = cv.visitMethod(
Opcodes.ACC_PUBLIC or Opcodes.ACC_SYNCHRONIZED,
toAddMethodName,
toAddMethodDescriptor,
null,
null
)
methodVisitor.visitCode()
//...此處增加方法的具體實現(xiàn)
methodVisitor.visitInsn(Opcodes.RETURN)
methodVisitor.visitMaxs(0, 1)
methodVisitor.visitEnd()
}
super.visitEnd()
}
}
修改前的ComponentManager
package com.sogou.iot.trplugin;
public class ComponentManager {
public String toDeleteFiled;
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
修改后的ComponentManager
package com.sogou.iot.trplugin;
public class ComponentManager {
public String toDeleteFiled;
public ComponentManager() {
}
public synchronized void initComponet() {
}
public void toDeleteMethod() {
}
}
2.5鲤看、如何修改原有類的特定方法
原始類信息
class TestCost {
public void haveATry() {
int i = 0;
int j = i + 10;
System.out.println("haveATry j:" + j);
}
}
修改目標:修改HaveATry方法,在方法執(zhí)行前和執(zhí)行后分別一行代碼,以達到統(tǒng)計方法執(zhí)行時長的作用耍群。
2.5.1义桂、方案一
在haveATry()方法開始后和結(jié)束前 分別利用臨時變量記錄當前時間start和end,最后利用end-start 計算方法耗時
class TestCost {
public void haveATry() {
//插入一
Long start = System.currentTimeMillis();
int i = 0;
int j = i + 10;
System.out.println("haveATry j:" + j);
//插入二
Long end = System.currentTimeMillis();
System.out.println("calucate cost:" + (end - start));
}
}
fun scanClassModifyMethod(file: File, out: File) {
var inputStream = FileInputStream(file)
var outputStream = FileOutputStream(out)
var cr = ClassReader(inputStream)
var cw = ClassWriter(cr, ClassWriter.COMPUTE_MAXS)
var classAdapter =
ModifyMethodClassVisitor(Opcodes.ASM7, cw)
cr.accept(classAdapter, ClassReader.EXPAND_FRAMES)
var newClassBytes = cw.toByteArray()
outputStream.write(newClassBytes)
inputStream.close()
outputStream.close()
}
class ModifyMethodClassVisitor(api: Int, classVisitor: ClassVisitor?) :
ClassVisitor(api, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
if (name.equals("haveATry")) {
val mv = cv.visitMethod(access, name, descriptor, signature, exceptions)
return ScanMethodvisitor(api, mv, access, name, descriptor)
} else {
return cv.visitMethod(access, name, descriptor, signature, exceptions)
}
}
}
class ScanMethodvisitor(
api: Int,
methodVisitor: MethodVisitor?,
access: Int,
name: String?,
descriptor: String?
) : AdviceAdapter(api, methodVisitor, access, name, descriptor) {
override fun onMethodEnter() {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
//注意這里新增的局部變量 不能和方法原有的局部變量有沖突
mv.visitVarInsn(ASTORE, 10)
super.onMethodEnter()
}
override fun onMethodExit(opcode: Int) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
mv.visitVarInsn(ASTORE, 11)
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn("calucate cost:");
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
false
);
mv.visitVarInsn(ALOAD, 11);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
mv.visitVarInsn(ALOAD, 10);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
mv.visitInsn(LSUB);
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuilder",
"append",
"(J)Ljava/lang/StringBuilder;",
false
);
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/lang/StringBuilder",
"toString",
"()Ljava/lang/String;",
false
);
mv.visitMethodInsn(
INVOKEVIRTUAL,
"java/io/PrintStream",
"println",
"(Ljava/lang/String;)V",
false
);
super.onMethodExit(opcode)
}
}
修改后的TestCost如下:
package com.sogou.iot.trplugin;
import com.sogou.iot.annotations.Cost;
class TestCost {
TestCost() {
}
@Cost
public void haveATry() {
Long var10 = System.currentTimeMillis();
int i = 0;
int j = i + 10;
System.out.println("haveATry j:" + j);
Long var11 = System.currentTimeMillis();
System.out.println("calucate cost:" + (var11 - var10));
}
}
2.5.2、 隱患
方案一中統(tǒng)計方法耗時,在haveATry()中新增了兩個局部變量start和end蹈垢。這是對原有的局部變量表有干擾的,容易和原有類的局部變量表差生沖突覆蓋等慷吊,使最終生成的class產(chǎn)生錯誤。
所以修改類中的方法,應(yīng)該做到盡量不影響原方法棧的局部變量表曹抬,以減少錯誤發(fā)生溉瓶。
所以為達到統(tǒng)計方法耗時的目的,可以參照方案二:
- (1)新建一個輔助類TimeCostCache,用于記錄方法進入和退出的時間點
public class TimeCostCache {
public static HashMap<String, Long> times = new HashMap<>();
public static void enterInMethod(String methodName) {
times.put(methodName, System.currentTimeMillis());
}
public static void enterOutMethod(String methodName){
long start = times.get(methodName);
long end = System.currentTimeMillis();
System.out.println("TimeCostCache --> "+methodName+" coast:"+(end-start)+"ms");
}
}
- (2)TestCost改為如下形式,規(guī)避對原有方法棧局部變量表產(chǎn)生的干擾谤民。
class TestCost {
public void haveATry() {
//插入一
TimeCostCache.enterInMethod("haveATry");
int i = 0;
int j = i + 10;
System.out.println("haveATry j:" + j);
//插入二
TimeCostCache.enterOutMethod("haveATry");
}
}
2.6嚷闭、工具類
2.6.1、 Type
Type 對象代表一個java 類赖临,可以從一個Type 描述或者Class 進行構(gòu)造.
利用Type類型可以幫助我們生成descriptor,通過descriptor提取方法的參數(shù)和返回值胞锰。
- 對于原始數(shù)據(jù)類型,Type中進行了內(nèi)置兢榨,如 Type.INT
- 對于非原始數(shù)據(jù)類型,可以通過Type.getType()構(gòu)造Type
String類型的Type
Type.getType(String.class)
構(gòu)造Array<String>類型的Type
Type.getType(Array<String>::class.java)
- getInternalName(),可以Type實際代表的Java類
Type.getType(String.class).getInternalName() gives the internal
name of the String class, i.e. "java/lang/String".
- getDescriptor().
instead of using "Ljava/lang/String;" 嗅榕," in your code you could use Type.getType(String.class).getDescriptor()
- getArgumentTypes 和 getReturnType
the getArgumentTypes and getReturnType methods
can be used to get the Type objects corresponding to the argument types and return types of a method.
Type.getArgumentTypes("(I)V") returns an array containing the single element Type.INT_TYPE.
. Similarly, a call
to Type.getReturnType("(I)V") returns the Type.VOID_TYPE object.
2.6.2、 AdviceAdapter
dviceAdapter 是一個抽象方法吵聪,用于幫助我們在一個方法前 和RETRUN前 插入代碼凌那。
class AddTimerMethodAdapter6 extends AdviceAdapter {
public AddTimerMethodAdapter6(int access, String name, String desc,
MethodVisitor mv) {
super(ASM4, mv, access, name, desc);
}
@Override protected void onMethodEnter() {
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J");
mv.visitInsn(LSUB);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
@Override protected void onMethodExit(int opcode) {
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System",
"currentTimeMillis", "()J");
mv.visitInsn(LADD);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
@Override public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals);
}
}
2.6.3、ASMifier
ASMifier 允許我們打印生成某種Class的ASM 的源碼吟逝。 例如帽蝶,我們不知道如何用ASM 生成一個class文件,我們可以這樣做: 首先編寫該類的java源碼,然后用javac編譯块攒,最后使用ASMifier 訪問編譯后的class. 你將會得到生成該java類的asm源碼
The ASMifier class can be used from the command line. For example using:
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable
Gradle插件ASM Bytecode Viewer Support Kotlin 提供了ASMifier的可視化操作,使用起來更方便励稳。
2.7、示例代碼
示例代碼 可參考
trplugin asm_demo 模塊,AsmPractice.kt