奇門遁甲之ASM操縱字節(jié)碼

奇門遁甲之字節(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的訪問鏈條如下所示:

visitor鏈條

常規(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文件的指令代碼


image

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

三囱井、參考文章

https://asm.ow2.io/asm4-guide.pdf

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驹尼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子庞呕,更是在濱河造成了極大的恐慌新翎,老刑警劉巖程帕,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異地啰,居然都是意外死亡愁拭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門亏吝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岭埠,“玉大人,你說我怎么就攤上這事顺呕》闩剩” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵株茶,是天一觀的道長来涨。 經(jīng)常有香客問我,道長启盛,這世上最難降的妖魔是什么蹦掐? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮僵闯,結(jié)果婚禮上卧抗,老公的妹妹穿的比我還像新娘。我一直安慰自己鳖粟,他們只是感情好社裆,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著向图,像睡著了一般泳秀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榄攀,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天嗜傅,我揣著相機與錄音,去河邊找鬼檩赢。 笑死吕嘀,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的贞瞒。 我是一名探鬼主播偶房,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼憔狞!你這毒婦竟也來了蝴悉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瘾敢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體簇抵,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡庆杜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碟摆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晃财。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖典蜕,靈堂內(nèi)的尸體忽然破棺而出断盛,到底是詐尸還是另有隱情,我是刑警寧澤愉舔,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布钢猛,位于F島的核電站,受9級特大地震影響轩缤,放射性物質(zhì)發(fā)生泄漏命迈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一火的、第九天 我趴在偏房一處隱蔽的房頂上張望壶愤。 院中可真熱鬧,春花似錦馏鹤、人聲如沸征椒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勃救。三九已至,卻和暖如春脱茉,著一層夾襖步出監(jiān)牢的瞬間剪芥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工琴许, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留税肪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓榜田,卻偏偏與公主長得像益兄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子箭券,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容