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 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("") && !mn.name.equals("")) {
                    //不修改抽象方法,静态初始化方法或者无参构造方法
                    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", "", "()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();
    }
}

你可能感兴趣的:(字节码)