0

I'm trying to add to user bytecode from a premain method that adds certain information in String form to a list whenever a new line number node is encountered, although when I run the agent and application jars the following exception occurs:

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)

Here's the premain's transform method:

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

            }

I've seen answers to previous questions where the exception message was more or less similar, however, the fault in those cases seemed more obvious to me at least (i.e. a LOAD instruction with no corresponding STORE). I've also looked through the ASM4 user guide, although I think I'm not quite grasping discussion about stackmap frames...

Any advice on how this can be sorted would be appreciated - the more thorough the explanation the better; I would like to be able to understand and apply advice for any such issues I might encounter in future.

1
  • 2
    First of all, a volatile field is not a “synchronized list field”. What you are trying to do, is not thread-safe. Further, you are never initializing the field. Besides that, your issue is related to this one, except you are using the Tree API. If a LabelNode (potential branch target) is followed by a FrameNode, you must not insert code between them, but place the code after the FrameNode. Side note: instead of \u0001 you can simply write \1 in string constants. Commented Jul 26, 2022 at 16:53

1 Answer 1

0

It's not the prettiest, but I think this is one way of implementing what Holger is suggesting:

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

    }

}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.