Question

I'm experimenting with ASM 4.0 and I'm having some trouble running my agent. I have a class TraceTransformer that should transform the bytecode, but something is going wrong -- it seems that every class I visit gets ruined when I attempt to transform it.

Agent

JavaTraceAgent.java:

package traceagent;

import java.lang.instrument.Instrumentation;


public class JavaTraceAgent {
    private static Instrumentation instrumentation;
    private static String agentId = "Uninitialized";

    public static void premain(String args, Instrumentation inst) throws Exception {
        instrumentation = inst;
        instrumentation.addTransformer(new TraceTransformer());
    }

    public static void agentmain(String args, Instrumentation inst) throws Exception {
        instrumentation = inst;
        instrumentation.addTransformer(new TraceTransformer());
    }

    public static void initialize() {
        if (instrumentation == null) {
            JavaAgentLoader.loadAgent();
        }
    }

    public static void logMethodInvocation() {
        System.out.println("Logging invocation...");
        //Thread.dumpStack();
        System.out.println("logging complete!");
    }

    public static void setAgentId(String _agentId) {
        agentId = _agentId;
    }
}

TraceTransformer.java

package traceagent;

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

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class TraceTransformer implements ClassFileTransformer {

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

        if (!className.contains("superman")) return classfileBuffer;

        System.out.println("TraceTransformer invoked on " + className);

        ClassReader reader = new ClassReader(classfileBuffer);
        ClassWriter writer = new ClassWriter(reader, 0);

        ClassVisitor visitor = new ClassVisitor(Opcodes.ASM4, writer) {

            @Override
            public MethodVisitor visitMethod(int access, final String name, String desc, String signature, String[] exceptions) {
            return new MethodVisitor(Opcodes.ASM4) {
                @Override
                public void visitCode() {
                    if (name.contains("sayHello")) {
                            super.visitMethodInsn(Opcodes.INVOKESTATIC, "traceagent/JavaTraceAgent", "logMethodInvocation", "()V");
                            System.out.println("Injecting logger on " + name);
                    } 
                    super.visitCode();
                }
            };
        }
    };

    reader.accept(visitor, 0);

    return writer.toByteArray();    
}

}

Manifest

build.xml:

    ...
    <manifest>
        <attribute name="Built-By" value="${user.name}"/>
        <attribute name="Class-Path" value="${manifest.classpath}"/>
        <attribute name="Agent-Class" value="traceagent.JavaTraceAgent" />
        <attribute name="Can-Redefine-Classes" value="true" />
        <attribute name="Can-Retransform-Classes" value="true" />
        <attribute name="Premain-Class" value="traceagent.JavaTraceAgent" />
    </manifest>
    ...

Target

Program.java (entry point):

package jack;

import jack.superman.Hello;

public class Program {
    /**
    * @param args
    */
    public static void main(String[] args) {
        Hello.sayHello();
    }

}

Hello.java:

package jack.superman;

public class Hello {

    public static void sayHello() {
        System.out.println("Hello");
    }
}

Current procedure

...\src> javac -d . jack\Program.java
...\src> java --javagent:jack\TAgent.jar jack.Program
TraceTransformer invoked on jack/superman/Hello
Injecting logger on sayHello
Exception in thread "main" java.lang.NoSuchMethodError: jack.superman.Hello.sayHello()V
        at jack.Program.main(Program.java:10)

What am I doing wrong here? How do I avoid "destroying" the method?

Was it helpful?

Solution

I tested your code and I got the same error, but after some testing and reading of docs I made it work. The problem is in your MethodVisitor of your ClassVisitor. Here is the code.

ClassVisitor visitor = new ClassVisitor(Opcodes.ASM4, writer) {
    @Override
    public MethodVisitor visitMethod(int access, final String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        if (!name.contains("sayHello")) return mv;
            
        MethodVisitor mv2 = new MethodVisitor(Opcodes.ASM4, mv) {
            public void visitCode() {
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "traceagent/JavaTraceAgent", "logMethodInvocation", "()V");
            }
        };
            
        return mv2;
    }
};

The output is

TraceTransformer invoked on jack/superman/Hello

Loggin invocation...

loggin complete!

Hello

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top