`
aswang
  • 浏览: 848229 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

ASM指南翻译-14 方法生成与转换的相关工具

 
阅读更多

 

3.3工具

org.objectweb.asm.commons包中预先定义了一些方法适配器,它们可以辅助你定义你自己的适配器。这个章节将介绍其中的三个适配器,展示如何在AddTimerAdapter示例中使用它们(3.2.4)。它也展示了如何使用前面章节中提到的工具来删除方法或者转换。

 

3.3.1基本工具

2.3节中出现的工具也可以针对方法使用。

Type  

 

很多字节码指令,如XloadxADD或者xRETURN,都依赖于它所处理的类型。Type这个类提供了一个getOpcode方法,针对这些指令,它可以获取一个给定类型的opcode。这个方法以一个int类型的opcode作为参数,然后返回针对调用该方法对象的类型的opcode。例如t.getOpcode(IMUL)返回FMUL,其中tType.FLOAT_TYPE.

 

TraceClassVisitor

 

这个类在前面章节已经介绍过了,主要是打印它所访问类的字节码的文本表示,当然也包括方法的文本表示。这个类也可以用来追踪转换链中方法生成和转换的内容。例如:

java -classpath asm.jar:asm-util.jar \

         org.objectweb.asm.util.TraceClassVisitor \

         java.lang.Void

将会打印出一下内容:

// 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 { … },即<clinit>方法。注意,如果你想在链的某个点追踪一个方法的内容,你可以选择使用TraceMethodVisitor,而不是用TraceClassVisitor来追踪所有的内容:

public MethodVisitor visitMethod(int access, String name,

         String desc, String signature, String[] exceptions) {

         MethodVisitor mv = cv.visitMethod(access, name, desc, signature,

         exceptions);

         if (debug && mv != null && ...) { // if this method must be traced

                   mv = new TraceMethodVisitor(mv) {

                            @Override public void visitEnd() {

                                     print(aPrintWriter); // print it after it has been visited

                            }

                   };

         }

         return new MyMethodAdapter(mv);

}

上面的代码将会打印出使用MyMethodAdapter转换后的内容。

 

CheckClassAdapter

 

这个类在前面的章节中也介绍过,用来检查ClassVisitor方法是否按照合适的顺序来调用,如果给以合适的参数,它可以用来检查MethodVisitor中的方法。也就是说它可以用来检查MethodVisitor接口在转换链中是否被正确使用。就像TraceMethodVisitor一样,你可以选择使用CheckMethodAdapter来检查单个方法而不是所有方法:

public MethodVisitor visitMethod(int access, String name,

String desc, String signature, String[] exceptions) {

         MethodVisitor mv = cv.visitMethod(access, name, desc, signature,

         exceptions);

         if (debug && mv != null && ...) { // if this method must be checked

                   mv = new CheckMethodAdapter(mv);

         }

         return new MyMethodAdapter(mv);

}

上面的代码用来检查MyMethodAdapter是否正确使用了MethodVisitor接口。但是它不能检查字节码是否正确:如它无法检测ISTORE 1 ALOAD 1是无效的。

 

ASMifierClassVisitor

 

这个类同样在前面章节中介绍过了,它同样也可以用在方法上。它可以用来了解如何使用ASM来生成一些编译后的代码:编写对应的源代码,然后使用javac编译,在使用ASMifierClassVisitor来访问这个类即可。这样你就可以得到ASM代码来生成源码对应的字节码。

 

3.3.2 AnalyzerAdapter

 

这个方法适配器基于visitFrame访问过的帧,来对比每个指令的栈映射帧。如在3.1.5节中解释的一样,visitFrame方法只能在一个方法中的某些指令之前被调用,这样可以节省空间,并且“其它的帧可以很容易很快地从这些帧推断出来”。这就是这个章节即将做的事情。这个适配器只能工作在那些包含预先计算过的栈映射帧的类上,并且这些类需要使用java6或者更高编译器编译。

 

AddTimerAdapter示例中,这个适配器可以获得在RETURN指令之前的操作数栈的大小,从而可以在visitMaxs方法中为maxStack计算一个最佳的值(事实上,在实际操作中这个方法不推荐使用,因为它没有COMPUTE_MAXS有效):

class AddTimerMethodAdapter2 extends AnalyzerAdapter {

         private int maxStack;

         public AddTimerMethodAdapter2(String owner, int access,

                   String name, String desc, MethodVisitor mv) {

                  super(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类中定义的,包含了操作数栈中的类型。更经确定地讲,在一个visitXxxInsn方法中,并且在被重写的方法调用之前,这个列表包含了操作数栈的状态。注意,被重写的那个方法也必须被调用,这样stack字段才会被正确的更新(以后将使用super来代替原始代码中的mv

 

此外,可以通过调用父类的方法来插入新的指令:效果就是AnalyzerAdapter将会计算这些指令帧大小。因此,因为这个适配器会基于自己的计算来更新visitMaxs方法的参数,我们就不需要自己更新它们了:

class AddTimerMethodAdapter3 extends AnalyzerAdapter {

         public AddTimerMethodAdapter3(String owner, int access,

                   String name, String desc, MethodVisitor mv) {

                   super(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,那么前面还应该有三个局部变量,以及两个方法参数,它们是不能改变的,因此第一个局部变量的索引值应该是3,第二个就应该是4(有疑问?),后面依次类推.这个适配器对于想在方法中插入新的变量会有帮助。如果没有这个适配器,虽然也可以实现添加新的局部变量到代码的最后,但是不幸地是,它们的编号直到visitMaxs方法的末尾才可知。

 

为了展示如何使用这个适配器,假设我们希望通过添加一个局部变量来实现AddTimerAdapter

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方法,很容易就实现上面的功能。

class AddTimerMethodAdapter4 extends LocalVariablesSorter {

         private int time;

         public AddTimerMethodAdapter4(int access, String desc,

                   MethodVisitor mv) {

                   super(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,你必须通过委托的方式来使用这些适配器而不是继承(因为多继承是不可能的):

 

class AddTimerMethodAdapter5 extends MethodAdapter {

         public LocalVariablesSorter lvs;

         public AnalyzerAdapter aa;

         private int time;

         private int maxStack;

         public AddTimerMethodAdapter5(MethodVisitor mv) {

                   super(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);

         }

}

 

为了使用上面的适配器,你必要将LocalVariablesSorterAnalyzerAdapter链接起来,然后链接你的适配器:第一个适配器将对局部变量进行排序并更新帧,AnalyzerAdapter适配器将根据前一步骤中执行结果计算中间帧,这样你的适配器就可以访问那些已经重编号的帧了。这个链可以在visitMethod方法中如下构造:

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,因此我们也可以很容易地使用一个局部变量):

class AddTimerMethodAdapter6 extends AdviceAdapter {

         public AddTimerMethodAdapter6(int access, String name, String desc,

                   MethodVisitor mv) {

                   super(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);

         }

}

2
1
分享到:
评论

相关推荐

    ASM3.0指南翻译

    ### ASM3.0指南翻译——深入理解Java字节码引擎库 #### 1. 引言与背景 在软件开发领域,程序分析、生成及转换技术的应用极为广泛,覆盖从语法解析到语义分析,再到代码优化、混淆、调试、性能监控及面向切面编程等...

    ASM4使用指南.pdf

    ASM的主要动机在于提供一套高效的工具来支持程序分析、程序生成和程序转换。这些技术可以用于多种应用环境,包括但不限于: - 程序分析:分析程序以查找潜在的bug、检测未使用的代码以及对代码执行逆向工程。 - 程序...

    ASM4使用指南

    ASM4是针对Java语言设计的一个工具库,用于在运行时动态生成和转换已编译的Java类。它在程序分析、生成和转换技术中有着广泛的应用,可以用于多种场景,比如查找潜在bug、性能优化、面向方面的程序设计(AOP)等。本...

    asm4-guide

    - Adding and removing class members:说明了如何使用ASM库向类中添加或移除成员(字段、方法等)。 - Generating classes:描述了如何使用ASM生成新的类。 - Parsing classes:说明了ASM如何解析已有的类文件。 ...

    asm操作指南(中文)

    - **高速与小型化**:ASM被设计为快速且占用空间小的库,这对于运行时需要动态生成或转换类的应用程序尤为重要。 - **兼容性**:支持最新的Java版本,如Java 7。 - **广泛的用户支持**:拥有庞大的用户社区,能够...

    ASM7使用指南.pdf

    **ASM**(Abstract Syntax Model)是一款专门针对Java字节码操作的强大工具库,它主要用于程序分析、生成及转换等场景。根据提供的文档信息来看,尽管标题为“ASM7使用指南”,但文档内容却提到了“ASM4使用指南”。...

    asm4使用指南(强烈推荐)

    标题《asm4使用指南(强烈推荐)》以及描述《java使用asm4操作字节码技术,详细介绍了asm如何对java的字节码进行操作,强烈推荐》指明了文档的核心内容是介绍ASM4这一工具在Java字节码层面操作的指南,并且给出了...

    asm-guide.rar

    在阅读《ASM指南》的过程中,你将逐步掌握ASM的基本用法,包括类、字段和方法的解析,以及如何生成和修改字节码。同时,书中还会提供丰富的示例代码,帮助你快速上手并应用于实际项目中。 总之,《ASM指南》是一本...

    asm-2.0.jar.zip

    2. **字节码生成**:除了解析,ASM还支持生成新的字节码,允许开发者动态创建或修改类和方法。这对于实现Java代理、AOP(面向切面编程)或者动态语言的JVM实现是非常关键的。 3. **高性能**:ASM设计时注重性能,其...

    C32Asm 反汇编工具

    C32Asm是一款专为处理C32格式代码的反汇编工具,它能够将C32编译后的二进制程序转换成汇编语言代码,帮助开发者理解和分析程序的内部工作原理。在IT领域,反汇编是逆向工程的一部分,常用于软件调试、漏洞分析、代码...

    ASM4使用指南_全网最完美版本.pdf

    《ASM4 使用指南》是一本深入探讨ASM库的著作,ASM是专为Java设计的类生成和转换工具。这本书详细介绍了ASM如何在运行时对已编译的Java类进行分析、生成和转换,同时强调了ASM库的高效、小巧和易用性。以下是ASM4的...

    Asm-Guide

    - **接口与组件**:介绍用于解析、生成、转换类的各种接口和工具类。 - **工具**:提供一些实用工具类,如 Type 和 TraceClassVisitor。 ##### 1.4 致谢 感谢 Eric Bruneton 对 Asm 项目的贡献和支持,以及所有...

    ASM4 使用指南.pdf

    **ASM**(Adapter Software Model)是一款专门针对Java字节码操作的强大工具库,它主要用于实现类的生成与转换功能,并支持对编译后的Java类进行分析。该库特别适用于在运行时环境中对Java类进行操作的需求场景。 *...

    asm-all_3.1

    在ASM 3.1的压缩包中,`asm-3.1`文件包含了所有必要的类库和文档,包括ASM的主要API类、辅助工具类以及相关的用户指南和API文档。通过这些资源,开发者可以快速上手并熟练掌握ASM的用法。 使用ASM 3.1时,开发者...

    AsmToE汇编机器码转换工具

    - Asm.exe 可能是一个单独的汇编器或者与AsmToE相关的辅助工具。 - AsmToE.ini 是配置文件,存储用户的设置和工具的默认参数。 - 8086.txt 可能包含8086处理器的指令集参考,帮助用户了解如何编写针对该架构的汇编...

    asm的jar包

    ASM是一个Java字节码操控和分析框架,常用于动态代理、代码分析以及转换等场景。它的核心在于提供了一种能够直接操作和修改Java类文件的底层API,这使得开发者能够深入到Java字节码级别进行编程,实现一些高级功能,...

    ASM_3.0_使用指南

    ASM(Abstract Syntax Tree Manipulation)是一个Java字节码操作工具,它提供了一种方式,允许开发者在运行时动态地生成和转换Java类。ASM库提供了一种轻量级的方法,用来分析和修改已编译的Java类文件,而无需重新...

    ASM中文帮助文档

    "jb51.net.txt"可能包含了一些与ASM相关的链接或者教程,"电子书大全.url"和"PDF阅读器下载.url"可能是推荐的电子书资源网站或PDF阅读器下载地址,对于深入学习和查找更多资料非常有帮助。"脚本之家.url"可能是一个...

Global site tag (gtag.js) - Google Analytics