1. 程式人生 > >Agent agentmain+ASM記錄方法執行時引數資訊

Agent agentmain+ASM記錄方法執行時引數資訊

agentmain提供了執行時修改指定程序中位元組碼的能力,配合ASM框架,達到隨時修改位元組碼的效果。

agentmain方法:

public static void agentmain(String agentArgs, Instrumentation inst){
        System.out.println("Agent Main called");
        inst.addTransformer(new TimeTransformer(),true);
        inst.addTransformer(new LogTransformer(),true);

        Class[] cs =inst.getAllLoadedClasses();
        if (cs!=null){
            for (Class c: cs) {
                if (c.isInterface() ||c.isAnnotation() ||c.isArray() ||c.isEnum()){
                    continue;
                }
                if (c.getName().contains(paths)) {
                    try {
                        System.out.println(c.getName());
                        inst.retransformClasses(c);
                    } catch (UnmodifiableClassException e) {
                        System.err.println(c.getName());
                        e.printStackTrace();
                    }
                }
            }
        }
    }

啟動方法,用於連線指定程序,Main是這裡測試目標的程序名,如果是tomcat專案,那麼名字可能是Bootstrap,具體名字使用jps工具檢視。

public static void main( String[] args ) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vmd : list) {
            if (vmd.displayName().endsWith("Main")) {
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
                virtualMachine.loadAgent("D:\\專案\\bytecode\\target\\bytecode-1.0-SNAPSHOT.jar");
                System.out.println("ok");
                virtualMachine.detach();
            }
        }
    }

轉換器LogTransformer :

public class LogTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        LogAdapter logAdapter=new LogAdapter();
        return logAdapter.addLog(classfileBuffer);
    }
}

實際作用類:

這裡使用asm的treeApi修改類。引數資訊直接列印在控制檯的,事實上,可以把它輸出到任意位置。

package com.runtime.bytecode.adapter;

import com.runtime.bytecode.UdAgent;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;

import static java.lang.System.out;

/**
 * Created by lenovo on 2018/11/20.
 */
public class LogAdapter implements Opcodes {
    public static boolean debug=false;
    public byte[] addLog(byte[] sourceCodes) {
        ClassReader cr = new ClassReader(sourceCodes);

        ClassNode cn = new ClassNode();
        cr.accept(cn, ClassReader.EXPAND_FRAMES);

        int access = cn.access;
        if (debug)
            out.println(cn.name);
        if ((access & ACC_INTERFACE) == 0 && cn.name.replace('/','.').contains(UdAgent.paths)) {//不處理介面
            if (debug)
                out.println(cn.name+"-進入if條件,即將處理methods物件");
            //不修改介面
            cn.methods.forEach(mn -> {

                if ((mn.access & ACC_ABSTRACT) == 0 && !mn.name.equals("<init>") && !mn.name.equals("<clinit>")) {
                    //不修改抽象方法,靜態初始化方法或者無參構造方法
                    if (mn.instructions != null && mn.instructions.size() > 0) {
                        if (debug){
                            out.println("方法"+mn.name+"指令數為:"+mn.instructions.size());
                        }
                        String methodDescriptor = mn.desc;//方法描述符
                        boolean isStaticMethod = (mn.access & ACC_STATIC) != 0;
                        //方法本身有指令。不修改空方法
                        AbstractInsnNode insnNode = mn.instructions.getFirst();
                        int argumentsSize=0;

                        //methodnodes尚未發現新增區域性變數的方法,因為新增區域性變數需要遍歷修改後續所有的位元組碼指令
                        while (insnNode != null) {
                            int opcode = insnNode.getOpcode();
                            if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {

                                InsnList insnList = new InsnList();
                                insnList.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));

                                insnList.add(new TypeInsnNode(NEW, "java/lang/StringBuilder"));
                                insnList.add(new InsnNode(DUP));
                                insnList.add(new MethodInsnNode(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false));

                                int currentOffset = 1;
                                int currentSlot = 1;
                                if (isStaticMethod) {
                                    currentSlot = 0;//靜態方法沒有this
                                }
                                char currentChar;
                                for (currentChar = methodDescriptor.charAt(currentOffset); currentChar != 41; currentChar = methodDescriptor.charAt(currentOffset)) {

                                    //判斷當前引數是什麼型別
                                    switch (currentChar) {
                                        case 68:
                                            insnList.add(new VarInsnNode(DLOAD, currentSlot));
                                            insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(D)Ljava/lang/StringBuilder;", false));
                                            currentSlot += 2;
                                            currentOffset+=1;
                                            break;
                                        case 74:
                                            insnList.add(new VarInsnNode(LLOAD, currentSlot));
                                            insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false));
                                            currentSlot += 2;
                                            currentOffset+=1;
                                            break;
                                        case 91://陣列型別
                                        case 76://引用型別
                                            while (methodDescriptor.charAt(currentOffset) == 91) {
                                                ++currentOffset;
                                            }
                                            if(methodDescriptor.charAt(currentOffset++) == 76) {//以L開頭
                                                while(methodDescriptor.charAt(currentOffset++) != 59) {
                                                }
                                            }
                                            insnList.add(new VarInsnNode(ALOAD, currentSlot));
                                            insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false));

                                            insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false));

                                            currentSlot+=1;
                                            break;
                                        case 65:
                                        case 66:
                                        case 73:
                                            insnList.add(new VarInsnNode(ILOAD, currentSlot));
                                            insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false));
                                            currentSlot+=1;
                                            currentOffset+=1;
                                            break;
                                        case 70:
                                            insnList.add(new VarInsnNode(FLOAD, currentSlot));
                                            insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(F)Ljava/lang/StringBuilder;", false));
                                            currentSlot+=1;
                                            currentOffset+=1;
                                            break;
                                    }
                                    ++argumentsSize;
                                    insnList.add(new LdcInsnNode(" | "));
                                    insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false));
                                }

                                insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false));
                                insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
                                if (argumentsSize>0) {
                                    mn.instructions.insertBefore(insnNode, insnList);
                                }
                                if (debug){
                                    out.println("方法"+mn.name+"指令數為:"+mn.instructions.size()+",成功新增指令");
                                }
                            }
                            insnNode=insnNode.getNext();
                        }
                        if (argumentsSize>0)
                            mn.maxStack+=4;

                    }

                }
            });
        }

        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cn.accept(cw);
        return cw.toByteArray();
    }
}