插桩的时候使用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++;
}