字节伙伴 AsmVisitorWrapper

Bytebuddy AsmVisitorWrapper

我用 asm 检测了一个已经加载的 class(名为 test 的方法)(有效):

public class Test {

public void test() {
    System.out.println("Can I call test2 private void ?");
    test2();
}

private void test2() {
    System.out.println("test2 called");
}

public static class TestAgent {

    public static void agentmain(String arg, Instrumentation instrumentation) {
        System.out.println(TestAgent.class.getName() + " loaded");
        instrumentation.addTransformer(new TestAgentClassFileTransformer());
        try {
            instrumentation.redefineClasses(new ClassDefinition(Test.class, Tools.getBytesFromClass(Test.class)));
        } catch (ClassNotFoundException | UnmodifiableClassException | IOException e) {
            e.printStackTrace();
        }
    }

}

public static class TestAgentMethodVisitor extends MethodVisitor {

    public TestAgentMethodVisitor(int api, MethodVisitor mv) {
        super(api, mv);
    }

    @Override
    public void visitCode() {
        Label l0 = new Label();
        super.visitLabel(l0);
        super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        super.visitLdcInsn("yes");
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        Label l1 = new Label();
        super.visitLabel(l1);
        super.visitVarInsn(Opcodes.ALOAD, 0);
        super.visitMethodInsn(Opcodes.INVOKESPECIAL, "test/Test", "test2", "()V", false);
        Label l2 = new Label();
        super.visitLabel(l2);
        super.visitInsn(Opcodes.RETURN);
        Label l3 = new Label();
        super.visitLabel(l3);
        super.visitLocalVariable("this", "Ltest/Test;", null, l0, l3, 0);
        super.visitMaxs(2, 1);
        super.visitEnd();
    }

}

public static class TestAgentClassVisitor extends ClassVisitor {

    public TestAgentClassVisitor(int api, ClassVisitor cv) {
        super(api, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if (name.equals("test")) {
            return new TestAgentMethodVisitor(Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions));
        }
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

}

public static class TestAgentClassFileTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if (className.equals("test/Test")) {
            ClassReader classReader = new ClassReader(classfileBuffer);
            ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
            classReader.accept(new TestAgentClassVisitor(Opcodes.ASM5, classWriter), ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return classWriter.toByteArray();
        }
        return classfileBuffer;
    }

}
}

输出:

Before agent
Can I call test2 private void ?
test2 called
test.Test$TestAgent loaded
After agent
yes
test2 called

而且我想用byte-buddy来使用ResettableClassFileTransformer。所以我尝试转换代码。我使用 MethodDelagation.to 来修改我的方法。但是我不知道如何在 class TestAgentMethodInterceptor 中使用 field/private Test 的私有 field/private 方法(这可能吗?),所以我使用了 AsmVisitorWrapper(见后文)。有效

public class Test {

public void test() {
    System.out.println("Can I call test2 private void ?");
    test2();
}

private void test2() {
    System.out.println("test2 called");
}

public static class TestAgent {

    private static Instrumentation inst;
    private static ResettableClassFileTransformer resettableClassFileTransformer;

    public static void agentmain(String arg, Instrumentation instrumentation) {
        System.out.println(TestAgent.class.getName() + " loaded");
        inst = instrumentation;
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            @Override
            public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) {
                return builder.method(ElementMatchers.named("test")).intercept(MethodDelegation.to(TestAgentMethodInterceptor.class));
            }
        };
        AgentBuilder builder = new AgentBuilder.Default()    
                .with(RedefinitionStrategy.RETRANSFORMATION)
                .disableClassFormatChanges();
        Narrowable narrowable = builder.type(ElementMatchers.named(Test.class.getName()));
        Extendable extendable = narrowable.transform(transformer);
        resettableClassFileTransformer = extendable.installOn(instrumentation);
    }

    public static boolean reset() {
        if (inst != null && resettableClassFileTransformer != null) {
            return resettableClassFileTransformer.reset(inst, RedefinitionStrategy.RETRANSFORMATION);
        }
        return true;
    }

}

public static class TestAgentMethodInterceptor {

    public static void intercept() {
        System.out.println("I don't know how to call private void test2");
    }

}

}

输出:

Before agent
Can I call test2 private void ?
test2 called
test.Test$TestAgent loaded
After agent
I don't know how to call private void test2

所以我尝试使用 AsmVisitorWrapper 来使用我的代码,但它什么也没做。

public class Test {

public void test() {
    System.out.println("Can I call test2 private void ?");
    test2();
}

private void test2() {
    System.out.println("test2 called");
}

public static class TestAgent {

    private static Instrumentation inst;
    private static ResettableClassFileTransformer resettableClassFileTransformer;

    public static void agentmain(String arg, Instrumentation instrumentation) {
        System.out.println(TestAgent.class.getName() + " loaded");
        inst = instrumentation;
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            @Override
            public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) {
                    return builder.visit(new AsmVisitorWrapper() {

                    @Override
                    public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor, Implementation.Context implementationContext, TypePool typePool, FieldList<FieldDescription.InDefinedShape> fields, MethodList<?> methods, int writerFlags, int readerFlags) {
                        return new TestAgentClassVisitor(Opcodes.ASM5, classVisitor);
                    }

                    @Override
                    public int mergeWriter(int flags) {
                        return flags;
                    }

                    @Override
                    public int mergeReader(int flags) {
                        return flags;
                    }
                });
            }
        };
        AgentBuilder builder = new AgentBuilder.Default()    
                .with(RedefinitionStrategy.RETRANSFORMATION)
                .disableClassFormatChanges();
        Narrowable narrowable = builder.type(ElementMatchers.named(Test.class.getName()));
        Extendable extendable = narrowable.transform(transformer);
        resettableClassFileTransformer = extendable.installOn(instrumentation);
    }

    public static boolean reset() {
        if (inst != null && resettableClassFileTransformer != null) {
            return resettableClassFileTransformer.reset(inst, RedefinitionStrategy.RETRANSFORMATION);
        }
        return true;
    }

}

public static class TestAgentMethodVisitor extends MethodVisitor {

    public TestAgentMethodVisitor(int api, MethodVisitor mv) {
        super(api, mv);
    }

    @Override
    public void visitCode() {
        Label l0 = new Label();
        super.visitLabel(l0);
        super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        super.visitLdcInsn("yes");
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        Label l1 = new Label();
        super.visitLabel(l1);
        super.visitVarInsn(Opcodes.ALOAD, 0);
        super.visitMethodInsn(Opcodes.INVOKESPECIAL, "test/Test", "test2", "()V", false);
        Label l2 = new Label();
        super.visitLabel(l2);
        super.visitInsn(Opcodes.RETURN);
        Label l3 = new Label();
        super.visitLabel(l3);
        super.visitLocalVariable("this", "Ltest/Test;", null, l0, l3, 0);
        super.visitMaxs(2, 1);
        super.visitEnd();
    }

}

public static class TestAgentClassVisitor extends ClassVisitor {

    public TestAgentClassVisitor(int api, ClassVisitor cv) {
        super(api, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if (name.equals("test")) {
            return new TestAgentMethodVisitor(Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions));
        }
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

}

}

输出:

Before agent
Can I call test2 private void ?
test2 called
test.Test$TestAgent loaded
After agent
Can I call test2 private void ?
test2 called

那么如何使用 AsmVisitorWrapper 呢?

您目前只能使用反射或实现您自己的 ParameterBinder 并使用 MethodDelegation 注册自定义代理。我希望在某个时候添加标准组件这样的东西。此问题由以下人员跟踪:https://github.com/raphw/byte-buddy/issues/252