Java ASM edit class/method that is aleready loaded by JVM

41 views Asked by At

I'm currently trying to edit the bytecode of a java class after it has been loaded by the JVM.

I use Java 8 and ASM 5.0.3. I can't change the command line or the JVM arguments.

Here is a minimal example of what I'm trying to do:

import org.objectweb.asm.*;

// Is in a library
class ExampleObject {
    public void exampleMethod(Object o) {
        System.out.println("Body " + o);
    }
}

public class Example {

    // Comes from the same library
    public static final ExampleObject exampleObject = new ExampleObject();

    public static void main(String[] args) {

        exampleObject.exampleMethod(1);
        // Output:
        // Body 1

        injectAtHead();

        exampleObject.exampleMethod(2);
        // Output:
        // Head 2
        // Body 2
    }

    public static void injectAtHead() {
        // Inject methodToInject at the head of exampleObject#exampleMethod
    }

    public static void methodToInject(Object o) {
        System.out.println("Head " + o);
    }
}

After a lot of research, I came across quite a few topics that talked about dynamicly modifying bytecode with ASM. The problem is that they all talk about modifying the bytecode of a class before it is loaded by the JVM. So, I don't know how I can do this, or even if it's possible at all.

1

There are 1 answers

0
crazycat256 On BEST ANSWER

This comment pretty much answers the question.

After a few adaptations, the solution to the initial problem is:

import net.bytebuddy.agent.ByteBuddyAgent;
import org.objectweb.asm.*;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

import static net.bytebuddy.jar.asm.Opcodes.*;

public class Example {

    // Also comes from a library
    public static final ExampleObject exampleObject = new ExampleObject();

    public static void main(String[] args) {

        exampleObject.exampleMethod(1);
        // Output:
        // Body 1

        injectAtHead();

        exampleObject.exampleMethod(2);
        // Output:
        // Head 2
        // Body 2
    }

    public static void injectAtHead() {
        try {
            ByteBuddyAgent.install();
            Instrumentation instrumentation = ByteBuddyAgent.getInstrumentation();
            instrumentation.addTransformer(new SimpleClassFileTransformer(), true);

            instrumentation.retransformClasses(ExampleObject.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    public static void methodToInject(Object o) {
        System.out.println("Head " + o);
    }

}


class ModifierMethodWriter extends MethodVisitor {

    private String methodName;

    public ModifierMethodWriter(int api, MethodVisitor mv, String methodName) {
        super(api, mv);
        this.methodName = methodName;
    }

    // This is the point we insert the code. Note that the instructions are
    // added right after
    // the visitCode method of the super class. This ordering is very
    // important.
    @Override
    public void visitCode() {
        // invoke methodToInject
        super.visitCode();
        super.visitVarInsn(ALOAD, 1);
        super.visitMethodInsn(INVOKESTATIC, "Example", "methodToInject", "(Ljava/lang/Object;)V", false);
    }

}

// Our class modifier class visitor. It delegate all calls to the super
// class
// Only makes sure that it returns our MethodVisitor for every method
class ModifierClassWriter extends ClassVisitor {
    private int api;

    public ModifierClassWriter(int api, ClassWriter cv) {
        super(api, cv);
        this.api = api;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

        if (!name.equals("exampleMethod")){
            return super.visitMethod(access, name, desc, signature, exceptions);
        } else {
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
            ModifierMethodWriter mvw = new ModifierMethodWriter(api, mv, name);
            return mvw;
        }
    }

}

class SimpleClassFileTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {

        if (!className.equals("ExampleObject")) {
            return classfileBuffer;
        }
        else {
            ClassReader classReader = new ClassReader(classfileBuffer);

            final ClassWriter cw = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);

            // Wrap the ClassWriter with our custom ClassVisitor
            ModifierClassWriter mcw = new ModifierClassWriter(Opcodes.ASM5, cw);
            classReader.accept(mcw, 0);

            byte[] byteArray = cw.toByteArray();

            return byteArray;
        }
    }

}