ASM (iv) Dynamic injection of method logic by means component

Source: Internet
Author: User

This article continues with an example to gain an in-depth understanding of the implementation of bytecode for dynamic change methods of method components. By the previous article, you know that Classvisitor's Visitmethod () method can return an instance of Methodvisitor. Then we can also basically know that with classvisitor change class members, methodvisistor if need to change method members, inject logic, can also inherit Methodvisitor, To write a methodxxxadapter to implement the injection of the method logic. The following two examples are presented to illustrate the implementation of the non-state injection and stateful injection method logic. Examples of the main reference to the official documentation, you can follow this idea to expand the application of more than a variety of scenarios.

I. Non-state injection

The first example is a more common scenario where we need to inject a timed logic into all the methods of the following class.

The source code is as follows:

Package asm.core.methord;/** * Created by Yunshen.ljy on 2015/6/29. */public class Time {public    void MyCount () throws Exception {        int i = 5;        int j = Ten;        System.out.println (j-i);    }    public void Mydeal () {        try {            int[] myInt = {1, 2, 3, 4, 5};            int f = myint[10];            System.out.println (f);        } catch (ArrayIndexOutOfBoundsException e) {            e.printstacktrace ();}}    }

The class byte code of our target is as follows:

Source code recreated from a. class file by IntelliJ idea//(powered by Fernflower Decompiler)//package Asm.core.meth Ord;public class Time {public    static long timer;    Public time () {    } public    void MyCount () throws Exception {        Timer-= System.currenttimemillis ();        byte i = 5;        byte j = Ten;        System.out.println (j-i);        Timer + = System.currenttimemillis ();    }    public void Mydeal () {        Timer-= System.currenttimemillis ();        try {            int[] e = new Int[]{1, 2, 3, 4, 5};            int f = e[10];            System.out.println (f);        } catch (ArrayIndexOutOfBoundsException var3) {            var3.printstacktrace ();        }        Timer + = System.currenttimemillis ();    }}

By looking at the bytecode structure you know, first we need to add a field to the time class. Then in addition to the constructor the method injects the timing logic of the bytecode. Let's take the first Method Mycount () as an example, and use the JAVAP tool to view the bytecode information as follows:

public void MyCount () throws java.lang.Exception;                 Descriptor: () V flags:acc_public code:stack=7, locals=3, args_size=1 0:getstatic #18         Field timer:j 3:invokestatic #24//Method Java/lang/system.currenttimemillis: () J  6:lsub 7:putstatic #18//Field timer:j 10:iconst_5 11:istore_1 12: Bipush 14:istore_2 15:getstatic #28//Field java/lang/system.out:ljava/io/        PrintStream; 18:iload_2 19:iload_1 20:isub 21:invokevirtual #34//Method Java/io/printstream.  println: (I) V 24:getstatic #18//Field timer:j 27:invokestatic #24// Method Java/lang/system.currenttimemillis: () J 30:ladd 31:putstatic #18//Field Timer : J 34:return Localvariabletable:start Length Slot Name Signature 0 this lasm/core/methord/time;        1 I i 2 J i linenumbertable:line 8:10 line 9:12 Line 10:15 line 11:24 exceptions:throws java.lang.Exception

The offset from the method 0 to 7 is our timer-=system.currenttimemillis (), corresponding to the bytecode implementation. 24 to 31 is the timer + = System.currenttimemillis (), the bytecode implementation. It is basically possible to determine that we need to re-enter the method just as the timer-= System.currenttimemillis (), the bytecode, and then generate the timer+= before the method returns the return instruction or the athrow instruction The byte code of the System.currenttimemillis ().

Timer +=system.currenttimemillis () We can do this by adding several ways that ASM provides bytecode instruction generation through the Visitcode (method start is called by this method) method:

@Override public        void Visitcode () {            mv.visitcode ();            MV.VISITFIELDINSN (opcodes.getstatic, owner, "Timer", "J");            MV.VISITMETHODINSN (opcodes.invokestatic, "Java/lang/system", "Currenttimemillis", "() J", false);            MV.VISITINSN (opcodes.lsub);            MV.VISITFIELDINSN (opcodes.putstatic, owner, "Timer", "J");        }

