使用 ASM 修改 <clinit>
Modifiying <clinit> with ASM
您好,我在使用 ASM 时遇到了一些小问题。它产生一个 class 字节码错误:
Exception in thread "main" java.lang.VerifyError: Bad instruction
Exception Details:
Location:
me/test/Main.<clinit>()V @0: wide
Reason:
Error exists in the bytecode
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.privateGetMethodRecursive(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
当我使用以下代码将代码注入 <clinit>
方法时,该方法使用 "key" (byte[]
) 变量填充静态字节数组:
if (key.length > 128)
this.visitVarInsn(SIPUSH, key.length);
else
this.visitVarInsn(BIPUSH, key.length);
this.visitVarInsn(NEWARRAY, 8);
for (int i = 0; i < key.length; i++) {
this.visitInsn(DUP);
if (i > 127) {
this.visitVarInsn(SIPUSH, i + 32768);
} else if (i < 6) {
switch (i) {
case 0:
this.visitInsn(ICONST_0);
break;
case 1:
this.visitInsn(ICONST_1);
break;
case 2:
this.visitInsn(ICONST_2);
break;
case 3:
this.visitInsn(ICONST_3);
break;
case 4:
this.visitInsn(ICONST_4);
break;
case 5:
this.visitInsn(ICONST_5);
break;
default:
System.out.println("Logic mistake!");
break;
}
}
else {
this.visitVarInsn(BIPUSH, i);
}
if (key[i] < 6 && key[i] >= 0) {
switch (key[i]) {
case 0:
this.visitInsn(ICONST_0);
break;
case 1:
this.visitInsn(ICONST_1);
break;
case 2:
this.visitInsn(ICONST_2);
break;
case 3:
this.visitInsn(ICONST_3);
break;
case 4:
this.visitInsn(ICONST_4);
break;
case 5:
this.visitInsn(ICONST_5);
break;
default:
System.out.println("Logic mistake!");
break;
}
} else {
this.visitVarInsn(BIPUSH, key[i]);
}
this.visitInsn(BASTORE);
您的代码不正确的主要原因是您多次误用 visitVarInsn
。
visitVarInsn
专为参数为局部变量索引的指令而设计。由于它取决于特定的指令,参数是如何编码到指令中的,因此当与指令一起使用时,此方法会产生不正确的代码,它不是为此而设计的。所以,而不是
this.visitVarInsn(NEWARRAY, 8);
你应该使用
this.visitIntInsn(NEWARRAY, Opcodes.T_BYTE);
这同样适用于 this.visitVarInsn(SIPUSH, key.length);
和 this.visitVarInsn(BIPUSH, key.length);
的错误调用,但在这些情况下,您的重复代码中还有其他错误,例如对你的第一次出现,
if (key.length > 128)
this.visitVarInsn(SIPUSH, key.length);
else
this.visitVarInsn(BIPUSH, key.length);
会中断,当常量值恰好为 128
时。此外,this.visitVarInsn(SIPUSH, i + 32768);
除了使用错误的方法外,还包含虚假的 + 32768
。
一般来说,您不应该重复此代码来生成最佳指令。要么创建一个实用方法,其正确性只需证明一次,要么使用现有方法。例如,如果您继承自 InstructionAdapter
而不是直接继承自 MethodVisitor
,则可以使用 iconst(int)
which will automatically generate ICONST_x
, BIPUSH
, SIPUSH
or LDC
. Or, when you inherit from GeneratorAdapter
instead, you can use push(int)
实现相同的效果。
所以使用GeneratorAdapter
,代码会像
一样简单
push(key.length);
newArray(Type.BYTE_TYPE);
for(int i = 0; i < key.length; i++) {
this.visitInsn(Opcodes.DUP);
push(i);
push(key[i]);
visitInsn(Opcodes.BASTORE);
}
您好,我在使用 ASM 时遇到了一些小问题。它产生一个 class 字节码错误:
Exception in thread "main" java.lang.VerifyError: Bad instruction
Exception Details:
Location:
me/test/Main.<clinit>()V @0: wide
Reason:
Error exists in the bytecode
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.privateGetMethodRecursive(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
当我使用以下代码将代码注入 <clinit>
方法时,该方法使用 "key" (byte[]
) 变量填充静态字节数组:
if (key.length > 128)
this.visitVarInsn(SIPUSH, key.length);
else
this.visitVarInsn(BIPUSH, key.length);
this.visitVarInsn(NEWARRAY, 8);
for (int i = 0; i < key.length; i++) {
this.visitInsn(DUP);
if (i > 127) {
this.visitVarInsn(SIPUSH, i + 32768);
} else if (i < 6) {
switch (i) {
case 0:
this.visitInsn(ICONST_0);
break;
case 1:
this.visitInsn(ICONST_1);
break;
case 2:
this.visitInsn(ICONST_2);
break;
case 3:
this.visitInsn(ICONST_3);
break;
case 4:
this.visitInsn(ICONST_4);
break;
case 5:
this.visitInsn(ICONST_5);
break;
default:
System.out.println("Logic mistake!");
break;
}
}
else {
this.visitVarInsn(BIPUSH, i);
}
if (key[i] < 6 && key[i] >= 0) {
switch (key[i]) {
case 0:
this.visitInsn(ICONST_0);
break;
case 1:
this.visitInsn(ICONST_1);
break;
case 2:
this.visitInsn(ICONST_2);
break;
case 3:
this.visitInsn(ICONST_3);
break;
case 4:
this.visitInsn(ICONST_4);
break;
case 5:
this.visitInsn(ICONST_5);
break;
default:
System.out.println("Logic mistake!");
break;
}
} else {
this.visitVarInsn(BIPUSH, key[i]);
}
this.visitInsn(BASTORE);
您的代码不正确的主要原因是您多次误用 visitVarInsn
。
visitVarInsn
专为参数为局部变量索引的指令而设计。由于它取决于特定的指令,参数是如何编码到指令中的,因此当与指令一起使用时,此方法会产生不正确的代码,它不是为此而设计的。所以,而不是
this.visitVarInsn(NEWARRAY, 8);
你应该使用
this.visitIntInsn(NEWARRAY, Opcodes.T_BYTE);
这同样适用于 this.visitVarInsn(SIPUSH, key.length);
和 this.visitVarInsn(BIPUSH, key.length);
的错误调用,但在这些情况下,您的重复代码中还有其他错误,例如对你的第一次出现,
if (key.length > 128)
this.visitVarInsn(SIPUSH, key.length);
else
this.visitVarInsn(BIPUSH, key.length);
会中断,当常量值恰好为 128
时。此外,this.visitVarInsn(SIPUSH, i + 32768);
除了使用错误的方法外,还包含虚假的 + 32768
。
一般来说,您不应该重复此代码来生成最佳指令。要么创建一个实用方法,其正确性只需证明一次,要么使用现有方法。例如,如果您继承自 InstructionAdapter
而不是直接继承自 MethodVisitor
,则可以使用 iconst(int)
which will automatically generate ICONST_x
, BIPUSH
, SIPUSH
or LDC
. Or, when you inherit from GeneratorAdapter
instead, you can use push(int)
实现相同的效果。
所以使用GeneratorAdapter
,代码会像
push(key.length);
newArray(Type.BYTE_TYPE);
for(int i = 0; i < key.length; i++) {
this.visitInsn(Opcodes.DUP);
push(i);
push(key[i]);
visitInsn(Opcodes.BASTORE);
}