栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

修改已有的方法(删除-移除Instruction)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

修改已有的方法(删除-移除Instruction)

1. 如何移除Instruction

在修改方法体的代码时,如何移除一条Instruction呢?其实,很简单,就是让中间的某一个MethodVisitor对象不向后“传递该instruction”就可以了。

但是,需要要注意一点:无论是添加instruction,还是删除instruction,还是要替换instruction,都要保持operand stack修改前和修改后是一致的。这句话该怎么理解呢?我们举个例子来进行说明。

假如,有一条打印语句,如下:

System.out.println("Hello World");

这条打印语句,对应着三个instruction,如下:

GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "Hello World"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V

上面三条instruction的执行过程如下:

  • 第一步,执行GETSTATIC java/lang/System.out : Ljava/io/PrintStream;,会把一个System.out对象push到operand stack上。
  • 第二步,执行LDC “Hello World”,会将一个字符串对象push到operand stack上。
  • 第三步,执行INVOKEVIRTUALjava/io/PrintStream.println (Ljava/lang/String;)V,会消耗掉operand stack栈顶上的两个元素,然后打印出结果。

如果我们只想删除第三条INVOKEVIRTUAL对应的instruction,是不行的,因为它会让operand stack栈顶上多出两个元素。这三条instruction应该一起删除,才能保证operand stack在修改前和修改后是一致的。

2. 示例:移除NOP

为了让示例容易一些,我们先来处理一个比较简单的情况,那就是移除NOP指令。那么,为什么要移除NOP指令呢?因为NOP表示no operation,它是一个单独的指令,本身也不做什么操作,我们删除它不会影响任何实质性的操作,也不会牵连其它的instruction。

当然,一般情况下,由.java编译生成的.class文件中不会包含NOP指令。那么,我们就自己生成一个.class文件,让它带有NOP指令。

2.1. 预期目标

我们想实现的预期目标:删除代码当中的NOP指令。

首先,我们来生成一个包含NOP指令的.class文件,如下:

import lsieun.utils.FileUtils;
import org.objectweb.asm.*;

import static org.objectweb.asm.Opcodes.*;

public class HelloWorldGenerateCore {
    public static void main(String[] args) throws Exception {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);

        // (1) 生成byte[]内容
        byte[] bytes = dump();

        // (2) 保存byte[]到文件
        FileUtils.writeBytes(filepath, bytes);
    }

    public static byte[] dump() throws Exception {
        // (1) 创建ClassWriter对象
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // (2) 调用visitXxx()方法
        cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "sample/HelloWorld",
                null, "java/lang/Object", null);

        {
            MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
            mv1.visitCode();
            mv1.visitVarInsn(ALOAD, 0);
            mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
            mv1.visitInsn(RETURN);
            mv1.visitMaxs(1, 1);
            mv1.visitEnd();
        }

        {
            MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            mv2.visitCode();
            mv2.visitInsn(NOP);
            mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv2.visitInsn(NOP);
            mv2.visitLdcInsn("Hello World");
            mv2.visitInsn(NOP);
            mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv2.visitInsn(NOP);
            mv2.visitInsn(RETURN);
            mv2.visitMaxs(2, 1);
            mv2.visitEnd();
        }
        cw.visitEnd();

        // (3) 调用toByteArray()方法
        return cw.toByteArray();
    }
}

查看生成后的效果:

$ javap -c sample.HelloWorld
public class sample.HelloWorld {
  public sample.HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."":()V
       4: return

  public void test();
    Code:
       0: nop
       1: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
       4: nop
       5: ldc           #17                 // String Hello World
       7: nop
       8: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      11: nop
      12: return
}
2.2. 编码实现
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;

import static org.objectweb.asm.Opcodes.*;

public class MethodRemoveNopVisitor extends ClassVisitor {
    public MethodRemoveNopVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if (mv != null && !"".equals(name) && !"".equals(name)) {
            boolean isAbstractMethod = (access & ACC_ABSTRACT) != 0;
            boolean isNativeMethod = (access & ACC_NATIVE) != 0;
            if (!isAbstractMethod && !isNativeMethod) {
                mv = new MethodRemoveNopAdapter(api, mv);
            }

        }
        return mv;
    }

    private static class MethodRemoveNopAdapter extends MethodVisitor {
        public MethodRemoveNopAdapter(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitInsn(int opcode) {
            // if (opcode == NOP) {
            //     // do nothing
            // }
            // else {
            //     super.visitInsn(opcode);
            // }
            if (opcode != NOP) {
                super.visitInsn(opcode);
            }
        }
    }
}
2.3. 进行转换
import lsieun.utils.FileUtils;
import org.objectweb.asm.*;

public class HelloWorldTransformCore {
    public static void main(String[] args) {
        String relative_path = "sample/HelloWorld.class";
        String filepath = FileUtils.getFilePath(relative_path);
        byte[] bytes1 = FileUtils.readBytes(filepath);

        //(1)构建ClassReader
        ClassReader cr = new ClassReader(bytes1);

        //(2)构建ClassWriter
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        //(3)串连ClassVisitor
        int api = Opcodes.ASM9;
        ClassVisitor cv = new MethodRemoveNopVisitor(api, cw);

        //(4)结合ClassReader和ClassVisitor
        int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        cr.accept(cv, parsingOptions);

        //(5)生成byte[]
        byte[] bytes2 = cw.toByteArray();

        FileUtils.writeBytes(filepath, bytes2);
    }
}
2.4. 验证结果
$ javap -c sample.HelloWorld
public class sample.HelloWorld {
  public sample.HelloWorld();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."":()V
       4: return

  public void test();
    Code:
       0: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #17                 // String Hello World
       5: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}
3. 总结
  • 第一点,移除Instruction方式,就是让中间的某一个MethodVisitor对象不向后“传递该instruction”就可以了。
  • 第二点,在移除instruction的过程中,要保证operand stack在修改前和修改后是一致的。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/889159.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号