字节码工具

Java变量内部名字:java/lang/String、boolean(Z)、long(J)、int(I)

方法签名:void a(int i, float f)   —>  (IF)V

ASM提供两组API:Core和Tree,Core是基于访问者模式来操作类的,Tree是基于树节点来操作类的

Core模式:主要类有ClassReader、ClassWriter、ClassAdapter、ClassVisitor、MethodVisitor、FieldVisitor,其中ClassVisitor实现的接口如下:visit [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* (visitInnerClass | visitField | visitMethod )* visitEnd,ClassReader的accept方法是流程的起点,参数接受一个具体的ClassVisitor,ClassAdapter也是ClassVisitor的实现类,ClassAdapter可以看成是事件过滤器或逻辑处理器,另外一个比较重要的知识点是CoreApi的使用,主要理解指令如何操纵java堆栈、LOAD指令将局部变量表中的变量加载到堆栈、storeLocal(Generates the instruction to store the top stack value in the given local variable.)和loadLocal(Generates the instruction to load the given local variable on the stack.)用来操作局部变量等

//int取值0~5时JVM采用iconst_0、iconst_1、iconst_2、iconst_3、iconst_4、iconst_5指令将常量压入栈中,取值-1时采用iconst_m1指令将常量压入栈中
public static void main(String[] args) {
   int i = 5;
   int j = -1;
}

0: iconst_5
1: istore_1
2: iconst_m1
3: istore_2
4: return

//当int取值-128~127时,JVM采用bipush指令将常量压入栈中
public static void main(String[] args) {
  int i = 127;
}

0: bipush 127
1: istore_1
2: return

//当int取值-32768~32767时,JVM采用sipush指令将常量压入栈中
//当int取值-2147483648~2147483647时,JVM采用ldc指令将常量压入栈中

//load指令和store指令用于操作局部变量表和堆栈
public static int add(int a,int b){
  int c=0;
  return a+b;
}

0: iconst_0
1: istore_2
2: iload_0
3: iload_1
4: iadd
5: ireturn
ClassWriter classWriter = new ClassWriter(0); 
classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "org/victor/core/MyCls", null, "java/lang/Object", null); 
ClassAdapter classAdapter = new MyClassAdapter(classWriter);  
classAdapter.visitField(Opcodes.ACC_PRIVATE, "name", Type.getDescriptor(String.class), null, null);//定义name属性
classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null).visitCode();//定义构造方法
byte[] classFile = classWriter.toByteArray();
public void visitCode() {
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");//调用父类的构造方法
    mv.visitVarInsn(Opcodes.ALOAD, 0);
    mv.visitLdcInsn("zhangzhuo");//将常量池中的字符串常量加载到栈顶   mv.visitFieldInsn(Opcodes.PUTFIELD, "org/victor/core/MyCls", "name", Type.getDescriptor(String.class));//对属性赋值
    mv.visitInsn(Opcodes.RETURN);//设置返回值
    mv.visitMaxs(2, 1);//设置方法的栈和本地变量表的大小
}
public void visitInsn(int opcode) {     
    //此方法可以获取方法中每一条指令的操作类型,被访问多次,如应在方法结尾处添加新指令,则应判断:
    if(opcode == Opcodes.RETURN)
    {
          mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
          mv.visitLdcInsn("this is a modify method!");
          mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
          mv.visitInsn(RETURN);
    }
    super.visitInsn(opcode);
}

总结:我们可以通过代理ClassVisitor和MethodVisitor来截获调用,最终来判断是否执行真正的visitor逻辑

Javassist主要类有ClassPool、CtClass、CtMethod,其中,ClassPool 是一个存储 CtClass 的 Hash 表,类的名称作为 Hash 表的 key。ClassPool 的 get() 函数用于从 Hash 表中查找 key 对应的 CtClass 对象。如果没有找到,get() 函数会创建并返回一个新的 CtClass 对象,这个新对象会保存在 Hash 表中

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("test.Rectangle");
cc.setSuperclass(pool.get("test.Point"));
cc.writeFile();
byte[] b = cc.toBytecode();
Class clazz = cc.toClass(); //请求当前线程的 ClassLoader 加载 CtClass 所代表的类文件
//定义新类,类的成员方法可以通过 CtNewMethod 类的工厂方法来创建,
//然后使用 CtClass 的 addMethod() 方法将其添加到类中
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point"); 
//如果一个 CtClass 对象通过 writeFile(), toClass(), toBytecode() 被转换成一个类文件,此 CtClass 对象会被冻结起来,
//不允许再修改,因为一个类只能被 JVM 加载一次。但是,一个冷冻的 CtClass 也可以被解冻
CtClass cc = ...;
cc.writeFile();
cc.defrost();
cc.setSuperclass(...);//因为类已经被解冻,所以这里可以调用成功
//CtClass 对象所代表的类的名称 Point 被修改为 Pair,因此,如果后续在 ClassPool 对象上再次调用 get("Point"),
//则它不会返回变量 cc 所指的 CtClass 对象。 而是再次读取类文件 Point.class,并为类 Point 构造一个新的 CtClass 对象
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.setName("Pair"); 
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
CtClass cc1 = pool.get("Point");   // cc1 is identical to cc.
cc.setName("Pair");                // cc1 is not identical to cc.
CtClass cc2 = pool.get("Pair");    // cc2 is identical to cc.
CtClass cc3 = pool.get("Point");   // cc3 is identical to cc1.

