Bytebuddy AsmVisitorWrapper

Я инструментирую уже загруженный класс (метод с именем test) с помощью asm (он работает):

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, чтобы изменить свой метод. Но я не знал, как использовать закрытое поле/частный метод класса Test в классе TestAgentMethodInterceptor (возможно ли это?), поэтому я использовал 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?


person Light    schedule 12.01.2017    source источник


Ответы (1)


В настоящее время для этого можно использовать только отражение или реализовать собственный ParameterBinder и зарегистрировать собственный прокси-сервер с помощью MethodDelegation. Я надеюсь добавить такую ​​вещь в качестве стандартного компонента в какой-то момент. Эта проблема отслеживается: https://github.com/raphw/byte-buddy/issues/252

person Rafael Winterhalter    schedule 13.01.2017
comment
Спасибо за ваш ответ, и поэтому для доступа к частному полю мне нужно использовать аннотацию @FieldValue в моем перехватчике? Но я хотел бы знать, как заменить код метода байт-кодом, массивом байтов или использовать мой TestAgentClassVisitor. Вы знаете, почему AsmVisitorWrapper не работает? - person Light; 13.01.2017
comment
Привет Рафаэль, я искал одну неделю, чтобы найти проблему, но я не нашел ее. Я добавил отладку в конце метода visitCode(), чтобы проверить, вызывается ли метод и вызывается ли он. Я не понимаю, почему он не работает при вызове метода visitCode, это очень странно. Вы знаете, что я делаю неправильно с AsmVisitorWrapper, пожалуйста? Спасибо. - person Light; 22.01.2017