ASM-MehotdVisitor实践

news2024/11/26 0:40:37

使用ASM几乎用户全部的精力都是对MethodVisitor的处理,方法code的处理都需要使用这个类进行操作。还是之前文章说过的,ASM单独学习意义并不大,难以达到触类旁通,先行掌握字节码基础后再玩起ASM才能体会真正的乐趣,不然真的蛮折磨人的。

场景一:需要在方法开始插入代码

这个应该非常简单的,相信对于熟练掌握MethodVisitor方法调用顺序及方法含义的你来说应当不是问题的,visitCode方法是访问code的开始,所以我们最优的选择就是重写该方法,在该方法中开始书写需要插入的代码

import org.objectweb.asm.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class HelloWorld {
    public static void hello() {
        System.out.println("hello world");
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ClassReader cr = new ClassReader("HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @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) {
                    //这里判断需要处理的方法,真实场景一般需要对类名、方法名、方法签名一起进行校验
                    if (name.equals("hello") && descriptor.equals("()V")) {
                        mv = new MethodVisitor(Opcodes.ASM6, mv) {
                            @Override
                            public void visitCode() {
                                super.visitCode();
                                //插入代码,此处插入的代码为 System.out.println("code from asm insert");
                                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                                mv.visitLdcInsn("code from asm insert");
                                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                            }
                        };
                    }
                }
                return mv;
            }
        }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
        
        //通过ClassLoader加载bytes后执行hello方法
        ASMGenClassLoader classLoader = new ASMGenClassLoader(cw.toByteArray());
        Class<?> helloWorld = classLoader.findClass("HelloWorld");
        Method hello = helloWorld.getDeclaredMethod("hello");
        hello.invoke(null);
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

执行代码后不出意外可以看到控制台输出code from asm insert和hello world。在方法首部插入代码的模板非常简单,往往我们需要在ClassVisitor#visit方法记录类信息后判断出是否是需要处理的类,在ClassVisitor#visitMethod方法中判断是否是需要处理的方法,然后通过一个MethodVisitor的子类中重写visitCode方法插入相应的代码。

场景二:在方法尾部插入代码

在方法尾部插入代码相比在方法首部插入相对来说更困难些,因为方法首部我们无需更多的考虑,而方法的尾部却需要考虑到正常方法退出、异常退出相关的指令, 所以我们可以通过对这些指令进行监控,并且在这些指令入栈前插入我们相应的代码便可以达到在方法尾部插入代码的目的。

import org.objectweb.asm.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class HelloWorld {
    public static void hello() {
        System.out.println("hello world");
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ClassReader cr = new ClassReader("HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @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) {
                    //这里判断需要处理的方法,真实场景一般需要对类名、方法名、方法签名一起进行校验
                    if (name.equals("hello") && descriptor.equals("()V")) {
                        mv = new MethodVisitor(Opcodes.ASM6, mv) {
                            @Override
                            public void visitInsn(int opcode) {
                                //opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN条件为正常方法退出操作码
                                //opcode == Opcodes.ATHROW条件为异常时方法退出操作码
                                if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
                                    //插入代码,此处插入的代码为 System.out.println("code from asm insert");
                                    mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                                    mv.visitLdcInsn("code from asm insert");
                                    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                                }
                                super.visitInsn(opcode);
                            }
                        };
                    }
                }
                return mv;
            }
        }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);

        //通过ClassLoader加载bytes后执行hello方法
        ASMGenClassLoader classLoader = new ASMGenClassLoader(cw.toByteArray());
        Class<?> helloWorld = classLoader.findClass("HelloWorld");
        Method hello = helloWorld.getDeclaredMethod("hello");
        hello.invoke(null);
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

执行代码后不出意外可以看到控制台输出hello world和code from asm insert。方法尾部插入代码的套路和方法首部插入的套路很相似,难点在于如何拦截到方法退出操作码,而这么多退出操作码书写也非常的麻烦,我们可以通过封装抽象出一个类帮助我们屏蔽掉内部的逻辑,幸运的时ASM也考虑到了这一点为我们提供了封装好的AdviceAdapter

public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes {
	protected void onMethodEnter()
    
    protected void onMethodExit(int opcode)
}

关于这个类的使用非常简单,我们着重关注的两个方法onMethodEnter、onMethodExit,命名不难看出一个方法为方法进入时的回调,一个为方法退出的回调及相关的退出操作码。我们通过使用这个类替换上面两个例子

