ASM(字节码处理工具)

Posted 凉茶方便面

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASM(字节码处理工具)相关的知识,希望对你有一定的参考价值。

ASM是一种Java字节码生成和分析的框架,我们可以用它通过操作二进制的方式来修改现成的类或者动态生成class文件。ASM不仅提供了和其他字节码生成框架相类似的功能,此外它还关注框架的易用性和性能。

ASM的源代码中包含了若干packages:

  • org.objectweb.asm:是项目的核心包,它提供了ASM的访问API,并且提供了ClassReader和ClassWriter来读取和生成Java的class文件。这个不依赖于其他包,可以单独使用。
  • org.objectweb.asm.signature:提供了读写签名(这些签名可以是类型的签名,或者是方法的签名)的API,它独立于核心包,同时完善核心包的内容。
  • org.objectweb.asm.tree:提供一种类似于DOM操作的API,从而便于实现复杂形式的类。
  • org.objectweb.asm.tree.analysis:在tree包的基础上形成了一种静态字节码分析工具,可以用来实现需要明确知道栈帧中每条指令的状态的复杂情况。
  • org.objectweb.asm.util:可以提供一些类的visitor和adapter,用来调试,通常运行时不需要使用。
  • org.objectweb.asm.xml:提供了class文件和XML文件之间的相互转换的功能,但是基于性能考虑不建议在现实中使用(最好使用核心包)。
  • org.objectweb.asm.commons:提供一些有用的adapter(基于核心包),这些adapter可被用于实现更复杂的类转换功能。

整个项目中的转换和编译文件的工作都是由ClassReader来做的,而转换处理工作则是由与之相关的其他类来做的。

  • Classes:ClassWriter 是主要的入口,它包含着类文件的版本,访问标记,类名等信息,同时还包含着表示常量池,域,方法,注解,类的额外属性之类的内容;
  • Constant pool:Item类被用于表示常量池中的条目;
  • Field:FieldWriter用于写类的域,它包含着域的名字,类型,签名以及值,同时它还包含着描述域的注解和属性的对象;
  • Methods:MethodWriter用于编写类的方法,它包含方法名字,签名,异常列表,方法注解,以及额外属性;Handler用于表示try-catch代码块,每个Handler包含三个Lable对象用于表示try的开始和结束以及catch代码块;Label类被用于表示指令,但是同时也表示基本代码块,可以使用它计算栈帧的大小;Frame用于自动处理每个方法的栈帧;Edge用于表示每个方法的控制流。
  • Annotations:AnnotationWriter被用于处理注解,它会被ClassWriter,FieldWriter和MethodWriter引用,因为所有的类,域,方法都可以包含注解;
  • Attributes:Attributes类可以被用来读写类的非标准属性;
  • Ressources:ByteVector被用来在访问类属性时序列化这些属性,它被用来表示常量池,注解值,方法代码,栈图,行号。


以下是官方给出的示例,用于生成一个包含main函数的Example类:
import java.io.FileOutputStream;
import java.io.PrintStream;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

/**
 * @author Eric Bruneton
 */
public class Helloworld extends ClassLoader implements Opcodes 

    public static void main(String args[]) throws Exception 
        // 生成如下类文件
        // public class Example 
        //     public static void main (String[] args) 
        //         System.out.println("Hello world!");
        //     
        // 

        // 为Example提供ClassWriter,要求Example继承自Object
        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_1, ACC_PUBLIC, "Example", null, "java/lang/Object", null);

        // 为隐含的默认构造器创建MethodWriter
        MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,
                null);
        // 将this变量放入局部变量表
        mw.visitVarInsn(ALOAD, 0);
        // 调用父类的默认构造函数
        mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V",false);
        mw.visitInsn(RETURN);
        // 这段代码使用了最大为1的栈元素,且只有一个局部变量
        mw.visitMaxs(1, 1);
        mw.visitEnd();

        // 为main方法创建MethodWriter 
        mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",
                "([Ljava/lang/String;)V", null, null);
        // 将System的out域入栈
        mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
                "Ljava/io/PrintStream;");
        // String类型的"Hello World!"常量入栈
        mw.visitLdcInsn("Hello world!");
        // 调用System.out的println方法
        mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
                "(Ljava/lang/String;)V", false);
        mw.visitInsn(RETURN);
        // 这段代码使用了最大为2的栈元素,包含两个局部变量
        mw.visitMaxs(2, 2);
        mw.visitEnd();

        // 获得Example类的字节码,并且动态加载它
        byte[] code = cw.toByteArray();

        FileOutputStream fos = new FileOutputStream("Example.class");
        fos.write(code);
        fos.close();

        Helloworld loader = new Helloworld();
        Class<?> exampleClass = loader.defineClass("Example", code, 0,
                code.length);

        // 使用动态生成的类打印'Helloworld'
        exampleClass.getMethods()[0].invoke(null, new Object[]  null );

        // ------------------------------------------------------------------------
        // 使用GeneratorAdapter的示例(很方便但是更慢)
        // ------------------------------------------------------------------------

        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(V1_1, ACC_PUBLIC, "Example", null, "java/lang/Object", null);

        // 为隐含的默认构造器创建GeneratorAdapter 
        Method m = Method.getMethod("void <init> ()");
        GeneratorAdapter mg = new GeneratorAdapter(ACC_PUBLIC, m, null, null, cw);
        mg.loadThis();
        mg.invokeConstructor(Type.getType(Object.class), m);
        mg.returnValue();
        mg.endMethod();

        // 为main方法创建GeneratorAdapter
        m = Method.getMethod("void main (String[])");
        mg = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, m, null, null, cw);
        mg.getStatic(Type.getType(System.class), "out",
                Type.getType(PrintStream.class));
        mg.push("Hello world!");
        mg.invokeVirtual(Type.getType(PrintStream.class),
                Method.getMethod("void println (String)"));
        mg.returnValue();
        mg.endMethod();

        cw.visitEnd();

        code = cw.toByteArray();
        loader = new Helloworld();
        exampleClass = loader.defineClass("Example", code, 0, code.length);

        // 使用动态生成的类打印'Helloworld'
        exampleClass.getMethods()[0].invoke(null, new Object[]  null );
    
从上边的代码可以看出,整个操作过程就是定义了字节码级别的类、方法处理的流程,将这些流程定义好后可以产生类中的类,方法,域,代码段等一系列信息,然后将这些信息汇总,最后就可以得到定义好流程的class文件的字节码了。

以上是关于ASM(字节码处理工具)的主要内容,如果未能解决你的问题,请参考以下文章

字节码插桩AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )

ASM字节码操作 工具类与常用类 CheckClassAdapter 介绍

ASM字节码操作 工具类与常用类 PrinterASMifierTextifier 介绍

ASM字节码操作 工具类与常用类 SerialVersionUIDAdder 介绍

ASM字节码操作 工具类与常用类 asm-utils 与 asm-commons

ASM字节码操作 工具类与常用类 InstructionAdapter 介绍