0%

ASM4-方法

方法

3.1 结构

3.1.1 执行模型

每个线程都有自己的执行栈,栈由栈帧组成。每个帧表示一个方法调用。每次调用一个方法时,会将一个新栈帧push入当前线程的执行栈。当方法返回时,会讲这个帧从执行栈中弹出,执行过程在发出调用的方法中继续进行每一帧包括

  • 操作数栈
  • 局部变量表
  • 动态链接(DL)
  • 方法返回

局部变量表与操作数栈大小取决于方法的代码。

在创建一个帧时会将其初始化,提供一个空栈,并用目标对象this(非静态方法),以及该方法的参数来初始化局部变量。比如调用a.equals(b)将创建一个帧,它有一个空栈,前两个局部变量被初始化为a和b

3.1.2 字节代码指令

字节代码指令由一个表示该指令的操作码和固定数目的参数组成

  • 操作码 是由一个无符号字节值,由助记符号标识。
  • 参数时静态值,确定了精确的指令行为

字节代码指令可以分为两类

  1. 一小组指令。设计用在局部变量表和操作数栈之间传送值
  2. 仅用于操作数栈:从栈中弹出一些值,根据这些值计算一个结果,并将它压回栈中

ILOAD,LLOAD,FLOAD,DLOAD,ALOAD。 他们的参数必须是读取的局部变量的索引i。

  • ILOAD 加载 boolean、byte、char、short、int局部变量
  • LLoad、FLoad、DLoad 分别用于记载long,float,double的值
  • ALoad 用于加载任意非基元值即对象和数组

ISTORE, LSTORE,FSTORE, DSTORE, ASTORE 从操作数栈中弹出一个值,并且将它存储在由索引i指定的局部变量中

xLoad和xStore指令被赋入了类型。确保不会执行非法转换。实际上,将一个值存储在局部变量中,然后再以不同的类型加载它是非法的。比方说ISTORE 1ALOAD1 序列是非法的。但是向一个局部变量中存储一个值,而这个值的类型不同于该局部变量中存储的当前值,却是完全合法的。

  • POP弹出栈顶部的值,DUP 压入顶部,SWAP弹出两个值,并且按逆序压入他们

  • 常量

    这些指令在操作数栈压入一个常量值:ACONST_NULL 压入null,ICONST_0 压入int0, FCONST_0 压入0f,DCONST_0压入0d,BIPUSH b 压入字节值b,SIPUSH s 压入short值 s,LDC 压入任意int、float、long、double、String或者class 常量cst

  • 算数与逻辑

    这些指令从操作数栈弹出数值,合并他们,并将结果压入栈中。他们没有任何参数。xADD,xSUB,xMUL,xDIV和xREM。

  • 类型变换

    这些指令从栈中弹出一个值,将其转换为另一种类型,并将结果压入栈中。他们对应Java中的类型转换表达式。I2F,F2D,L2D等将数值由一种数值类型转换为另一种类型。CHECKCAST t将一个引用值类型转换为类型t

  • 对象

    这些指令用于创建对象、锁定他们、检测他们的类型,等等。 NEW type 指令将一个type类型的新对象压入栈中

  • 字段

    这些指令读或者写一个字段的值。GETFIELD ower name desc 弹出一个对象引用,并将name字段中的值压入栈中。PUTFIELD owner name desc 弹出一个值和一个对象引用,并且将这个值存储在它的name字段中。在这两种情况下,该对象都必须是owner类型,它的字段必须是desc类型。GETSTATIC和PUTSTATIC是类似的指令

  • 方法

    这些指令调用一个方法或构造器。它们弹出值的个数等于其方法参数个数 + 1(用于目标对象),并压回方法调用的结果。 INVOKEVIRTUREAL owner name des调用在类owner中定义的name方法,其方法描述符为desc。INVOKESTATIC 用于静态方法,INVOKESPECIAL用于私有方法和构造器。INVOKDYNAMIC用于动态方法调用机制

  • 数组

    这些指令用于读写数组中的值。xALOAD指令弹出一个索引和一个数组,并压入此索引处数组元素的值。xASTORE指令弹出一个值,一个索引和一个数组,并将这个值存入在该数组的这一索引处。x可以为I(int)、L(long)、F(float)、D(double)、A(other)、B(byte)、C(char)、S(short)

  • 跳转

    这些指令无条件的或者在某一条件为真的时候跳转到任意指令。它们用于编译if、for、do、while、break、continue指令。IFEQ label 从栈中弹出一个int值,如果这个值是0,跳转到label指定的指令处。还有其他的指令IFNE、IFGE、TABLESWITCH、LOOKUPSWITCH 对应switch 指令

  • 返回

    RETURN用于返回void方法,xRETURN用于其他方法

