• ASM:(9)Label


    原文:https://lsieun.github.io/java-asm-01/label-intro.html

    Label介绍

    在Java程序中,有三种基本控制结构:顺序、选择和循环。

    在Bytecode层面,只存在顺序(sequence)和跳转(jump)两种指令(Instruction)执行顺序:

                              ┌─── sequence
                              │
    Bytecode: control flow ───┤
                              │                ┌─── selection (if, switch)
                              └─── jump ───────┤
                                               └─── looping (for, while)
    

    那么,Label类起到一个什么样的作用呢?我们现在已经知道,MethodVisitor类是用于生成方法体的代码,

    • 如果没有Label类的参与,那么MethodVisitor类只能生成“顺序”结构的代码;
    • 如果有Label类的参与,那么MethodVisitor类就能生成“选择”和“循环”结构的代码。

    Label类

    Label类当中,定义了很多的字段和方法。为了方便,将Label类简化一下,内容如下:

    public class Label {
        int bytecodeOffset;
    
        public Label() {
            // Nothing to do.
        }
    
        public int getOffset() {
            return bytecodeOffset;
        }
    }
    

    经过这样简单之后,Label类当中就只包含一个bytecodeOffset字段,那么这个字段代表什么含义呢?bytecodeOffset字段就是a position in the bytecode of a method。

    举例子来说明一下。假如有一个test(boolean flag)方法,它包含的Instruction内容如下:

    === === ===  === === ===  === === ===
    Method test:(Z)V
    === === ===  === === ===  === === ===
    max_stack = 2
    max_locals = 2
    code_length = 24
    code = 1B99000EB200021203B60004A7000BB200021205B60004B1
    === === ===  === === ===  === === ===
    0000: iload_1              // 1B
    0001: ifeq            14   // 99000E
    0004: getstatic       #2   // B20002     || java/lang/System.out:Ljava/io/PrintStream;
    0007: ldc             #3   // 1203       || value is true
    0009: invokevirtual   #4   // B60004     || java/io/PrintStream.println:(Ljava/lang/String;)V
    0012: goto            11   // A7000B
    0015: getstatic       #2   // B20002     || java/lang/System.out:Ljava/io/PrintStream;
    0018: ldc             #5   // 1205       || value is false
    0020: invokevirtual   #4   // B60004     || java/io/PrintStream.println:(Ljava/lang/String;)V
    0023: return               // B1
    === === ===  === === ===  === === ===
    LocalVariableTable:
    index  start_pc  length  name_and_type
        0         0      24  this:Lsample/HelloWorld;
        1         0      24  flag:Z
    

    那么,Label类当中的bytecodeOffset字段,就表示当前Instruction“索引值”。

    那么,这个bytecodeOffset字段是做什么用的呢?它用来计算一个“相对偏移量”。比如说,bytecodeOffset字段的值是15,它标识了getstatic指令的位置,而在索引值为1的位置是ifeq指令,ifeq后面跟的14,这个14就是一个“相对偏移量”。换一个角度来说,由于ifeq的索引位置是1,“相对偏移量”是14,那么1+14=15,也就是说,如果ifeq的条件成立,那么下一条执行的指令就是索引值为15getstatic指令了。

    在ASM当中,Label类可以用于实现选择(if、switch)、循环(for、while)和try-catch语句。

    在编写ASM代码的过程中,我们所要表达的是一种代码的跳转逻辑,就是从一个地方跳转到另外一个地方;在这两者之间,可以编写其它的代码逻辑,可能长一些,也可能短一些,所以,Instruction所对应的“索引值”还不确定。

    Label类的出现,就是代表一个“抽象的位置”,也就是将来要跳转的目标。 当我们调用ClassWriter.toByteArray()方法时,这些ASM代码会被转换成byte[],在这个过程中,需要计算出Label对象中bytecodeOffset字段的值到底是多少,从而再进一步计算出跳转的相对偏移量(offset)。

    如何使用Label类

    从编写代码的角度来说,Label类是属于MethodVisitor类的一部分:通过调用MethodVisitor.visitLabel(Label)方法,来为代码逻辑添加一个潜在的“跳转目标”。

    我们先来看一个简单的示例代码:

    public class HelloWorld {
        public void test(boolean flag) {
            if (flag) {
                System.out.println("value is true");
            }
            else {
                System.out.println("value is false");
            }
            return;
        }
    }
    

    那么,test(boolean flag)方法对应的ASM代码如下:

    public class LabelTest implements Opcodes {
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), bytes);
        }
    
        public static byte[] dump() throws Exception {
    
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
            cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "sample/HelloWorld", null, "java/lang/Object", null);
    
            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "test", "(Z)V", null, null);
            Label elseLabel = new Label();      // 首先,准备两个Label对象
            Label returnLabel = new Label();
    
            // 第1段
            mv.visitCode();
            mv.visitVarInsn(ILOAD, 1);
            mv.visitJumpInsn(IFEQ, elseLabel);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("value is true");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitJumpInsn(GOTO, returnLabel);
    
            // 第2段
            mv.visitLabel(elseLabel);         // 将第一个Label放到这里
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("value is false");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    
            // 第3段
            mv.visitLabel(returnLabel);      // 将第二个Label放到这里
            mv.visitInsn(RETURN);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
            return cw.toByteArray();
        }
    }
    

    如何使用Label类:

    • 首先,创建Label类的实例;
    • 其次,确定label的位置。通过MethodVisitor.visitLabel()方法,确定label的位置。
    • 最后,与label建立联系,实现程序的逻辑跳转。在条件合适的情况下,通过MethodVisitor类跳转相关的方法(例如,visitJumpInsn())与label建立联系。

    Frame的变化

    对于HelloWorld类中test()方法对应的Instruction内容如下:

    public void test(boolean);
      Code:
         0: iload_1
         1: ifeq          15
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                  // String value is true
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: goto          23
        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: ldc           #5                  // String value is false
        20: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        23: return
    

    该方法对应的Frame变化情况如下:

    test(Z)V
    [sample/HelloWorld, int] []
    [sample/HelloWorld, int] [int]
    [sample/HelloWorld, int] []
    [sample/HelloWorld, int] [java/io/PrintStream]
    [sample/HelloWorld, int] [java/io/PrintStream, java/lang/String]
    [sample/HelloWorld, int] []
    [] []
    [sample/HelloWorld, int] [java/io/PrintStream]                      // 注意,从上一行到这里是“非线性”的变化
    [sample/HelloWorld, int] [java/io/PrintStream, java/lang/String]
    [sample/HelloWorld, int] []
    [] []
    

    或者:

    test:(Z)V
                                   // {this, int} | {}
    0000: iload_1                  // {this, int} | {int}
    0001: ifeq            14       // {this, int} | {}
    0004: getstatic       #2       // {this, int} | {PrintStream}
    0007: ldc             #3       // {this, int} | {PrintStream, String}
    0009: invokevirtual   #4       // {this, int} | {}
    0012: goto            11       // {} | {}
                                   // {this, int} | {}                 // 注意,从上一行到这里是“非线性”的变化
    0015: getstatic       #2       // {this, int} | {PrintStream}
    0018: ldc             #5       // {this, int} | {PrintStream, String}
    0020: invokevirtual   #4       // {this, int} | {}
                                   // {this, int} | {}
    0023: return                   // {} | {}
    

    通过上面的输出结果,可得出:由于程序代码逻辑发生了跳转(if-else),那么相应的local variables和operand stack结构也发生了“非线性”的变化。这部分内容与MethodVisitor.visitFrame()方法有关系。

    示例

    switch语句

    实现switch语句可以使用lookupswitchtableswitch指令。

    预期目标:

    public class HelloWorld {
        public void test(int val) {
            switch (val) {
                case 1:
                    System.out.println("val = 1");
                    break;
                case 2:
                    System.out.println("val = 2");
                    break;
                case 3:
                    System.out.println("val = 3");
                    break;
                case 4:
                    System.out.println("val = 4");
                    break;
                default:
                    System.out.println("val is unknown");
            }
        }
    }
    

    编码实现:

    public class LabelTest2 implements Opcodes {
        public static void main(String[] args) throws Exception {
            // (1) 生成byte[]内容
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), 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, "<init>", "()V", null, null);
                mv1.visitCode();
                mv1.visitVarInsn(ALOAD, 0);
                mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                mv1.visitInsn(RETURN);
                mv1.visitMaxs(0, 0);
                mv1.visitEnd();
            }
    
            {
                MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "(I)V", null, null);
                Label caseLabel1 = new Label();
                Label caseLabel2 = new Label();
                Label caseLabel3 = new Label();
                Label caseLabel4 = new Label();
                Label defaultLabel = new Label();
                Label returnLabel = new Label();
    
                // 第1段
                mv2.visitCode();
                mv2.visitVarInsn(ILOAD, 1);
                mv2.visitTableSwitchInsn(1, 4, defaultLabel, new Label[]{caseLabel1, caseLabel2, caseLabel3, caseLabel4});
    
                // 第2段
                mv2.visitLabel(caseLabel1);
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitLdcInsn("val = 1");
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv2.visitJumpInsn(GOTO, returnLabel);
    
                // 第3段
                mv2.visitLabel(caseLabel2);
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitLdcInsn("val = 2");
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv2.visitJumpInsn(GOTO, returnLabel);
    
                // 第4段
                mv2.visitLabel(caseLabel3);
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitLdcInsn("val = 3");
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv2.visitJumpInsn(GOTO, returnLabel);
    
                // 第5段
                mv2.visitLabel(caseLabel4);
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitLdcInsn("val = 4");
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv2.visitJumpInsn(GOTO, returnLabel);
    
                // 第6段
                mv2.visitLabel(defaultLabel);
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitLdcInsn("val is unknown");
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    
                // 第7段
                mv2.visitLabel(returnLabel);
                mv2.visitInsn(RETURN);
                mv2.visitMaxs(0, 0);
                mv2.visitEnd();
            }
    
            cw.visitEnd();
    
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    }
    

    本示例当中,使用了MethodVisitor.visitTableSwitchInsn()方法,也可以使用MethodVisitor.visitLookupSwitchInsn()方法。

    mv2.visitLookupSwitchInsn(defaultLabel, new int[]{1, 2, 3, 4}, new Label[]{caseLabel1, caseLabel2, caseLabel3, caseLabel4});
    

    for语句

    预期目标:

    public class HelloWorld {
        public void test() {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
        }
    }
    

    编码实现:

    public class LabelTest3 implements Opcodes {
        public static void main(String[] args) throws Exception {
            byte[] bytes = dump();
            FileUtils.writeByteArrayToFile(new File("sample/HelloWord.class"), 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, "<init>", "()V", null, null);
                mv1.visitCode();
                mv1.visitVarInsn(ALOAD, 0);
                mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                mv1.visitInsn(RETURN);
                mv1.visitMaxs(0, 0);
                mv1.visitEnd();
            }
    
            {
                MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
                Label conditionLabel = new Label();
                Label returnLabel = new Label();
    
                // 第1段
                methodVisitor.visitCode();
                methodVisitor.visitInsn(ICONST_0);
                methodVisitor.visitVarInsn(ISTORE, 1);
    
                // 第2段
                methodVisitor.visitLabel(conditionLabel);
                methodVisitor.visitVarInsn(ILOAD, 1);
                methodVisitor.visitIntInsn(BIPUSH, 10);
                methodVisitor.visitJumpInsn(IF_ICMPGE, returnLabel);
                methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                methodVisitor.visitVarInsn(ILOAD, 1);
                methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
                methodVisitor.visitIincInsn(1, 1);
                methodVisitor.visitJumpInsn(GOTO, conditionLabel);
    
                // 第3段
                methodVisitor.visitLabel(returnLabel);
                methodVisitor.visitInsn(RETURN);
                methodVisitor.visitMaxs(0, 0);
                methodVisitor.visitEnd();
            }
    
            cw.visitEnd();
    
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
        }
    }
    
    

    try-catch语句

    预期目标:

    public class HelloWorld {
        public void test() {
            try {
                System.out.println("Before Sleep");
                Thread.sleep(1000);
                System.out.println("After Sleep");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    编码实现:

        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, "<init>", "()V", null, null);
                mv1.visitCode();
                mv1.visitVarInsn(ALOAD, 0);
                mv1.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
                mv1.visitInsn(RETURN);
                mv1.visitMaxs(0, 0);
                mv1.visitEnd();
            }
    
            {
                MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
                Label startLabel = new Label();
                Label endLabel = new Label();
                Label exceptionHandlerLabel = new Label();
                Label returnLabel = new Label();
    
                // 第1段
                mv2.visitCode();
                // visitTryCatchBlock可以在这里访问
                mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandlerLabel, "java/lang/InterruptedException");
    
                // 第2段
                mv2.visitLabel(startLabel);
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitLdcInsn("Before Sleep");
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv2.visitLdcInsn(new Long(1000L));
                mv2.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
                mv2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv2.visitLdcInsn("After Sleep");
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    
                // 第3段
                mv2.visitLabel(endLabel);
                mv2.visitJumpInsn(GOTO, returnLabel);
    
                // 第4段
                mv2.visitLabel(exceptionHandlerLabel);
                mv2.visitVarInsn(ASTORE, 1);
                mv2.visitVarInsn(ALOAD, 1);
                mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);
    
                // 第5段
                mv2.visitLabel(returnLabel);
                mv2.visitInsn(RETURN);
    
                // 第6段
                // visitTryCatchBlock也可以在这里访问
                // mv2.visitTryCatchBlock(startLabel, endLabel, exceptionHandlerLabel, "java/lang/InterruptedException");
                mv2.visitMaxs(0, 0);
                mv2.visitEnd();
            }
    
            cw.visitEnd();
    
            // (3) 调用toByteArray()方法
            return cw.toByteArray();
    
        }
    

    有一个问题,visitTryCatchBlock()方法为什么可以在后边的位置调用呢?这与Code属性的结构有关系:

    Code_attribute {
        u2 attribute_name_index;
        u4 attribute_length;
        u2 max_stack;
        u2 max_locals;
        u4 code_length;
        u1 code[code_length];
        u2 exception_table_length;
        {   u2 start_pc;
            u2 end_pc;
            u2 handler_pc;
            u2 catch_type;
        } exception_table[exception_table_length];
        u2 attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    因为instruction的内容(对应于visitXxxInsn()方法的调用)存储于Code结构当中的code[]内,而try-catch的内容(对应于visitTryCatchBlock()方法的调用),存储在Code结构当中的exception_table[]内,所以visitTryCatchBlock()方法的调用时机,可以早一点,也可以晚一点,只要整体上遵循MethodVisitor类对就于visitXxx()方法调用的顺序要求就可以了。

    |                |          |     instruction     |
    |                |  label1  |     instruction     |
    |                |          |     instruction     |
    |    try-catch   |  label2  |     instruction     |
    |                |          |     instruction     |
    |                |  label3  |     instruction     |
    |                |          |     instruction     |
    |                |  label4  |     instruction     |
    |                |          |     instruction     |
    
  • 相关阅读:
    谈谈 OC 中的内联函数
    Spring 新手教程(二) 生命周期和作用域
    实时竞价(RTB) 介绍(基础篇)
    oracle数据库性能优化方案精髓整理收集回想
    HNU 13411 Reverse a Road II(最大流+BFS)经典
    CSS3主要知识点复习总结+HTML5新增标签
    修改默认MYSQL数据库data存放位置
    mysql状态查看 QPS/TPS/缓存命中率查看
    Mysql5.7.10新加用户
    很靠谱linux常用命令
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/16164131.html
Copyright © 2020-2023  润新知