import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class HelloWorld {
    public static void hello() {
        System.out.println("hello world");
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ClassReader cr = new ClassReader("HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @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) {
                    //这里判断需要处理的方法,真实场景一般需要对类名、方法名、方法签名一起进行校验
                    if (name.equals("hello") && descriptor.equals("()V")) {
                        mv = new AdviceAdapter(Opcodes.ASM6, mv, access, name, descriptor) {
                            @Override
                            protected void onMethodEnter() {
                                super.onMethodEnter();
                                //插入代码,此处插入的代码为 System.out.println("code from asm onMethodEnter insert");
                                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                                mv.visitLdcInsn("code from asm onMethodEnter insert");
                                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                            }

                            @Override
                            protected void onMethodExit(int opcode) {
                                super.onMethodExit(opcode);
                                //插入代码,此处插入的代码为 System.out.println("code from asm onMethodExit insert");
                                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                                mv.visitLdcInsn("code from asm onMethodExit insert");
                                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                            }
                        };
                    }
                }
                return mv;
            }
        }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);

        //通过ClassLoader加载bytes后执行hello方法
        ASMGenClassLoader classLoader = new ASMGenClassLoader(cw.toByteArray());
        Class<?> helloWorld = classLoader.findClass("HelloWorld");
        Method hello = helloWorld.getDeclaredMethod("hello");
        hello.invoke(null);
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

执行代码后不出意外可以看到控制台输出code from asm onMethodEnter insert、hello world和code from asm onMethodExit insert。这些例子中我们使用的字符串都是直接通过字面量的形式直接传入方法的,但是根据我们书写代码的经验来看声明局部变量更加高频,那么我们如何在ASM中在方法中生命变量存储数据并且如何在后续中使用该变量呢?为了编写出相关的代码,首先需要你对ClassFile中的method_info的结构有一定的了解,每个方法都有这自己对应的局部变量池,而这个变量池在编译期间确定长度,变量池的长度由方法结束时依旧存在的所有变量的和确定,需要注意的时变量池存在复用的机制,所以并不是说变量池的长度为方法中出现变量最高次数的值。对应到ASM中确定变量池大小所对应的方法为MethodVistor#visitMaxs(int maxStack, int maxLocals)。

在ASM中我们可以通过自己计算变量池大小并且在方法结束前调用该方法,或者通过参数配置让ASM自行计算,个人推荐交权给ASM,毕竟人家是专业的么,当然如果你是大牛觉得性能更为重要,那么手动计算更适合你。在确定掌握前置相关知识后对于聪明的我们来说声明局部变量应该不是什么问题了就,首先我们知道变量池是一个类似于数组的数据结构,而数据项都有相关的数据类型和值,在新建一个局部变量时我们肯定需要存储新建变量在变量池的位置和变量的类型,后续我们便可以通过这两个数据关系进行操作码的存储和读取操作,所以核心就是通过一定数据结构维护我们新建的变量在变量池的索引及对应的变量类型。当然这里StackFrame由于过于复杂暂不考虑它带来的影响

import org.objectweb.asm.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;


public class HelloWorld {
    public static void hello() throws InterruptedException {
        System.out.println("hello world");
        Thread.sleep(300);
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ClassReader cr = new ClassReader("HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @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) {
                    //这里判断需要处理的方法,真实场景一般需要对类名、方法名、方法签名一起进行校验
                    if (name.equals("hello") && descriptor.equals("()V")) {
                        mv = new MethodVisitor(Opcodes.ASM6, mv) {
                            private int argSize = Type.getArgumentTypes(descriptor).length;
                            private HashMap<Integer, Type> argIndexTypes = new HashMap<>();

                            private int timeArgIndex;

                            @Override
                            public void visitCode() {
                                super.visitCode();
                                timeArgIndex = newLocal(Type.LONG_TYPE);
                                visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                                visitVarInsn(Opcodes.LSTORE, timeArgIndex);
                            }

                            @Override
                            public void visitInsn(int opcode) {
                                if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
                                    visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                                    visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                                    visitVarInsn(Opcodes.LLOAD, timeArgIndex);
                                    visitInsn(Opcodes.LSUB);
                                    visitVarInsn(Opcodes.LSTORE, timeArgIndex);

                                    visitLdcInsn("method %s cost %d ms");

                                    visitInsn(Opcodes.ICONST_2);
                                    visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
                                    visitInsn(Opcodes.DUP);
                                    visitInsn(Opcodes.ICONST_0);
                                    visitLdcInsn(name);
                                    visitInsn(Opcodes.AASTORE);

                                    visitInsn(Opcodes.DUP);
                                    visitInsn(Opcodes.ICONST_1);
                                    visitVarInsn(Opcodes.LLOAD, timeArgIndex);
                                    visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
                                    visitInsn(Opcodes.AASTORE);
                                    visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false);

                                    visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                                }
                                super.visitInsn(opcode);
                            }

                            protected int newLocal(Type type) {
                                argIndexTypes.put(++argSize, type);
                                return argSize;
                            }
                        };
                    }
                }
                return mv;
            }
        }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);

        //通过ClassLoader加载bytes后执行hello方法
        ASMGenClassLoader classLoader = new ASMGenClassLoader(cw.toByteArray());
        Class<?> helloWorld = classLoader.findClass("HelloWorld");
        Method hello = helloWorld.getDeclaredMethod("hello");
        hello.invoke(null);
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

执行代码后不出意外可以看到控制台输出hello world和method hello cost 315 ms。如果单从声明局部变量起始实现非常简单,这里通过初始化时先确定下 argSize的值,即当前函数参数占用的变量池大小,Type类时ASM提供给开发者使用的工具类,这个类提供很多有用的转换操作,比如常见的Class到Type的转换后可以便捷的取到全限定名等信息,当然这里我们通过利用它来拿到方法签名中方法参数个数值。然后维护一个为名argIndexTypes的HashMap将变量索引和类型一一对应。最后添加一个newLocal方法用于新建变量并返回这个变量所在变量池中的索引。

上方代码完成的功能就是一个统计函数耗时的功能,执行完后输出函数耗时。有一个点需要注意到的是程序利用String.format格式化字符串的时候第二个参数为数组,而对数组初始化后在将第二个参数为long类型添加进数组时需要将J转为Long,而我们平时说的自动装箱自动拆箱其实就是这么做的,只不过真正在我们接触字节码时才会更有体会。当然ASM也想到了局部变量自己维护不容易所以也提供了相应的类LocalVariablesSorter帮助我们快速起飞。使用上和我们现在的这个类一样通过调用int newLocal(final Type type)方法新建变量。让我们通过该类实现上方一样性质的代码

import org.objectweb.asm.*;
import org.objectweb.asm.commons.LocalVariablesSorter;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class HelloWorld {
    public static void hello() throws InterruptedException {
        System.out.println("hello world");
        Thread.sleep(300);
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ClassReader cr = new ClassReader("HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @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) {
                    //这里判断需要处理的方法,真实场景一般需要对类名、方法名、方法签名一起进行校验
                    if (name.equals("hello") && descriptor.equals("()V")) {
                        mv = new LocalVariablesSorter(Opcodes.ASM6, access, descriptor, mv) {
                            private int timeArgIndex;

                            @Override
                            public void visitCode() {
                                super.visitCode();
                                timeArgIndex = newLocal(Type.LONG_TYPE);
                                visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                                visitVarInsn(Opcodes.LSTORE, timeArgIndex);
                            }

                            @Override
                            public void visitInsn(int opcode) {
                                if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
                                    visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                                    visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                                    visitVarInsn(Opcodes.LLOAD, timeArgIndex);
                                    visitInsn(Opcodes.LSUB);
                                    visitVarInsn(Opcodes.LSTORE, timeArgIndex);

                                    visitLdcInsn("method %s cost %d ms");

                                    visitInsn(Opcodes.ICONST_2);
                                    visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
                                    visitInsn(Opcodes.DUP);
                                    visitInsn(Opcodes.ICONST_0);
                                    visitLdcInsn(name);
                                    visitInsn(Opcodes.AASTORE);

                                    visitInsn(Opcodes.DUP);
                                    visitInsn(Opcodes.ICONST_1);
                                    visitVarInsn(Opcodes.LLOAD, timeArgIndex);
                                    visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
                                    visitInsn(Opcodes.AASTORE);
                                    visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false);

                                    visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                                }
                                super.visitInsn(opcode);
                            }
                        };
                    }
                }
                return mv;
            }
        }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);

        //通过ClassLoader加载bytes后执行hello方法
        ASMGenClassLoader classLoader = new ASMGenClassLoader(cw.toByteArray());
        Class<?> helloWorld = classLoader.findClass("HelloWorld");
        Method hello = helloWorld.getDeclaredMethod("hello");
        hello.invoke(null);
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

AdviceAdapter是LocalVariablesSorter的子类,因此可以通过AdviceAdapter继续优化我们的代码,这里就不再贴出代码,留给聪明且勤奋的你来敲敲代码了。事实上很多统计相关的工作我们便可以已当前的模板再加优化便可以完成相应的很好。以上的代码我们都是通过写死的对hello方法进行处理,但是真实场景我们不可能把所有的方法都写进判断语句中,比较常见的是依赖注解来处理方法,所以这里我们已通过注解的标记来处理方法

import lombok.Data;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class HelloWorld {
    @Event(eventName = "event_hello")
    public static void hello() {
        System.out.println("hello world");
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        final int readerOption = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        ClassReader cr = new ClassReader("HelloWorld");
        CollectClassVisitor collectClassVisitor = new CollectClassVisitor();
        cr.accept(collectClassVisitor, readerOption);

        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            private String clzName;

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

            @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) {
                    EventBean eventBean = collectClassVisitor.matchMethod(clzName, name, descriptor);
                    if (eventBean != null) {
                        mv = new AdviceAdapter(Opcodes.ASM6, mv, access, name, descriptor) {
                            @Override
                            protected void onMethodEnter() {
                                super.onMethodEnter();
                                visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                                visitLdcInsn("event name: " + eventBean.eventName);
                                visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                            }
                        };
                    }
                }
                return mv;
            }
        }, readerOption);

        //通过ClassLoader加载bytes后执行hello方法
        ASMGenClassLoader classLoader = new ASMGenClassLoader(cw.toByteArray());
        Class<?> helloWorld = classLoader.findClass("HelloWorld");
        Method hello = helloWorld.getDeclaredMethod("hello");
        hello.invoke(null);
    }

    static class CollectClassVisitor extends ClassVisitor {
        private String clzName;
        private ArrayList<EventBean> eventBeans = new ArrayList<>();

        public CollectClassVisitor() {
            super(Opcodes.ASM6);
        }

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

        @Override
        public MethodVisitor visitMethod(int access, String name, String methodDescriptor, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, methodDescriptor, signature, exceptions);
            mv = new MethodVisitor(Opcodes.ASM6, mv) {
                @Override
                public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
                    AnnotationVisitor an = super.visitAnnotation(descriptor, visible);
                    if (descriptor.equals("LHelloWorld$Event;")) {
                        final EventBean eventBean = new EventBean();
                        eventBean.clzName = clzName;
                        eventBean.methodName = name;
                        eventBean.methodDesc = methodDescriptor;
                        an = new AnnotationVisitor(Opcodes.ASM6, an) {
                            @Override
                            public void visit(String name, Object value) {
                                super.visit(name, value);
                                eventBean.eventName = (String) value;
                            }

                            @Override
                            public void visitEnd() {
                                super.visitEnd();
                                eventBeans.add(eventBean);
                            }
                        };
                    }
                    return an;
                }
            };
            return mv;
        }

        public EventBean matchMethod(String clzName, String name, String descriptor) {
            for (EventBean eventBean : eventBeans) {
                if (eventBean.clzName.equals(clzName) && eventBean.methodName.equals(name) && eventBean.methodDesc.equals(descriptor)) {
                    return eventBean;
                }
            }
            return null;
        }
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Event {
        String eventName();
    }

    @Data
    static class EventBean {
        private String clzName;
        private String methodName;
        private String methodDesc;
        private String eventName;
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

通过注解处理方法首先我们需要手机被注解标记的类的方法,收集完成后再次通过ASM处理类进行匹配,匹配成功后进行相应代码的处理

场景三:偷梁换柱

在很多情况我们可以通过偷梁换柱达到我们的目的,而偷梁换柱的主要精力时对关心的字节操作码匹配,匹配成功后再进行偷梁换柱,首先我们通过替换hello方法中字面量字符串开启第一个偷梁换柱的例子吧

import org.objectweb.asm.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class HelloWorld {
    public static void hello() {
        System.out.println("hello world");
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        final int readerOption = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        ClassReader cr = new ClassReader("HelloWorld");

        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            private String clzName;

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

            @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) {
                    if (name.equals("hello") && descriptor.equals("()V")) {
                        mv = new MethodVisitor(Opcodes.ASM6, mv) {
                            @Override
                            public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
                                if (opcode == Opcodes.INVOKEVIRTUAL && owner.equals("java/io/PrintStream") && name.equals("println") && descriptor.equals("(Ljava/lang/String;)V")) {
                                    visitInsn(Opcodes.POP);
                                    visitLdcInsn("hello river!");
                                }
                                super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                            }
                        };
                    }
                }
                return mv;
            }
        }, readerOption);

        //通过ClassLoader加载bytes后执行hello方法
        ASMGenClassLoader classLoader = new ASMGenClassLoader(cw.toByteArray());
        Class<?> helloWorld = classLoader.findClass("HelloWorld");
        Method hello = helloWorld.getDeclaredMethod("hello");
        hello.invoke(null);
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

执行代码后不出意外可以看到控制台输出的是hello river!而并不是hello world。方法其实很多,这里经过分析没法方法执行前应该将所需要的参数压入操作栈内,而我们需要替换的话很简单将操作栈内的原有字符串数据弹出并且压入我们需要的字符串那么是不是就达到了偷梁换柱的目的了。这里我们通过拦截PrintStream#println的方法将操作栈内数据替换便可以看到运行结果为hello river!。而利用这种我们可以通过对各种校验相关的函数进行劫持已达到绕过验证的机制。这里我们已Charler为例子,看看我们如何真实的破解该软件,这里已4.6.2版本为例

import org.objectweb.asm.*;

import java.io.*;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;

/**
 * @Author: River
 * @Emial: 1632958163@qq.com
 * @Create: 2022/3/24
 **/
public class CharlesCrack {
    public static void main(String[] args) throws Exception {
        //This is a 30 day trial version. If you continue using Charles you must\npurchase a license. Please see the Help menu for details.
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入charles安装的目录:");

        String charlesPath = br.readLine();
        String charlesJarPath = charlesPath + "\\lib\\charles.jar";
        File charlesJarFile = new File(charlesJarPath);
        if (!charlesJarFile.exists()) {
            System.out.println("charles目录(" + charlesJarPath + ")不存在!");
            return;
        }
        System.out.println("charles检测通过");
        System.out.println("正在处理文件...");

        final int readerOption = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        ClassReader cr;
        ClassWriter cw = null;

        JarFile jarFile = new JarFile(charlesJarPath);
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String entryName = jarEntry.getName();
            if (entryName.equals("com/xk72/charles/p.class")) {
                ZipEntry zipEntry = new ZipEntry(entryName);
                InputStream inputStream = jarFile.getInputStream(zipEntry);
                cr = new ClassReader(inputStream);
                cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
                cr.accept(new ClassVisitor(Opcodes.ASM7, cw) {
                    @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) {
                            if (name.equals("c") && descriptor.equals("()Ljava/lang/String;")) {
                                mv = new MethodVisitor(Opcodes.ASM7, mv) {
                                    @Override
                                    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
                                    }

                                    @Override
                                    public void visitInsn(int opcode) {
                                        if (opcode == Opcodes.ARETURN) {
                                            visitInsn(Opcodes.POP);
                                            visitLdcInsn("River破解");
                                        }
                                        super.visitInsn(opcode);
                                    }
                                };
                            }

                            if (name.equals("a") && descriptor.equals("()Z")) {
                                mv = new MethodVisitor(Opcodes.ASM6, mv) {
                                    @Override
                                    public void visitInsn(int opcode) {
                                        if (opcode == Opcodes.IRETURN) {
                                            visitInsn(Opcodes.POP);
                                            visitInsn(Opcodes.ICONST_1);
                                        }
                                        super.visitInsn(opcode);
                                    }
                                };
                            }
                        }
                        return mv;
                    }
                }, readerOption);
            }
        }

        writeJarFile(charlesJarPath, "com/xk72/charles/p.class", cw.toByteArray());

        System.out.println("破解完成!");
    }


    public static void writeJarFile(String jarFilePath, String entryName, byte[] data) throws Exception {

        //1、首先将原Jar包里的所有内容读取到内存里,用TreeMap保存
        JarFile jarFile = new JarFile(jarFilePath);
        //可以保持排列的顺序,所以用TreeMap 而不用HashMap
        TreeMap tm = new TreeMap();
        Enumeration es = jarFile.entries();
        while (es.hasMoreElements()) {
            JarEntry je = (JarEntry) es.nextElement();
            byte[] b = readStream(jarFile.getInputStream(je));
            tm.put(je.getName(), b);
        }

        JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFilePath));
        Iterator it = tm.entrySet().iterator();
        boolean has = false;

        //2、将TreeMap重新写到原jar里,如果TreeMap里已经有entryName文件那么覆盖,否则在最后添加
        while (it.hasNext()) {
            Map.Entry item = (Map.Entry) it.next();
            String name = (String) item.getKey();
            JarEntry entry = new JarEntry(name);
            jos.putNextEntry(entry);
            byte[] temp;
            if (name.equals(entryName)) {
                //覆盖
                temp = data;
                has = true;
            } else {
                temp = (byte[]) item.getValue();
            }
            jos.write(temp, 0, temp.length);
        }

        if (!has) {
            //最后添加
            JarEntry newEntry = new JarEntry(entryName);
            jos.putNextEntry(newEntry);
            jos.write(data, 0, data.length);
        }
        jos.finish();
        jos.close();

    }

    public static byte[] readStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        return outSteam.toByteArray();
    }
}

通过反编译软件查看Charles的源码,而通过分析This is a 30 day trial version. If you continue using Charles you must\npurchase a license. Please see the Help menu for details这段字符串的位置调用可以分析出关键类及校验方法,通过ASM将该核心类的方法进行修改后使得永远校验通过,当然为了保险我们将方法内部所有的字节码全部删除值保留我们需要的return true字节码。在我们自己开发时破解应该注意并且最好做些机制防止使用这种办法就简单的给到攻击者可乘之机,最好将核心代码通过native书写。再次运行Charles将看到破解后的相关信息

场景四:AOP

AOP没有使用过至少你也应该听说过,这里不再赘述相关内容,我们将实现一个非常简单的aspectj版本

import lombok.Data;
import lombok.SneakyThrows;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class HelloWorld {
    public static void hello() {
        System.out.println("hello world");
    }

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        final int readerOption = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
        ClassReader cr = new ClassReader("HelloWorld$AopTest");
        AopCollectClassVisitor aopCollectClassVisitor = new AopCollectClassVisitor();
        cr.accept(aopCollectClassVisitor, readerOption);

        cr = new ClassReader("HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            private String clzName;

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

            @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) {

                    mv = new AdviceAdapter(Opcodes.ASM6, mv, access, name, descriptor) {
                        @Override
                        protected void onMethodEnter() {
                            super.onMethodEnter();
                            AopBean aopBean = aopCollectClassVisitor.matchAop(clzName, name, descriptor, Before.class);

                            if (aopBean != null) {
                                visitMethodInsn(Opcodes.INVOKESTATIC, aopBean.targetClzName, aopBean.targetMethodName, aopBean.targetMethodDesc, false);
                            }
                        }

                        @Override
                        protected void onMethodExit(int opcode) {
                            super.onMethodExit(opcode);
                            AopBean aopBean = aopCollectClassVisitor.matchAop(clzName, name, descriptor, Exit.class);
                            if (aopBean != null) {
                                visitMethodInsn(Opcodes.INVOKESTATIC, aopBean.targetClzName, aopBean.targetMethodName, aopBean.targetMethodDesc, false);
                            }
                        }
                    };
                }
                return mv;
            }
        }, readerOption);

        //通过ClassLoader加载bytes后执行hello方法
        ASMGenClassLoader classLoader = new ASMGenClassLoader(cw.toByteArray());
        Class<?> helloWorld = classLoader.findClass("HelloWorld");
        Method hello = helloWorld.getDeclaredMethod("hello");
        hello.invoke(null);
    }

    static class AopCollectClassVisitor extends ClassVisitor {
        private String clzName;
        private ArrayList<AopBean> aopBeans = new ArrayList<>();

        public AopCollectClassVisitor() {
            super(Opcodes.ASM6);
        }

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

        @Override
        public MethodVisitor visitMethod(int access, String name, String methodDescriptor, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, methodDescriptor, signature, exceptions);
            mv = new MethodVisitor(Opcodes.ASM6, mv) {
                @Override
                public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
                    AnnotationVisitor av = super.visitAnnotation(descriptor, visible);
                    if (descriptor.equals("LHelloWorld$Before;") || descriptor.equals("LHelloWorld$Exit;")) {
                        AopBean aopBean = new AopBean();
                        aopBean.targetClzName = clzName;
                        aopBean.targetMethodName = name;
                        aopBean.targetMethodDesc = methodDescriptor;
                        aopBean.aopAnnClzName = descriptor;

                        av = new AnnotationVisitor(Opcodes.ASM6, av) {
                            @SneakyThrows
                            @Override
                            public void visit(String name, Object value) {
                                super.visit(name, value);

                                String setMethodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
                                Method setMethod = aopBean.getClass().getDeclaredMethod(setMethodName, String.class);
                                setMethod.invoke(aopBean, (String) value);
                            }

                            @Override
                            public void visitEnd() {
                                super.visitEnd();
                                aopBeans.add(aopBean);
                            }
                        };
                    }
                    return av;
                }
            };
            return mv;
        }

        public AopBean matchAop(String clzName, String methodName, String methodDesc, Class annoClz) {
            for (AopBean aopBean : aopBeans) {
                boolean clzMatch = aopBean.clzName.equals(clzName) || aopBean.clzName.equals("*");
                boolean methodNameMatch = aopBean.methodName.equals(methodName) || aopBean.methodName.equals("*");
                boolean methodDescMatch = aopBean.methodDesc.equals(methodDesc) || aopBean.methodDesc.equals("*");
                if (clzMatch && methodNameMatch && methodDescMatch && aopBean.aopAnnClzName.equals(Type.getDescriptor(annoClz))) {
                    return aopBean;
                }
            }
            return null;
        }
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Before {
        String clzName();

        String methodName();

        String methodDesc();
    }

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Exit {
        String clzName();

        String methodName();

        String methodDesc();
    }

    public static class AopTest {
        @Before(clzName = "*", methodName = "hello", methodDesc = "()V")
        public static void sayBefore() {
            System.out.println("aop code insert from AopTest.sayBefore");
        }

        @Exit(clzName = "*", methodName = "hello", methodDesc = "()V")
        public static void sayAfter() {
            System.out.println("aop code insert from AopTest.sayAfter");
        }
    }

    @Data
    static class AopBean {
        private String clzName;
        private String methodName;
        private String methodDesc;
        private String aopAnnClzName;

        private String targetClzName;
        private String targetMethodName;
        private String targetMethodDesc;
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

代码实现的AOP非常简单,相对来说实现成熟一些的AOP无非就是额外添加些注解,额外添加更复杂的匹配机制,但作为一个AOP的实现例子最好不过了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1816922.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python】已解决TypeError: unsupported operand type(s) for ...报错方案合集

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主。 &#x1f913; 同时欢迎大家关注其他专栏&#xff0c;我将分享Web前后端开发、人工智能、机器学习、深…

【Python】selenium 点击某个按钮 click() 出现的报错问题--ElementClickInterceptedException(全!)

写在前面&#xff1a; 我们在使用selenium 点击某个元素时或者获取find_element的某个网页元素时&#xff0c;总会遇到一些问题。本人经验是&#xff0c;最直接的方法是用try_except 报错&#xff0c;直接绕过问题&#xff0c;可以直接看第一条。如果有兴趣具体解决&#xff0c…

让你事半功倍的高效管理微信方法

随着私域运营的需求不断增长&#xff0c;对于使用微信进行运营的企业或个人来说&#xff0c;高效的管理微信变得越发重要&#xff0c;今天分享一些高效管理微信的实用方法&#xff1a;

图像的混合与渐进变换

1.实验目的 平常我们看到的美图秀秀等两个图片混合是怎么生成的呢&#xff0c;今天我们看看图像处理关于这部分怎么做的&#xff1f; 2.实验条件 pycharm python编译器 3.实验代码 # File: 图像混合与渐进变换.py # Author: chen_song # Time: 2024/6/11 下午6:08 "…

Java--三种初始化内存及内存分析

1.静态初始化&#xff1a;创建赋值 int[] a{1,2,3}; Man[] mans{new Man(1,1),new Man(2,2)}; 2.动态初始化&#xff1a;开辟新的存储空间 int[] anew int[2]; a[0]1; a[1]2; 3.默认初始化&#xff1a;数组是引用类型&#xff0c;他的元素相当于类的实例变量&#xff0c;因…

C++ 14 之 宏函数

c14宏函数.cpp #include <iostream> using namespace std;// #define PI 3.14 // 宏函数 // 宏函数缺陷1: 必须用括号保证运算的完整性 #define MY_ADD(x,y) ((x)(y))// 宏函数缺陷2&#xff1a;即使加了括号&#xff0c;有些运算依然与预期不符 #define MY_COM(a,b) ((…

调用腾讯智能云实现人脸融合

目录 1. 作者介绍2. 人脸识别内容介绍2.1 人脸识别简介2.2 技术原理 3. 实现流程及代码实现3.1 实现流程3.2 代码实现3.2.1 图片为url格式3.2.2 图片为base64格式 3.3 完整代码3.4 问题分析 1. 作者介绍 杨煜星&#xff0c;女&#xff0c;西安工程大学电子信息学院&#xff0c…

TextCtrl输入文本类

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 wx.StaticText类只能够用于显示纯粹的静态文本&#xff0c;但是有时需要输入文本与用户进行交互&#xff0c;此时&#xff0c;就需要使用wx.TextCtrl…

简单项目——前后端分离实现博客系统

文章目录 一、项目实现的准备工作二、数据库的设计以及构建三、封装数据库连接、创建实体类四、封装数据库的增删查改操作五、实现博客系统核心操作1.获取博客列表页2.获取博客详情页3. 实现博客登录页4. 实现所有页面检查并强制登录5.退出登录状态6. 实现博客发布7. 实现删除文…

线上观看人次2万+!「飞天技术沙龙-CentOS 迁移替换专场」北京站圆满结束

5 月 29 日&#xff0c;阿里云联合龙蜥社区共同举办的「飞天技术沙龙-CentOS 迁移替换专场」于北京圆满结束&#xff0c;在线观看人次 2 万。本次活动现场汇聚了来自浪潮信息、Intel、龙芯、统信软件、红旗软件、电子五所等多家操作系统产业头部企业和机构&#xff0c;大家围绕…

零基础非科班也能掌握的C语言知识21 编译链接(介于作者实力有限并且没有可以演示的过程软件仅仅浅谈)

编译链接 1.翻译环境和运行环境2.翻译环境2.1 编译2.1.1 预处理&#xff08;预编译&#xff09;2.1.2 编译2.1.3 汇编 2.2 链接 3.运行环境 1.翻译环境和运行环境 在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 编译环境运行环境 2.翻译环境 翻译环境由编译和…

极氪汽车交出上市首份答卷:业绩交付“双开花”,新车型质量保优

近日&#xff0c;极氪汽车&#xff08;NYSE&#xff1a;ZK&#xff0c;下同“极氪”&#xff09;发布2024年第一季度财报&#xff0c;这也是该公司自5月20日登陆美股以来对外披露的首份业绩报告。 极氪汽车是继“蔚小理”之后第四家在美国上市的新能源车企&#xff0c;发布至今…

图形学初识--定义摄像机类(实战)

文章目录 前言正文定义摄像机的操作方式键盘操作鼠标操作 定义摄像机类核心数据视图矩阵回顾&#xff1a;模拟摄像机的移动模拟摄像机的旋转 结尾&#xff1a;喜欢的小伙伴点点关注赞哦! 前言 前面一些章节讲解了图形学的比较原理性的内容&#xff0c;这一章节咱就实战一下&am…

监控登录用户数

检查登录用户数 当登录系统用户数超过3个报警&#xff0c;并发送邮件提示 首先&#xff0c;配置发送邮件功能。 1、安装mailx [rootnode1 ~]# yum install mailx2、配置/etc/mail.rc [rootnode1 ~]# vim /etc/mail.rc set fromxxx163.com #发件人地址 set smtpsmtp.163…

深层网络:层数多真的更好吗?

深层网络&#xff1a;层数多真的更好吗&#xff1f; 在深度学习的世界里&#xff0c;"深度"始终是一个热门话题。随着技术的发展&#xff0c;我们有了越来越多的方法来构建更深的神经网络&#xff0c;这似乎暗示着“层数越多&#xff0c;效果越好”。然而&#xff0…

通用大模型与垂直大模型:双轨并进的人工智能未来

在人工智能(AI)的浩瀚宇宙中&#xff0c;大模型以其强大的学习能力和广泛的适用性&#xff0c;正逐步成为推动技术进步和产业革新的核心动力。在这股浪潮中&#xff0c;通用大模型与垂直大模型如同两颗璀璨的星辰&#xff0c;各自散发着独特的光芒&#xff0c;共同照亮了AI发展…

哪个品牌洗地机专业?四款明星精湛产品集结

当代快节奏的生活&#xff0c;人们每天下班回到家只想瘫倒在沙发&#xff0c;打扫卫生成为了一种负担......但洗地机的出现&#xff0c;大大的减轻了人们地板清洁的焦虑&#xff0c;因为它只需轻轻地推拉机子转悠房屋一圈&#xff0c;地面上的赃污便能清理干净&#xff0c;清洁…

如何优化大屏网站的响应式设计?技巧一览

为了显示不同屏幕尺寸设备的显示效果&#xff0c;有必要优先考虑响应设计&#xff0c;因为开发人员可以在不同的设备中构建应用程序。响应设计是一种灵活的设计&#xff0c;可以兼顾多屏幕和多场景&#xff0c;可以使我们的网页布局在各种屏幕下呈现出更好的效果。今天&#xf…

“JS加密在线”:简单直接的在线JS加密网站

网站名&#xff1a;“JS加密在线”&#xff0c; 功能&#xff1a;JavaScript源代码加密。 UI&#xff1a; http://jsjiami.online/ 非常简洁的JS加密网站&#xff0c;几乎只有两个功能&#xff1a;上传JS文件、下载加密后的JS文件。 JS加密&#xff0c;就应该这样简单直接。…

Splashtop 荣获“2024年安全校园白金奖”

2024年6月12日 加利福尼亚州库比蒂诺 作为远程访问和 IT 支持领域的领先企业&#xff0c;Splashtop 很荣幸地宣布获得“2024年安全校园白金奖”。Splashtop 的 Foxpass Cloud RADIUS 解决方案在专注校园安全的重要杂志《今日校园安全》颁发的访问控制和云端管理类别奖项中荣获…