3.1.3 举例说明

1
2
3
4
5
6
7
8
9
10
11
public class Bean {
private int f;

public int getF() {
return f;
}

public void setF(int f) {
this.f = f;
}
}

3.1.3.1 getter

getter的字节码为

1
2
3
ALOAD 0
GETFIELD pkg/Bean f I
IRETURN

读取局部变量0,(局部变量0在为这个方法调用创建帧期间被初始化为this),并且将这个值压入操作数栈中。

从操作数栈中弹出这个值,并且将这个对象的f字段压入栈中

从栈中弹出这个值,返回给调用者

3.1.3.2 setter

setter方法的字节码

1
2
3
4
ALOAD 0
ILOAD 1
SETFIELD pkg/Bean f I
RETURN

读取局部变量0,(局部变量0在为这个方法调用创建帧期间被初始化为this),并且将这个值压入操作数栈中。

压入局部变量1

弹出这两个值,并将int值存储在被引用对象的f字段中。

销毁当前执行帧,并返回调用者。

3.1.3.3 contractor

bean类有一个默认的构造函数,在没有定义的情况下是由编译器生成的。

1
2
3
Bean() {
super();
}

构造函数

1
2
3
ALOAD 1
INVOKESPECIAL java/lang/Object <init> ()V
RETURN

读取局部变量0,(局部变量0在为这个方法调用创建帧期间被初始化为this),并且将这个值压入操作数栈中。

调用Object对象中定义的<init>方法

3.1.3.4 复杂的使用

1
2
3
4
5
6
7
public void checkAndSetF(int f) {
if (f >= 0) {
this.f = f;
} else {
throw new IllegalArgumentException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
ILOAD 1
IFLT label
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean f I
GOTO end
label:
NEW java/lang/IllegalArgumentException
DUP
INVOKESPECIAL java/lang/IllegalArgumentException <init>()V
ATHROW
end:
RETURN

3.1.3.5 异常处理器

1
2
3
4
5
6
7
public static void sleep(long d) {
try {
Thread.sleep(d);
} catch (Exception e) {
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
TRYCATCHBLOCK try catch catch java/lang/InterrutedExcetion
try:
LLOAD 0
INVOKESTATIC java/lang/Thread sleep(J)V
RETURN
catch:
INVOKEVIRTUAL java/lang/InterruptedException printStackTrace ()V
RETURN

TRYCATCHBLOCK行制定了一个异常处理器, 覆盖了try和catch标记之间的范围,有一个开始于catch标记的处理器,用于处理一些异常,这些异常是InterruptedException的子类。如果在try和catch之间抛出了一个异常,栈将被清空,异常被压入这个空栈中,执行过程在catch中继续。

3.1.3.6 帧

除了字节码指令外,Java6或者更高的编译类中还包括一组栈映射帧,用于加快Java虚拟机中类验证过程的速度。它给出了就要执行某一特定字节码指令之前,每个局部变量槽和每个操作数栈槽总包含的值的类型

为节省空间,已编译方法中没有为每个指令包含一个帧 它仅为那些应对跳转目标或者异常处理器的指令,或者跟在无条件跳转指令之后的指令创建帧。

在checkAndSetF 方法的情景中。仅存储两个帧 一个是用于NEW指令,另一个用于RETURN指令。因为它是GOTO指令的目标,还因为它跟在无条件跳转ATHROW指令后。

3.2 接口和组件

用于生成和转换已编译方法的ASM api 是基于MethodVisitor抽象类的。它由ClassVisitor的visitMethod返回。这些方法必须按照以下顺序调用

visitAnntationDefault?

(visitAnnotation | visitParameterAnnotation | visitAttribute )*

(visitCode

(visitTryCatchBlock | vistLabel | visitFrame | visitXxxInsn | visitLocalVariable | visitLineNumber) *

visitMaxs)?

visitEnd