Core API Method之接口和组件
来源:互联网 发布:云计算的概念 编辑:程序博客网 时间:2024/05/20 22:02
Presentation
生成和转换编译方法的ASM API是基于MethodVisitor接口,它通过ClassVisitor的vistMethod方法返回。除了annotation和debug信息外,基于指令参数类型个数,该接口为每个字节码指令类别定义了一个方法。这些方法必须以以下顺序调用:
visitAnnotationDefault?( visitAnnotation | visitParameterAnnotation | visitAttribute )*( visitCode( visitTryCatchBlock | visitLabel | visitFrame | visitXxxInsn |visitLocalVariable | visitLineNumber )*visitMaxs )?visitEnd如果有annotation和attribute,必须首先访问。接着就是方法的指令。对这些方法来说,在visitCode和visitMaxs之间,代码必须顺序访问。
public interface MethodVisitor { AnnotationVisitor visitAnnotationDefault(); AnnotationVisitor visitAnnotation(String desc, boolean visible); AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible); void visitAttribute(Attribute attr); void visitCode(); void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack); void visitInsn(int opcode); void visitIntInsn(int opcode, int operand); void visitVarInsn(int opcode, int var); void visitTypeInsn(int opcode, String desc); void visitFieldInsn(int opc, String owner, String name, String desc); void visitMethodInsn(int opc, String owner, String name, String desc); void visitJumpInsn(int opcode, Label label); void visitLabel(Label label); void visitLdcInsn(Object cst); void visitIincInsn(int var, int increment); void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]); void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]); void visitMultiANewArrayInsn(String desc, int dims); void visitTryCatchBlock(Label start, Label end, Label handler, String type); void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index); void visitLineNumber(int line, Label start); void visitMaxs(int maxStack, int maxLocals); void visitEnd();}visitCode和visitMaxs方法可以用来探测方法指令代码的开始和结束。
ClassVisitor和MethodVisitor接口结合使用来生成完整的类:
ClassVisitor cv = ...;cv.visit(...);MethodVisitor mv1 = cv.visitMethod(..., "m1", ...);mv1.visitCode();mv1.visitInsn(...);...mv1.visitMaxs(...);mv1.visitEnd();MethodVisitor mv2 = cv.visitMethod(..., "m2", ...);mv2.visitCode();mv2.visitInsn(...);...mv2.visitMaxs(...);mv2.visitEnd();cv.visitEnd();记住,没有必要来完成一个method来结束访问另外一个方法。实际上MethodVisitor实例是完全独立的,可以以任何顺序访问。
ClassVisitor cv = ...;cv.visit(...);MethodVisitor mv1 = cv.visitMethod(..., "m1", ...);mv1.visitCode();mv1.visitInsn(...);...MethodVisitor mv2 = cv.visitMethod(..., "m2", ...);mv2.visitCode();mv2.visitInsn(...);...mv1.visitMaxs(...);mv1.visitEnd();...mv2.visitMaxs(...);mv2.visitEnd();cv.visitEnd();ASM基于MethodVisotr接口提供了三个核心组件来生成和转换方法:ClassReader类转换编译方法的内容,且调用MethodVisitor对象的相关方法。
1) ClassReader类转换编译方法的内容,且调用MethodVisitor对象的相关方法。
2) ClassWriter的visitMethod方法返回MethodVisitor接口的实现,它直接以二进制方式构建编译方法。
3) MethodAdapter是MethodVisitor的一个实现,它代理另一个MethodVisitor实例的所有方法。
ClassWriter可选项
计算一个方法的stack map frame并不容易,你必须计算所有的frame,且找到对应跳转到目标对象的frame,或无条件跳转,最后压缩剩余的frames。同样,计算本地变量和操作栈的大小并不容易。幸运的是ASM可以为你计算,通过指定什么被自动计算的方式来创建ClassWriter。
1) new ClassWriter(0),不会计算任何东西,你必须计算自己的frames和本地变量和操作栈大小。
2) new ClassWriter(ClassWriter.COMPUTER_MAXS),本地变量和操作栈的大小被计算。你必须调用visitMaxs,但是可是使用任何参数,它们会被忽略,重计算。使用这个选项,你不得不计算frames。
3) new ClassWriter(ClassWriter.COMPUTER_FRAMES),一切都被计算好了。没有必要调用visitFrame,但是必须调用visitMaxs。
使用这些选项很便利,但有很大的代价:COMPUTE_MAXS选项使得ClassWriter慢10%,使用COMPUTE_FRAMES慢2倍。
如果你选择自己计算frames,你可使得ClassWriter类为你压缩。如果这样的话,你必须访问未压缩frames,visitFrame(F_NEW,nLocals,locals,nStack, stack),这里的nLocals和nStacks是本地变量和操作栈大小,locals和stack是包含对应类型的数组。
Generating methods
在上面提及的getF方法的字节码可以通过以下方法来生成:mv.visitVarInsn(ALOAD, 0);mv.visitFieldInsn(GETFIELD, "pkg/Bean", "f", "I");mv.visitInsn(IRETURN);mv.visitMaxs(1, 1);mv.visitEnd();
第一个调用开始字节码生成,后面的三个调用生成方法的三条指令。visitMaxs必须在所有指令被访问过后才被调用。它定义执行帧的本地变量和操作栈大小。最后一个方法调用结束方法的生成。
setF方法的字节码和构造器的字节码生成的方式类似。更加复杂的是checkAndSetF方法:
mv.visitCode();mv.visitVarInsn(ILOAD, 1);Label label = new Label();mv.visitJumpInsn(IFLT, label);mv.visitVarInsn(ALOAD, 0);mv.visitVarInsn(ILOAD, 1);mv.visitFieldInsn(PUTFIELD, "pkg/Bean", "f", "I");Label end = new Label();mv.visitJumpInsn(GOTO, end);mv.visitLabel(label);mv.visitFrame(F_SAME, 0, null, 0, null);mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL,"java/lang/IllegalArgumentException", "<init>", "()V");mv.visitInsn(ATHROW);mv.visitLabel(end);mv.visitFrame(F_SAME, 0, null, 0, null);mv.visitInsn(RETURN);mv.visitMaxs(2, 2);mv.visitEnd();visitCode和visitEnd之间的调用和checkAndSetF方法做了精确的映射。一个Lable对象指明了visitLable后面的指令是属于这个lable范畴的。可能有多个lable指向同一个指令,但是一个label必须指向一个指令。换句话说,可能使用不同的lable多次调用visitLable,但是使用在一个指令中的lable必须使用visitLable一次。最后一个约束,lable常量是不能共享的,每个方法必须有它自己的lable。
Transforming methods
方法可以像类一样被转换,例如通过方法适配器(传递它接受的方法调用):更改参数来改变个别指令。MethodAdapter类提供了诸如method visitor的基本实现,它除了传递接它接受的方法调用外,不做任何事情。
为了理解method adapter怎么使用,看下以下的简单例子:移除方法内部的NOP指令。
public class RemoveNopAdapter extends MethodAdapter { public RemoveNopAdapter(MethodVisitor mv) { super(mv); } @Override public void visitInsn(int opcode) { if (opcode != NOP) { mv.visitInsn(opcode); } }}public class RemoveNopClassAdapter extends ClassAdapter { public RemoveNopClassAdapter(ClassVisitor cv) { super(cv); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv; mv = cv.visitMethod(access, name, desc, signature, exceptions); if (mv != null) { mv = new RemoveNopAdapter(mv); } return mv; }}class adapter仅仅构建了一个method adapter(它封装了在chain中被下一个class visitor返回的method visitor),并且返回这个adapter。method adapter连的构建与class adapter chain相似。
然而,这不是强制的,能以不同于class adapter chain的构建方式来构建method adapter chain。甚至,每个方法能有不同的method adapter chain。例如,class adapter可以仅在方法中选择移除NOPs,不在构造器。
mv = cv.visitMethod(access, name, desc, signature, exceptions);if (mv != null && !name.equals("<init>")) { mv = new RemoveNopAdapter(mv);}method adapter chian可以拥有比class adapter chain多的拓扑图。例如class adapter chain是线性的,method adapter可以有分支。
public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) { MethodVisitor mv1, mv2; mv1 = cv.visitMethod(access, name, desc, signature, exceptions); mv2 = cv.visitMethod(access, "_" + name, desc, signature, exceptions); return new MultiMethodAdapter(mv1, mv2);}
Stateless transformations
假如我们想测量程序中每个类的花费时间,我们需要在每个类中加入一个静态timer域,且需要加入每个方法的执行时间到这个timer域。我们需要在类中加入一下代码:
public static long timer;public void m() throws Exception { timer -= System.currentTimeMillis(); Thread.sleep(100); timer += System.currentTimeMillis();}为了对ASM怎么实现这个有大体的理解,我们编译两个类且比较这两个版本的输出。
GETSTATIC C.timer : JINVOKESTATIC java/lang/System.currentTimeMillis()JLSUBPUTSTATIC C.timer : JLDC 100INVOKESTATIC java/lang/Thread.sleep(J)VGETSTATIC C.timer : JINVOKESTATIC java/lang/System.currentTimeMillis()JLADDPUTSTATIC C.timer : JRETURNMAXSTACK = 4MAXLOCALS = 1我们必须在方法的开头添加4个指令,且在返回指令浅四个其他指令。我们需要更新最大操作栈大小。方法开头的代码使用visitCode方法访问。我们通过覆写该方法添加前4个指令:
public void visitCode() { mv.visitCode(); mv.visitFieldInsn(GETSTATIC, owner, "timer", "J"); mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J"); mv.visitInsn(LSUB); mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");}代码中的owner必须设置为被转换的class的名称。我们必须在return之前添加后四个指令(在return或athrow之前)。这些指令没有任何参数,可以在visitInsn方法中访问。
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"); } mv.visitInsn(opcode);}最后我们必须更新操作栈的大小。我们添加了push long值的指令,因此需要4个slot。在方法开始的时候,操作站初始化所以我们知道在方法开头添加的4个指令需要4个slot。我们插入代码离开栈时,状态为改变(入栈多少,出栈多少)。结果是,如果原始代码需要栈的大小为s,栈大小为max(s,4)。不幸的是,我们在返回之前添加了指令。我们并不知道在此之前操作栈的大小。我们仅知道小于或等于s。因此我们仅能说添加代码后,在返回指令之前需要的操作栈最大值s+4。我们必须覆盖visitMaxs方法:
public void visitMaxs(int maxStack, int maxLocals) { mv.visitMaxs(maxStack + 4, maxLocals);}
依赖于COMPUTE_MAX选项,不必为操作栈大小的事烦心。
package com.fanshadoop.example;import org.objectweb.asm.ClassVisitor;import org.objectweb.asm.FieldVisitor;import org.objectweb.asm.MethodVisitor;import org.objectweb.asm.Opcodes;public class AddTimerAdapter extends ClassVisitor implements Opcodes {private String owner;private boolean isInterface;public AddTimerAdapter(ClassVisitor cv) {super(ASM4, cv);}@Overridepublic 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 & ACC_INTERFACE) != 0;}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions) {MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);if (!isInterface && mv != null && !name.equals("<init>")) {mv = new AddTimerMethodAdapter(mv);}return mv;}@Overridepublic void visitEnd() {if (!isInterface) {FieldVisitor fv = cv.visitField(ACC_PUBLIC + ACC_STATIC, "timer","J", null, null);if (fv != null) {fv.visitEnd();}}cv.visitEnd();}class AddTimerMethodAdapter extends MethodVisitor {public AddTimerMethodAdapter(MethodVisitor mv) {super(ASM4, mv);}@Overridepublic void visitCode() {mv.visitCode();mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");mv.visitInsn(LSUB);mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");}@Overridepublic 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");}mv.visitInsn(opcode);}@Overridepublic void visitMaxs(int maxStack, int maxLocals) {mv.visitMaxs(maxStack + 4, maxLocals);}}/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stub}}
Statefull transformations
上章节看的transformation是本地的,不依赖我们访问的当前的代码的指令:在开始添加的代码总是相同,总是被添加。这些transformation被成为stateless transformation。
更复杂的transformation需要记住当前代码访问的一些指令状态。例如,移除所有ICONST_0_IADD。很明确的是,当IADD指令访问时,仅当最后一个访问指令是ICONST_0时被移除。这需要在method adapter内部存储状态,类似这些transformation被称为statefull transformation。
当ICONST_0被加载时,仅当下一个指令为IADD时,它应该被删除。但下一指令到目前为止未知。解决的方法是将这个决定延期到下一个指令:如果是IADD移除两个指令,否则忽略ICONST_0和当前指令。
为了实现移除或替换指令序列,引入MethodVisitor子类,它的vistXxxInsn方法调用普通的visitInsn()方法:
public abstract class PatternMethodAdapter extends MethodVisitor { protected final static int SEEN_NOTHING = 0; protected int state; public PatternMethodAdapter(int api, MethodVisitor mv) { super(api, mv); } @Overrid public void visitInsn(int opcode) { visitInsn(); mv.visitInsn(opcode); } @Override public void visitIntInsn(int opcode, int operand) { visitInsn(); mv.visitIntInsn(opcode, operand); } protected abstract void visitInsn(); } public class RemoveAddZeroAdapter extends PatternMethodAdapter { private static int SEEN_ICONST_0 = 1; public RemoveAddZeroAdapter(MethodVisitor mv) { super(ASM4, mv); } @Override public void visitInsn(int opcode) { if (state == SEEN_ICONST_0) { if (opcode == IADD) { state = SEEN_NOTHING; return; } } visitInsn(); if (opcode == ICONST_0) { state = SEEN_ICONST_0; return; } mv.visitInsn(opcode); } @Override protected void visitInsn() { if (state == SEEN_ICONST_0) { mv.visitInsn(ICONST_0); } state = SEEN_NOTHING; }}
Labels and frames
labels和frames仅在它们相关的指令之前被访问。换句话说,他们在与指令的同时被访问,尽管他们不是指令本身。这对探测指令序列有影响,但事实上,这也是优点所在。如果某些指令会跳转到ICONST_0,这意味着这儿有lable指向该指令。移除这两个指令后,该label会指向被移除的指令的后继者。这种情况下,在ICONST_0和IADD之间必须存在一个容易被探测的label。
对于stack map frames来说也是如此:如果stack map frame在两个指令之间被访问,我么不能移除他们。这两种情况都可以以模式匹配逻辑的方式考虑将lable和frames做为指令的方式来处理。
public abstract class PatternMethodAdapter extends MethodVisitor { @Override public void visitFrame(int type, int nLocal, Object[] local,int nStack, Object[] stack) {visitInsn();mv.visitFrame(type, nLocal, local, nStack, stack); } @Override public void visitLabel(Label label) {visitInsn();mv.visitLabel(label); } @Override public void visitMaxs(int maxStack, int maxLocals) {visitInsn();mv.visitMaxs(maxStack, maxLocals); }}注意:visitMaxs也调用visitInsn方法,用来处理方法的尾部是序列的前缀,且必须被探测的情况。
- Core API Method之接口和组件
- Core API之Class接口和组件
- Core API之Method结构
- Core API之Method工具类
- core组件之基本数据结构和绘图函数
- 扩展Unity3d 组件方法,简化API使用 - C#特性之 Extension Method
- 组件和接口
- 接口和组件
- 组件和接口
- 组件接口(API)设计指南-目录
- 【openCV入门之四】 Core组件进阶
- Spring架构详解之Core组件详解
- core组件之操作图像中的像素
- 函数API调用,COM组件接口调用和SOA服务调用
- asm-giude阅读笔记003(ASM核心API接口和对应组件---读取字节码)
- asm-giude阅读笔记004(ASM核心API接口和对应组件---生成字节码)
- asm-giude阅读笔记005(ASM核心API接口和对应组件---转换字节码)
- 组件接口(API)设计指南[3]-委托(delegate)和数据源协议(data-source protocols)
- 最小二乘法直线拟合
- 嵌入式SQL(C)
- 算法之Linq
- oracle的job时间设置参考
- C++ 编译器的函数名修饰规则
- Core API Method之接口和组件
- [sicily online]1050. Numbers & Letters
- VS无法读取项目文件"***.csproj"
- 免费游戏开发引擎,各种游戏开发引擎
- DOM解析XML文件实例(一)
- 解决vc2010添加外部自定义时类,无法打开包含的stdafx.h
- Oracle DB 服务器 系统时间修改问题 与 SCN 关系的深入研究 .
- iOS高效开发必备的10款Objective-C类库
- pdf设置研究保护色