In depth study of java bytecode framework ASM

  • 2020-06-01 09:41:23
  • OfStack

1. What is ASM

ASM is an java bytecode manipulation framework that can be used to dynamically generate classes or enhance the functionality of existing classes. ASM can either directly generate the base 2 class file or dynamically change the class behavior before the class is loaded into the Java virtual machine. Java class is stored in strictly formatted.class files that have enough metadata to parse all the elements of the class: the class name, methods, attributes, and Java bytecode (instructions). After reading information from a class file, ASM can change class behavior, analyze class information, and even generate new classes based on user requests.

To use the ASM framework, you need to import the jar package from asm. Download the asm-3.2.jar link.

2. How to use ASM

The core classes in the ASM framework are as follows:

ClassReader: this class is used to parse the compiled class bytecode file.

ClassWriter: this class is used to rebuild the compiled class, such as modifying the class name, properties and methods, and even generating new class bytecode files.

ClassAdapter: this class also implements the ClassVisitor interface, which delegates method calls to it to another ClassVisitor object.

Example 1. Generation of class bytecode via asm


package com.asm3;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

/**
 *  through asm Generates the bytecode of the class 
 * @author Administrator
 *
 */
public class GeneratorClass {

 public static void main(String[] args) throws IOException {
 // generate 1 All you need is a class ClassWriter Components can be 
 ClassWriter cw = new ClassWriter(0);
 // through visit Method determines the header information for the class 
 cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT+Opcodes.ACC_INTERFACE,
  "com/asm3/Comparable", null, "java/lang/Object", new String[]{"com/asm3/Mesurable"});
 // Define the properties of the class 
 cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
  "LESS", "I", null, new Integer(-1)).visitEnd();
 cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
  "EQUAL", "I", null, new Integer(0)).visitEnd();
 cw.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_FINAL+Opcodes.ACC_STATIC,
  "GREATER", "I", null, new Integer(1)).visitEnd();
 // Defines the methods of a class 
 cw.visitMethod(Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT, "compareTo",
  "(Ljava/lang/Object;)I", null, null).visitEnd();
 cw.visitEnd(); // make cw Class is done 
 // will cw Convert it into a byte array and write it to the file 
 byte[] data = cw.toByteArray();
 File file = new File("D://Comparable.class");
 FileOutputStream fout = new FileOutputStream(file);
 fout.write(data);
 fout.close();
 }
}

Generate 1 class of bytecode file only need to use the ClassWriter class, generate Comparable.class Then decompile it with javap instruction: javap -c Comparable.class >test.txt  , the compiled results are as follows:


public interface com.asm3.Comparable extends com.asm3.Mesurable {
 public static final int LESS;

 public static final int EQUAL;

 public static final int GREATER;

 public abstract int compareTo(java.lang.Object);
}

Note: a compiled java class does not contain the package and import segments, so all types in the class file use full paths.

Example 2. Modify the class's bytecode file

C.java


package com.asm5;

public class C {
 public void m() throws InterruptedException{
 Thread.sleep(100); 
 }
}

Change the content of the C.java class to read:


package com.asm5;

public class C {
 public static long timer;
 public void m() throws InterruptedException{
 timer -= System.currentTimeMillis();
 Thread.sleep(100); 
 timer += System.currentTimeMillis();
 }
}

To see how ASM is implemented, we compile the two classes and compare their TraceClassVisitor output. We can see the following differences (in bold)

GETSTATIC C.timer : J

INVOKESTATIC java/lang/System.currentTimilis()J

LSUB

PUTSTATIC C.timer : J

LDC 100

INVOKESTATIC java/lang/Thread.sleep(J)V

GETSTATIC C.timer : J

INVOKESTATIC java/lang/System.currentTimilis()J

LADD

PUTSTATIC C.timer : J

RETURN

MAXSTACK=4

MAXLOCALS=1

By comparing the above instructions, we can see that four more instructions must be added at the beginning of the m() method, and four more before the RETURN directive, and these four must be before xRETURN and ATHROW, because these instructions always end the method execution.

The specific code is as follows:

AddTimeClassAdapter.java


package com.asm5;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class AddTimeClassAdapter extends ClassAdapter {
 private String owner;
 private boolean isInterface;
 public AddTimeClassAdapter(ClassVisitor cv) {
 super(cv);
 }
 @Override
 public void visit(int version, int access, String name, String signature,
  String superName, String[] interfaces) {
 cv.visit(version, access, name, signature, superName, interfaces);
 owner = name;
 isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
 }
 @Override
 public MethodVisitor visitMethod(int access, String name, String desc,
  String signature, String[] exceptions) {
 MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
 if(!name.equals("<init>") && !isInterface && mv!=null){
  // Add timing to methods 
  mv = new AddTimeMethodAdapter(mv);
 }
 return mv;
 }
 @Override
 public void visitEnd() {
 // Add fields 
 if(!isInterface){
  FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_STATIC, "timer", "J", null, null);
  if(fv!=null){
  fv.visitEnd();
  }
 }
 cv.visitEnd();
 }
 
 class AddTimeMethodAdapter extends MethodAdapter{
 public AddTimeMethodAdapter(MethodVisitor mv) {
  super(mv);
 }
 @Override
 public void visitCode() {
  mv.visitCode();
  mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
  mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
  mv.visitInsn(Opcodes.LSUB);
  mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
 }
 @Override
 public void visitInsn(int opcode) {
  if((opcode>=Opcodes.IRETURN && opcode<=Opcodes.RETURN) || opcode==Opcodes.ATHROW){
  mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
  mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
  mv.visitInsn(Opcodes.LADD);
  mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
  }
  mv.visitInsn(opcode);
 }
 @Override
 public void visitMaxs(int maxStack, int maxLocal) {
  mv.visitMaxs(maxStack+4, maxLocal);
 }
 }
 
}

Generator.java


package com.asm5;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;



public class Generator {

 public static void main(String[] args){
 try {
  ClassReader cr = new ClassReader("com/asm5/C");
  ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
  ClassAdapter classAdapter = new AddTimeClassAdapter(cw);
  // Give a given visitor access Java Of the class ClassReader
  cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
  byte[] data = cw.toByteArray();
  File file = new File(System.getProperty("user.dir") + "\\WebRoot\\WEB-INF\\classes\\com\\asm5\\C.class");
  FileOutputStream fout = new FileOutputStream(file);
  fout.write(data);
  fout.close();
  System.out.println("success!");
 } catch (FileNotFoundException e) {
  e.printStackTrace();
 } catch (IOException e) {
  e.printStackTrace();
 }
 }

}

Here is a test class:


package com.asm5;

public class Test {
 public static void main(String[] args) throws InterruptedException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
 C c = new C();
 c.m();
 Class cc = c.getClass();
 System.out.println(cc.getField("timer").get(c));
 }
}

The output is: 100

conclusion

The above is the whole content of this article, I hope the content of this article to your study or work can bring 1 definite help, if you have questions you can leave a message to communicate.


Related articles: