美文网首页
ASM ByteCode

ASM ByteCode

作者: 壹零二肆 | 来源:发表于2020-09-25 17:02 被阅读0次
    • 1 概述
    • 2 类加载
    • 3 字节码
    • 4 简单使用

    https://mp.weixin.qq.com/s/wDwU7ndRwHdxqIIR43YNuQ

    ASM: https://asm.ow2.io/
    Guid: https://www.baeldung.com/java-asm
    极客时间:https://github.com/AndroidAdvanceWithGeektime/Chapter-ASM

    1 概述

    • 背景:高效修改字节码,编码 source code 中的功能需要拓展,修改 、增强

    • ASM :字节码修改工具,可以修改class文件,实现在 非运行期 修改字节码

    • ASM 和平台无关提供修改字节码的能力

    • 场景

      • 代码插桩
      • 性能优化
      • 热修复
      • AOP编程
    字节码 处理

    2 类加载

    方法区: 保存 Class 相关信息,同类存一份,method等相关信息


    自定义类加载器

    3 字节码

    操作数入栈
    操作符入栈

    (最底层控制线程栈执行方法的寄存器 ebp esp)

    一个个方法都构成一个个 栈帧 运行在线程

    线程本身也是以栈来执行方法

    线程栈最下面就是main 或者 run

    然后调用的方法会在其上方入栈执行

    入栈方法占用栈空间,执行结束从线程栈pop

    一段代码被多个线程执行,那么其中的寄存器,或者内存中的存储变量就会被多个线程同时操作,出现并发问题


    对于修改字节码,可以使用工具直接生成修改的代码
    IDE Plugin : ASM ByteCode Outline


    java code

    public class MainActivity {
    
        private int id;
    
        public MainActivity(int id) {
            this.id = id;
        }
    
        @Override
        public String toString() {
            return super.toString();
        }
    
        public int sum(int l, int r){
            int result = l + r;
            return result;
        }
    }
    
    

    bytecode

    // class version 49.0 (49)
    // access flags 0x21
    public class MainActivity {
    
      // compiled from: MainActivity.java
    
      // access flags 0x2
      private I id
    
      // access flags 0x1
      public <init>(I)V
       L0
        LINENUMBER 5 L0
        ALOAD 0
        INVOKESPECIAL java/lang/Object.<init> ()V
       L1
        LINENUMBER 6 L1
        ALOAD 0
        ILOAD 1
        PUTFIELD MainActivity.id : I
       L2
        LINENUMBER 7 L2
        RETURN
       L3
        LOCALVARIABLE this LMainActivity; L0 L3 0
        LOCALVARIABLE id I L0 L3 1
        MAXSTACK = 2
        MAXLOCALS = 2
    
      // access flags 0x1
      public toString()Ljava/lang/String;
       L0
        LINENUMBER 11 L0
        ALOAD 0
        INVOKESPECIAL java/lang/Object.toString ()Ljava/lang/String;
        ARETURN
       L1
        LOCALVARIABLE this LMainActivity; L0 L1 0
        MAXSTACK = 1
        MAXLOCALS = 1
    
      // access flags 0x1
      public sum(II)I
       L0
        LINENUMBER 15 L0
        ILOAD 1
        ILOAD 2
        IADD
        ISTORE 3
       L1
        LINENUMBER 16 L1
        ILOAD 3
        IRETURN
       L2
        LOCALVARIABLE this LMainActivity; L0 L2 0
        LOCALVARIABLE l I L0 L2 1
        LOCALVARIABLE r I L0 L2 2
        LOCALVARIABLE result I L1 L2 3
        MAXSTACK = 2
        MAXLOCALS = 4
    }
    
    

    ASMifed

    import java.util.*;
    
    import org.objectweb.asm.*;
    
    public class MainActivityDump 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_SUPER, "MainActivity", null, "java/lang/Object", null);
    
            cw.visitSource("MainActivity.java", null);
    
            {
                fv = cw.visitField(ACC_PRIVATE, "id", "I", null, null);
                fv.visitEnd();
            }
            {
                mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(I)V", null, null);
                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitLineNumber(5, l0);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitLineNumber(6, l1);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitVarInsn(ILOAD, 1);
                mv.visitFieldInsn(PUTFIELD, "MainActivity", "id", "I");
                Label l2 = new Label();
                mv.visitLabel(l2);
                mv.visitLineNumber(7, l2);
                mv.visitInsn(RETURN);
                Label l3 = new Label();
                mv.visitLabel(l3);
                mv.visitLocalVariable("this", "LMainActivity;", null, l0, l3, 0);
                mv.visitLocalVariable("id", "I", null, l0, l3, 1);
                mv.visitMaxs(2, 2);
                mv.visitEnd();
            }
            {
                mv = cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitLineNumber(11, l0);
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false);
                mv.visitInsn(ARETURN);
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitLocalVariable("this", "LMainActivity;", null, l0, l1, 0);
                mv.visitMaxs(1, 1);
                mv.visitEnd();
            }
            {
                mv = cw.visitMethod(ACC_PUBLIC, "sum", "(II)I", null, null);
                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitLineNumber(15, l0);
                mv.visitVarInsn(ILOAD, 1);
                mv.visitVarInsn(ILOAD, 2);
                mv.visitInsn(IADD);
                mv.visitVarInsn(ISTORE, 3);
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitLineNumber(16, l1);
                mv.visitVarInsn(ILOAD, 3);
                mv.visitInsn(IRETURN);
                Label l2 = new Label();
                mv.visitLabel(l2);
                mv.visitLocalVariable("this", "LMainActivity;", null, l0, l2, 0);
                mv.visitLocalVariable("l", "I", null, l0, l2, 1);
                mv.visitLocalVariable("r", "I", null, l0, l2, 2);
                mv.visitLocalVariable("result", "I", null, l1, l2, 3);
                mv.visitMaxs(2, 4);
                mv.visitEnd();
            }
            cw.visitEnd();
    
            return cw.toByteArray();
        }
    }
    
    

    4 简单使用

    通过一个例子简单学习ASM的使用

    目的:在指定方法执行之前 插入一行代码

    修改前

    public class MainActivity {
        @Override
        public String toString() {
            return super.toString();
        }
    }
    

    修改后

    public class MainActivity {
        public String toString() {
            System.out.println("插入:Hello World!");
            return super.toString();
        }
    }
    

    实现:

    /**
     * @author gongshijie
     *
     */
    public class Main {
    
        public static void main(String[] args) {
    
            try {
                // 修改字节码
                ClassReader classReader = new ClassReader(MainActivity.class.getName());
                ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
                ClassVisitor classVisitor = new LogVisitor(Opcodes.ASM5, classWriter);
                classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
                byte[] bytecode = classWriter.toByteArray();
    
                // 修改后的结果
                FileOutputStream fos = new FileOutputStream(new File("MainActivity.class"));
                fos.write(bytecode);
                fos.flush();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    /**
     * @author gongshijie
     */
    public class MainActivity {
        @Override
        public String toString() {
            return super.toString();
        }
    }
    
    
    
    /**
     * @author gongshijie
     * 遍历方法 定位 目标方法
     */
    public class LogVisitor extends ClassVisitor {
        public LogVisitor(int i) {
            super(i);
        }
    
        public LogVisitor(int i, ClassVisitor classVisitor) {
            super(i, classVisitor);
        }
    
        @Override
        public MethodVisitor visitMethod(int i, String s, String s1, String s2, String[] strings) {
            MethodVisitor methodVisitor = cv.visitMethod(i, s, s1, s2, strings);
            if (s.equals("toString")) {
                // 若是我们需要增强的方法 toString() 就进入我们的 visitor 操作字节码
                methodVisitor = new LogAdapter(Opcodes.ASM5, methodVisitor, i, s, s1);
            }
            return methodVisitor;
        }
    }
    
    
    
    /**
     * @author gongshijie
     * 对 目标方法 进行字节码修改
     */
    public class LogAdapter extends AdviceAdapter {
        protected LogAdapter(int i, MethodVisitor methodVisitor, int i1, String s, String s1) {
            super(i, methodVisitor, i1, s, s1);
        }
    
        @Override
        protected void onMethodEnter() {
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("\u63d2\u5165\uff1aHello World!");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
    }
    
    

    结果

    总之, ASM提供修改字节码的能力,怎么使用可以结合场景
    不会带来性能变化,在 编译期 完成字节码操作

    相关文章

      网友评论

          本文标题:ASM ByteCode

          本文链接:https://www.haomeiwen.com/subject/lojfuktx.html