Modifiying <clinit> with ASM

安稳与你 提交于 2019-12-08 11:22:48

问题


Hello I have a little problem with ASM. It produces a class with bytecode error:

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)

When i use the following code to inject code into the <clinit> method, which fills an static byte array with the "key" (byte[]) variable:

   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);

回答1:


The main cause for your incorrect code is your multiple misuse of visitVarInsn.

visitVarInsn is designed for instructions whose argument is the index of a local variable. Since it depends on the particular instruction, how the argument is encoded into the instruction, this method produces incorrect code when used together with instructions, it is not designed for. So, instead of

this.visitVarInsn(NEWARRAY, 8);

you should use

this.visitIntInsn(NEWARRAY, Opcodes.T_BYTE);

The same applies to the incorrect invocations of this.visitVarInsn(SIPUSH, key.length); and this.visitVarInsn(BIPUSH, key.length);, but in these cases there are additional errors in your duplicated code, e.g. right your first occurrence,

if (key.length > 128)
    this.visitVarInsn(SIPUSH, key.length);
else
    this.visitVarInsn(BIPUSH, key.length);

will break, when the constant value is exactly 128. Further, this.visitVarInsn(SIPUSH, i + 32768); contains a spurious + 32768 additionally to the use of the wrong method.

Generally, you should not repeat this code for generating an optimal instruction. Do either, create a utility method whose correctness you have to prove only once, or use an existing method. E.g., if you inherit from InstructionAdapter rather than directly from MethodVisitor, you can use iconst(int) which will automatically generate ICONST_x, BIPUSH, SIPUSH or LDC. Or, when you inherit from GeneratorAdapter instead, you can use push(int) to achieve the same.

So using GeneratorAdapter, the code would be as simple as

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);
}


来源:https://stackoverflow.com/questions/37749182/modifiying-clinit-with-asm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!