您的位置:首页 > 编程语言 > Java开发

java字节码及ASM编程

2015-08-30 16:11 477 查看

java字节码及ASM编程

方式:内容来自《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》

java字节码

Java 源文件经过 javac 编译器编译之后,将会生成对应的二进制文件。

每个合法的 Java 字节码文件都具备精确的定义,而正是这种精确的定义,才使得 Java 虚拟机得以正确读取和解释所有的 Java 字节码文件。

如最简单的helloworld

public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world");
}
}




图示为javac编译的过程;java文件经过javac编译成class文件;class文件遵守java类定义规范;

可以在这里下载到完整的书籍

编译后的class文件可以被加载到jvm中运行;

下图为class文件的几个重要组成部分:



Magic:一个 Java 字节码文件的前 4 个字节被称为它的魔数。每个正确的 Java 字节码文件都是以 0xCAFEBABE 开头的,这样保证了 Java 虚拟机能很轻松的分辨出 Java 文件和非 Java 文件。

Version:该项存放了 Java 字节码文件的版本信息,它对于一个 Java 文件具有重要的意义。高版本的虚拟机可以处理低版本的文件;反之则不能处理

Constant Pool:该项存放了类中各种文字字符串、类名、方法名和接口名称、final 变量以及对外部类的引用信息等常量。虚拟机必须为每一个被装载的类维护一个常量池,常量池中存储了相应类型所用到的所有类型、字段和方法的符号引用;

Access_flag:该项指明了该文件中定义的是类还是接口,同时还指名了类或接口的访问标志,如 public,private, abstract 等信息。访问标记定义如下:

ACC_PUBLIC:是否为public类型
ACC_PRIVATE:是否为private类型
ACC_PROTECTED:是否为protected类型
ACC_STATIC:是否为static类型
ACC_VOLATILE:是否为volatile类型
ACC_TRANSIENT:是否是transient类型
ACC_SYNTHETIC:是否是编译器自动生成
ACC_FINAL:是否是final
ACC_SUPER:是否允许使用invokespecial的新语义;jdk1.0.2后一直为真
ACC_INTERFACE:是否是接口
ACC_ABSTRACE:是否是抽象类
ACC_SYNTHETIC:是否由用户代码生成
ACC_ANNOTATION:是否是注解
ACC_ENUM:是否是枚举类型


This Class:指向表示该类全限定名称的字符串常量的指针。

Super Class:指向表示父类全限定名称的字符串常量的指针。

Interfaces:一个指针数组,存放了该类或父类实现的所有接口名称的字符串常量的指针。

Fields:该项对类或接口中声明的字段进行了细致的描述。需要注意的是,fields 列表中仅列出了本类或接口中的字段,并不包括从超类和父接口继承而来的字段。字节码定义如下:

B:对应byte类型
J:对应long
C:对应char
S:对应short
D:对应duble
Z:对应boolean
F:对应float
V:对应void
I:对应int
L:类的全限定符开始,以;结束。如  Ljava.lang.String;
[:数组的一个维度;如int[][] 就表示为 [[I


Methods:该项对类或接口中声明的方法进行了细致的描述。例如方法的名称、参数和返回值类型等。需要注意的是,methods 列表里仅存放了本类或本接口中的方法,并不包括从超类和父接口继承而来的方法。使用 ASM 进行 AOP 编程,通常是通过调整 Method 中的指令来实现的。

Class attributes:该项存放了在该文件中类或接口所定义的属性的基本信息。

ASM

ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。

ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。

使用ASM框架需要引入ASM的包:可以在你的pom文件中加入下面这段来引入ASM;

<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>


ASM核心类

ASM 提供了三个基于 ClassVisitor 接口的类来实现 class 文件的生成和转换:

ClassReader:ClassReader 解析一个类的 class 字节码,该类的 accept 方法接受一个 ClassVisitor 的对象,在 accept 方法中,会按上文描述的顺序逐个调用 ClassVisitor 对象的方法。它可以被看做事件的生产者。

ClassAdapter:ClassAdapter 是 ClassVisitor 的实现类。它的构造方法中需要一个 ClassVisitor 对象,并保存为字段 protected ClassVisitor cv。在它的实现中,每个方法都是原封不动的直接调用 cv 的对应方法,并传递同样的参数。可以通过继承 ClassAdapter 并修改其中的部分方法达到过滤的作用。它可以看做是事件的过滤器。

ClassWriter:ClassWriter 也是 ClassVisitor 的实现类。ClassWriter 可以用来以二进制的方式创建一个类的字节码。对于 ClassWriter 的每个方法的调用会创建类的相应部分。例如:调用 visit 方法就是创建一个类的声明部分,每调用一次 visitMethod 方法就会在这个类中创建一个新的方法。在调用 visitEnd 方法后即表明该类的创建已经完成。

HelloWorld

package com.violetgo.asm;

/**
* @author weigao
* @since 15/6/8
*/
public class HelloWorld {
public void sayHello() {
System.out.println("Hello World!");
}
}

package com.violetgo.asm;

import org.objectweb.asm.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

/**
* @author weigao
* @since 15/6/8
*/
public class TestASM extends ClassLoader implements Opcodes {

public static void main(String[] args) throws IOException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException,InstantiationException {
ClassReader cr=new ClassReader(HelloWorld.class.getName());
ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter adapter = new TestClassAdapter(cw);
cr.accept(adapter, 0);

byte[] code=cw.toByteArray();

//自定义加载器
TestASM loader=new TestASM();
Class<?> appClass=loader.defineClass(null, code, 0,code.length);
appClass.getMethods()[0].invoke(appClass.newInstance(), new Object[]{});

}

public static class TestClassAdapter extends ClassAdapter{

public TestClassAdapter(ClassVisitor classVisitor) {
super(classVisitor);
}

public MethodVisitor visitMethod(int arg, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(arg, name, descriptor, signature, exceptions);
if (name.equals("sayHello")) {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V");
}
return mv;
}
}
}


输出结果如下、标红部分即为我们注入进去的代码::



java常用操作码

Tips:在使用asm编码时;如果遇到较为复杂的类;可以先使用java写一遍;然后再使用javap –c –p 进行反编译;然后针对javap的指令集转换成asm编码即可;

下面介绍下java常用指令;此处不是全部指令

aconst_null 将null推送至栈顶;相当于定义null

ldc 将int, float或String型常量值从常量池中推送至栈顶

iload 将指定的int型本地变量推送至栈顶;在方法内iload_n就是第几个变量;对应的还有lload、fload、dload、aload

iload_0 将第一个int型本地变量推送至栈顶;

aload_0 将第一个引用类型本地变量推送至栈顶;在类中;aload_0通常指向this指针

astore 将栈顶引用型数值存入指定本地变量;用于存储变量

areturn 从当前方法返回对象引用

return 从当前方法返回void

getstatic 获取指定类的静态域,并将其值压入栈顶

putstatic 为指定的类的静态域赋值

getfield 获取指定类的实例域,并将其值压入栈顶

putfield 为指定的类的实例域赋值

invokevirtual 调用实例方法

invokespecial 调用超类构造方法,实例初始化方法,私有方法

invokestatic 调用静态方法

invokeinterface 调用接口方法

arraylength 获得数组的长度值并压入栈顶

new 创建一个对象,并将其引用值压入栈顶

newarray 创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶

goto 无条件跳转

ifeq 当栈顶int型数值等于0时跳转

pop 将栈顶数值弹出 (数值不能是long或double类型的)

dup 复制栈顶数值并将复制值压入栈顶

iadd 将栈顶两int型数值相加并将结果压入栈顶

isub 将栈顶两int型数值相减并将结果压入栈顶

imul 将栈顶两int型数值相乘并将结果压入栈顶

idiv 将栈顶两int型数值相除并将结果压入栈顶

if_icmpeq 比较栈顶两int型数值大小,当结果等于0时跳转

athrow 将栈顶的异常抛出

checkcast 检验类型转换,检验未通过将抛出ClassCastException

instanceof 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶

monitorenter 获得对象的锁,用于同步方法或同步块

monitorexit 释放对象的锁,用于同步方法或同步块

ifnull 为null时跳转

ASM常用操作码

visitInsn 可以用于的操作指令:NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1,IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1,DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG,LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S,LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT.

visitFieldInsn 可以用于的操作指令:GETSTATIC, PUTSTATIC, GETFIELD, or PUTFIELD.

visitIntInsn 可以用于的操作指令:BIPUSH, SIPUSH, or NEWARRAY.

visitJumpInsn 可以用于的操作指令:IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL, or IFNONNULL.

visitTypeInsn 可以用于的操作指令:NEW, ANEWARRAY, CHECKCAST, or INSTANCEOF.

visitVarInsn 可以用于的操作指令:ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE, or RET.

visitMethodInsn 可以用于的操作指令:INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, or INVOKEINTERFACE.

visitIincInsn 自增.

visitLdcInsn 定义变量LDC

visitLabel label.用于跳转

相关项目

Tprofile-依赖分析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: