BaiFan
文章目录
  1. 1. 3.3 工具集 Tools
    1. 1.1. 3.3.1 基础工具集 Basic tools
      1. 1.1.1. Type
      2. 1.1.2. TraceClassVisitor
      3. 1.1.3. CheckClassAdapter
      4. 1.1.4. ASMifier
    2. 1.2. 3.3.2. AnalyzerAdapter
    3. 1.3. 3.3.3 局部变量排序器 LocalVariablesSorter
    4. 1.4. 3.3.4 AdviceAdapter

ASM-方法-工具类

3.3 工具集 Tools

‘org.objectweb.asm.commons’包含了一些预定义的方法适配器,可以用来定义自己的适配器。
本节介绍三个工具类,并且会展示它们如何和3.2.4节中的‘AddTimerAdapter’示例结合使用。
也展示了如何使用上一章中的工具简化方法的生成和转化。

3.3.1 基础工具集 Basic tools

在2.3章中介绍的工具也可以使用在方法操作上。

Type

许多字节码指令,例如‘xLOAD‘、 ‘xADD’或者‘xRETURN’,取决于它们所应用的类型。
‘Type’类提供了一个‘getOpcode’方法,对于这些指令,可以用来获取操作码所对应的给定类型。
这些方法需要一个整数型的操作码作为参数,调用后返回一个对应该类型的操作码。
例如‘t.getOpcode(IMUL)’,如果t是‘Type.FLOAT_TYPE’的话,就会返回‘FMUL’

TraceClassVisitor

这个类,在上一章已经介绍过了,以文本型的描述打印它所访问的类,包括文本型描述该类的方法,和本章中使用的方式非常类似。
因此可以用于在转换链中的任何一点上,跟踪生成或者转换方法的内容。例如:

1
2
3
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.TraceClassVisitor \
java.lang.Void

打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// class version 49.0 (49)
// access flags 49
public final class java/lang/Void {
// access flags 25
// signature Ljava/lang/Class<Ljava/lang/Void;>;
// declaration: java.lang.Class<java.lang.Void>
public final static Ljava/lang/Class; TYPE
// access flags 2
private <init>()V
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
MAXSTACK = 1
MAXLOCALS = 1
// access flags 8
static <clinit>()V
LDC "void"
INVOKESTATIC java/lang/Class.getPrimitiveClass (...)...
PUTSTATIC java/lang/Void.TYPE : Ljava/lang/Class;
RETURN
MAXSTACK = 1
MAXLOCALS = 0
}

这里展示类如何生成一个静态代码块‘static { … }’,使用命名为的方法(对应类的初始化)。
注意,如果你想跟踪链路某一点上单个方法,而不是跟踪该类的全部内容,你可以使用‘TraceMethodVisitor’类代替‘TraceClassVisitor’(在这种情况下,你必须明确指定后端;在这里我们使用‘Textifier’):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
// if this method must be traced
if (debug && mv != null &&...){
Printer p = new Textifier(ASM4) {
@Override
public void visitMethodEnd() {
// print it after it has been visited
print(aPrintWriter);
}
};
mv = new TraceMethodVisitor(mv, p);
}
return new MyMethodAdapter(mv);
}

这段代码将会打印经过‘MyMethodAdapter’转换的方法。

CheckClassAdapter

这个类已经在上一章中介绍过了,用于检查ClassVisitor方法是否按照适当顺序被调用、方法参数是否合法,它也同样适用于MethodVisitor的方法。
因此它可以被用于转换链路中的任意一点,用于检测MethodVisitor API是否被正确使用。
TraceMethodVisitor一样,你可以使用CheckMethodAdapter检测单个方法而不是整个类:

1
2
3
4
5
6
7
8
9
10
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
// if this method must be checked
if (debug && mv != null && ...) {
mv = new CheckMethodAdapter(mv);
}
return new MyMethodAdapter (mv);
}

这段代码检测MyMethodAdapter类使用‘MethodVisitor API’是否正确。
需要注意的是,这个是配器不会检测字节码是否争取:例如它不会见从
‘ISTORE 1 ALOAD 1’是否正确。
事实上,这类错误是可以被检测到的,如果你使用
CheckMethodAdapter其他的构造函数(参考Javadoc),
并且如果你为
visitMaxs方法提供合法的方maxStackmaxLocals**参数。

ASMifier

这个类在上一章中介绍过,也可以用于方法的内容。
它可以被用来知道如果通过ASM生成某些编译后的代码:在Java中只要编写相应的源码即可,使用javac编译,并且使用ASMifier访问编译后的类。
你将会得到ASM代码,来生成相应源码的字节码。

3.3.2. AnalyzerAdapter

这个方法适配器会根据visitFrame方法中被访问的帧,计算出每一个指令之前的栈哈希帧。
事实上,如前面3.1.5节所描述的,为了节省空间,visitFrame仅仅会在一个方法中某些特定的指令前调用,并且“其他的帧也可以从这些帧简单容易的推算出来”。
这就是AnalyzerAdapter的作用。当然在它仅能作用在包含了预先计算过栈哈希帧的编译类,即使用Java 6或者更改版本编译的类(或者像之前的示例一样,使用含有COMPUTE_FRAMES参数的ASM adapter将类升级到Java 6):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class AddTimerMethodAdapter2 extends AnalyzerAdapter {
private int maxStack;
public AddTimerMethodAdapter2(String owner, int access, String name, String desc, MethodVisitor mv) {
super(ASM4, owner, access, name, desc, mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitInsn(LSUB);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
maxStack = 4;
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitInsn(LADD);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
maxStack = Math.max(maxStack, stack.size() + 4);
}
super.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals);
}
}

‘stack’属性在AnalyzerAdapter类中有定义,并且包含了在操作栈中的类型。
更确切地说,对于一个‘visitXxx Insn’指令,在覆盖方法被调用前,这个列表包含了在该条指令前操作栈的状态。
需要注意的是覆盖方法必须被调用,这样栈里的属性才能正确地更新(因此使用父类的原始方法,而不是mv的方法)。
另外,调用父类的方法也可以插入新指令:效果是AyalyzerAdapter会计算出这些指令对应的帧。
因此,该适配器会基于它计算出的帧更新visitMaxs方法的参数,我们就不必更新这些参数了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class AddTimerMethodAdapter3 extends AnalyzerAdapter {
public AddTimerMethodAdapter3(String owner, int access, String name, String desc, MethodVisitor mv) {
super(ASM4, owner, access, name, desc, mv);
}
@Override
public void visitCode() {
super.visitCode();
super.visitFieldInsn(GETSTATIC, owner, "timer", "J");
super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
super.visitInsn(LSUB);
super.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
super.visitFieldInsn(GETSTATIC, owner, "timer", "J");
super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
super.visitInsn(LADD);
super.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
super.visitInsn(opcode);
}
}

3.3.3 局部变量排序器 LocalVariablesSorter

这个方法适配器会根据本地变量出现的顺序重新排列本地变量。
例如一个包含两个参数的方法,第一个局部变量读写的索引大于等于3(索引值是从0开始的),前三个本地变量对应this和两个方法参数(这是不可改变的),第一个变量索引值是3,第二个是4,依此类推。
这个适配器对于插入新的本地变量很有帮助。
如果没有这个适配器,就必须在所有本地变量之后插入新的本地变量,但不幸的是这些变量的编号在方法结束前(即visitMaxs方法前)是不知道的。
为了展示这个适配器是如何被使用的,我们假设想使用一个本地变量实现AddTimerAdapter

1
2
3
4
5
6
7
8
public class C {
public static long timer;
public void m() throws Exception {
long t = System.currentTimeMillis();
Thread.sleep(100);
timer += System.currentTimeMillis() - t;
}
}

这个用继承LocalVariablesSorter类的方式可以很容易实现,通过使用该类中的newLocal方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class AddTimerMethodAdapter4 extends LocalVariablesSorter {
private int time;
public AddTimerMethodAdapter4(int access, String desc, MethodVisitor mv) {
super(ASM4, access, desc, mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
time = newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, time);
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitVarInsn(LLOAD, time);
mv.visitInsn(LSUB);
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitInsn(LADD);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
super.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals);
}
}

注意,当本地变量重写编号后,该方法关联的原始帧就都失效了,更不用说插入新的本地变量了。
庆幸的是避免重头计算这些帧是可行的:事实上没有任何帧需要被添加或者删除,在原本的帧上有足够的空间可以重新排列转换后方法的本地变量。
LocalVariablesSorter会自动处理这些。
如果需要为一个新的方法适配器增加栈哈希帧,你可以从这个类的源码获得提示。
如上所见,在该类的原始版本中,考虑到maxStack的最坏情况,使用一个本地变量不能解决这个问题。
如果你想使用AnalyzerAdapter来解决这个问题,除了LocalVariablesSorter,你必须通过委托机制使用这些适配器,而不是继承(因为多继承是不被允许的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class AddTimerMethodAdapter5 extends MethodVisitor {
public LocalVariablesSorter lvs;
public AnalyzerAdapter aa;
private int time;
private int maxStack;
public AddTimerMethodAdapter5(MethodVisitor mv) {
super(ASM4, mv);
}
@Override
public void visitCode() {
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
time = lvs.newLocal(Type.LONG_TYPE);
mv.visitVarInsn(LSTORE, time);
maxStack = 4;
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitVarInsn(LLOAD, time);
mv.visitInsn(LSUB);
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitInsn(LADD);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
maxStack = Math.max(aa.stack.size() + 4, maxStack);
}
mv.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
mv.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals);
}
}

为了使用这个适配器,你必须将一个LocalVariablesSorter连接到AnalyzerAdapter,它自身则链接到自定义的适配器上:因此第一个适配器会排列本地变量并且更新帧,考虑到上一章中的重编号,analyzer adapter会计算中间帧,自定义的适配器可以访问到中间帧中重排序的编号。
visitMethod方法中,这个链路可以被构造成如下所示:

1
2
3
4
5
6
7
mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (!isInterface && mv != null && !name.equals("<init>")) {
AddTimerMethodAdapter5 at = new AddTimerMethodAdapter5(mv);
at.aa = new AnalyzerAdapter(owner, access, name, desc, at);
at.lvs = new LocalVariablesSorter(access, desc, at.aa);
return at.lvs;
}

3.3.4 AdviceAdapter

这个方法适配器是一个抽象类,可以用于在方法的开始和RETURNATHROW指令前插入代码。
它的主要优点是,也可以对构造函数起作用,代码不能在构造函数的一开始就插入,要在调用父类的构造函数后插入。
事实上,本节的大部分代码都是为了检测父类的构造函数而调用的。
如果仔细研究3.2.4节中的AddTimerAdapter类,你会发现因为这个原因,AddTimerMethodAdapter没有用于构造函数。
通过继承AdviceAdapter这个方法适配器,改进后也可以适用构造函数(注意,AdviceAdapter继承了LocalVariablesSorter,因此我们可以非常方便的使用一个本地变量):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class AddTimerMethodAdapter6 extends AdviceAdapter {
public AddTimerMethodAdapter6(int access, String name, String desc, MethodVisitor mv) {
super(ASM4, mv, access, name, desc);
}
@Override
protected void onMethodEnter() {
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitInsn(LSUB);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
@Override
protected void onMethodExit(int opcode) {
mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
mv.visitInsn(LADD);
mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals);
}
}

文章目录
  1. 1. 3.3 工具集 Tools
    1. 1.1. 3.3.1 基础工具集 Basic tools
      1. 1.1.1. Type
      2. 1.1.2. TraceClassVisitor
      3. 1.1.3. CheckClassAdapter
      4. 1.1.4. ASMifier
    2. 1.2. 3.3.2. AnalyzerAdapter
    3. 1.3. 3.3.3 局部变量排序器 LocalVariablesSorter
    4. 1.4. 3.3.4 AdviceAdapter