Javassist 不允许删除方法或字段,但它允许更改名称,所以,如果一个方法是没有必要的,可以通过调用 CtMethod 的 setName() 和 setModifiers() 中将其改为一个私有方法。

Javassist 不允许向现有方法添加额外的参数。你可以通过新建一个方法达到同样的效果。

CtMethod 和 CtConstructor 提供了 insertBefore(),insertAfter() 和 addCatch() 方法。 它们可以将用 Java 编写的代码片段插入到现有方法中。Javassist 包括一个用于处理源代码的简单编译器,它接收用 Java 编写的源代码,并将其编译成 Java 字节码,并内联方法体中。

方法 insertBefore() ,insertAfter(),addCatch() 和 insertAt() 接收一个表示语句或语句块的 String 对象。一个语句是一个单一的控制结构,比如 if 和 while 或者以分号结尾的表达式。语句块是一组用大括号 {} 包围的语句。因此,以下每行都是有效语句或块的示例:

System.out.println("Hello");
{ System.out.println("Hello"); }
if (i < 0) { i = -i; }

由于编译器支持语言扩展,以 $ 开头的几个标识符有特殊的含义:

符号含义
$0$1$2, ...this and 方法的参数
$args方法参数数组.它的类型为 Object[]
$$所有实参。例如, m($$) 等价于 m($1,$2,...)
$cflow(...)cflow 变量
$r返回结果的类型,用于强制类型转换
$w返回结果的类型,用于强制类型转换
$_返回值

下面有一些使用这些特殊变量的例子。假设一个类 Point,要在调用方法 move() 时打印 dx 和 dy 的值,请执行以下程序:

class Point {
    int x, y;
    void move(int dx, int dy) { x += dx; y += dy; }
}


ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
CtMethod m = cc.getDeclaredMethod("move");
m.insertBefore("{ System.out.println($1); System.out.println($2); }");
cc.writeFile();
exMove($$, context); //这个表达式等价于
exMove($1, $2, $3, context);

$cflow表示控制流,此只读变量返回特定方法的递归调用的深度。

int fact(int n) {
    if (n <= 1)
        return n;
    else
        return n * fact(n - 1);
}

CtMethod cm = ...;
cm.useCflow("fact");
cm.insertBefore("if ($cflow(fact) == 0)   System.out.println(\"fact \" + $1);");
return ($r)result;

Integer i = ($w)5;

javassist.expr.ExprEditor 是一个用于替换方法体中的表达式的类。用户可以定义 ExprEditor 的子类来指定修改表达式的方式。

CtMethod cm = ... ;
cm.instrument(
    new ExprEditor() {
        public void edit(MethodCall m) throws CannotCompileException {
            if (m.getClassName().equals("Point") && m.getMethodName().equals("move"))
                m.replace("{ $1 = 0; $_ = $proceed($$); }");
        }
    });

//javassist.expr.MethodCall表示方法调用
//javassist.expr.ConstructorCall表示构造函数调用
//javassist.expr.FieldAccess表示字段访问
//javassist.expr.NewExpr表示使用new运算符(不包括数组创建)创建对象的表达式
//javassist.expr.NewArray表示使用new运算符创建数组
//javassist.expr.Instanceof表示一个instanceof表达式
//javassist.expr.Cast表示一个cast表达式
//javassist.expr.Handler表示try-catch语句的catch子句

添加一个新方法:

//向类 Point 添加了一个公共方法 xmove()
CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make("public int xmove(int dx) { x += dx; }", point);
point.addMethod(m);

//如果目标对象和目标方法名也被传递给 make() 方法,源文本中也可以包括 $proceed
CtClass point = ClassPool.getDefault().get("Point");
CtMethod m = CtNewMethod.make("public int ymove(int dy) { $proceed(0, dy); }", point, "this", "move");
//这个程序创建一个 ymove() 方法,定义如下:
public int ymove(int dy) { this.move(0, dy); }

//Javassist 还提供了另一种添加新方法的方式。你可以先创建一个抽象方法,然后给它一个方法体:
CtClass cc = ... ;
CtMethod m = new CtMethod(CtClass.intType, "move", new CtClass[] { CtClass.intType }, cc);
cc.addMethod(m);
m.setBody("{ x += $1; }");
cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT);
//因为Javassist在类中添加了的方法是抽象的,所以在调用 setBody() 之后,必须将类显式地改回非抽象类

添加一个字段:

//向类Point添加一个名为z的字段
CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f);
//指定添加字段的初始值
CtClass point = ClassPool.getDefault().get("Point");
CtField f = new CtField(CtClass.intType, "z", point);
point.addField(f, "0");  // initial value is 0

//上述代码可以重写为更简单代码:
CtClass point = ClassPool.getDefault().get("Point");
CtField f = CtField.make("public int z = 0;", point);
point.addField(f);

要删除字段或方法,请在 CtClass 的 removeField() 或 removeMethod() 方法。

 

https://www.jianshu.com/p/43424242846b

https://www.jianshu.com/p/b9b3ff0e1bf8

https://www.jianshu.com/p/7803ffcc81c8

https://blog.csdn.net/mbugatti/article/details/53410506

https://www.diycode.cc/topics/581

https://www.ibm.com/developerworks/cn/java/j-lo-asm30/index.html

https://blog.csdn.net/conquer0715/article/details/51283610

https://asm.ow2.io

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值