ASM使用要点记录

13 篇文章 0 订阅
new ClassWriter(ClassWriter.COMPUTE_MAXS)时,将为你计算局部变量与操作数栈部分的大小。还是必须调用 visitMaxs,但可以使用任何参数:它们将被
忽略并重新计算。使用这一选项时,仍然必须自行计算这些帧。
new ClassWriter(ClassWriter.COMPUTE_FRAMES)时,一切都是自动计算。 不再需要调用 visitFrame,但仍然必须调用 visitMaxs(参数将被忽略并重新计
算)。
//可以从命令行使用ASMifier类。可以写好目标方法然后用这个查看ASM的写法
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable


//产生缩进后的代码为:
package asm.java.lang;
import org.objectweb.asm.*;

public class RunnableDump implements Opcodes {

    public static byte[] dump() throws Exception {
        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;
        
        cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
        "java/lang/Runnable", null, "java/lang/Object", null);
        {
            mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run", "()V",
            null, null);
            mv.visitEnd();
        }
        cw.visitEnd();
        return cw.toByteArray();
    }
}
字节码指令可分为两类:
一小组指令旨在将值从局部变量传输到操作数堆栈,反之亦然;
其他指令仅作用于操作数堆栈:它们从堆栈中弹出一些值,根据这些值计算结果,然后将其压回堆栈。
ILOAD,LLOAD,FLOAD,DLOAD和ALOAD指令读取局部变量并将其值压入操作数堆栈。他们将必须读取的局部变量的索引i作为参数。
ILOAD用于加载布尔,字节,char,short或int局部变量。
LLOAD,FLOAD和DLOAD分别用于加载long,float或double值(LLOAD和DLOAD实际上加载了两个插槽i和i + 1)。
最后,ALOAD用于加载任何非原始值,即对象和数组引用。

对称地,ISTORE,LSTORE,FSTORE,DSTORE和ASTORE指令从操作数堆栈中弹出一个值,并将其存储在其索引i所指定的局部变量中。
public static void sleep(long d) {
    try {
        Thread.sleep(d);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

可以被编译为字节码:
TRYCATCHBLOCK try catch catch java/lang/InterruptedException
try:
LLOAD 0
INVOKESTATIC java/lang/Thread sleep (J)V
RETURN
catch:
INVOKEVIRTUAL java/lang/InterruptedException printStackTrace ()V
RETURN
try和catch标签之间的代码对应于try块,而catch标签之后的代码对应于catch块。

TRYCATCHBLOCK行指定一个异常处理程序,该处理程序覆盖try和catch标签之间的范围,该处理程序从catch标签开始,并且其类是InterruptedException的子类的异常。

这意味着,如果在try和catch之间的任何地方抛出了这样的异常,则该堆栈被清除,该异常将被推入该空堆栈,并且在catch处继续执行。

————————————————
版权声明:此段引用了CSDN博主「老马啸西风」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接: https://blog.csdn.net/ryo1060732496/article/details/103655608
//参考:https://www.jianshu.com/p/50882f3af59d
class TryCatchMethodVisitor extends AdviceAdapter {

        private int localIndex;
        private final Label startLabel = new Label();// try开始
        private final Label endLabel = new Label();// try结束
        private final Label handlerLabel = new Label();// catch开始处理异常
        private String methodDescriptor;

        protected TryCatchMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
            methodDescriptor = descriptor;
            System.out.println(name + " " + descriptor);
        }

        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
            mv.visitLabel(startLabel);//标记开始
        }

        @Override
        public void visitMaxs(int maxStack, int maxLocals) {

            mv.visitLabel(endLabel);//方法结束时标记接收
            mv.visitLabel(handlerLabel);//开始处理catch部分
            
            //-----------catch代码开始-----------
            localIndex = newLocal(Type.LONG_TYPE);
            mv.visitVarInsn(Opcodes.ALOAD, localIndex);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false);

            if (String.class.getName().equals(Type.getType(methodDescriptor).getReturnType().getClassName())) {
                int index = newLocal(Type.LONG_TYPE);
                mv.visitLdcInsn("669");
                mv.visitVarInsn(ASTORE, index);
                mv.visitVarInsn(ALOAD, index);
                mv.visitInsn(Opcodes.ARETURN);
            }
            //-----------catch代码结束-----------

            //
            mv.visitTryCatchBlock(startLabel, endLabel, handlerLabel, "java/lang/Exception");

            super.visitMaxs(maxStack, maxLocals);
        }
}
//方法整体替换
public class MethodReplace {

    public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader("com.moon.asm.learning.day0302.Person");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassVisitor classVisitor = new MyClassVisitor(Opcodes.ASM9, classWriter);
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES + ClassReader.SKIP_FRAMES);
        byte[] bytes = classWriter.toByteArray();
        FileUtils.writeByteArrayToFile(new File("d://Person.class"), bytes);
    }

    private static class MyClassVisitor extends ClassVisitor {

        protected MyClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }

        /**
         * 笔记ClassReader里面调用完visitMethod方法后得到MethodVisitor会继续调用MethodVisitor里面的方法。
         * 因此直接在这里执行MethodVisitor相关方法,返回给ClassReader一个null可以达到方法内容替换的目的
         * @return
         */
        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            if ("replaceAllMethod".equals(name)) {
                MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
                methodVisitor.visitLdcInsn("123");
                methodVisitor.visitInsn(Opcodes.ARETURN);
                return null;
            }
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }
    }
}
//替换某个静态方法调用
public class StaticMethodReplace {

    public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader("com.moon.asm.learning.day0302.Person");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassVisitor classVisitor = new MyClassVisitor(Opcodes.ASM9, classWriter);
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES + ClassReader.SKIP_FRAMES);
        byte[] bytes = classWriter.toByteArray();
        FileUtils.writeByteArrayToFile(new File("d://Person.class"), bytes);
    }

    private static class MyClassVisitor extends ClassVisitor {

        protected MyClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            if ("staticMethodReplace".equals(name)) {
                MethodVisitor originalMethodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
                return new MyMethodVisitor(Opcodes.ASM9, originalMethodVisitor, access, name, descriptor);
            }
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }
    }

    private static class MyMethodVisitor extends AdviceAdapter {

        protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
        }

        @Override
        public void visitMethodInsn(int opcodeAndSource, String owner, String name, String descriptor, boolean isInterface) {
            if ("java/lang/System".equals(owner) && "getProperty".equals(name)) {
                mv.visitMethodInsn(opcodeAndSource, "com/moon/asm/learning/day0302/Person", "staticMethodReplaceProxy", descriptor, isInterface);
                return;
            }
            super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
        }
    }
}
//替换某个非静态方法调用
public class VirtualMethodReplace {

    public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader("com.moon.asm.learning.day0302.Person");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassVisitor classVisitor = new VirtualMethodReplace.MyClassVisitor(Opcodes.ASM9, classWriter);
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES + ClassReader.SKIP_FRAMES);
        byte[] bytes = classWriter.toByteArray();
        FileUtils.writeByteArrayToFile(new File("d://Person.class"), bytes);
    }

    private static class MyClassVisitor extends ClassVisitor {

        protected MyClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            if ("virtualMethodReplace".equals(name)) {
                MethodVisitor originalMethodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
                return new VirtualMethodReplace.MyMethodVisitor(Opcodes.ASM9, originalMethodVisitor);
            }
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }
    }

    private static class MyMethodVisitor extends MethodVisitor {

        protected MyMethodVisitor(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitMethodInsn(int opcodeAndSource, String owner, String name, String descriptor, boolean isInterface) {

//            public String virtualMethodReplace(String name) {
//                return name.substring(2);  修改成---> Person.virtualMethodReplaceProxy(name, 2);
//            }
//
//            public static String virtualMethodReplaceProxy(String text, int beginIndex) {
//                return text.substring(beginIndex);
//            }

            //调用substring前会执行ALOAD 1, LDC 2,因此调用静态的virtualMethodReplaceProxy将这两个参数给使用掉了
            if ("java/lang/String".equals(owner) && "substring".equals(name)) {
                descriptor = "(Ljava/lang/String;I)Ljava/lang/String;";//virtualMethodReplaceProxy方法的descriptor

                //也可以写成
                //descriptor = descriptor.replace("(","(L" + owner +";");
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/moon/asm/learning/day0302/Person", "virtualMethodReplaceProxy",
                        descriptor, isInterface);
                return;
            }
            super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
        }
    }
}
//修改父类
//https://blog.csdn.net/ryo1060732496/article/details/103655574
//        byte[] b1 = ...
//        ClassReader cr = new ClassReader(b1);
//        ClassWriter cw = new ClassWriter(cr, 0);//优化把cr出传进去
//        ChangeVersionAdapter ca = new ChangeVersionAdapter(cw);
//        cr.accept(ca, 0);
//        byte[] b2 = cw.toByteArray();
//对于添加字段,方法或指令的转换来说,这不是问题,但是与未优化的情况相比,对于删除或重命名许多类元素的转换,这导致更大的类文件。因此,建议仅将此优化用于“附加”转换。
public class ModifySuperClass {


    public static void main(String[] args) throws IOException {
        ClassReader classReader = new ClassReader("com.moon.asm.learning.day0302.SuperMan");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor classVisitor = new ModifySuperClassVisitor(Opcodes.ASM9, classWriter);
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES + ClassReader.SKIP_FRAMES);
        byte[] bytes = classWriter.toByteArray();
        FileUtils.writeByteArrayToFile(new File("d://SuperMan.class"), bytes);
    }

    static class ModifySuperClassVisitor extends ClassVisitor {

        protected ModifySuperClassVisitor(int api) {
            super(api);
        }

        protected ModifySuperClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            //就这样就把SuperMan的父类修改为Person了
            super.visit(version, access, name, signature, "com/moon/asm/learning/day0302/Person", interfaces);  
        }
    }

}
//添加删除类成员
public class ModifyMemberClass {


    public static void main(String[] args) throws IOException {
        ClassReader classReader = new ClassReader("com.moon.asm.learning.day0302.SuperMan");
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        ClassVisitor classVisitor = new ModifySuperClassVisitor(Opcodes.ASM9, classWriter);
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES + ClassReader.SKIP_FRAMES);
        byte[] bytes = classWriter.toByteArray();
        FileUtils.writeByteArrayToFile(new File("d://SuperMan.class"), bytes);
    }

    static class ModifyMemberClassVisitor extends ClassVisitor {

        protected ModifyMemberClassVisitor(int api) {
            super(api);
        }

        protected ModifyMemberClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces);

//            放在这里其实是不太合适的,最好是在visitEnd中添加成员
//            例如,如果要向类添加字段,则必须在原始方法调用之间插入对visitField的新调用,并且必须将此新调用放入类适配器的visit方法之一。
//            例如,您无法在visit方法中执行此操作,因为这可能会导致对visitField的调用,随后是无效的visitSource,visitOuterClass,visitAnnotation或visitAttribute。
//            出于相同的原因,您不能将此新调用放入visitSource,visitOuterClass,visitAnnotation或visitAttribute方法中。
//            唯一的可能性是visitInnerClass,visitField,visitMethod或visitEnd方法。
//            如果将新调用放入visitEnd方法中,则将始终添加该字段(除非您添加显式条件),因为始终会调用此方法。

            //相当于向cv多转发了一部分内容
            //public static final int LESS = -1;
            cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "LESS", "I",
                    null, -1).visitEnd();
            //public static final String text = "Hello";
            cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "text", "Ljava/lang/String;",
                    null, "Hello").visitEnd();
            //添加方法
            // public abstract int compareTo(Object o);
            cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "compareTo",
                    "(Ljava/lang/Object;)I", null, null).visitEnd();
        }
    }

}
//Person
public class Person {

    public String getName() {
        int a = 1;
        int b = 2;
        int c = a + b;
        return "123";
    }

    public void setName() {
        int a = 1;
        int b = 2;
        int c = a + b;

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String replaceAllMethod() {
        int a = 10;
        int b = a + 2;
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

    public String staticMethodReplace() {
        return System.getProperty("xxx");
    }

    public static String staticMethodReplaceProxy(String key) {
        return System.getProperty(key);
    }

    public String virtualMethodReplace(String name) {
        return name.substring(2);
    }

    public static String virtualMethodReplaceProxy(String text, int beginIndex) {
        return text.substring(beginIndex);
    }

}
轮子
transform框架:
https://github.com/Leaking/Hunter
http://quinnchen.cn/2018/09/13/2018-09-13-asm-transform/

https://github.com/bytedance/ByteX
优秀文章:
分享 4 个案例,一起玩一下 ASM
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值