使用ASM的时候,在Jump指令插桩出现问题

本文探讨了在使用ASM库进行Java字节码插桩时遇到的问题,特别是在visitJumpInsn之后插入代码导致的不连续number值。问题源于ASM处理jump指令时的优化,对于不可达代码,ASM会用nop指令替换并附加athrow。解决方案是在visitJumpInsn时过滤掉goto指令,以避免插入无效代码。
摘要由CSDN通过智能技术生成

插桩的时候使用ASM访问jump指令出现问题

当尝试在visitJumpInsn的位置之后进行插桩,发现了一些问题。

@Override
       public void visitJumpInsn(final int opcode, final Label label) {
            super.visitJumpInsn(opcode,label);//同样,在jump之后插入代码
            // if(opcode==167) return;
            // inst();
            System.out.println("opcode is :"+opcode+ "label is :"+label);
            visitLdcInsn(number);
            visitMethodInsn(Opcodes.INVOKESTATIC, "afljava/logger/Logger", "writeTo", "(I)V", false);
            number++;
        }

可以看到,主要就是在visitJump之后插入了一个函数调用,插入一个自增的数。

测试函数如下所示:

public class testJump{
    public static void main(String[] args){
        int a = 0, b = 1;
        int sum;
        if(a < b){
            sum = a + b;
        }
        else if(a == b){
            sum = a - b;
        }
        else{
            sum = b - a;
        }
        System.out.println(sum);
        int c = sum * 2;
        if(c < 1){
            System.out.println("c < 1");
        }
        if(c == 1){
            System.out.println("c == 1");
        }
        if(c > 1){
            System.out.println("c > 1");
        }
    }    
}

通过javap可以看到汇编代码如下:

Compiled from "testJump.java"
public class testJump {
  public testJump();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_1
       3: istore_2
       4: iload_1
       5: iload_2
       6: if_icmpge     16
       9: iload_1
      10: iload_2
      11: iadd
      12: istore_3
      13: goto          32
      16: iload_1
      17: iload_2
      18: if_icmpne     28
      21: iload_1
      22: iload_2
      23: isub
      24: istore_3
      25: goto          32
      28: iload_2
      29: iload_1
      30: isub
      31: istore_3
      32: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      35: iload_3
      36: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      39: iload_3
      40: iconst_2
      41: imul
      42: istore        4
      44: iload         4
      46: iconst_1
      47: if_icmpge     58
      50: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      53: ldc           #4                  // String c < 1
      55: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      58: iload         4
      60: iconst_1
      61: if_icmpne     72
      64: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      67: ldc           #6                  // String c == 1
      69: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      72: iload         4
      74: iconst_1
      75: if_icmple     86
      78: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      81: ldc           #7                  // String c > 1
      83: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      86: return
}

使用ASM插桩之后的反汇编代码如下所示:

Compiled from "testJump.java"
public class testJump {
  public testJump();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iconst_1
       3: istore_2
       4: iload_1
       5: iload_2
       6: if_icmpge     26
       9: ldc           #37                 // int 0
      11: invokestatic  #42                 // Method afljava/logger/Logger.writeTo:(I)V
      14: iload_1
      15: iload_2
      16: iadd
      17: istore_3
      18: goto          52
      21: nop
      22: nop
      23: nop
      24: nop
      25: athrow
      26: iload_1
      27: iload_2
      28: if_icmpne     48
      31: ldc           #44                 // int 2
      33: invokestatic  #42                 // Method afljava/logger/Logger.writeTo:(I)V
      36: iload_1
      37: iload_2
      38: isub
      39: istore_3
      40: goto          52
      43: nop
      44: nop
      45: nop
      46: nop
      47: athrow
      48: iload_2
      49: iload_1
      50: isub
      51: istore_3
      52: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      55: iload_3
      56: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
      59: iload_3
      60: iconst_2
      61: imul
      62: istore        4
      64: iload         4
      66: iconst_1
      67: if_icmpge     83
      70: ldc           #46                 // int 4
      72: invokestatic  #42                 // Method afljava/logger/Logger.writeTo:(I)V
      75: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      78: ldc           #4                  // String c < 1
      80: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      83: iload         4
      85: iconst_1
      86: if_icmpne     102
      89: ldc           #47                 // int 5
      91: invokestatic  #42                 // Method afljava/logger/Logger.writeTo:(I)V
      94: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      97: ldc           #6                  // String c == 1
      99: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     102: iload         4
     104: iconst_1
     105: if_icmple     121
     108: ldc           #48                 // int 6
     110: invokestatic  #42                 // Method afljava/logger/Logger.writeTo:(I)V
     113: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     116: ldc           #7                  // String c > 1
     118: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     121: return
}

可以发现,我们插入的number数据并不是连续的,number从0到6,缺少了1和3。

原因在于:在访问visitJumpInsn的时候,会对跳转指令访问,具体如下所示,可以看到jump指令中也有goto跳转。

public void visitJumpInsn(int opcode, Label label)

Visits a jump instruction. A jump instruction is an instruction that may jump to another instruction.

  • Parameters:

    opcode - the opcode of the type instruction to be visited. This opcode is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.

    label - the operand of the instruction to be visited. This operand is a label that designates the instruction to which the jump instruction may jump.

显然,我使用 COMPUTE_FRAMES 选项让 ASM 从头开始重新计算堆栈映射帧,但由于未知的初始堆栈状态,无法计算无法访问代码的帧。 ASM 通过用 nop 指令替换无法访问的代码来解决这个问题,然后是一条 athrow 语句。对于这个序列,可以指定一个有效的初始堆栈帧,并且它对执行没有影响(因为代码无法访问)

这里有一个大佬的回答解释地很好:

https://stackoverflow.com/questions/53232522/asm-visitlabel-generates-too-many-labels-and-nop-instructions/53261666#53261666

按照原来的逻辑,在访问jump指令之后插入调用函数,但是可以发现如果在goto之后插入调用函数,这些代码是永远不会被执行的,所以这些代码在ASM的逻辑中,会用nop指令代替,并加上athrow。nop指令就是啥也不干,具体是几条指令,跟插入的无效代码有关。

所以,可以在访问jump指令的时候把goto过滤掉即可。

@Override
       public void visitJumpInsn(final int opcode, final Label label) {
            super.visitJumpInsn(opcode,label);//同样,在jump之后插入代码
            if(opcode==167) return;
            System.out.println("opcode is :"+opcode+ "label is :"+label);
            visitLdcInsn(number);
            visitMethodInsn(Opcodes.INVOKESTATIC, "afljava/logger/Logger", "writeTo", "(I)V", false);
            number++;
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

meilidekcl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值