繁体   English   中英

使用 ASM 监控 Java 中的 object 创建时出现“VerifyError: Expecting to find object/array on stack”?

[英]“VerifyError: Expecting to find object/array on stack” when using ASM to monitor object creation in Java?

我想要做的是监视 object 的创建并记录该 object 的唯一 ID。 所以我使用 ASM 来监控“NEW”指令。 在我的方法访问者适配器中:

public void visitTypeInsn(int opcode, String desc){
    mv.visitTypeInsn(opcode, desc);
    if (opcode == NEW){
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESTATIC, "org/.../.../MyRecorder", "object_new",
                "(Ljava/lang/Object;)V", false);
    }
}

MyRecorder.java

public static void object_new(Object ref){
    log("object_new !");
    log("MyRecorder: " + ref);
    log("ref.getClass().getName(): " + ref.getClass().getName());
}

但是,这些代码导致java.lang.VerifyError: (...) Expecting to find object/array on stack 我不知道为什么这行不通。 如果这种方式不对,如何监控 object 的创建?


实际上,我也尝试监视object.<init>而不是监视NEW指令。 但是,使用以下代码,它会抛出java.lang.VerifyError: (...) Unable to pop operand off an empty stack

public void visitMethodInsn(int opc, String owner, String name, String desc, boolean isInterface) {
    ...
    mv.visitMethodInsn(opc, owner, name, desc, isInterface);
    if (opc == INVOKESPECIAL && name.equals("<init>")) {
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESTATIC, "org/myekstazi/agent/PurityRecorder", "object_new",
                "(Ljava/lang/Object;)V", false);
    }
}

object 实例化分为两条指令。 这会使 object 实例化难以工作。 这实际上是我的多个项目中最大的问题。 简单的表达式new Integer(0)可能会编译为以下代码。

new java/lang/Integer                       ═╤═
dup                                         ═╪═══╤═
iconst_0                                     │   │  ═╤═
invokespecial java/lang/Integer.<init>(I)V   │  ═╧═══╧═
                                             ↓

new创建的引用在被invokespecial初始化之前不能使用。 因此,您不能将您的仪器直接放在new之后。 在上面的示例中,您可以将检测放在invokespecial后面。 但是,您需要将其与对超级构造函数的调用区分开来。 super(0)这样的语句可能会编译为以下代码。

aload_0                                     ═╤═
iconst_0                                     │  ═╤═
invokespecial {super class}.<init>(I)V      ═╧═══╧═

这两个示例可能涵盖了调用<init>的两种最常见的情况。 如果您不对new Integer(0)创建的值做任何事情,编译器也可能会决定跳过dup指令。 我不确定他们是否真的这样做。 无论如何,第一个示例将成为以下代码。

new java/lang/Integer                       ═╤═
iconst_0                                     │  ═╤═
invokespecial java/lang/Integer.<init>(I)V  ═╧═══╧═

这仍然可以通过在new后面注入dup和在invokestatic后面注入invokespecial来处理。

无论如何,如果您想处理更多奇特的情况,字节码还可以包含dup_x1dup_x2类的指令。 GeneratorAdapter.box(Type)实际上会生成这样的代码。 一些“惰性”字节码操纵器也可能导致new指令,其中实例在使用pop删除之前从未初始化。 如您所见,在字节码中使用 object 实例化可能需要大量工作。 但是,根据您的情况,您可能不需要支持所有这些情况。 不幸的是,我没有经验来估计您需要支持哪些案例。


这是一个不处理dup_x1和类似指令的示例。 因此,它不适用于GeneratorAdapter.box(Type)生成的代码。 除此之外,它似乎在我的测试中运行良好。 它使用AnalyzerAdapter获取有关操作数堆栈的一些信息。

public final class InstrumentationMethodVisitor extends AnalyzerAdapter {
  public InstrumentationMethodVisitor(
      String owner, int access, String name, String descriptor,
      MethodVisitor methodVisitor) {
    super(ASM8, owner, access, name, descriptor, methodVisitor);
  }

  @Override
  public void visitTypeInsn(int opcode, String type) {
    super.visitTypeInsn(opcode, type);
    if (opcode == NEW && stack != null) {
      mv.visitInsn(DUP);
    }
  }

  @Override
  public void visitInsn(int opcode) {
    if (opcode == POP && stack != null && stack.get(stack.size() - 1) instanceof Label) {
      mv.visitInsn(POP);
    }
    super.visitInsn(opcode);
  }

  @Override
  public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
    if (opcode == INVOKESPECIAL && name.equals("<init>")) {
      boolean hasRelatedNew = stack != null && stack.get(stack.size() - getAmountOfArgumentSlots(descriptor) - 1) instanceof Label;

      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

      if (hasRelatedNew) {
        mv.visitMethodInsn(
            INVOKESTATIC,
            "org/myekstazi/agent/PurityRecorder",
            "object_new",
            "(Ljava/lang/Object;)V",
            false);
      }
    }
    else {
      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }
  }

  private static int getAmountOfArgumentSlots(String descriptor) {
    int slots = 0;
    for (Type type : Type.getArgumentTypes(descriptor)) {
      slots += type.getSize();
    }
    return slots;
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM