提问者:小点点

ASM方法变换stackmap帧异常


我试图从premain方法添加到用户字节码,该方法在遇到新的行号节点时将String形式的某些信息添加到列表中,尽管当我运行代理和应用程序jar时会发生以下异常:

Error: A JNI error has occurred, please check your installation and try again 
Exception in thread "main" java.lang.VerifyError: Expecting a stackmap frame at branch target 
134
Exception Details:
  Location:
    sketches/UserCode.main([Ljava/lang/String;)V @24: if_icmpge
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: b200 12b8 0018 b600 1cba 0037 0000 b900
    0x0000010: 2e02 0057 033c 1b07 a200 6eb2 0012 b800
    0x0000020: 18b6 001c ba00 3a00 00b9 002e 0200 57bb
    0x0000030: 0014 59ba 004a 0000 1bb2 0012 b800 18b6
    0x0000040: 001c ba00 4d00 00b9 002e 0200 57b8 0053
    0x0000050: b700 564d b200 12b8 0018 b600 1cba 0059
    0x0000060: 0000 b900 2e02 0057 2cb6 005c b200 12b8
    0x0000070: 0018 b600 1cba 0037 0000 b900 2e02 0057
    0x0000080: 8401 01a7 ff93 b200 12b8 0018 b600 1cba
    0x0000090: 005f 0000 b900 2e02 0057 b1
  Stackmap Table:
    append_frame(@22,Integer)
    chop_frame(@154,1)

以下是premain的转换方法:

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                // bootstrap loader is not what we're looking for and will be signified by null
                if (loader == null) {
                    return null;
                }

                ClassNode cn = new ClassNode(ASM9);
                ClassReader cr1 = new ClassReader(classfileBuffer);
                cr1.accept(cn, 0);

                ...

                // make the synchronized list field
                cn.fields.add(new FieldNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_VOLATILE,
                        listName, "Ljava/util/List;", null, null));

                // go through all MethodNodes and all instruction lists in methods and add synchronized list insert op
                // also, add initialisation for synchronized list (may include creating a static initialiser)
                for (MethodNode mn : cn.methods) {
                    InsnList insns = mn.instructions;
                    if (insns.size() == 0) {
                        continue;
                    }

                    for (AbstractInsnNode node : insns) {
                        if (node instanceof LineNumberNode) {
                            InsnList addedInsns = new InsnList();

                            //TODO: fix this
                            addedInsns.add(new FieldInsnNode(GETSTATIC, cn.name, "list",
                                    "Ljava/util/List;"));
                            addedInsns.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Thread",
                                    "currentThread", "()Ljava/lang/Thread;", false));
                            addedInsns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Thread",
                                    "getId", "()J", false));
                            addedInsns.add(new InvokeDynamicInsnNode("makeConcatWithConstants",
                                    "(J)Ljava/lang/String;",
                                    new Handle(H_INVOKESTATIC,
                                            "java/lang/invoke/StringConcatFactory",
                                            "makeConcatWithConstants",
                                            "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
                                            false),
                                    "\u0001|" + ((LineNumberNode) node).line + "|" + className));
                            addedInsns.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/List",
                                    "add", "(Ljava/lang/Object;)Z", true));
                            addedInsns.add(new InsnNode(POP));

                            insns.insert(node.getPrevious(), addedInsns);

                        }
                    }

                }

                ClassWriter cw1 = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                cn.accept(cw1);
                return cw1.toByteArray();

            }

我已经看到了对以前问题的回答,其中异常消息或多或少相似,但是,至少对我来说,这些情况下的错误似乎更明显(即没有相应STORE的LOAD指令)。我还查看了ASM4用户指南,尽管我认为我不太理解关于堆栈映射帧的讨论…

任何关于如何解决这个问题的建议都将不胜感激——解释得越彻底越好;我希望能够理解和应用我将来可能遇到的任何此类问题的建议。


共1个答案

匿名用户

这不是最漂亮的,但我认为这是实现霍尔格建议的一种方式:

for (MethodNode mn : cn.methods) {
    InsnList insns = mn.instructions;
    if (insns.size() == 0) {
        continue;
    }

    int lineNum = -1;
    int l1 = -1;
    int l2 = -1;
    AbstractInsnNode node;
    int numAdded;
    for (int i = 0; i < insns.size(); i++) {
        node = insns.get(i);
        if (node instanceof LineNumberNode) {
            lineNum = ((LineNumberNode) node).line;
        } else if (node instanceof LabelNode) {
            if (l1 == -1) {
                l1 = i;
            } else {
                l2 = i;
            }
        } else if (node instanceof FrameNode) {
            l1 = i;
        }
        if (lineNum > -1 && l1 < l2) {
            InsnList addedInsns = new InsnList();

            // bytecode insertion code

            numAdded = addedInsns.size(); // get this before inserting into insns as the insert operation empties addedInsns
            insns.insert(insns.get(l1), addedInsns);
            lineNum = -1;

            i += numAdded - 1; // -1 to counteract i incrementing with next iteration
            l1 = -1;
            l1 = -1;
        }

    }

}