之前一篇简单介绍了一下ASM框架。这一篇继续对CoreApi进行扩展。这里还是继续对ClassWriter ,ClassReader和ClassVisitor的应用的扩展。前面一篇主要介绍的是ClassWriter和ClassReader单独应用的场景。这一篇把这两者作为producer(ClassReader)和consumer(ClassWriter)来结合起来介绍一下另外一些用途。
一、动态迁移转换类
事件的生产者ClassReader通过accept方法可以传递给ClassWriter。上一篇我们知道ClassWriter继承自ClassVisitor。而ClassReader可以接收ClassVisitor具体实现类,通过顺序访问实现类的方法来解析整个class文件结构。先看个例子。为了简便,我们读取一个现成的class文件ChildClass.class(前一篇用ASM生成的class,源码见前一篇)。然后经过解析拿到一个ClassReader实例。然后再通过ClassWriter重新构造了一个Class ,通过cw.toByteArray()返回一个和前面一样的Class 的字节数组。
package asm.core; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.*; /** * Created by yunshen.ljy on 2015/6/9. */ public class TransformClasses { public static void main(String[] args) throws IOException { File file = new File("ChildClass.class"); InputStream input = new FileInputStream(file); // 构造一个byte数组 byte[] byt = new byte[input.available()]; input.read(byt); ClassWriter cw = new ClassWriter(0); ClassVisitor cv = new ClassVisitor (cw){}; // 改变class的访问修饰 // ClassVisitor cv = new ChangeAccessAdapter(cw); ClassReader cr = new ClassReader(byt); cr.accept(cv, 0); byte[] toByte = cw.toByteArray();// byt 和toByte其实是相同的数组 // 输出到class文件 File tofile = new File("ChildClass.class"); FileOutputStream fout = new FileOutputStream(tofile); fout.write(toByte); fout.close(); } }
光这样解析然后构造一个相同的Class觉得没什么实际意义,但是我们注意到ClassVisitor 可以接收一个ClassVisitor 实例,而ClassWriter 作为Visitor的子类,是可以被Visitor接收调用的。。ASM官方文档的下面这张图,很好地描述了整个调用链。而这其中也可以套用更多的adapter层层传递,顺序调用。
所以我们这里可以创建一个定制化的Visitor。ClassVisitor cv = new ChangeAccessAdapter(cw);这行我们去掉注释再看看,这里我们写了一个自己的ClassVisitor来修改class的访问修饰。把public abstract变成public。根据第一篇的介绍,我们需要自己实现visit方法,并设置访问参数。ChangeAccessAdapter 代码如下:
package asm.core; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; /** * Created by yunshen.ljy on 2015/6/10. */ public class ChangeAccessAdapter extends ClassVisitor { public ChangeAccessAdapter(ClassVisitor cv) { super(Opcodes.ASM4, cv); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { cv.visit(version, Opcodes.ACC_PUBLIC , name, signature, superName, interfaces); } }
二、移除类成员
通过visit()方法,我们可以访问、解析类成员。当我们需要移除一个类成员,比如InnerClass、OuterClass就可以直接通过继承响应的visitOuterClass、visitInnerClass方法,但是不去实现方法体来达到移除目的。Method和Field成员的移除需要终止下一层继续调用,也就是返回null 而不是MethodVisitor 或者FieldVisitor实例。例子中需要移除的Class 还是以第一篇的Task 类为例。这次我们加入了一个内部类给Task。代码如下:
package asm.core; /** * Created by yunshen.ljy on 2015/6/8. */ public class Task { private int isTask = 0; private long tell = 0; public void isTask(boolean test){ System.out.println("call isTask"); } public void tellMe(){ System.out.println("call tellMe"); } class TaskInner{ int inner; } }
我们这次把Task的内部类以及 isTask方法移除,一样,需要实现自己的ClassVisitor,Visitor 代码如下。
package asm.core; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; /** * Created by yunshen.ljy on 2015/6/12. */ public class RemovingClassesVisitor extends ClassVisitor{ public RemovingClassesVisitor(int api) { super(api); } public RemovingClassesVisitor(ClassWriter cw) { super(Opcodes.ASM4,cw); } // 移除内部类 @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (name.startsWith("is")) { // 移除以is开头的方法名的方法 return null; } return cv.visitMethod(access, name, desc, signature, exceptions); } }
下面就来构造整个调用链,将移除后的class字节流输出到文件中:
package asm.core; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; public class RemovingClassesTest { public static void main(String[] args) throws IOException { ClassReader cr = new ClassReader("asm.core.Task"); ClassWriter cw = new ClassWriter(0); ClassVisitor cv = new RemovingClassesVisitor(cw); cr.accept(cv, 0); byte[] toByte = cw.toByteArray();// byt 和toByte其实是相同的数组 // 输出到class文件 File file = new File("Task.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(toByte); fout.close(); } }
然后,Task.class 文件就变成了下面我们期望的class文件。isTask()方法已经被移除。
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package asm.core; public class Task { private int isTask = 0; private long tell = 0L; public Task() { } public void tellMe() { System.out.println("call tellMe"); } }
三、添加类成员
添加类成员,我们一样需要继承ClassVisitor 来写我们自己的适配器。移除的情况,我们是终止class字节流的遍历和调用。那么添加的时候我们就需要去多调用一次visitField或者visitMethod方法。但这里我们需要注意的一点是,如果我们无法单纯在visit方法中去添加一个FieldVisitor或MehtodVisitor实例来实现再次调用visitField或者visitMethod。因为ASM是按照顺序来解析class二进制字节流的,visit方法后续还会再次触发visitSource, visitOuterClass, visitAttribute,等方法。那么实现在visitField或者visitMethod方法中也会有问题,因为比如每次调用visitField方法,会重复产生很多你需要添加的Field。
为了解决这个问题,我们可以在visitEnd方法中去实际添加类成员(因为visitEnd方法总是会被调用到),在visitField方法中加入判断是否已经存在类成员,再继续往下执行。也就是通过counter的方式,防止重复添加,我们可以在每个新加的属性上加一个counter,也可以添加一个计数方法分别在每个方法中调用。
下面看一个简单的例子。首先先写一个adapter 来添加类成员。例子中我们添加一个私有的int类型的Filed 到Task.class中。我们把counter写在visitField中,判断是否已经有这个属性,如果没有,进行一次标记。然后在visitEnd中去构建。
package asm.core; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Opcodes; /** * Created by yunshen.ljy on 2015/6/13. */ public class AddingClassesVisitor extends ClassVisitor { private int fAcc; private String fName; private String fDesc; private boolean isFieldPresent; public AddingClassesVisitor(ClassVisitor cv, int fAcc, String fName, String fDesc) { super(Opcodes.ASM4, cv); this.fAcc = fAcc; this.fName = fName; this.fDesc = fDesc; } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { if (name.equals(fName)) { isFieldPresent = true; } return cv.visitField(access, name, desc, signature, value); } @Override public void visitEnd() { if (!isFieldPresent) { FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null); if (fv != null) { fv.visitEnd(); } } cv.visitEnd(); } }
在visitEnd方法中我们需要判断FieldVisitor实例是否为空,因为visitField方法的实现中,是会有返回null的情况。
调用的代码中,只要把前面的Test类替换成如下的调用就可以了
ClassReader cr = new ClassReader("asm.core.Task"); ClassWriter cw = new ClassWriter(0); ClassVisitor cv = new AddingClassesVisitor(cw, Opcodes.ACC_PRIVATE,"addedField","I"); cr.accept(cv, 0);
再来看一下这次生成的Task.class 已经添加了我们期望的类成员。
package asm.core; public class Task { private int isTask = 0; private long tell = 0L; private int addedField; public Task() { } public void isTask(boolean test) { System.out.println("call isTask"); } public void tellMe() { System.out.println("call tellMe"); } }
这里我们发现,可以把各种adapter链式调用,来实现复杂的调用链,定制更加复杂的逻辑。我们可以在外层链式调用,ClassVisitor vca = new AClassVisitor(classWriter);ClassVisitor cvb= new BClassVisitor(cva)…。也可以通过传入一个调用链数组给一个Adalter。这里直接把官方说明文档的例子拿出来看下MultiClassAdapter 就是我们的ClassVisitor 的“总代理”:
package asm.core; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; public class MultiClassAdapter extends ClassVisitor { protected ClassVisitor[] cvs; public MultiClassAdapter(ClassVisitor[] cvs) { super(Opcodes.ASM4); this.cvs = cvs; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { for (ClassVisitor cv : cvs) { cv.visit(version, access, name, signature, superName, interfaces); } } }
四、工具Api
ASM的Core API 中给我们提供了一些工具类,都在org.objectweb.asm.util包中。有TraceClassVisitor、CheckClassAdapter、ASMifier、Type等。通过这些工具类,能更方便实现我们的动态生成字节码逻辑。这里就简述一下TraceClassVisitor 。
TraceClassVisitor 顾名思义,我们可以“trace”也就是打印一些信息,这些信息就是ClassWriter 提供给我们的byte字节数组。因为我们阅读一个二进制字节流还是比较难以理解和解析一个类文件的结构。TraceClassVisitor通过初始化一个classWriter 和一个Printer对象,来实现打印我们需要的字节流信息。通过TraceClassVisitor 我们能更好地比较两个类文件,更轻松得分析class的数据结构。
下面看个例子,我们用TraceClassVisitor 来打印Task 类信息。
package asm.core; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.util.TraceClassVisitor; import java.io.IOException; import java.io.PrintWriter; /** * Created by yunshen.ljy on 2015/6/13. */ public class TraceClassVisitorTest { public static void main(String[] args) throws IOException { ClassReader cr = new ClassReader("asm.core.Task"); ClassWriter cw = new ClassWriter(0); TraceClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out)); cr.accept(cv, 0); } }
控制台的结果如下,Task的类的局部变量表、操作数栈的一些信息也能打印出来,这比看二进制字节码文件舒服多了。
// class version 50.0 (50) // access flags 0x21 public class asm/core/Task { // compiled from: Task.java // access flags 0x0 INNERCLASS asm/core/Task$TaskInner asm/core/Task TaskInner // access flags 0x2 private I isTask // access flags 0x2 private J tell // access flags 0x1 public <init>()V L0 LINENUMBER 6 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V L1 LINENUMBER 8 L1 ALOAD 0 ICONST_0 PUTFIELD asm/core/Task.isTask : I L2 LINENUMBER 10 L2 ALOAD 0 LCONST_0 PUTFIELD asm/core/Task.tell : J L3 LINENUMBER 19 L3 RETURN L4 LOCALVARIABLE this Lasm/core/Task; L0 L4 0 MAXSTACK = 3 MAXLOCALS = 1 // access flags 0x1 public isTask(Z)V L0 LINENUMBER 13 L0 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "call isTask" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V L1 LINENUMBER 14 L1 RETURN L2 LOCALVARIABLE this Lasm/core/Task; L0 L2 0 LOCALVARIABLE test Z L0 L2 1 MAXSTACK = 2 MAXLOCALS = 2 // access flags 0x1 public tellMe()V L0 LINENUMBER 16 L0 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "call tellMe" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V L1 LINENUMBER 17 L1 RETURN L2 LOCALVARIABLE this Lasm/core/Task; L0 L2 0 MAXSTACK = 2 MAXLOCALS = 1 }
ASM框架的CoreApi的基础类已经介绍完毕。后面会陆续介绍CoreApi 中的Methods接口和组件。以及TreeApi。在Methods 类库之前,需要先了解下JVM中的运行期方法调用和执行,能帮助我们更好地理解怎么样用ASM实现动态扩展。
相关推荐
ASM 存储迁移方案 ASM(Automated Storage Management)存储迁移是指将现有的存储系统迁移到新的存储系统,以提高存储效率、可靠性和可扩展性。下面是 ASM 存储迁移方案的详细知识点: 一、迁移方式 ASM 存储迁移...
6.ASM 的热添加和删除磁盘技术:ASM 的热添加和删除磁盘技术可以实现在线迁移数据,可以随时添加或删除磁盘来实现存储的扩展和收缩。 7.Oracle 11g R2 RAC 的存储架构:Oracle 11g R2 RAC 的存储架构主要包括 ASM ...
赠送jar包:asm-9.1.jar; 赠送原API文档:asm-9.1-javadoc.jar; 赠送源代码:asm-9.1-sources.jar; 赠送Maven依赖信息文件:asm-9.1.pom; 包含翻译后的API文档:asm-9.1-javadoc-API文档-中文(简体)版.zip; ...
Oracle ASM维护手册之ASM存储迁移 一、ASM 存储迁移概述 Oracle ASM(Automatic Storage Management)是一种高效的存储管理系统,旨在简化存储管理和提高数据可用性。ASM 存储迁移是指将现有的存储设备迁移到新的...
Oracle数据库迁移至ASM存储系统 在本文中,我们将详细介绍如何使用RMAN将数据库从文件系统迁移到ASM存储系统。ASM(Automatic Storage Management)是一种高性能、可扩展的存储管理系统,能够自动管理数据库文件,...
通过分析这个源码,我们可以深入理解如何在易语言环境中利用汇编语言获取并调用API函数,这涉及到的知识点可能包括: 1. 易语言的基本语法和结构,如何在易语言程序中嵌入汇编代码。 2. 汇编语言的基本知识,如指令...
包含翻译后的API文档:asm-all-5.0.2-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.ow2.asm:asm-all:5.0.2; 标签:all、ow2、jar包、java、API文档、中英对照版; 使用方法:解压翻译后的API文档...
赠送原API文档:asm-6.0-javadoc.jar 赠送源代码:asm-6.0-sources.jar 包含翻译后的API文档:asm-6.0-javadoc-API文档-中文(简体)-英语-对照版.zip 对应Maven信息:groupId:org.ow2.asm,artifactId:asm,...
赠送jar包:asm-4.2.jar; 赠送原API文档:asm-4.2-javadoc.jar; 赠送源代码:asm-4.2-sources.jar; 赠送Maven依赖信息文件:asm-4.2.pom; 包含翻译后的API文档:asm-4.2-javadoc-API文档-中文(简体)版.zip; ...
本文将深入探讨如何利用ASM来获取API(应用程序编程接口)地址,这在逆向工程、软件调试和安全研究等领域中具有实际应用价值。 API是操作系统或库提供的一组预定义函数,供开发者调用以实现特定功能。了解如何通过...
2. **简化配置**:ASM简化了存储资源的配置和管理,使得数据库合并的存储利用更高效。 3. **大文件支持**:ASM内建对大文件的支持,适应现代大数据需求。 4. **动态扩展**:在增加或减少存储容量后,ASM能够自动重新...
赠送jar包:asm-7.1.jar; 赠送原API文档:asm-7.1-javadoc.jar; 赠送源代码:asm-7.1-sources.jar; 赠送Maven依赖信息文件:asm-7.1.pom; 包含翻译后的API文档:asm-7.1-javadoc-API文档-中文(简体)版.zip; ...
它利用ASM库和Android的transform API,实现了对R类中常量整型字段的内联处理,从而达到减小APK体积的目的。 首先,我们需要理解R类的作用。在Android应用中,R类是由编译器自动生成的,用于存储资源的ID,如布局、...
赠送jar包:asm-5.0.4.jar; 赠送原API文档:asm-5.0.4-javadoc.jar; 赠送源代码:asm-5.0.4-sources.jar; 赠送Maven依赖信息文件:asm-5.0.4.pom; 包含翻译后的API文档:asm-5.0.4-javadoc-API文档-中文(简体)版...
然而,随着技术的发展,自动存储管理(ASM)的出现简化了存储的管理,因此需要将裸设备表空间迁移到ASM中。 本文主要介绍的是在ORACLE 11G RAC(Real Application Clusters)环境下,通过RMAN(Recovery Manager)...
标题 "CAPI Cypher FASM_cryptoAPI_Encrypt_Asm_" 指示的是一个使用Microsoft CryptoAPI(CAPI)加密的示例程序,该程序是用flat assembler(平面汇编器)编写的。这个示例可能包含了一系列用汇编语言实现的加密算法...
在这个名为"HOOKAPI.rar"的压缩包中,很可能包含了一系列用于实现ASM HOOK的源代码、示例、文档或库文件。 API(应用程序编程接口)是操作系统或其他软件提供给开发者用于交互的接口,如Windows API、Linux API等。...
赠送jar包:asm-all-5.0.2.jar; 赠送原API文档:asm-all-5.0.2-javadoc.jar; 赠送源代码:asm-all-5.0.2-sources.jar; 赠送Maven依赖信息文件:asm-all-5.0.2.pom; 包含翻译后的API文档:asm-all-5.0.2-javadoc-...