【问题标题】:Inserting to InsnList before several nodes在几个节点之前插入到 InsnList
【发布时间】:2019-08-16 20:41:13
【问题描述】:

我正在尝试:

1) 迭代指令并找到所有相关节点

2) 在找到的节点前插入自定义代码

我使用流和迭代器来制作和插入,这仅适用于第一个节点

InsnList instructions = methodNode.instructions;
InsnList addition = ...

//It work: found n nodes for n return instructions
Stream<AbstractInsnNode> returnNodes = 
    Stream.iterate(instructions.getFirst(), AbstractInsnNode::getNext).limit(instructions.size())
        .filter(n -> returnOpcodes.contains(n.getOpcode()));

//It not work: inserted only before first node
returnNodes.forEach(n -> instructions.insertBefore(n, addition));

我也试过迭代器,它也不起作用

ListIterator<AbstractInsnNode> iterator = instructions.iterator();
while (iterator.hasNext()) {
     AbstractInsnNode node = iterator.next();
     if (returnOpcodes.contains(node.getOpcode()))
            instructions.insertBefore(node, addition);
}

我原以为addition 会插入到所有返回节点之前,但它插入到第一个之前。

InsnList 是一个链表,这样的插入必须有效。 我哪里错了?

【问题讨论】:

  • 我做了一些实验。结果:插入单个节点(无列表)正常工作,但插入列表(有任意数量的节点)不起作用
  • 哈,insertBefore 插入后从参数中清除列表。为了解决这个问题需要复制添加列表

标签: java java-bytecode-asm jvm-bytecode


【解决方案1】:

正如类名AbstractInsnNode 中的术语节点 所暗示的,此类的实例是链接对象图的一部分,因此只能是InsnList 的一部分。另见this answer

复制列表可能会变得非常低效,尤其是在 ASM’s visitor API 的基础上构建的 Tree API 可以轻松地一次性处理在某些原始指令出现处插入指令的任务。

由于InsnList 可以很好地与访问者 API 互操作,您仍然可以使用它来定义要插入的指令序列,但您可以使用它在正确的位置发出指令,而不是将其复制到另一个列表中。

这是在某些位置插入指令序列时复制整个类定义的草图:

class Victim {
    static void foo() {
        System.out.println("original code");
    }
}
public class InjectCode extends ClassVisitor {
    public static void main(String[] args) throws IOException, IllegalAccessException {
        ClassReader cr = new ClassReader(
            InjectCode.class.getResourceAsStream("Victim.class"));
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        cr.accept(new InjectCode(cw), 0);
        byte[] code = cw.toByteArray();

        MethodHandles.lookup().defineClass(code); // Java 9, for simplification

        Victim.foo();
    }

    InsnList insnList; // to be filled

    public InjectCode(ClassVisitor target) {
        super(Opcodes.ASM5, target);

        // just an example
        insnList = new InsnList();
        insnList.add(new FieldInsnNode(Opcodes.GETSTATIC,
                "java/lang/System", "out", "Ljava/io/PrintStream;"));
        insnList.add(new LdcInsnNode("Hello World"));
        insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                "java/io/PrintStream", "println", "(Ljava/lang/Object;)V"));
    }

    @Override
    public MethodVisitor visitMethod(
            int access, String name, String desc, String sign, String[] excp) {
        MethodVisitor target = super.visitMethod(access, name, desc, sign, excp);
        if(name.equals("foo")) { // fill your own trigger condition
            target = new InjectCodeMethodVisitor(api, target);
        }
        return target;
    }

    class InjectCodeMethodVisitor extends MethodVisitor {
        public InjectCodeMethodVisitor(int api, MethodVisitor methodVisitor) {
            super(api, methodVisitor);
        }

        @Override
        public void visitInsn(int opcode) {
            switch(opcode) {
                case Opcodes.RETURN: case Opcodes.ARETURN: case Opcodes.IRETURN: 
                case Opcodes.LRETURN: case Opcodes.FRETURN: case Opcodes.DRETURN: 
                case Opcodes.ATHROW:
                    insnList.accept(mv); // inject exiting method
            }
            super.visitInsn(opcode);
        }
    }
}

【讨论】:

  • 谢谢帮助。但我想避免使用 CoreAPI,因为它纠缠不清。执行时间对我的任务来说不是那么重要。
猜你喜欢
  • 2011-12-23
  • 1970-01-01
  • 2021-08-02
  • 2017-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多