The timer-=system.currenttimemillis () needs to be done through the visitinsn (int opcode) method, traversing all opcode to determine if our current instruction is return or athrow. If so, insert the instructions we need before continuing to call the next layer of mv.visitinsn (opcode). The code is as follows:

@Override public        void visitinsn (int opcode) {            if (opcode >= opcodes.ireturn && opcode <= opcodes.r Eturn) | | opcode = = opcodes.athrow) {                mv.visitfieldinsn (opcodes.getstatic, owner, "Timer", "J");                MV.VISITMETHODINSN (opcodes.invokestatic, "Java/lang/system", "Currenttimemillis", "() J", false);                MV.VISITINSN (opcodes.ladd);                MV.VISITFIELDINSN (opcodes.putstatic, owner, "Timer", "J");            }            mv.visitinsn (opcode);        }

So finally, there is a timer attribute that needs to be generated in class, as described in the previous classvisitor, where we need to insert our fieldvisitor in the Visitend () method in Classvisitor's appropriate gamete class.

@Override public    void Visitend () {        if (!isinterface) {            Fieldvisitor FV = Cv.visitfield (Opcodes.acc_public + Opcodes.acc_static, "Timer", "J", NULL, NULL);            if (FV! = null) {                fv.visitend ();            }        }        Cv.visitend ();    }

At this point, our bytecode has been created and generated, for the sake of robustness, we just add whether it is interface judgment, because the interface is not a method implementation of the body, and also to determine that the constructor method does not add timer timing logic. Here we pass the name of the class that needs to be injected into logic through the parameter owner to Methodvisitor. The overall adapter method is as follows:

