Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to modify a Java bytecode using ASM 4.0

I am new to ASM framework. I have been working around this ASM framework for a week. I saw tutorials in net regarding parsing a class and Generating a .class file from scratch. But am unable to follow how to modify a existing class in ASM.

I am unable to follow the flow of execution between the ClassVisitor, ClassWriter and ClassReader.

Kindly solve my issue by giving me a ASM example for the following code.

public class ClassName {
  public void showOne() {
    System.out.println("Show One Method");
  }

  public static void main(String[] args) {
    ClassName c = new ClassName();
    c.showOne();
  }
}

The above class should be modified as:

public class ClassName {
  public void showOne() {
    System.out.println("Show One Method");
  }

  public void showTwo() { // <- Newly added method
    System.out.println("Show Two Method");
  }

  public static void main(String[] args) {
    ClassName c = new ClassName();
    c.showOne();
    c.showTwo(); // <- Newly inserted method call
  }
}

What should be the ASM code to modify it?

I used the ASMifier tool to generate the code. But I don't know where to apply it.

like image 971
Narayana Avatar asked Aug 29 '14 05:08

Narayana


People also ask

What is ASM jar used for?

It can be used to modify existing classes or to dynamically generate classes, directly in binary form. ASM provides some common bytecode transformations and analysis algorithms from which custom complex transformations and code analysis tools can be built.

What does ASM mean Java?

ASM simply stands for ASMifier tool.

What is bytecode manipulation?

Bytecode manipulation consists in modifying the classes - represented by bytecode - compiled by the Java compiler, at runtime. It is used extensively for instance by frameworks such as Spring (IoC) and Hibernate (ORM) to inject dynamic behaviour to Java objects at runtime.

Can I write Java bytecode?

When you write a Java program and compile it you get a class file. That class file is Java byte code. It is a binary data file that contains instructions for the Java Virtual Machine to execute your program.


1 Answers

Your requirements are a bit underspecified. Below is an example program which uses ASM’s visitor API for transforming a class assumed to have the structure of your question to the resulting class. I added a convenience method taking a byte array and returning a byte array. Such a method can be used in both cases, a static transformation applied to class files on disk as well as in an Instrumentation agent.

When combining a ClassWriter with a ClassVisitor passed to a ClassReader as below, it will automatically replicate every feature of the source class so you have to override only these methods where you want to apply changes.

Here, visitMethod is overridden to intercept when encountering the main method to modify it and visitEnd is overridden to append the entirely new showTwo method. The MainTransformer will intercept RETURN instructions (there should be only one in your example) to insert the call to showTwo before it.

import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;

public class MyTransformer extends ClassVisitor {

  public static byte[] transform(byte[] b) {
    final ClassReader classReader = new ClassReader(b);
    final ClassWriter cw = new ClassWriter(classReader,
      ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
    classReader.accept(new MyTransformer(cw), ClassReader.EXPAND_FRAMES);
    return cw.toByteArray();
  }

  public MyTransformer(ClassVisitor cv) {
    super(Opcodes.ASM5, cv);
  }
  @Override
  public MethodVisitor visitMethod(int access, String name, String desc,
      String signature, String[] exceptions) {

    MethodVisitor v=super.visitMethod(access, name, desc, signature, exceptions);
    if(name.equals("main") && desc.equals("([Ljava/lang/String;)V"))
      v=new MainTransformer(v, access, name, desc, signature, exceptions);
    return v;
  }
  @Override
  public void visitEnd() {
    appendShowTwo();
    super.visitEnd();
  }
  private void appendShowTwo() {
    final MethodVisitor defVisitor = super.visitMethod(
      Opcodes.ACC_PUBLIC, "showTwo", "()V", null, null);
    defVisitor.visitCode();
    defVisitor.visitFieldInsn(Opcodes.GETSTATIC,
      "java/lang/System", "out", "Ljava/io/PrintStream;");
    defVisitor.visitLdcInsn("Show Two Method");
    defVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
      "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    defVisitor.visitInsn(Opcodes.RETURN);
    defVisitor.visitMaxs(0, 0);
    defVisitor.visitEnd();
  }
  class MainTransformer extends GeneratorAdapter
  {
    MainTransformer(MethodVisitor delegate, int access, String name, String desc,
        String signature, String[] exceptions) {
      super(Opcodes.ASM5, delegate, access, name, desc);
    }
    @Override
    public void visitInsn(int opcode) {
      if(opcode==Opcodes.RETURN) {
        // before return insert c.showTwo();
        super.visitVarInsn(Opcodes.ALOAD, 1); // variable c
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
            "ClassName", "showTwo", "()V", false);
      }
      super.visitInsn(opcode);
    }
  }
}
like image 99
Holger Avatar answered Oct 18 '22 15:10

Holger