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.
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;
}
}
}