Package Asm.core.methord;import Org.objectweb.asm.classvisitor;import Org.objectweb.asm.fieldvisitor;import Org.objectweb.asm.methodvisitor;import org.objectweb.asm.opcodes;/** * Created by Yunshen.ljy on 2015/6/29.    */public class Addtimeradapter extends Classvisitor {private String owner;    Private Boolean isinterface;    Public Addtimeradapter (Classvisitor CV) {super (OPCODES.ASM4, CV); } @Override public void visit (int version, int access, string name, string signature, String supername, string[] int        erfaces) {cv.visit (version, access, name, signature, supername, interfaces);        Owner = name;    Isinterface = (Access & opcodes.acc_interface)! = 0; } @Override public methodvisitor visitmethod (int access, string name, String desc, string signature, string[] except        ions) {Methodvisitor mv = Cv.visitmethod (access, name, desc, signature, exceptions); if (!isinterface && mv! = null &&!name.equals ("<init>")) {mv = new Addtimermethodadapter (MV);    } return MV; } @Override public void Visitend () {if (!isinterface) {Fieldvisitor FV = Cv.visitfield (OPCODES.A            Cc_public + opcodes.acc_static, "Timer", "J", NULL, NULL);            if (FV! = null) {fv.visitend ();    }} cv.visitend ();            } class Addtimermethodadapter extends Methodvisitor {public addtimermethodadapter (Methodvisitor mv) {        Super (OPCODES.ASM4, MV);            } @Override public void Visitcode () {//Mv.visitcode ();            MV.VISITFIELDINSN (opcodes.getstatic, owner, "Timer", "J");            MV.VISITMETHODINSN (opcodes.invokestatic, "Java/lang/system", "Currenttimemillis", "() J", false);            MV.VISITINSN (opcodes.lsub);        MV.VISITFIELDINSN (opcodes.putstatic, owner, "Timer", "J"); } @Override public void visitinsn (int opcode) {if (opcode >= opcodes.ireturn && opcode <= opcodes.return) | |                opcode = = Opcodes.athrow) {mv.visitfieldinsn (opcodes.getstatic, owner, "Timer", "J");                MV.VISITMETHODINSN (opcodes.invokestatic, "Java/lang/system", "Currenttimemillis", "() J", false);                MV.VISITINSN (Opcodes.ladd);            MV.VISITFIELDINSN (opcodes.putstatic, owner, "Timer", "J");        } mv.visitinsn (opcode); } @Override public void visitmaxs (int maxstack, int maxlocals) {//manual block requires calculation stack space, here the operation of two long variables requires 4        Slot Mv.visitmaxs (Maxstack + 4, maxlocals); }    }}

Second, stateful injection

The state here is relative to the state of being stateless. The example above is a logical injection of the absolute offset of the method. In simple terms, the injected logic does not depend on the operation of the previous instruction or the parameters of the instruction. All methods for the class file are the same logical injection. However, if one is considered, it is that the current bytecode instruction that needs to be injected depends on the execution result state of the preceding instruction. Then we have to store the state of the previous instruction.

The following example comes from an example from an official document. Consider the following methods:

public void MyCount () {        int i = 5;        int j = Ten;        System.out.println (j-i);        System.out.println (j + i);        System.out.println (j + 0);          }
Here we know that the output of j+0 or j-0 is J. So if we're going to let the above code get rid of the two operations of +0 and-0, then we need to turn into the following method:

public void MyCount () {        byte i = 5;        byte j = Ten;        System.out.println (j-i);        System.out.println (j + i);        System.out.println (j);          }
By looking at the bytecode information of the original method is as follows:


  0:iconst_5         1:istore_1         2:bipush         4:istore_2         5:getstatic     #2                  //Field java/lang/ System.out:ljava/io/printstream;         8:iload_2         9:iload_1        10:isub        11:invokevirtual #3                  //Method java/io/printstream.println: (I) V        14:getstatic     #2                  //Field Java/lang/system.out:ljava/io/printstream;        17:iload_2        18:iload_1        19:iadd        20:invokevirtual #3                  //Method java/io/printstream.println: (I) v< C21/>23:getstatic     #2                  //Field Java/lang/system.out:ljava/io/printstream;        26:iload_2        27:iconst_0        28:iadd        29:invokevirtual #3                  //Method java/io/printstream.println: (I) V        32:return
You can see that the IADD directive is the post-command of ICONST_0. But we can not simply judge the current bytecode instruction when iadd or iconst_0 directly remove. Of course, remove is implemented in a manner similar to that of the Classvisitor adapter, both by not continuing to invoke mv.visitinsn (opcode), or by means of the Methodvisitor method. But here we need to mark the state of the ICONST_0 directive. The ICONST_0 instruction executes when a state is marked, the state value is judged when the next instruction is executed, and if the next command is Iadd then the method is removed directly to remove the instruction. The official implementation is very elegant, there are some comments, easy to understand the implementation.
Package Asm.core.methord;import org.objectweb.asm.methodvisitor;import org.objectweb.asm.opcodes;/** * Created by Yunshen.ljy on 2015/7/1.    */public class Removeaddzeroadapter extends Methodvisitor {private static int seen_iconst_0 = 1;    protected final static int seen_nothing = 0;    protected int State;    Public Removeaddzeroadapter (Methodvisitor mv) {super (OPCODES.ASM4, MV);            } @Override public void visitinsn (int opcode) {//whether the previous instruction was detected as ICONST_0 if (state = = Seen_iconst_0) { And the current instruction is iadd if (opcode = = Opcodes.iadd) {//re-initializes the instruction status state = Seen_                nothing;            Remove the instruction sequence return;        }} visitinsn ();            If the current instruction is ICONST_0 record the state of the instruction, and returns (removes) the if (opcode = = opcodes.iconst_0) (= = Seen_iconst_0;        Return    }//Continue to access the next instruction mv.visitinsn (opcode); } protected void Visitinsn () {//If the last access is Seen_icoNST_0 instruction, then the restore instruction (because it was just removed) if (state = = Seen_iconst_0) {mv.visitinsn (OPCODES.ICONST_0);    } state = seen_nothing; }}

Here we add that we do not need to deal with stacckmapframe and the size of the local variable table and operand stack as in the previous section, because we do not add extra attributes, and there are no unconditional jump statements in the example, and we need to verify the operation. But if we want to achieve more complex situations, we also need to cover the Visitmaxs method, the Visitframe visitlable method, and so on. (Ensure that removing instructions does not affect normal jumps of other instructions, call the VISITINSN () method)

In fact, I personally feel that the processing of stateful bytecode instructions to remove, add, transfer or need to pay attention to the various bytecode instruction situation. The order, context, and stack information of bytecode directives are critical for writing a robust ASM logic injection code. Sometimes it is advisable to first analyze the class file before and after the injection, and then encode it.

Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

ASM (iv) Dynamic injection of method logic by means component

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.