簡體   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